| #! /usr/bin/env python |
| # encoding: utf-8 |
| # XCode 3/XCode 4 generator for Waf |
| # Nicolas Mercier 2011 |
| |
| """ |
| Usage: |
| |
| def options(opt): |
| opt.load('xcode') |
| |
| $ waf configure xcode |
| """ |
| |
| # TODO: support iOS projects |
| |
| from waflib import Context, TaskGen, Build, Utils |
| import os, sys |
| |
| HEADERS_GLOB = '**/(*.h|*.hpp|*.H|*.inl)' |
| |
| MAP_EXT = { |
| '.h' : "sourcecode.c.h", |
| |
| '.hh': "sourcecode.cpp.h", |
| '.inl': "sourcecode.cpp.h", |
| '.hpp': "sourcecode.cpp.h", |
| |
| '.c': "sourcecode.c.c", |
| |
| '.m': "sourcecode.c.objc", |
| |
| '.mm': "sourcecode.cpp.objcpp", |
| |
| '.cc': "sourcecode.cpp.cpp", |
| |
| '.cpp': "sourcecode.cpp.cpp", |
| '.C': "sourcecode.cpp.cpp", |
| '.cxx': "sourcecode.cpp.cpp", |
| '.c++': "sourcecode.cpp.cpp", |
| |
| '.l': "sourcecode.lex", # luthor |
| '.ll': "sourcecode.lex", |
| |
| '.y': "sourcecode.yacc", |
| '.yy': "sourcecode.yacc", |
| |
| '.plist': "text.plist.xml", |
| ".nib": "wrapper.nib", |
| ".xib": "text.xib", |
| } |
| |
| |
| part1 = 0 |
| part2 = 10000 |
| part3 = 0 |
| id = 562000999 |
| def newid(): |
| global id |
| id = id + 1 |
| return "%04X%04X%04X%012d" % (0, 10000, 0, id) |
| |
| class XCodeNode: |
| def __init__(self): |
| self._id = newid() |
| |
| def tostring(self, value): |
| if isinstance(value, dict): |
| result = "{\n" |
| for k,v in value.items(): |
| result = result + "\t\t\t%s = %s;\n" % (k, self.tostring(v)) |
| result = result + "\t\t}" |
| return result |
| elif isinstance(value, str): |
| return "\"%s\"" % value |
| elif isinstance(value, list): |
| result = "(\n" |
| for i in value: |
| result = result + "\t\t\t%s,\n" % self.tostring(i) |
| result = result + "\t\t)" |
| return result |
| elif isinstance(value, XCodeNode): |
| return value._id |
| else: |
| return str(value) |
| |
| def write_recursive(self, value, file): |
| if isinstance(value, dict): |
| for k,v in value.items(): |
| self.write_recursive(v, file) |
| elif isinstance(value, list): |
| for i in value: |
| self.write_recursive(i, file) |
| elif isinstance(value, XCodeNode): |
| value.write(file) |
| |
| def write(self, file): |
| for attribute,value in self.__dict__.items(): |
| if attribute[0] != '_': |
| self.write_recursive(value, file) |
| |
| w = file.write |
| w("\t%s = {\n" % self._id) |
| w("\t\tisa = %s;\n" % self.__class__.__name__) |
| for attribute,value in self.__dict__.items(): |
| if attribute[0] != '_': |
| w("\t\t%s = %s;\n" % (attribute, self.tostring(value))) |
| w("\t};\n\n") |
| |
| |
| |
| # Configurations |
| class XCBuildConfiguration(XCodeNode): |
| def __init__(self, name, settings = {}, env=None): |
| XCodeNode.__init__(self) |
| self.baseConfigurationReference = "" |
| self.buildSettings = settings |
| self.name = name |
| if env and env.ARCH: |
| settings['ARCHS'] = " ".join(env.ARCH) |
| |
| |
| class XCConfigurationList(XCodeNode): |
| def __init__(self, settings): |
| XCodeNode.__init__(self) |
| self.buildConfigurations = settings |
| self.defaultConfigurationIsVisible = 0 |
| self.defaultConfigurationName = settings and settings[0].name or "" |
| |
| # Group/Files |
| class PBXFileReference(XCodeNode): |
| def __init__(self, name, path, filetype = '', sourcetree = "SOURCE_ROOT"): |
| XCodeNode.__init__(self) |
| self.fileEncoding = 4 |
| if not filetype: |
| _, ext = os.path.splitext(name) |
| filetype = MAP_EXT.get(ext, 'text') |
| self.lastKnownFileType = filetype |
| self.name = name |
| self.path = path |
| self.sourceTree = sourcetree |
| |
| class PBXGroup(XCodeNode): |
| def __init__(self, name, sourcetree = "<group>"): |
| XCodeNode.__init__(self) |
| self.children = [] |
| self.name = name |
| self.sourceTree = sourcetree |
| |
| def add(self, root, sources): |
| folders = {} |
| def folder(n): |
| if not n.is_child_of(root): |
| return self |
| try: |
| return folders[n] |
| except KeyError: |
| f = PBXGroup(n.name) |
| p = folder(n.parent) |
| folders[n] = f |
| p.children.append(f) |
| return f |
| for s in sources: |
| f = folder(s.parent) |
| source = PBXFileReference(s.name, s.abspath()) |
| f.children.append(source) |
| |
| |
| # Targets |
| class PBXLegacyTarget(XCodeNode): |
| def __init__(self, action, target=''): |
| XCodeNode.__init__(self) |
| self.buildConfigurationList = XCConfigurationList([XCBuildConfiguration('waf', {})]) |
| if not target: |
| self.buildArgumentsString = "%s %s" % (sys.argv[0], action) |
| else: |
| self.buildArgumentsString = "%s %s --targets=%s" % (sys.argv[0], action, target) |
| self.buildPhases = [] |
| self.buildToolPath = sys.executable |
| self.buildWorkingDirectory = "" |
| self.dependencies = [] |
| self.name = target or action |
| self.productName = target or action |
| self.passBuildSettingsInEnvironment = 0 |
| |
| class PBXShellScriptBuildPhase(XCodeNode): |
| def __init__(self, action, target): |
| XCodeNode.__init__(self) |
| self.buildActionMask = 2147483647 |
| self.files = [] |
| self.inputPaths = [] |
| self.outputPaths = [] |
| self.runOnlyForDeploymentPostProcessing = 0 |
| self.shellPath = "/bin/sh" |
| self.shellScript = "%s %s %s --targets=%s" % (sys.executable, sys.argv[0], action, target) |
| |
| class PBXNativeTarget(XCodeNode): |
| def __init__(self, action, target, node, env): |
| XCodeNode.__init__(self) |
| conf = XCBuildConfiguration('waf', {'PRODUCT_NAME':target, 'CONFIGURATION_BUILD_DIR':node.parent.abspath()}, env) |
| self.buildConfigurationList = XCConfigurationList([conf]) |
| self.buildPhases = [PBXShellScriptBuildPhase(action, target)] |
| self.buildRules = [] |
| self.dependencies = [] |
| self.name = target |
| self.productName = target |
| self.productType = "com.apple.product-type.application" |
| self.productReference = PBXFileReference(target, node.abspath(), 'wrapper.application', 'BUILT_PRODUCTS_DIR') |
| |
| # Root project object |
| class PBXProject(XCodeNode): |
| def __init__(self, name, version): |
| XCodeNode.__init__(self) |
| self.buildConfigurationList = XCConfigurationList([XCBuildConfiguration('waf', {})]) |
| self.compatibilityVersion = version[0] |
| self.hasScannedForEncodings = 1 |
| self.mainGroup = PBXGroup(name) |
| self.projectRoot = "" |
| self.projectDirPath = "" |
| self.targets = [] |
| self._objectVersion = version[1] |
| self._output = PBXGroup('out') |
| self.mainGroup.children.append(self._output) |
| |
| def write(self, file): |
| w = file.write |
| w("// !$*UTF8*$!\n") |
| w("{\n") |
| w("\tarchiveVersion = 1;\n") |
| w("\tclasses = {\n") |
| w("\t};\n") |
| w("\tobjectVersion = %d;\n" % self._objectVersion) |
| w("\tobjects = {\n\n") |
| |
| XCodeNode.write(self, file) |
| |
| w("\t};\n") |
| w("\trootObject = %s;\n" % self._id) |
| w("}\n") |
| |
| def add_task_gen(self, tg): |
| if not getattr(tg, 'mac_app', False): |
| self.targets.append(PBXLegacyTarget('build', tg.name)) |
| else: |
| target = PBXNativeTarget('build', tg.name, tg.link_task.outputs[0].change_ext('.app'), tg.env) |
| self.targets.append(target) |
| self._output.children.append(target.productReference) |
| |
| class xcode(Build.BuildContext): |
| cmd = 'xcode' |
| fun = 'build' |
| |
| def collect_source(self, tg): |
| source_files = tg.to_nodes(getattr(tg, 'source', [])) |
| plist_files = tg.to_nodes(getattr(tg, 'mac_plist', [])) |
| resource_files = [tg.path.find_node(i) for i in Utils.to_list(getattr(tg, 'mac_resources', []))] |
| include_dirs = Utils.to_list(getattr(tg, 'includes', [])) + Utils.to_list(getattr(tg, 'export_dirs', [])) |
| include_files = [] |
| for x in include_dirs: |
| if not isinstance(x, str): |
| include_files.append(x) |
| continue |
| d = tg.path.find_node(x) |
| if d: |
| lst = [y for y in d.ant_glob(HEADERS_GLOB, flat=False)] |
| include_files.extend(lst) |
| |
| # remove duplicates |
| source = list(set(source_files + plist_files + resource_files + include_files)) |
| source.sort(key=lambda x: x.abspath()) |
| return source |
| |
| def execute(self): |
| """ |
| Entry point |
| """ |
| self.restore() |
| if not self.all_envs: |
| self.load_envs() |
| self.recurse([self.run_dir]) |
| |
| appname = getattr(Context.g_module, Context.APPNAME, os.path.basename(self.srcnode.abspath())) |
| p = PBXProject(appname, ('Xcode 3.2', 46)) |
| |
| for g in self.groups: |
| for tg in g: |
| if not isinstance(tg, TaskGen.task_gen): |
| continue |
| |
| tg.post() |
| |
| features = Utils.to_list(getattr(tg, 'features', '')) |
| |
| group = PBXGroup(tg.name) |
| group.add(tg.path, self.collect_source(tg)) |
| p.mainGroup.children.append(group) |
| |
| if 'cprogram' or 'cxxprogram' in features: |
| p.add_task_gen(tg) |
| |
| |
| # targets that don't produce the executable but that you might want to run |
| p.targets.append(PBXLegacyTarget('configure')) |
| p.targets.append(PBXLegacyTarget('dist')) |
| p.targets.append(PBXLegacyTarget('install')) |
| p.targets.append(PBXLegacyTarget('check')) |
| node = self.srcnode.make_node('%s.xcodeproj' % appname) |
| node.mkdir() |
| node = node.make_node('project.pbxproj') |
| p.write(open(node.abspath(), 'w')) |
| |
| |