<html>
<head>
<title>Xcode's Plugin Interface : Compilers & Linkers</title>
<link rel="stylesheet" href="doc.css" />
</head>
<body>
<h1>Xcode's Plugin Interface : Compilers & Linkers</h1>
<!-- --- --- --- --- -->
<h2>Table of Contents</h2>
<div class="toc">
<div class="toc1"><a href="index.html">UP</a> <small>(Xcode's Plugin Interface)</small></div>
<div class="toc1"><a href="#buildsystem">1. Xcode's Build System</a></div>
<div class="toc1"><a href="#api">2. Graph Creation API</a></div>
<div class="toc1"><a href="#compiler">3. Compiler Sample Code (<code>.pbcompspec</code>)</a></div>
<div class="toc1"><a href="#linker">4. Linker Sample Code (<code>.pblinkspec</code>)</a></div>
<div class="toc1"><a href="#outputparsing">5. Parsing the ouput of command line tools</a> <small>(TODO)</small></div>
<div class="toc1"><a href="#producttype">6. Product Type Sample Code (<code>.pbprodspec</code>)</a></div>
</div>
<!-- --- --- --- --- -->
<h2><a name="buildsystem">1. Xcode's Build System</a></h2>
<p>Everything starts when Xcode loads your plugin bundle, just after you launch it. Xcode looks at
the content of the <code>Info.plist</code> file to initialize your plugin, and read all
<code>*.pb*spec</code> or <code>.xcspec</code> files of your plugin <code>Resources</code> folder.
At this time, Xcode don't execute any code, except if you add a <code>NSPrincipalClass</code> entry
in the <code>Info.plist</code> file, it just stores informations about what provide your plugin.</p>
<p>The second phase, the most important one, is the dependency graph creation. It appends every
time you open a project, add/remove files to/from a project, modify some build settings… It's the
phase where your plugin code is run.</p>
<p>Your code must create one <code>XCDependencyNode</code> for each file involved in the compilation
(source file, object files, final or product files), and add dependency relations between each nodes.
Some of these nodes will be marked as "product nodes" by calling <code>addProductNode:</code>, nodes
representing a file of your final product. Nodes are identified by a name which is, by convention,
the POSIX path of the corresponding file.</p>
<p>Here is the description of how is run the dependency creation phase :</p>
<ul>
<li><strong>Xcode creates a <code>PBXTargetBuildContext</code> object</strong>. This object will
be passed as parameter to all your fonctions. You must use it to modify/expand environment
variables, modify the dependency graph and create commands.</li>
<li>Xcode looks at the current target to know what are the product file created. All informations
attached to a target are stored inside the project file. When you add a new target to a project,
Xcode copies the content of the corresponding <code>.trgttmpl</code> file (in
<code>Library/Application Support/Apple/Developer Tools/Target Templates/</code>) to the project.</li>
<li>In the target description, there're references to all product files (files that are contained in
your final product). The product files are described in <code>.pbprodspec</code> and <code>.pbpackspec</code>
files. I did not really understood the differences between these two specification types, except
that they are strongly related.</li>
<li><strong>If you've created a custom <code>XCProductTypeSpecification</code> class, Xcode calls
it's <code>computeProductDependencies…:</code> method</strong> : it must contains code to
define product nodes.</li>
<li>Then, Xcode lists all source files (from the "source build phase" of the target). It looks
at the complier rules (described in <code>.buildrules</code> files, or created by the user in
the "Rules" tab of the target information window) to find which compiler must be used to compile
the source file. <strong>Xcode now call for each source file the <code>computeDependenciesForFilePath…</code>
method of the appropriated compiler</strong> (an object of a class inherited from
<code>XCCompilerSpecification</code>). The code of this method must create dependency links
between source and compiled file, add the compiled file to the <code>object_files_$(variant)_$(arch)</code>
environment variable, create the command which will be used to compile the source file, and
return the list of output nodes created by this command.</li>
<li>If there're source files the returned files, Xcode automatically send it to the appropriated
compiler. It may be useful for preprocessor commands like lex and yacc.</li>
<li><strong>Now, Xcode enters the linking phase.</strong> It first needs to know which linker to
use, so it calls the <code>linkerSpecificationForObjectFiles…</code> method of the product
specification object (inherited from the <code>XCProductTypeSpecification</code> class). If a
linker is specified by the <code>compiler_mandated_linker</code> variable, Xcode use it instead of
the linker returned by <code>linkerSpecificationForObjectFiles…</code>. You may also set
the key <code>RequiredLinker</code> in a compiler specification to override the value defined by
the product specification. Then, Xcode calls the <code>computeDependenciesForFilePaths…</code>
of this linker : this method must create dependency links between product nodes and object files,
the command to be runned to call the linker, and return the list of files created by the linker.</li>
</ul>
<p>The last phase is the real compilation/linking of files, the building phase. Here, Xcode read the
dependency graph and run the command only if they are needed (i.e. if the output file doesn't exist
or if the input files have been modified since last compilation) and in an best order that satisfy
the dependencies.</p>
<p>(only for Xcode 2.1) During the building phase, Xcode create <code>.dot</code> files describing the dependency graph
after and before running the commands (you'll find them in <code>build/«project».build/«configuration»/«target».build/</code>).
You need the <a href="http://www.pixelglow.com/graphviz/">Graphviz</a> application to view them.
These .dot files are very useful to check if your code create the good dependency graph.</p>
<!-- --- --- --- --- -->
<h2><a name="api">2. Graph Creation API</a></h2>
<p>Xcode methods called by your plugin :</p>
<ul>
<li>Node creation (name is the file path) :<br />
<code>-[PBXTargetBuildContext dependencyNodeForName:createIfNeeded:]</code></li>
<li>Mark a node as "product node" :<br />
<code>-[PBXTargetBuildContext addProductNode:]</code></li>
<li>Create command :<br />
<code>-[PBXTargetBuildContext createCommandWithRuleInfo:commandPath:arguments:forNode:]</code></li>
<li>Dependency definition :<br />
<code>-[XCDependencyNode addDependedNode:]</code> (must be called after creating a dependency
command for the output file) <br />
<code>-[XCDependencyNode addIncludeNode:]</code> (only for included header files)</li>
<li>Add source <-> compiled file links :<br />
<code>-[PBXTargetBuildContext setCompiledFilePath:forSourceFilePath:]</code></li>
</ul>
<p>Methods of your plugin called by Xcode :</p>
<ul>
<li>Create product nodes :<br />
<code>-[MyProductTypeSpecification computeProductDependenciesInTargetBuildContext:]</code></li>
<li>Choose a linker :<br />
<code>-[MyProductTypeSpecification linkerSpecificationForObjectFilesInTargetBuildContext:]</code></li>
<li>Create compilation rules (called for each source file) :<br />
<code>-[MyCompilerSpecification computeDependenciesForFilePath:ofType:outputDirectory:inTargetBuildContext:]</code></li>
<li>Create linking rules :<br />
<code>-[MyLinkerSpecification computeDependenciesForFilePaths:outputPath:inTargetBuildContext:]</code></li>
</ul>
<p>For more informations, see the <code>XCPSpecifications.h</code>, <code>XCPBuildSystem.h</code> and
<code>XCPDependencyGraph.h</code> header files available <a href="xcode-plugin-headers.zip">here</a>.</p>
<!-- --- --- --- --- -->
<h2><a name="compiler">3. Compiler Sample Code (<code>.pbcompspec</code>)</a></h2>
<h3>First solution : no code, less generic</h3>
<p>I've never tried this solution myself, and, according to people trying to use it, it doesn't work.
It's here for the case where you've more chance than others ;).</p>
<div style="color: grey; font-size: small">
<p>Starting with Xcode version 2.3, it's possible to integrate a compiler, without writing any code.
All you have to do is to write a <code>mycompiler.pbcompspec</code> file with the following format :</p>
<pre>{
Identifier = com.domain.me.compiler;
Class = MyCompilerSpecification; // optional
CommandInvocationClass = MyToolInvocation; // optional
Name = "My Compiler";
Description = "My source compiler";
Version = "Default"; // optional
Vendor = "Me"; // optional
Architectures = ("ppc","i386"); // optional
IsArchitectureNeutral = No; // optional (if "Yes", "Architectures" is useless)
Languages = (mytype); // optional
FileTypes = (sourcecode.mytype);
ExecPath = "/usr/local/bin/mycompiler";
RuleName = ( "ProcessingXXX", "$(OutputFile)", "$(InputFile)" );
OutputFileSuffix = ".o";
RequiredLinker = com.domain.me.linker; // optional (linker that must be used if using this
// compiler for at least one source file)
RequiredLibrarian = ??; // optional
CommandOutputParser = (…); // optional
PatternsOfFlagsNotAffectingPrecomps = (…) // optional
OptionsForCommandLine = (…); // optional
Options = {
{ Name = Input;
Type = stringlist;
CommandLineArgs = ( "$(value)" );
InputDependencies = "$(Input)"; // add to list of files needed before running the compiler
IsCommandInput = YES;
},
{ Name = Output;
Type = stringlist;
CommandLineArgs = ( "-o", "$(value)" );
OutputDependencies = "$(Output)"; // add to list of files created by the compiler
IsCommandOutput = YES;
},
{ Name = "build_file_compiler_flags"; // compiler flags specific to this source file
Type = stringlist;
CommandLineArgs = { "" = (); "<<otherwise>>" = ( "$(value)" ); };
}
… // see <a href="build-settings.html#buildsettings">here</a> for more details on options
};
OptionCategories = {…}; // optional (see <a href="build-settings.html#buildsettings">here</a>)
}</pre>
<p>As you can see, Xcode creates to new environment variables <code>$(OutputFile)</code> and
<code>$(InputFile)</code>. The output file path is derived from the input path by replacing the
extension with the value of the <code>OutputFileSuffix</code> key.</p>
</div>
<h3>Second solution : with Objective-C code, more generic</h3>
<p>If the first solution don't allow you to do what you want, you need to write a custom compiler
specification class (inheriting from <code>XCCompilerSpecification</code>). In this case you need
to add the <code>Class</code> key, but <code>RuleName</code>, <code>OutputFileSuffix</code>,
<code>OutputFileSuffix</code> are not required. You also don't need to use the <code>InputFile</code>,
<code>OutputFile</code> and <code>build_file_compiler_flags</code> options.</p>
<p>Code file <code>MyCompilerSpecification.m</code> :</p>
<pre>#import "MyCompilerSpecification.h"
#import "XCPBuildSystem.h"
#import "XCPDependencyGraph.h"
#import "XCPSupport.h"
@implementation MyCompilerSpecification
- (NSArray*) computeDependenciesForInputFile:(NSString*)input ofType:(PBXFileType*)type
variant:(NSString*)variant architecture:(NSString*)arch
outputDirectory:(NSString*)outputDir
inTargetBuildContext:(PBXTargetBuildContext*)context
{
<strong>// compute input path (for variable substitution)</strong>
input = [context expandedValueForString:input];
<strong>// compute output path</strong>
NSString* output = [outputDir stringByAppendingPathComponent:[[input lastPathComponent] stringByDeletingPathExtension]];
output = [context expandedValueForString:output];
<strong>// create dependency nodes</strong>
XCDependencyNode* outputNode = [context dependencyNodeForName:output createIfNeeded:YES];
XCDependencyNode* inputNode = [context dependencyNodeForName:input createIfNeeded:YES];
<strong>// create compiler command</strong>
XCDependencyCommand* dep = [context
createCommandWithRuleInfo:[NSArray arrayWithObjects:@"MyCompile",[context naturalPathForPath:input],nil]
commandPath:[context expandedValueForString:[self path]]
arguments:nil
forNode:outputNode];
[dep setToolSpecification:self];
[dep addArgumentsFromArray:[self commandLineForAutogeneratedOptionsInTargetBuildContext:context]];
[dep addArgumentsFromArray:[[context expandedValueForString:@"$(build_file_compiler_flags)"] arrayByParsingAsStringList]];
[dep addArgument:@"-o"];
[dep addArgument:output];
[dep addArgument:input];
<strong>// create dependency rules</strong>
[outputNode addDependedNode:inputNode];
<strong>// update source-compiled links</strong>
[context setCompiledFilePath:output forSourceFilePath:input];
<strong>// add to the list of file for the linker (only for non-".o" objects)</strong>
//NSString* object_files_variant_arch = [context expandedValueForString:@"object_files"];
//[context appendStringOrStringListValue:output toDynamicSetting:object_files_variant_arch];
<strong>// return output object node (Xcode will automaticaly add .o files to $(object_files))</strong>
return [NSArray arrayWithObject:outputNode];
}
@end</pre>
<!-- --- --- --- --- -->
<h2><a name="linker">4. Linker Sample Code (<code>.pblinkspec</code>)</a></h2>
<p>Specification file <code>mylinker.pblinkspec</code> :</p>
<pre>{
Identifier = com.domain.me.linker;
Class = MyLinkerSpecification;
Name = "My linker";
Description = "My linker";
Version = "Default"; // optional
Vendor = "Me"; // optional
BinaryFormats = ("mach-o");
Architectures = (ppc);
ExecPath = "/usr/local/bin/mylinker";
InputFileTypes = (compiled.mach-o.objfile);
CommandOutputParser = (…);
PatternsOfFlagsNotAffectingPrecomps = (…) // optional
OptionsForCommandLine = (…); // optional (see <a href="build-settings.html#buildsettings">here</a>)
Options = {…}; // optional (see <a href="build-settings.html#buildsettings">here</a>)
OptionCategories = {…}; // optional (see <a href="build-settings.html#buildsettings">here</a>)
}</pre>
<p>Code file <code>MyLinkerSpecification.m</code> :</p>
<pre>#import "MyLinkerSpecification.h"
#import "XCPBuildSystem.h"
#import "XCPDependencyGraph.h"
@implementation MyLinkerSpecification
- (NSArray*)createCommandsInBuildContext:(PBXTargetBuildContext*)context
{
<strong>// this method must be override since Xcode 3.0 : the default implementation doesn't call
// computeDependenciesForFilePaths: anymore</strong>
NSArray* inputs = [[context expandedValueForString:@"$(Inputs)"] arrayByParsingAsStringList];
NSString* output = [context expandedValueForString:@"$(Output)"];
return [self computeDependenciesForFilePaths:inputs outputPath:output inTargetBuildContext:context];
}
- (NSArray*)computeDependenciesForFilePaths:(NSArray*)inputs
outputPath:(NSString*)output
inTargetBuildContext:(PBXTargetBuildContext*)context
{
<strong>// compute output path (for variable substitution)</strong>
output = [context expandedValueForString:output];
<strong>// create linker command</strong>
XCDependencyNode* outputNode = [context dependencyNodeForName:output createIfNeeded:YES];
XCDependencyCommand* dep = [context
createCommandWithRuleInfo:[NSArray arrayWithObjects:@"MyLink",[context naturalPathForPath:output],nil]
commandPath:[context expandedValueForString:[self path]]
arguments:nil
forNode:outputNode];
[dep setToolSpecification:self];
[dep addArgumentsFromArray:[self commandLineForAutogeneratedOptionsInTargetBuildContext:context]];
[dep addArgument:@"-o"];
[dep addArgument:output];
<strong>// some types</strong>
PBXFileType* myObjectFileType = [PBXFileType specificationForIdentifier:@"compiled.myobjfile"];
PBXFileType* myLibraryFileType = [PBXFileType specificationForIdentifier:@"compiled.mylibraryfile"];
<strong>// create dependency rules & command arguments for libraries</strong>
NSEnumerator* libraryEnum = [[context linkedLibraryPaths] objectEnumerator];
NSString* library;
while((library = [libraryEnum nextObject]) != nil) {
library = [context expandedValueForString:library];
PBXFileType* type = [PBXFileType fileTypeForFileName:[library lastPathComponent]];
if([type isKindOfSpecification:myLibraryFileType] || [type isKindOfSpecification:myObjectFileType]) {
XCDependencyNode* libraryNode = [context dependencyNodeForName:library createIfNeeded:YES];
[outputNode addDependedNode:libraryNode];
[dep addArgument:library];
} else {
[context addDependencyAnalysisWarningMessageFormat:
@"warning: skipping file '%@' (unexpected file type '%@' in Frameworks & Libraries build phase)",
library, [type identifier]];
}
}
<strong>// create dependency rules & command arguments for compiled object</strong>
NSEnumerator* objectEnum = [inputs objectEnumerator];
NSString* input;
while((input = [objectEnum nextObject]) != nil) {
input = [context expandedValueForString:input];
PBXFileType* type = [PBXFileType fileTypeForFileName:[input lastPathComponent]];
if([type isKindOfSpecification:myObjectFileType]) {
XCDependencyNode* inputNode = [context dependencyNodeForName:input createIfNeeded:YES];
[outputNode addDependedNode:inputNode];
[dep addArgument:input];
} else {
[context addDependencyAnalysisWarningMessageFormat:
@"warning: skipping file '%@' (unexpected file type '%@' in link phase)",
input, [type identifier]];
}
}
return [NSArray arrayWithObject:outputNode];
}
@end</pre>
<!-- --- --- --- --- -->
<h2><a name="outputparsing">5. Parsing the ouput of command line tools</a></h2>
<p>TODO</p>
<!-- --- --- --- --- -->
<h2><a name="producttype">6. Product Type Sample Code (<code>.pbprodspec</code>)</a></h2>
<p>Specification file <code>myproducttype.pbprodspec</code> :</p>
<pre>{
Identifier = com.domain.me.product-type.tool;
Class = MyProductTypeSpecification;
Name = "My Command-line Tool";
Description = "My Command-line Tool";
//Image name for inactive target is <IconNamePrefix>
//Image name for active target is <IconNamePrefix>Active
IconNamePrefix = "TargetPlugin";
DefaultTargetName = "MyTool";
DefaultBuildProperties = { … };
AllowedBuildPhaseTypes = ( Headers, Sources, Frameworks );
BuildPropertiesToAppend = ??;
PackageTypes = (
com.domain.me.package-type.tool
);
}</pre>
<p>Code file <code>MyProductTypeSpecification.m</code> :</p>
<pre>#import "MyProductTypeSpecification.h"
#import "XCPBuildSystem.h"
@implementation MyProductTypeSpecification
- (void)computeProductDependenciesInTargetBuildContext:(PBXTargetBuildContext*)context
{
NSString* productPath = [context expandedValueForString:@"$(TARGET_BUILD_DIR)/$(EXECUTABLE_PATH)"];
XCDependencyNode* productNode = [context dependencyNodeForName:productPath createIfNeeded:YES];
[context addProductNode:productNode];
}
- (XCLinkerSpecification*)linkerSpecificationForObjectFilesInTargetBuildContext:(PBXTargetBuildContext*)context
{
return [[XCLinkerSpecification specificationRegistry] objectForKey:@"com.domain.me.linker"];
}
@end</pre>
<!-- --- --- --- --- -->
<p class="footer">
Copyright © 2005-2006 Damien Bobillot,
E-Mail : <a href="mailto:damien.bobillot.2002_xcodeplugin CHEZ m4x.org?Subject=%5BXcode Plugins%5D%20">damien.bobillot.2002_xcodeplugin CHEZ m4x.org</a>
</p>
<script src="http://www.google-analytics.com/urchin.js" type="text/javascript">
</script>
<script type="text/javascript">
_uacct = "UA-445657-1";
urchinTracker();
</script>
</body>
</html>