1#! /usr/bin/env python 2# encoding: utf-8 3# XCode 3/XCode 4/XCode 6/Xcode 7 generator for Waf 4# Based on work by Nicolas Mercier 2011 5# Extended by Simon Warg 2015, https://github.com/mimon 6# XCode project file format based on http://www.monobjc.net/xcode-project-file-format.html 7 8""" 9See playground/xcode6/ for usage examples. 10 11""" 12 13from waflib import Context, TaskGen, Build, Utils, Errors, Logs 14import os, sys 15 16# FIXME too few extensions 17XCODE_EXTS = ['.c', '.cpp', '.m', '.mm'] 18 19HEADERS_GLOB = '**/(*.h|*.hpp|*.H|*.inl)' 20 21MAP_EXT = { 22 '': "folder", 23 '.h' : "sourcecode.c.h", 24 25 '.hh': "sourcecode.cpp.h", 26 '.inl': "sourcecode.cpp.h", 27 '.hpp': "sourcecode.cpp.h", 28 29 '.c': "sourcecode.c.c", 30 31 '.m': "sourcecode.c.objc", 32 33 '.mm': "sourcecode.cpp.objcpp", 34 35 '.cc': "sourcecode.cpp.cpp", 36 37 '.cpp': "sourcecode.cpp.cpp", 38 '.C': "sourcecode.cpp.cpp", 39 '.cxx': "sourcecode.cpp.cpp", 40 '.c++': "sourcecode.cpp.cpp", 41 42 '.l': "sourcecode.lex", # luthor 43 '.ll': "sourcecode.lex", 44 45 '.y': "sourcecode.yacc", 46 '.yy': "sourcecode.yacc", 47 48 '.plist': "text.plist.xml", 49 ".nib": "wrapper.nib", 50 ".xib": "text.xib", 51} 52 53# Used in PBXNativeTarget elements 54PRODUCT_TYPE_APPLICATION = 'com.apple.product-type.application' 55PRODUCT_TYPE_FRAMEWORK = 'com.apple.product-type.framework' 56PRODUCT_TYPE_EXECUTABLE = 'com.apple.product-type.tool' 57PRODUCT_TYPE_LIB_STATIC = 'com.apple.product-type.library.static' 58PRODUCT_TYPE_LIB_DYNAMIC = 'com.apple.product-type.library.dynamic' 59PRODUCT_TYPE_EXTENSION = 'com.apple.product-type.kernel-extension' 60PRODUCT_TYPE_IOKIT = 'com.apple.product-type.kernel-extension.iokit' 61 62# Used in PBXFileReference elements 63FILE_TYPE_APPLICATION = 'wrapper.cfbundle' 64FILE_TYPE_FRAMEWORK = 'wrapper.framework' 65FILE_TYPE_LIB_DYNAMIC = 'compiled.mach-o.dylib' 66FILE_TYPE_LIB_STATIC = 'archive.ar' 67FILE_TYPE_EXECUTABLE = 'compiled.mach-o.executable' 68 69# Tuple packs of the above 70TARGET_TYPE_FRAMEWORK = (PRODUCT_TYPE_FRAMEWORK, FILE_TYPE_FRAMEWORK, '.framework') 71TARGET_TYPE_APPLICATION = (PRODUCT_TYPE_APPLICATION, FILE_TYPE_APPLICATION, '.app') 72TARGET_TYPE_DYNAMIC_LIB = (PRODUCT_TYPE_LIB_DYNAMIC, FILE_TYPE_LIB_DYNAMIC, '.dylib') 73TARGET_TYPE_STATIC_LIB = (PRODUCT_TYPE_LIB_STATIC, FILE_TYPE_LIB_STATIC, '.a') 74TARGET_TYPE_EXECUTABLE = (PRODUCT_TYPE_EXECUTABLE, FILE_TYPE_EXECUTABLE, '') 75 76# Maps target type string to its data 77TARGET_TYPES = { 78 'framework': TARGET_TYPE_FRAMEWORK, 79 'app': TARGET_TYPE_APPLICATION, 80 'dylib': TARGET_TYPE_DYNAMIC_LIB, 81 'stlib': TARGET_TYPE_STATIC_LIB, 82 'exe' :TARGET_TYPE_EXECUTABLE, 83} 84 85def delete_invalid_values(dct): 86 """ Deletes entries that are dictionaries or sets """ 87 for k, v in list(dct.items()): 88 if isinstance(v, dict) or isinstance(v, set): 89 del dct[k] 90 return dct 91 92""" 93Configuration of the global project settings. Sets an environment variable 'PROJ_CONFIGURATION' 94which is a dictionary of configuration name and buildsettings pair. 95E.g.: 96env.PROJ_CONFIGURATION = { 97 'Debug': { 98 'ARCHS': 'x86', 99 ... 100 } 101 'Release': { 102 'ARCHS' x86_64' 103 ... 104 } 105} 106The user can define a completely customized dictionary in configure() stage. Otherwise a default Debug/Release will be created 107based on env variable 108""" 109def configure(self): 110 if not self.env.PROJ_CONFIGURATION: 111 self.to_log("A default project configuration was created since no custom one was given in the configure(conf) stage. Define your custom project settings by adding PROJ_CONFIGURATION to env. The env.PROJ_CONFIGURATION must be a dictionary with at least one key, where each key is the configuration name, and the value is a dictionary of key/value settings.\n") 112 113 # Check for any added config files added by the tool 'c_config'. 114 if 'cfg_files' in self.env: 115 self.env.INCLUDES = Utils.to_list(self.env.INCLUDES) + [os.path.abspath(os.path.dirname(f)) for f in self.env.cfg_files] 116 117 # Create default project configuration? 118 if 'PROJ_CONFIGURATION' not in self.env: 119 defaults = delete_invalid_values(self.env.get_merged_dict()) 120 self.env.PROJ_CONFIGURATION = { 121 "Debug": defaults, 122 "Release": defaults, 123 } 124 125 # Some build settings are required to be present by XCode. We will supply default values 126 # if user hasn't defined any. 127 defaults_required = [('PRODUCT_NAME', '$(TARGET_NAME)')] 128 for cfgname,settings in self.env.PROJ_CONFIGURATION.items(): 129 for default_var, default_val in defaults_required: 130 if default_var not in settings: 131 settings[default_var] = default_val 132 133 # Error check customization 134 if not isinstance(self.env.PROJ_CONFIGURATION, dict): 135 raise Errors.ConfigurationError("The env.PROJ_CONFIGURATION must be a dictionary with at least one key, where each key is the configuration name, and the value is a dictionary of key/value settings.") 136 137part1 = 0 138part2 = 10000 139part3 = 0 140id = 562000999 141def newid(): 142 global id 143 id += 1 144 return "%04X%04X%04X%012d" % (0, 10000, 0, id) 145 146""" 147Represents a tree node in the XCode project plist file format. 148When written to a file, all attributes of XCodeNode are stringified together with 149its value. However, attributes starting with an underscore _ are ignored 150during that process and allows you to store arbitrary values that are not supposed 151to be written out. 152""" 153class XCodeNode(object): 154 def __init__(self): 155 self._id = newid() 156 self._been_written = False 157 158 def tostring(self, value): 159 if isinstance(value, dict): 160 result = "{\n" 161 for k,v in value.items(): 162 result = result + "\t\t\t%s = %s;\n" % (k, self.tostring(v)) 163 result = result + "\t\t}" 164 return result 165 elif isinstance(value, str): 166 return "\"%s\"" % value 167 elif isinstance(value, list): 168 result = "(\n" 169 for i in value: 170 result = result + "\t\t\t%s,\n" % self.tostring(i) 171 result = result + "\t\t)" 172 return result 173 elif isinstance(value, XCodeNode): 174 return value._id 175 else: 176 return str(value) 177 178 def write_recursive(self, value, file): 179 if isinstance(value, dict): 180 for k,v in value.items(): 181 self.write_recursive(v, file) 182 elif isinstance(value, list): 183 for i in value: 184 self.write_recursive(i, file) 185 elif isinstance(value, XCodeNode): 186 value.write(file) 187 188 def write(self, file): 189 if not self._been_written: 190 self._been_written = True 191 for attribute,value in self.__dict__.items(): 192 if attribute[0] != '_': 193 self.write_recursive(value, file) 194 w = file.write 195 w("\t%s = {\n" % self._id) 196 w("\t\tisa = %s;\n" % self.__class__.__name__) 197 for attribute,value in self.__dict__.items(): 198 if attribute[0] != '_': 199 w("\t\t%s = %s;\n" % (attribute, self.tostring(value))) 200 w("\t};\n\n") 201 202# Configurations 203class XCBuildConfiguration(XCodeNode): 204 def __init__(self, name, settings = {}, env=None): 205 XCodeNode.__init__(self) 206 self.baseConfigurationReference = "" 207 self.buildSettings = settings 208 self.name = name 209 if env and env.ARCH: 210 settings['ARCHS'] = " ".join(env.ARCH) 211 212 213class XCConfigurationList(XCodeNode): 214 def __init__(self, configlst): 215 """ :param configlst: list of XCConfigurationList """ 216 XCodeNode.__init__(self) 217 self.buildConfigurations = configlst 218 self.defaultConfigurationIsVisible = 0 219 self.defaultConfigurationName = configlst and configlst[0].name or "" 220 221# Group/Files 222class PBXFileReference(XCodeNode): 223 def __init__(self, name, path, filetype = '', sourcetree = "SOURCE_ROOT"): 224 225 XCodeNode.__init__(self) 226 self.fileEncoding = 4 227 if not filetype: 228 _, ext = os.path.splitext(name) 229 filetype = MAP_EXT.get(ext, 'text') 230 self.lastKnownFileType = filetype 231 self.explicitFileType = filetype 232 self.name = name 233 self.path = path 234 self.sourceTree = sourcetree 235 236 def __hash__(self): 237 return (self.path+self.name).__hash__() 238 239 def __eq__(self, other): 240 return (self.path, self.name) == (other.path, other.name) 241 242class PBXBuildFile(XCodeNode): 243 """ This element indicate a file reference that is used in a PBXBuildPhase (either as an include or resource). """ 244 def __init__(self, fileRef, settings={}): 245 XCodeNode.__init__(self) 246 247 # fileRef is a reference to a PBXFileReference object 248 self.fileRef = fileRef 249 250 # A map of key/value pairs for additional settings. 251 self.settings = settings 252 253 def __hash__(self): 254 return (self.fileRef).__hash__() 255 256 def __eq__(self, other): 257 return self.fileRef == other.fileRef 258 259class PBXGroup(XCodeNode): 260 def __init__(self, name, sourcetree = 'SOURCE_TREE'): 261 XCodeNode.__init__(self) 262 self.children = [] 263 self.name = name 264 self.sourceTree = sourcetree 265 266 # Maintain a lookup table for all PBXFileReferences 267 # that are contained in this group. 268 self._filerefs = {} 269 270 def add(self, sources): 271 """ 272 Add a list of PBXFileReferences to this group 273 274 :param sources: list of PBXFileReferences objects 275 """ 276 self._filerefs.update(dict(zip(sources, sources))) 277 self.children.extend(sources) 278 279 def get_sub_groups(self): 280 """ 281 Returns all child PBXGroup objects contained in this group 282 """ 283 return list(filter(lambda x: isinstance(x, PBXGroup), self.children)) 284 285 def find_fileref(self, fileref): 286 """ 287 Recursively search this group for an existing PBXFileReference. Returns None 288 if none were found. 289 290 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 hierarchy. 291 If it isn't, the consequence is that certain UI features like 'Reveal in Finder' 292 stops working. 293 """ 294 if fileref in self._filerefs: 295 return self._filerefs[fileref] 296 elif self.children: 297 for childgroup in self.get_sub_groups(): 298 f = childgroup.find_fileref(fileref) 299 if f: 300 return f 301 return None 302 303class PBXContainerItemProxy(XCodeNode): 304 """ This is the element for to decorate a target item. """ 305 def __init__(self, containerPortal, remoteGlobalIDString, remoteInfo='', proxyType=1): 306 XCodeNode.__init__(self) 307 self.containerPortal = containerPortal # PBXProject 308 self.remoteGlobalIDString = remoteGlobalIDString # PBXNativeTarget 309 self.remoteInfo = remoteInfo # Target name 310 self.proxyType = proxyType 311 312class PBXTargetDependency(XCodeNode): 313 """ This is the element for referencing other target through content proxies. """ 314 def __init__(self, native_target, proxy): 315 XCodeNode.__init__(self) 316 self.target = native_target 317 self.targetProxy = proxy 318 319class PBXFrameworksBuildPhase(XCodeNode): 320 """ This is the element for the framework link build phase, i.e. linking to frameworks """ 321 def __init__(self, pbxbuildfiles): 322 XCodeNode.__init__(self) 323 self.buildActionMask = 2147483647 324 self.runOnlyForDeploymentPostprocessing = 0 325 self.files = pbxbuildfiles #List of PBXBuildFile (.o, .framework, .dylib) 326 327class PBXHeadersBuildPhase(XCodeNode): 328 """ This is the element for adding header files to be packaged into the .framework """ 329 def __init__(self, pbxbuildfiles): 330 XCodeNode.__init__(self) 331 self.buildActionMask = 2147483647 332 self.runOnlyForDeploymentPostprocessing = 0 333 self.files = pbxbuildfiles #List of PBXBuildFile (.o, .framework, .dylib) 334 335class PBXCopyFilesBuildPhase(XCodeNode): 336 """ 337 Represents the PBXCopyFilesBuildPhase section. PBXBuildFile 338 can be added to this node to copy files after build is done. 339 """ 340 def __init__(self, pbxbuildfiles, dstpath, dstSubpathSpec=0, *args, **kwargs): 341 XCodeNode.__init__(self) 342 self.files = pbxbuildfiles 343 self.dstPath = dstpath 344 self.dstSubfolderSpec = dstSubpathSpec 345 346class PBXSourcesBuildPhase(XCodeNode): 347 """ Represents the 'Compile Sources' build phase in a Xcode target """ 348 def __init__(self, buildfiles): 349 XCodeNode.__init__(self) 350 self.files = buildfiles # List of PBXBuildFile objects 351 352class PBXLegacyTarget(XCodeNode): 353 def __init__(self, action, target=''): 354 XCodeNode.__init__(self) 355 self.buildConfigurationList = XCConfigurationList([XCBuildConfiguration('waf', {})]) 356 if not target: 357 self.buildArgumentsString = "%s %s" % (sys.argv[0], action) 358 else: 359 self.buildArgumentsString = "%s %s --targets=%s" % (sys.argv[0], action, target) 360 self.buildPhases = [] 361 self.buildToolPath = sys.executable 362 self.buildWorkingDirectory = "" 363 self.dependencies = [] 364 self.name = target or action 365 self.productName = target or action 366 self.passBuildSettingsInEnvironment = 0 367 368class PBXShellScriptBuildPhase(XCodeNode): 369 def __init__(self, action, target): 370 XCodeNode.__init__(self) 371 self.buildActionMask = 2147483647 372 self.files = [] 373 self.inputPaths = [] 374 self.outputPaths = [] 375 self.runOnlyForDeploymentPostProcessing = 0 376 self.shellPath = "/bin/sh" 377 self.shellScript = "%s %s %s --targets=%s" % (sys.executable, sys.argv[0], action, target) 378 379class PBXNativeTarget(XCodeNode): 380 """ Represents a target in XCode, e.g. App, DyLib, Framework etc. """ 381 def __init__(self, target, node, target_type=TARGET_TYPE_APPLICATION, configlist=[], buildphases=[]): 382 XCodeNode.__init__(self) 383 product_type = target_type[0] 384 file_type = target_type[1] 385 386 self.buildConfigurationList = XCConfigurationList(configlist) 387 self.buildPhases = buildphases 388 self.buildRules = [] 389 self.dependencies = [] 390 self.name = target 391 self.productName = target 392 self.productType = product_type # See TARGET_TYPE_ tuples constants 393 self.productReference = PBXFileReference(node.name, node.abspath(), file_type, '') 394 395 def add_configuration(self, cf): 396 """ :type cf: XCBuildConfiguration """ 397 self.buildConfigurationList.buildConfigurations.append(cf) 398 399 def add_build_phase(self, phase): 400 # Some build phase types may appear only once. If a phase type already exists, then merge them. 401 if ( (phase.__class__ == PBXFrameworksBuildPhase) 402 or (phase.__class__ == PBXSourcesBuildPhase) ): 403 for b in self.buildPhases: 404 if b.__class__ == phase.__class__: 405 b.files.extend(phase.files) 406 return 407 self.buildPhases.append(phase) 408 409 def add_dependency(self, depnd): 410 self.dependencies.append(depnd) 411 412# Root project object 413class PBXProject(XCodeNode): 414 def __init__(self, name, version, env): 415 XCodeNode.__init__(self) 416 417 if not isinstance(env.PROJ_CONFIGURATION, dict): 418 raise Errors.WafError("Error: env.PROJ_CONFIGURATION must be a dictionary. This is done for you if you do not define one yourself. However, did you load the xcode module at the end of your wscript configure() ?") 419 420 # Retrieve project configuration 421 configurations = [] 422 for config_name, settings in env.PROJ_CONFIGURATION.items(): 423 cf = XCBuildConfiguration(config_name, settings) 424 configurations.append(cf) 425 426 self.buildConfigurationList = XCConfigurationList(configurations) 427 self.compatibilityVersion = version[0] 428 self.hasScannedForEncodings = 1 429 self.mainGroup = PBXGroup(name) 430 self.projectRoot = "" 431 self.projectDirPath = "" 432 self.targets = [] 433 self._objectVersion = version[1] 434 435 def create_target_dependency(self, target, name): 436 """ : param target : PXBNativeTarget """ 437 proxy = PBXContainerItemProxy(self, target, name) 438 dependency = PBXTargetDependency(target, proxy) 439 return dependency 440 441 def write(self, file): 442 443 # Make sure this is written only once 444 if self._been_written: 445 return 446 447 w = file.write 448 w("// !$*UTF8*$!\n") 449 w("{\n") 450 w("\tarchiveVersion = 1;\n") 451 w("\tclasses = {\n") 452 w("\t};\n") 453 w("\tobjectVersion = %d;\n" % self._objectVersion) 454 w("\tobjects = {\n\n") 455 456 XCodeNode.write(self, file) 457 458 w("\t};\n") 459 w("\trootObject = %s;\n" % self._id) 460 w("}\n") 461 462 def add_target(self, target): 463 self.targets.append(target) 464 465 def get_target(self, name): 466 """ Get a reference to PBXNativeTarget if it exists """ 467 for t in self.targets: 468 if t.name == name: 469 return t 470 return None 471 472@TaskGen.feature('c', 'cxx') 473@TaskGen.after('propagate_uselib_vars', 'apply_incpaths') 474def process_xcode(self): 475 bld = self.bld 476 try: 477 p = bld.project 478 except AttributeError: 479 return 480 481 if not hasattr(self, 'target_type'): 482 return 483 484 products_group = bld.products_group 485 486 target_group = PBXGroup(self.name) 487 p.mainGroup.children.append(target_group) 488 489 # Determine what type to build - framework, app bundle etc. 490 target_type = getattr(self, 'target_type', 'app') 491 if target_type not in TARGET_TYPES: 492 raise Errors.WafError("Target type '%s' does not exists. Available options are '%s'. In target '%s'" % (target_type, "', '".join(TARGET_TYPES.keys()), self.name)) 493 else: 494 target_type = TARGET_TYPES[target_type] 495 file_ext = target_type[2] 496 497 # Create the output node 498 target_node = self.path.find_or_declare(self.name+file_ext) 499 target = PBXNativeTarget(self.name, target_node, target_type, [], []) 500 501 products_group.children.append(target.productReference) 502 503 # Pull source files from the 'source' attribute and assign them to a UI group. 504 # Use a default UI group named 'Source' unless the user 505 # provides a 'group_files' dictionary to customize the UI grouping. 506 sources = getattr(self, 'source', []) 507 if hasattr(self, 'group_files'): 508 group_files = getattr(self, 'group_files', []) 509 for grpname,files in group_files.items(): 510 group = bld.create_group(grpname, files) 511 target_group.children.append(group) 512 else: 513 group = bld.create_group('Source', sources) 514 target_group.children.append(group) 515 516 # Create a PBXFileReference for each source file. 517 # If the source file already exists as a PBXFileReference in any of the UI groups, then 518 # reuse that PBXFileReference object (XCode does not like it if we don't reuse) 519 for idx, path in enumerate(sources): 520 fileref = PBXFileReference(path.name, path.abspath()) 521 existing_fileref = target_group.find_fileref(fileref) 522 if existing_fileref: 523 sources[idx] = existing_fileref 524 else: 525 sources[idx] = fileref 526 527 # If the 'source' attribute contains any file extension that XCode can't work with, 528 # then remove it. The allowed file extensions are defined in XCODE_EXTS. 529 is_valid_file_extension = lambda file: os.path.splitext(file.path)[1] in XCODE_EXTS 530 sources = list(filter(is_valid_file_extension, sources)) 531 532 buildfiles = [bld.unique_buildfile(PBXBuildFile(x)) for x in sources] 533 target.add_build_phase(PBXSourcesBuildPhase(buildfiles)) 534 535 # Check if any framework to link against is some other target we've made 536 libs = getattr(self, 'tmp_use_seen', []) 537 for lib in libs: 538 use_target = p.get_target(lib) 539 if use_target: 540 # Create an XCode dependency so that XCode knows to build the other target before this target 541 dependency = p.create_target_dependency(use_target, use_target.name) 542 target.add_dependency(dependency) 543 544 buildphase = PBXFrameworksBuildPhase([PBXBuildFile(use_target.productReference)]) 545 target.add_build_phase(buildphase) 546 if lib in self.env.LIB: 547 self.env.LIB = list(filter(lambda x: x != lib, self.env.LIB)) 548 549 # If 'export_headers' is present, add files to the Headers build phase in xcode. 550 # These are files that'll get packed into the Framework for instance. 551 exp_hdrs = getattr(self, 'export_headers', []) 552 hdrs = bld.as_nodes(Utils.to_list(exp_hdrs)) 553 files = [p.mainGroup.find_fileref(PBXFileReference(n.name, n.abspath())) for n in hdrs] 554 files = [PBXBuildFile(f, {'ATTRIBUTES': ('Public',)}) for f in files] 555 buildphase = PBXHeadersBuildPhase(files) 556 target.add_build_phase(buildphase) 557 558 # Merge frameworks and libs into one list, and prefix the frameworks 559 frameworks = Utils.to_list(self.env.FRAMEWORK) 560 frameworks = ' '.join(['-framework %s' % (f.split('.framework')[0]) for f in frameworks]) 561 562 libs = Utils.to_list(self.env.STLIB) + Utils.to_list(self.env.LIB) 563 libs = ' '.join(bld.env['STLIB_ST'] % t for t in libs) 564 565 # Override target specific build settings 566 bldsettings = { 567 'HEADER_SEARCH_PATHS': ['$(inherited)'] + self.env['INCPATHS'], 568 'LIBRARY_SEARCH_PATHS': ['$(inherited)'] + Utils.to_list(self.env.LIBPATH) + Utils.to_list(self.env.STLIBPATH) + Utils.to_list(self.env.LIBDIR) , 569 'FRAMEWORK_SEARCH_PATHS': ['$(inherited)'] + Utils.to_list(self.env.FRAMEWORKPATH), 570 'OTHER_LDFLAGS': libs + ' ' + frameworks, 571 'OTHER_LIBTOOLFLAGS': bld.env['LINKFLAGS'], 572 'OTHER_CPLUSPLUSFLAGS': Utils.to_list(self.env['CXXFLAGS']), 573 'OTHER_CFLAGS': Utils.to_list(self.env['CFLAGS']), 574 'INSTALL_PATH': [] 575 } 576 577 # Install path 578 installpaths = Utils.to_list(getattr(self, 'install', [])) 579 prodbuildfile = PBXBuildFile(target.productReference) 580 for instpath in installpaths: 581 bldsettings['INSTALL_PATH'].append(instpath) 582 target.add_build_phase(PBXCopyFilesBuildPhase([prodbuildfile], instpath)) 583 584 if not bldsettings['INSTALL_PATH']: 585 del bldsettings['INSTALL_PATH'] 586 587 # Create build settings which can override the project settings. Defaults to none if user 588 # did not pass argument. This will be filled up with target specific 589 # search paths, libs to link etc. 590 settings = getattr(self, 'settings', {}) 591 592 # The keys represents different build configuration, e.g. Debug, Release and so on.. 593 # Insert our generated build settings to all configuration names 594 keys = set(settings.keys() + bld.env.PROJ_CONFIGURATION.keys()) 595 for k in keys: 596 if k in settings: 597 settings[k].update(bldsettings) 598 else: 599 settings[k] = bldsettings 600 601 for k,v in settings.items(): 602 target.add_configuration(XCBuildConfiguration(k, v)) 603 604 p.add_target(target) 605 606 607class xcode(Build.BuildContext): 608 cmd = 'xcode6' 609 fun = 'build' 610 611 def as_nodes(self, files): 612 """ Returns a list of waflib.Nodes from a list of string of file paths """ 613 nodes = [] 614 for x in files: 615 if not isinstance(x, str): 616 d = x 617 else: 618 d = self.srcnode.find_node(x) 619 if not d: 620 raise Errors.WafError('File \'%s\' was not found' % x) 621 nodes.append(d) 622 return nodes 623 624 def create_group(self, name, files): 625 """ 626 Returns a new PBXGroup containing the files (paths) passed in the files arg 627 :type files: string 628 """ 629 group = PBXGroup(name) 630 """ 631 Do not use unique file reference here, since XCode seem to allow only one file reference 632 to be referenced by a group. 633 """ 634 files_ = [] 635 for d in self.as_nodes(Utils.to_list(files)): 636 fileref = PBXFileReference(d.name, d.abspath()) 637 files_.append(fileref) 638 group.add(files_) 639 return group 640 641 def unique_buildfile(self, buildfile): 642 """ 643 Returns a unique buildfile, possibly an existing one. 644 Use this after you've constructed a PBXBuildFile to make sure there is 645 only one PBXBuildFile for the same file in the same project. 646 """ 647 try: 648 build_files = self.build_files 649 except AttributeError: 650 build_files = self.build_files = {} 651 652 if buildfile not in build_files: 653 build_files[buildfile] = buildfile 654 return build_files[buildfile] 655 656 def execute(self): 657 """ 658 Entry point 659 """ 660 self.restore() 661 if not self.all_envs: 662 self.load_envs() 663 self.recurse([self.run_dir]) 664 665 appname = getattr(Context.g_module, Context.APPNAME, os.path.basename(self.srcnode.abspath())) 666 667 p = PBXProject(appname, ('Xcode 3.2', 46), self.env) 668 669 # If we don't create a Products group, then 670 # XCode will create one, which entails that 671 # we'll start to see duplicate files in the UI 672 # for some reason. 673 products_group = PBXGroup('Products') 674 p.mainGroup.children.append(products_group) 675 676 self.project = p 677 self.products_group = products_group 678 679 # post all task generators 680 # the process_xcode method above will be called for each target 681 if self.targets and self.targets != '*': 682 (self._min_grp, self._exact_tg) = self.get_targets() 683 684 self.current_group = 0 685 while self.current_group < len(self.groups): 686 self.post_group() 687 self.current_group += 1 688 689 node = self.bldnode.make_node('%s.xcodeproj' % appname) 690 node.mkdir() 691 node = node.make_node('project.pbxproj') 692 with open(node.abspath(), 'w') as f: 693 p.write(f) 694 Logs.pprint('GREEN', 'Wrote %r' % node.abspath()) 695 696def bind_fun(tgtype): 697 def fun(self, *k, **kw): 698 tgtype = fun.__name__ 699 if tgtype == 'shlib' or tgtype == 'dylib': 700 features = 'cxx cxxshlib' 701 tgtype = 'dylib' 702 elif tgtype == 'framework': 703 features = 'cxx cxxshlib' 704 tgtype = 'framework' 705 elif tgtype == 'program': 706 features = 'cxx cxxprogram' 707 tgtype = 'exe' 708 elif tgtype == 'app': 709 features = 'cxx cxxprogram' 710 tgtype = 'app' 711 elif tgtype == 'stlib': 712 features = 'cxx cxxstlib' 713 tgtype = 'stlib' 714 lst = kw['features'] = Utils.to_list(kw.get('features', [])) 715 for x in features.split(): 716 if not x in kw['features']: 717 lst.append(x) 718 719 kw['target_type'] = tgtype 720 return self(*k, **kw) 721 fun.__name__ = tgtype 722 setattr(Build.BuildContext, tgtype, fun) 723 return fun 724 725for xx in 'app framework dylib shlib stlib program'.split(): 726 bind_fun(xx) 727 728