From 059939ef607aa1b5833222578f9180559037c16c Mon Sep 17 00:00:00 2001 From: Thomas Nagy Date: Sat, 8 Apr 2017 01:57:46 +0200 Subject: [PATCH] Xcode 6 generator fixes #1939 --- playground/xcode6/wscript | 65 ++++---- waflib/extras/xcode.py | 312 -------------------------------------- waflib/extras/xcode6.py | 97 ++++++------ 3 files changed, 91 insertions(+), 383 deletions(-) delete mode 100644 waflib/extras/xcode.py diff --git a/playground/xcode6/wscript b/playground/xcode6/wscript index 11db85eb..89bd6102 100644 --- a/playground/xcode6/wscript +++ b/playground/xcode6/wscript @@ -8,14 +8,23 @@ APPNAME = 'TestProject' VERSION = '1.0' """ +To create the xcode project files: + waf configure xcode6 + +To configure and build using Waf: + waf configure build + This demo will create an XCode project containing an App bundle target, a dynamic library target, a static library target and an executable target. The generated XCode project can then be opened and XCode can then build those targets. -Tested in XCode 6 & 7. +Tested with XCode 8. """ +def options(opt): + opt.load('compiler_cxx xcode6') + def configure(conf): # Use environment variables to set default project configuration @@ -25,20 +34,21 @@ def configure(conf): conf.env.INSTALL_PATH = '/my/install/path' # The xcode6 tool will also pick up any c config files generated by - # the c_config tool, and it'll be added to your project's include path + # the c_config tool, and it'll be added to your project's include path conf.load('c_config') conf.define('NUMBER', 10) conf.write_config_header('config.h') # This must be called at the end of configure() - conf.load('xcode6') + conf.load('compiler_cxx xcode6') + + conf.check(cxxflags='-std=c++11', uselib_store='STD11', mandatory=False) def build(bld): - bld.load('xcode6') tg = bld.framework( includes='include', - # Specify source files. + # Specify source files for xcode # This will become the groups (folders) inside XCode. # Give a dictionary to group by name. Use a list to add everything in one source_files={ @@ -46,6 +56,9 @@ def build(bld): 'Include': bld.path.ant_glob(incl=['include/MyLib/*.h', 'include'], dir=True) }, + # source files for waf builds + source=bld.path.ant_glob('src/MyLib/*.cpp'), + # export_headers will put the files in the # 'Header Build Phase' in Xcode - i.e tell XCode to ship them with your .framework export_headers=bld.path.ant_glob(incl=['include/MyLib/*.h', 'include/MyLib/SupportLib'], dir=True), @@ -53,10 +66,28 @@ def build(bld): install='~/Library/Frameworks' ) - bld.env.LIB_SDL2 = '/Library/Frameworks/SDL2.framework/SDL2' + bld.stlib( + source=bld.path.ant_glob('src/MyLib/*.cpp'), + includes = 'include', + target='MyStaticLib', + ) + + bld.program( + source=['src/test.cpp'], + includes='include', + target='MyExe', + use='MyDynLib' + ) + bld.shlib( + source=bld.path.ant_glob('src/MyLib/*.cpp'), + includes='include', + target='MyDynLib', + ) + + #bld.env.LIB_SDL2 = '/Library/Frameworks/SDL2.framework/SDL2' tg2 = bld.app( - source_files=bld.path.ant_glob('src/*.cpp'), - includes=tg.includes, + source=bld.path.ant_glob('src/*.cpp'), + includes='include', target='MyApp', use='MyLib', uselib='SDL2', @@ -66,22 +97,4 @@ def build(bld): settings={"Debug": {"CONFIG_NAME": 'Debug'}} ) - bld.dylib( - source_files=bld.path.ant_glob('src/MyLib/*.cpp'), - includes=tg.includes, - target='MyDynLib', - ) - - bld.program( - source_files=['src/test.cpp'], - includes=tg.includes, - target='MyExe', - use='MyDynLib' - ) - - bld.stlib( - source_files=bld.path.ant_glob('src/MyLib/*.cpp'), - includes=tg.includes, - target='MyStaticLib', - ) diff --git a/waflib/extras/xcode.py b/waflib/extras/xcode.py deleted file mode 100644 index 19f9bb41..00000000 --- a/waflib/extras/xcode.py +++ /dev/null @@ -1,312 +0,0 @@ -#! /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 += 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' in features) 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')) - - diff --git a/waflib/extras/xcode6.py b/waflib/extras/xcode6.py index a785ed17..db9ebe36 100644 --- a/waflib/extras/xcode6.py +++ b/waflib/extras/xcode6.py @@ -11,11 +11,9 @@ Usage: See also demos/xcode6/ folder def options(opt): - opt.load('xcode6') + opt.load('compiler_cxx xcode6') def configure(cnf): - # - # For example cnf.env.SDKROOT = 'macosx10.9' @@ -31,13 +29,13 @@ def configure(cnf): # } # In the end of configure() do - cnf.load('xcode6') + cnf.load('compiler_cxx xcode6') def build(bld): # Make a Framework target bld.framework( - source_files={ + source={ 'Include': bld.path.ant_glob('include/MyLib/*.h'), 'Source': bld.path.ant_glob('src/MyLib/*.cpp') }, @@ -45,7 +43,6 @@ def build(bld): export_headers=bld.path.ant_glob('include/MyLib/*.h'), target='MyLib', ) - # You can also make bld.dylib, bld.app, bld.stlib ... # To generate your XCode project, open the folder with the wscript @@ -53,11 +50,14 @@ def build(bld): # $ waf configure xcode6 """ -# TODO: support iOS projects +# TODO: support iOS projects(?) from waflib import Context, TaskGen, Build, Utils, Errors, Logs import os, sys +# FIXME too few extensions +XCODE_EXTS = ['.c', '.cpp', '.m', '.mm'] + HEADERS_GLOB = '**/(*.h|*.hpp|*.H|*.inl)' MAP_EXT = { @@ -262,6 +262,7 @@ class PBXFileReference(XCodeNode): _, ext = os.path.splitext(name) filetype = MAP_EXT.get(ext, 'text') self.lastKnownFileType = filetype + self.explicitFileType = filetype self.name = name self.path = path self.sourceTree = sourcetree @@ -276,7 +277,7 @@ class PBXBuildFile(XCodeNode): """ This element indicate a file reference that is used in a PBXBuildPhase (either as an include or resource). """ def __init__(self, fileRef, settings={}): XCodeNode.__init__(self) - + # fileRef is a reference to a PBXFileReference object self.fileRef = fileRef @@ -308,7 +309,6 @@ class PBXContainerItemProxy(XCodeNode): self.remoteGlobalIDString = remoteGlobalIDString # PBXNativeTarget self.remoteInfo = remoteInfo # Target name self.proxyType = proxyType - class PBXTargetDependency(XCodeNode): """ This is the element for referencing other target through content proxies. """ @@ -316,7 +316,7 @@ class PBXTargetDependency(XCodeNode): XCodeNode.__init__(self) self.target = native_target self.targetProxy = proxy - + class PBXFrameworksBuildPhase(XCodeNode): """ This is the element for the framework link build phase, i.e. linking to frameworks """ def __init__(self, pbxbuildfiles): @@ -490,8 +490,8 @@ class xcode(Build.BuildContext): def create_group(self, name, files): """ - Returns a new PBXGroup containing the files (paths) passed in the files arg - :type files: string + Returns a new PBXGroup containing the files (paths) passed in the files arg + :type files: string """ group = PBXGroup(name) """ @@ -534,7 +534,7 @@ class xcode(Build.BuildContext): appname = getattr(Context.g_module, Context.APPNAME, os.path.basename(self.srcnode.abspath())) p = PBXProject(appname, ('Xcode 3.2', 46), self.env) - + # If we don't create a Products group, then # XCode will create one, which entails that # we'll start to see duplicate files in the UI @@ -562,28 +562,26 @@ class xcode(Build.BuildContext): # Create the output node target_node = tg.path.find_or_declare(tg.name+file_ext) - target = PBXNativeTarget(tg.name, target_node, target_type, [], []) products_group.children.append(target.productReference) - if hasattr(tg, 'source_files'): + if hasattr(tg, 'source_files') or hasattr(tg, 'source'): # Create list of PBXFileReferences sources = [] - if isinstance(tg.source_files, dict): + if hasattr(tg, 'source_files') and isinstance(tg.source_files, dict): for grpname,files in tg.source_files.items(): group = self.create_group(grpname, files) target_group.children.append(group) sources.extend(group.children) - elif isinstance(tg.source_files, list): - group = self.create_group("Source", tg.source_files) + else: + src = getattr(tg, 'source_files', []) or tg.source + group = self.create_group("Source", src) target_group.children.append(group) sources.extend(group.children) - else: - self.to_log("Argument 'source_files' passed to target '%s' was not a dictionary. Hence, some source files may not be included. Please provide a dictionary of source files, with group name as key and list of source files as value.\n" % tg.name) - supported_extensions = ['.c', '.cpp', '.m', '.mm'] - sources = filter(lambda fileref: os.path.splitext(fileref.path)[1] in supported_extensions, sources) + # FIXME too complex + sources = list(filter(lambda fileref: os.path.splitext(fileref.path)[1] in XCODE_EXTS, sources)) buildfiles = [self.unique_buildfile(PBXBuildFile(fileref)) for fileref in sources] target.add_build_phase(PBXSourcesBuildPhase(buildfiles)) @@ -613,7 +611,7 @@ class xcode(Build.BuildContext): # Merge frameworks and libs into one list, and prefix the frameworks ld_flags = ['-framework %s' % lib.split('.framework')[0] for lib in Utils.to_list(tg.env.FRAMEWORK)] ld_flags.extend(Utils.to_list(tg.env.STLIB) + Utils.to_list(tg.env.LIB)) - + # Override target specific build settings bldsettings = { 'HEADER_SEARCH_PATHS': ['$(inherited)'] + tg.env['INCPATHS'], @@ -630,7 +628,7 @@ class xcode(Build.BuildContext): bldsettings['INSTALL_PATH'].append(instpath) target.add_build_phase(PBXCopyFilesBuildPhase([prodbuildfile], instpath)) - if len(bldsettings['INSTALL_PATH']) == 0: + if not bldsettings['INSTALL_PATH']: del bldsettings['INSTALL_PATH'] # The keys represents different build configuration, e.g. Debug, Release and so on.. @@ -646,31 +644,40 @@ class xcode(Build.BuildContext): target.add_configuration(XCBuildConfiguration(k, v)) p.add_target(target) - + node = self.bldnode.make_node('%s.xcodeproj' % appname) node.mkdir() node = node.make_node('project.pbxproj') - p.write(open(node.abspath(), 'w')) - - def build_target(self, tgtype, *k, **kw): - """ - Provide aliases - E.g. bld.framework(source='..', ...) to build a Framework target. - E.g. bld.dylib(source='..', ...) to build a Dynamic library target. etc... - """ + with open(node.abspath(), 'w') as f: + p.write(f) + Logs.pprint('GREEN', 'Wrote %r' % node.abspath()) + +def bind_fun(tgtype): + def fun(self, *k, **kw): + tgtype = fun.__name__ + if tgtype == 'shlib' or tgtype == 'dylib': + features = 'cxx cxxshlib' + tgtype = 'dylib' + elif tgtype == 'framework': + features = 'cxx cxxshlib' + tgtype = 'framework' + elif tgtype == 'program': + features = 'cxx cxxprogram' + tgtype = 'exe' + elif tgtype == 'app': + features = 'cxx cxxprogram' + tgtype = 'app' + elif tgtype == 'stlib': + features = 'cxx cxxstlib' + tgtype = 'stlib' + kw['features'] = features - # The following features are needed for this tool's use of - # env['INCPATHS'], env['LIB_xxx'] etc. - self.load('ccroot') - kw['features'] = 'cxx cxxprogram' kw['target_type'] = tgtype return self(*k, **kw) + fun.__name__ = tgtype + setattr(Build.BuildContext, tgtype, fun) + return fun + +for xx in 'app framework dylib shlib stlib program'.split(): + bind_fun(xx) - def app(self, *k, **kw): return self.build_target('app', *k, **kw) - def framework(self, *k, **kw): return self.build_target('framework', *k, **kw) - def dylib(self, *k, **kw): return self.build_target('dylib', *k, **kw) - def stlib(self, *k, **kw): return self.build_target('stlib', *k, **kw) - def program(self, *k, **kw): return self.build_target('exe', *k, **kw) - def exe(self, *k, **kw): - Logs.warn("xcode6: alias 'bld.exe()' has changed name. Use 'bld.program()' instead.") - return self.build_target('exe', *k, **kw)