#! /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, random, time 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 = ""): 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'))