From c967d29e4882389918f3149c012d88c65c5ade3c Mon Sep 17 00:00:00 2001 From: Simon Date: Sat, 15 Apr 2017 16:08:52 +0200 Subject: [PATCH] Replace param 'source_files' with 'group_files'. The 'source_files' param to the xcode6 tool was originally separated from the conventional 'source' param because it was used to control how the source files would appear in the XCode folder UI. Also, it'd allow to add any file extensions, and not limited to those extensions supported by the loaded set of waf tools. This commit renames 'source_files' param to 'group_files'. It also changes the semantic so that 'group_files' now is used like the following: bld( source='...', # These are now the files compiled by XCode 'group_files': ..., # Optionally customize the way source files appear i the UI ) Previously, 'source_files' was used to collect source files for compilation in XCode, and to customize the UI folder structure. In this commit source_files is used only to let the user group files in different UI folders (and add additional resource files besides source files). I want to do the renaming to better reflect the param's meaning. Additional changes: * Remove unique_filereference * Updated examples --- playground/xcode6/src/sample.txt | 1 + playground/xcode6/wscript | 41 ++++--- waflib/extras/xcode6.py | 178 ++++++++++++++++--------------- 3 files changed, 120 insertions(+), 100 deletions(-) create mode 100644 playground/xcode6/src/sample.txt diff --git a/playground/xcode6/src/sample.txt b/playground/xcode6/src/sample.txt new file mode 100644 index 00000000..b68ede55 --- /dev/null +++ b/playground/xcode6/src/sample.txt @@ -0,0 +1 @@ +Sample text file. \ No newline at end of file diff --git a/playground/xcode6/wscript b/playground/xcode6/wscript index 89bd6102..f2d0d1be 100644 --- a/playground/xcode6/wscript +++ b/playground/xcode6/wscript @@ -20,6 +20,7 @@ a static library target and an executable target. The generated XCode project can then be opened and XCode can then build those targets. Tested with XCode 8. + """ def options(opt): @@ -45,46 +46,58 @@ def configure(conf): conf.check(cxxflags='-std=c++11', uselib_store='STD11', mandatory=False) def build(bld): + + # Make .framework targets tg = bld.framework( includes='include', - # 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={ - 'MyLibSource': bld.path.ant_glob('src/MyLib/*.cpp|*.m|*.mm'), - 'Include': bld.path.ant_glob(incl=['include/MyLib/*.h', 'include'], dir=True) - }, - - # source files for waf builds + # Source files 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), + # If you don't want the source files to appear in a default + # 'Source' folder, you can define your own folder structure + # using a dictionary, where the key is the desired name of the folder + # and the value are the files. + group_files={ + 'Source files': bld.path.ant_glob('src/MyLib/*.cpp|*.m|*.mm'), + 'Include': bld.path.ant_glob(incl=['include/MyLib/*.h'], dir=True), + 'Help': ['src/sample.txt'] + }, + + # If you want to ship your header files with your .framework, then + # specify them using the 'export_headers' param + export_headers=bld.path.ant_glob(incl=['include/MyLib/*.h', 'include/MyLib/SupportLib/*.h']), target='MyLib', + + # The 'install' param will set the INSTALL_PATH for the + # binary, and will also trigger XCode to copy the target to that + # path install='~/Library/Frameworks' ) + # Make .a static library targets bld.stlib( source=bld.path.ant_glob('src/MyLib/*.cpp'), includes = 'include', target='MyStaticLib', ) + # Make standard executable target bld.program( source=['src/test.cpp'], includes='include', target='MyExe', use='MyDynLib' ) + + # Make .dylib shared libraries bld.shlib( source=bld.path.ant_glob('src/MyLib/*.cpp'), includes='include', target='MyDynLib', ) - #bld.env.LIB_SDL2 = '/Library/Frameworks/SDL2.framework/SDL2' + # Make an app bundle target tg2 = bld.app( source=bld.path.ant_glob('src/*.cpp'), includes='include', @@ -96,5 +109,3 @@ def build(bld): # Override default setting in a target settings={"Debug": {"CONFIG_NAME": 'Debug'}} ) - - diff --git a/waflib/extras/xcode6.py b/waflib/extras/xcode6.py index 83dd5c50..47474a01 100644 --- a/waflib/extras/xcode6.py +++ b/waflib/extras/xcode6.py @@ -6,52 +6,10 @@ # XCode project file format based on http://www.monobjc.net/xcode-project-file-format.html """ -Usage: +See playground/xcode6/ for usage examples. -See also demos/xcode6/ folder - -def options(opt): - opt.load('compiler_cxx xcode6') - -def configure(cnf): - # For example - cnf.env.SDKROOT = 'macosx10.9' - - # Use cnf.PROJ_CONFIGURATION to completely set/override - # global project settings - # cnf.env.PROJ_CONFIGURATION = { - # 'Debug': { - # 'SDKROOT': 'macosx10.9' - # } - # 'MyCustomReleaseConfig': { - # 'SDKROOT': 'macosx10.10' - # } - # } - - # In the end of configure() do - cnf.load('compiler_cxx xcode6') - -def build(bld): - - # Make a Framework target - bld.framework( - source={ - 'Include': bld.path.ant_glob('include/MyLib/*.h'), - 'Source': bld.path.ant_glob('src/MyLib/*.cpp') - }, - includes='include', - 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 -# in your terminal and run the following: -# $ waf configure xcode6 """ -# TODO: support iOS projects(?) - from waflib import Context, TaskGen, Build, Utils, Errors, Logs import os, sys @@ -185,6 +143,13 @@ def newid(): id += 1 return "%04X%04X%04X%012d" % (0, 10000, 0, id) +""" +Represents a tree node in the XCode project plist file format. +When written to a file, all attributes of XCodeNode are stringified together with +its value. However, attributes starting with an underscore _ are ignored +during that process and allows you to store arbitray values that are not supposed +to be written out. +""" class XCodeNode: def __init__(self): self._id = newid() @@ -256,6 +221,7 @@ class XCConfigurationList(XCodeNode): # Group/Files class PBXFileReference(XCodeNode): def __init__(self, name, path, filetype = '', sourcetree = "SOURCE_ROOT"): + XCodeNode.__init__(self) self.fileEncoding = 4 if not filetype: @@ -291,16 +257,49 @@ class PBXBuildFile(XCodeNode): return self.fileRef == other.fileRef class PBXGroup(XCodeNode): - def __init__(self, name, sourcetree = ""): + def __init__(self, name, sourcetree = 'SOURCE_TREE'): XCodeNode.__init__(self) self.children = [] self.name = name self.sourceTree = sourcetree + # Maintain a lookup table for all PBXFileReferences + # that are contained in this group. + self._filerefs = {} + def add(self, sources): - """ sources param should be a list of PBXFileReference objects """ + """ + Add a list of PBXFileReferences to this group + + :param sources: list of PBXFileReferences objects + """ + self._filerefs.update(dict(zip(sources, sources))) self.children.extend(sources) + def get_sub_groups(self): + """ + Returns all child PBXGroup objects contained in this group + """ + return list(filter(lambda x: isinstance(x, PBXGroup), self.children)) + + def find_fileref(self, fileref): + """ + Recursively search this group for an existing PBXFileReference. Returns None + if none were found. + + The reason you'd want to reuse existing PBXFileReferences from a PBXGroup is that XCode doesn't like PBXFileReferences that aren't part of a PBXGroup heirarchy. + If it isn't, the consequence is that certain UI features like 'Reveal in Finder' + stops working. + """ + if fileref in self._filerefs: + return self._filerefs[fileref] + elif self.children: + for childgroup in self.get_sub_groups(): + f = childgroup.find_fileref(fileref) + if f: + return f + return None + class PBXContainerItemProxy(XCodeNode): """ This is the element for to decorate a target item. """ def __init__(self, containerPortal, remoteGlobalIDString, remoteInfo='', proxyType=1): @@ -501,29 +500,37 @@ def process_xcode(self): products_group.children.append(target.productReference) - if hasattr(self, 'source_files') or hasattr(self, 'source'): - # Create list of PBXFileReferences - sources = [] - if hasattr(self, 'source_files') and isinstance(self.source_files, dict): - for grpname,files in self.source_files.items(): - group = bld.create_group(grpname, files) - target_group.children.append(group) - sources.extend(group.children) - else: - src = getattr(self, 'source_files', []) or self.source - group = bld.create_group("Source", src) + # Pull source files from the 'source' attribute and assign them to a UI group. + # Use a default UI group named 'Source' unless the user + # provides a 'group_files' dictionary to customize the UI grouping. + sources = getattr(self, 'source', []) + if hasattr(self, 'group_files'): + group_files = getattr(self, 'group_files', []) + for grpname,files in group_files.items(): + group = bld.create_group(grpname, files) target_group.children.append(group) - sources.extend(group.children) + else: + group = bld.create_group('Source', sources) + target_group.children.append(group) - # FIXME too complex - sources = list(filter(lambda fileref: os.path.splitext(fileref.path)[1] in XCODE_EXTS, sources)) - buildfiles = [bld.unique_buildfile(PBXBuildFile(fileref)) for fileref in sources] - target.add_build_phase(PBXSourcesBuildPhase(buildfiles)) + # Create a PBXFileReference for each source file. + # If the source file already exists as a PBXFileReference in any of the UI groups, then + # reuse that PBXFileReference object (XCode does not like it if we don't reuse) + for idx, path in enumerate(sources): + fileref = PBXFileReference(path.name, path.abspath()) + existing_fileref = target_group.find_fileref(fileref) + if existing_fileref: + sources[idx] = existing_fileref + else: + sources[idx] = fileref - # Create build settings which can override the project settings. Defaults to none if user - # did not pass argument. However, this will be filled up further below with target specific - # search paths, libs to link etc. - settings = getattr(self, 'settings', {}) + # If the 'source' attribute contains any file extension that XCode can't work with, + # then remove it. The allowed file extensions are defined in XCODE_EXTS. + is_valid_file_extension = lambda file: os.path.splitext(file.path)[1] in XCODE_EXTS + sources = list(filter(is_valid_file_extension, sources)) + + buildfiles = [bld.unique_buildfile(PBXBuildFile(fileref)) for fileref in sources] + target.add_build_phase(PBXSourcesBuildPhase(buildfiles)) # Check if any framework to link against is some other target we've made libs = getattr(self, 'tmp_use_seen', []) @@ -531,8 +538,11 @@ def process_xcode(self): use_target = p.get_target(lib) if use_target: # Create an XCode dependency so that XCode knows to build the other target before this target - target.add_dependency(p.create_target_dependency(use_target, use_target.name)) - target.add_build_phase(PBXFrameworksBuildPhase([PBXBuildFile(use_target.productReference)])) + dependency = p.create_target_dependency(use_target, use_target.name) + target.add_dependency(dependency) + + buildphase = PBXFrameworksBuildPhase([PBXBuildFile(use_target.productReference)]) + target.add_build_phase(buildphase) if lib in self.env.LIB: self.env.LIB = list(filter(lambda x: x != lib, self.env.LIB)) @@ -540,8 +550,10 @@ def process_xcode(self): # These are files that'll get packed into the Framework for instance. exp_hdrs = getattr(self, 'export_headers', []) hdrs = bld.as_nodes(Utils.to_list(exp_hdrs)) - files = [bld.unique_filereference(PBXFileReference(n.name, n.abspath())) for n in hdrs] - target.add_build_phase(PBXHeadersBuildPhase([PBXBuildFile(f, {'ATTRIBUTES': ('Public',)}) for f in files])) + files = [p.mainGroup.find_fileref(PBXFileReference(n.name, n.abspath())) for n in hdrs] + files = [PBXBuildFile(f, {'ATTRIBUTES': ('Public',)}) for f in files] + buildphase = PBXHeadersBuildPhase(files) + target.add_build_phase(buildphase) # 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(self.env.FRAMEWORK)] @@ -566,6 +578,11 @@ def process_xcode(self): if not bldsettings['INSTALL_PATH']: del bldsettings['INSTALL_PATH'] + # Create build settings which can override the project settings. Defaults to none if user + # did not pass argument. This will be filled up with target specific + # search paths, libs to link etc. + settings = getattr(self, 'settings', {}) + # The keys represents different build configuration, e.g. Debug, Release and so on.. # Insert our generated build settings to all configuration names keys = set(settings.keys() + bld.env.PROJ_CONFIGURATION.keys()) @@ -593,6 +610,8 @@ class xcode(Build.BuildContext): d = x else: d = self.srcnode.find_node(x) + if not d: + raise Errors.WafError('File \'%s\' was not found' % x) nodes.append(d) return nodes @@ -606,24 +625,13 @@ class xcode(Build.BuildContext): Do not use unique file reference here, since XCode seem to allow only one file reference to be referenced by a group. """ - files = [(PBXFileReference(d.name, d.abspath())) for d in self.as_nodes(files)] - group.add(files) + files_ = [] + for d in self.as_nodes(Utils.to_list(files)): + fileref = PBXFileReference(d.name, d.abspath()) + files_.append(fileref) + group.add(files_) return group - def unique_filereference(self, ref): - """ - Returns a unique file reference, possibly an existing one if the paths are the same. - Use this after you've constructed a PBXFileReference to make sure there is - only one PBXFileReference for the same file in the same project. - """ - try: - file_refs = self.file_refs - except AttributeError: - file_refs = self.file_refs = {} - if ref not in file_refs: - file_refs[ref] = ref - return file_refs[ref] - def unique_buildfile(self, buildfile): """ Returns a unique buildfile, possibly an existing one.