1-- 2-- xcode_common.lua 3-- Functions to generate the different sections of an Xcode project. 4-- Copyright (c) 2009-2015 Jason Perkins and the Premake project 5-- 6 7 local p = premake 8 local xcode = p.modules.xcode 9 local tree = p.tree 10 local workspace = p.workspace 11 local project = p.project 12 local config = p.config 13 local fileconfig = p.fileconfig 14 15 16-- 17-- Return the Xcode build category for a given file, based on the file extension. 18-- 19-- @param node 20-- The node to identify. 21-- @returns 22-- An Xcode build category, one of "Sources", "Resources", "Frameworks", or nil. 23-- 24 25 function xcode.getbuildcategory(node) 26 local categories = { 27 [".a"] = "Frameworks", 28 [".c"] = "Sources", 29 [".cc"] = "Sources", 30 [".cpp"] = "Sources", 31 [".cxx"] = "Sources", 32 [".dylib"] = "Frameworks", 33 [".framework"] = "Frameworks", 34 [".m"] = "Sources", 35 [".mm"] = "Sources", 36 [".strings"] = "Resources", 37 [".nib"] = "Resources", 38 [".xib"] = "Resources", 39 [".storyboard"] = "Resources", 40 [".icns"] = "Resources", 41 [".s"] = "Sources", 42 [".S"] = "Sources", 43 } 44 if node.isResource then 45 return "Resources" 46 end 47 return categories[path.getextension(node.name)] 48 end 49 50 function xcode.isItemResource(project, node) 51 52 local res; 53 54 if project and project.xcodebuildresources then 55 if type(project.xcodebuildresources) == "table" then 56 res = project.xcodebuildresources 57 end 58 end 59 60 local function checkItemInList(item, list) 61 if item then 62 if list then 63 if type(list) == "table" then 64 for _,v in pairs(list) do 65 if string.find(item, v) then 66 return true 67 end 68 end 69 end 70 end 71 end 72 return false 73 end 74 75 --print (node.path, node.buildid, node.cfg, res) 76 if (checkItemInList(node.path, res)) then 77 return true 78 end 79 80 return false 81 end 82-- 83-- Return the Xcode type for a given file, based on the file extension. 84-- 85-- @param fname 86-- The file name to identify. 87-- @returns 88-- An Xcode file type, string. 89-- 90 91 function xcode.getfiletype(node, cfg) 92 93 if node.configs then 94 local filecfg = fileconfig.getconfig(node, cfg) 95 if filecfg then 96 if filecfg.language == "ObjC" then 97 return "sourcecode.c.objc" 98 elseif filecfg.language == "ObjCpp" then 99 return "sourcecode.cpp.objcpp" 100 end 101 end 102 end 103 104 local types = { 105 [".c"] = "sourcecode.c.c", 106 [".cc"] = "sourcecode.cpp.cpp", 107 [".cpp"] = "sourcecode.cpp.cpp", 108 [".css"] = "text.css", 109 [".cxx"] = "sourcecode.cpp.cpp", 110 [".S"] = "sourcecode.asm.asm", 111 [".framework"] = "wrapper.framework", 112 [".gif"] = "image.gif", 113 [".h"] = "sourcecode.c.h", 114 [".html"] = "text.html", 115 [".lua"] = "sourcecode.lua", 116 [".m"] = "sourcecode.c.objc", 117 [".mm"] = "sourcecode.cpp.objc", 118 [".nib"] = "wrapper.nib", 119 [".storyboard"] = "file.storyboard", 120 [".pch"] = "sourcecode.c.h", 121 [".plist"] = "text.plist.xml", 122 [".strings"] = "text.plist.strings", 123 [".xib"] = "file.xib", 124 [".icns"] = "image.icns", 125 [".s"] = "sourcecode.asm", 126 [".bmp"] = "image.bmp", 127 [".wav"] = "audio.wav", 128 [".xcassets"] = "folder.assetcatalog", 129 130 } 131 return types[path.getextension(node.path)] or "text" 132 end 133 134-- 135-- Print user configuration references contained in xcodeconfigreferences 136-- @param offset 137-- offset used by function _p 138-- @param cfg 139-- configuration 140-- 141 142 local function xcodePrintUserConfigReferences(offset, cfg, tr, kind) 143 local referenceName 144 if kind == "project" then 145 referenceName = cfg.xcodeconfigreferenceproject 146 elseif kind == "target" then 147 referenceName = cfg.xcodeconfigreferencetarget 148 end 149 tree.traverse(tr, { 150 onleaf = function(node) 151 filename = node.name 152 if node.id and path.getextension(filename) == ".xcconfig" then 153 if filename == referenceName then 154 _p(offset, 'baseConfigurationReference = %s /* %s */;', node.id, filename) 155 return 156 end 157 end 158 end 159 }, false) 160 end 161 162 163 164 local escapeSpecialChars = { 165 ['\n'] = '\\n', 166 ['\r'] = '\\r', 167 ['\t'] = '\\t', 168 } 169 170 local function escapeChar(c) 171 return escapeSpecialChars[c] or '\\'..c 172 end 173 174 local function escapeArg(value) 175 value = value:gsub('[\'"\\\n\r\t ]', escapeChar) 176 return value 177 end 178 179 local function escapeSetting(value) 180 value = value:gsub('["\\\n\r\t]', escapeChar) 181 return value 182 end 183 184 local function stringifySetting(value) 185 value = value..'' 186 if not value:match('^[%a%d_./]+$') then 187 value = '"'..escapeSetting(value)..'"' 188 end 189 return value 190 end 191 192 local function customStringifySetting(value) 193 value = value..'' 194 195 local test = value:match('^[%a%d_./%+]+$') 196 if test then 197 value = '"'..escapeSetting(value)..'"' 198 end 199 return value 200 end 201 202 local function printSetting(level, name, value) 203 if type(value) == 'function' then 204 value(level, name) 205 elseif type(value) ~= 'table' then 206 _p(level, '%s = %s;', stringifySetting(name), stringifySetting(value)) 207 --elseif #value == 1 then 208 --_p(level, '%s = %s;', stringifySetting(name), stringifySetting(value[1])) 209 elseif #value >= 1 then 210 _p(level, '%s = (', stringifySetting(name)) 211 for _, item in ipairs(value) do 212 _p(level + 1, '%s,', stringifySetting(item)) 213 end 214 _p(level, ');') 215 end 216 end 217 218 local function printSettingsTable(level, settings) 219 -- Maintain alphabetic order to be consistent 220 local keys = table.keys(settings) 221 table.sort(keys) 222 for _, k in ipairs(keys) do 223 printSetting(level, k, settings[k]) 224 end 225 end 226 227 local function overrideSettings(settings, overrides) 228 if type(overrides) == 'table' then 229 for name, value in pairs(overrides) do 230 -- Allow an override to remove a value by using false 231 settings[name] = iif(value ~= false, value, nil) 232 end 233 end 234 end 235 236-- 237-- Return the Xcode product type, based target kind. 238-- 239-- @param node 240-- The product node to identify. 241-- @returns 242-- An Xcode product type, string. 243-- 244 245 function xcode.getproducttype(node) 246 local types = { 247 ConsoleApp = "com.apple.product-type.tool", 248 WindowedApp = "com.apple.product-type.application", 249 StaticLib = "com.apple.product-type.library.static", 250 SharedLib = "com.apple.product-type.library.dynamic", 251 } 252 return types[node.cfg.kind] 253 end 254 255 256-- 257-- Return the Xcode target type, based on the target file extension. 258-- 259-- @param node 260-- The product node to identify. 261-- @returns 262-- An Xcode target type, string. 263-- 264 265 function xcode.gettargettype(node) 266 local types = { 267 ConsoleApp = "\"compiled.mach-o.executable\"", 268 WindowedApp = "wrapper.application", 269 StaticLib = "archive.ar", 270 SharedLib = "\"compiled.mach-o.dylib\"", 271 } 272 return types[node.cfg.kind] 273 end 274 275 276-- 277-- Return a unique file name for a project. Since Xcode uses .xcodeproj's to 278-- represent both workspaces and projects there is a likely change of a name 279-- collision. Tack on a number to differentiate them. 280-- 281-- @param prj 282-- The project being queried. 283-- @returns 284-- A uniqued file name 285-- 286 287 function xcode.getxcodeprojname(prj) 288 -- if there is a workspace with matching name, then use "projectname1.xcodeproj" 289 -- just get something working for now 290 local fname = p.filename(prj, ".xcodeproj") 291 return fname 292 end 293 294 295-- 296-- Returns true if the file name represents a framework. 297-- 298-- @param fname 299-- The name of the file to test. 300-- 301 302 function xcode.isframework(fname) 303 return (path.getextension(fname) == ".framework") 304 end 305 306 307-- 308-- Retrieves a unique 12 byte ID for an object. 309-- This function accepts an array of parameters that will be used to generate the id. 310-- 311-- @returns 312-- A 24-character string representing the 12 byte ID. 313-- 314 315 function xcode.newid(...) 316 local name = '' 317 local arg = {...} 318 for i, v in pairs(arg) do 319 name = name..v..'****' 320 end 321 322 323 return ("%08X%08X%08X"):format(name:hash(16777619), name:hash(2166136261), name:hash(46577619)) 324 end 325 326 327-- 328-- Create a product tree node and all projects in a workspace; assigning IDs 329-- that are needed for inter-project dependencies. 330-- 331-- @param wks 332-- The workspace to prepare. 333-- 334 335 function xcode.prepareWorkspace(wks) 336 -- create and cache a list of supported platforms 337 wks.xcode = { } 338 339 for prj in p.workspace.eachproject(wks) do 340 -- need a configuration to get the target information 341 local cfg = project.getconfig(prj, prj.configurations[1], prj.platforms[1]) 342 343 -- build the product tree node 344 local bundlepath = cfg.buildtarget.bundlename ~= "" and cfg.buildtarget.bundlename or cfg.buildtarget.name; 345 if (prj.external) then 346 bundlepath = cfg.project.name 347 end 348 349 local node = p.tree.new(path.getname(bundlepath)) 350 351 node.cfg = cfg 352 node.id = xcode.newid(node.name, "product") 353 node.targetid = xcode.newid(node.name, "target") 354 355 -- attach it to the project 356 prj.xcode = {} 357 prj.xcode.projectnode = node 358 end 359 end 360 361 362--------------------------------------------------------------------------- 363-- Section generator functions, in the same order in which they appear 364-- in the .pbxproj file 365--------------------------------------------------------------------------- 366 367 function xcode.PBXBuildFile(tr) 368 local settings = {}; 369 tree.traverse(tr, { 370 onnode = function(node) 371 if node.buildid then 372 settings[node.buildid] = function(level) 373 _p(level,'%s /* %s in %s */ = {isa = PBXBuildFile; fileRef = %s /* %s */; };', 374 node.buildid, node.name, xcode.getbuildcategory(node), node.id, node.name) 375 end 376 end 377 end 378 }) 379 380 if not table.isempty(settings) then 381 _p('/* Begin PBXBuildFile section */') 382 printSettingsTable(2, settings); 383 _p('/* End PBXBuildFile section */') 384 _p('') 385 end 386 end 387 388 389 function xcode.PBXContainerItemProxy(tr) 390 local settings = {} 391 for _, node in ipairs(tr.projects.children) do 392 settings[node.productproxyid] = function() 393 _p(2,'%s /* PBXContainerItemProxy */ = {', node.productproxyid) 394 _p(3,'isa = PBXContainerItemProxy;') 395 _p(3,'containerPortal = %s /* %s */;', node.id, path.getrelative(node.parent.parent.project.location, node.path)) 396 _p(3,'proxyType = 2;') 397 _p(3,'remoteGlobalIDString = %s;', node.project.xcode.projectnode.id) 398 _p(3,'remoteInfo = %s;', stringifySetting(node.project.xcode.projectnode.name)) 399 _p(2,'};') 400 end 401 settings[node.targetproxyid] = function() 402 _p(2,'%s /* PBXContainerItemProxy */ = {', node.targetproxyid) 403 _p(3,'isa = PBXContainerItemProxy;') 404 _p(3,'containerPortal = %s /* %s */;', node.id, path.getrelative(node.parent.parent.project.location, node.path)) 405 _p(3,'proxyType = 1;') 406 _p(3,'remoteGlobalIDString = %s;', node.project.xcode.projectnode.targetid) 407 _p(3,'remoteInfo = %s;', stringifySetting(node.project.xcode.projectnode.name)) 408 _p(2,'};') 409 end 410 end 411 412 if not table.isempty(settings) then 413 _p('/* Begin PBXContainerItemProxy section */') 414 printSettingsTable(2, settings); 415 _p('/* End PBXContainerItemProxy section */') 416 _p('') 417 end 418 end 419 420 421 function xcode.PBXFileReference(tr) 422 local cfg = project.getfirstconfig(tr.project) 423 local settings = {} 424 425 tree.traverse(tr, { 426 onleaf = function(node) 427 -- I'm only listing files here, so ignore anything without a path 428 if not node.path then 429 return 430 end 431 432 -- is this the product node, describing the output target? 433 if node.kind == "product" then 434 settings[node.id] = function(level) 435 _p(level,'%s /* %s */ = {isa = PBXFileReference; explicitFileType = %s; includeInIndex = 0; name = %s; path = %s; sourceTree = BUILT_PRODUCTS_DIR; };', 436 node.id, node.name, xcode.gettargettype(node), stringifySetting(node.name), stringifySetting(path.getname(node.cfg.buildtarget.bundlename ~= "" and node.cfg.buildtarget.bundlename or node.cfg.buildtarget.relpath))) 437 end 438 -- is this a project dependency? 439 elseif node.parent.parent == tr.projects then 440 settings[node.parent.id] = function(level) 441 -- ms Is there something wrong with path is relative ? 442 -- if we have a and b without slashes get relative should assume the same parent folder and return ../ 443 -- this works if we put it like below 444 local relpath = path.getrelative(path.getabsolute(tr.project.location), path.getabsolute(node.parent.project.location)) 445 _p(level,'%s /* %s */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = %s; path = %s; sourceTree = SOURCE_ROOT; };', 446 node.parent.id, node.name, customStringifySetting(node.parent.name), stringifySetting(path.join(relpath, node.parent.name))) 447 end 448 -- something else 449 else 450 settings[node.id] = function(level) 451 local pth, src 452 if xcode.isframework(node.path) then 453 --respect user supplied paths 454 -- look for special variable-starting paths for different sources 455 local nodePath = node.path 456 local _, matchEnd, variable = string.find(nodePath, "^%$%((.+)%)/") 457 if variable then 458 -- by skipping the last '/' we support the same absolute/relative 459 -- paths as before 460 nodePath = string.sub(nodePath, matchEnd + 1) 461 end 462 if string.find(nodePath,'/') then 463 if string.find(nodePath,'^%.')then 464 --error('relative paths are not currently supported for frameworks') 465 pth = path.getrelative(tr.project.location, node.path) 466 --print(tr.project.location, node.path , pth) 467 src = "SOURCE_ROOT" 468 variable = src 469 else 470 pth = nodePath 471 src = "<absolute>" 472 end 473 end 474 -- if it starts with a variable, use that as the src instead 475 if variable then 476 src = variable 477 -- if we are using a different source tree, it has to be relative 478 -- to that source tree, so get rid of any leading '/' 479 if string.find(pth, '^/') then 480 pth = string.sub(pth, 2) 481 end 482 else 483 pth = "System/Library/Frameworks/" .. node.path 484 src = "SDKROOT" 485 end 486 else 487 -- something else; probably a source code file 488 src = "<group>" 489 490 if node.abspath then 491 pth = path.getrelative(tr.project.location, node.abspath) 492 else 493 pth = node.path 494 end 495 --end 496 end 497 _p(level,'%s /* %s */ = {isa = PBXFileReference; lastKnownFileType = %s; name = %s; path = %s; sourceTree = %s; };', 498 node.id, node.name, xcode.getfiletype(node, cfg), stringifySetting(node.name), stringifySetting(pth), stringifySetting(src)) 499 end 500 end 501 end 502 }) 503 504 if not table.isempty(settings) then 505 _p('/* Begin PBXFileReference section */') 506 printSettingsTable(2, settings) 507 _p('/* End PBXFileReference section */') 508 _p('') 509 end 510 end 511 512 513 function xcode.PBXFrameworksBuildPhase(tr) 514 _p('/* Begin PBXFrameworksBuildPhase section */') 515 _p(2,'%s /* Frameworks */ = {', tr.products.children[1].fxstageid) 516 _p(3,'isa = PBXFrameworksBuildPhase;') 517 _p(3,'buildActionMask = 2147483647;') 518 _p(3,'files = (') 519 520 -- write out library dependencies 521 tree.traverse(tr.frameworks, { 522 onleaf = function(node) 523 if node.buildid then 524 _p(4,'%s /* %s in Frameworks */,', node.buildid, node.name) 525 end 526 end 527 }) 528 529 -- write out project dependencies 530 tree.traverse(tr.projects, { 531 onleaf = function(node) 532 if node.buildid then 533 _p(4,'%s /* %s in Frameworks */,', node.buildid, node.name) 534 end 535 end 536 }) 537 538 _p(3,');') 539 _p(3,'runOnlyForDeploymentPostprocessing = 0;') 540 _p(2,'};') 541 _p('/* End PBXFrameworksBuildPhase section */') 542 _p('') 543 end 544 545 546 function xcode.PBXGroup(tr) 547 local settings = {} 548 549 tree.traverse(tr, { 550 onnode = function(node) 551 -- Skip over anything that isn't a proper group 552 if (node.path and #node.children == 0) or node.kind == "vgroup" then 553 return 554 end 555 556 settings[node.productgroupid or node.id] = function() 557 -- project references get special treatment 558 if node.parent == tr.projects then 559 _p(2,'%s /* Products */ = {', node.productgroupid) 560 else 561 _p(2,'%s /* %s */ = {', node.id, node.name) 562 end 563 564 _p(3,'isa = PBXGroup;') 565 _p(3,'children = (') 566 for _, childnode in ipairs(node.children) do 567 _p(4,'%s /* %s */,', childnode.id, childnode.name) 568 end 569 _p(3,');') 570 571 if node.parent == tr.projects then 572 _p(3,'name = Products;') 573 else 574 _p(3,'name = %s;', stringifySetting(node.name)) 575 576 local vpath = project.getvpath(tr.project, node.name) 577 578 if node.path and node.name ~= vpath then 579 local p = node.path 580 if node.parent.path then 581 p = path.getrelative(node.parent.path, node.path) 582 end 583 _p(3,'path = %s;', stringifySetting(p)) 584 end 585 end 586 587 _p(3,'sourceTree = "<group>";') 588 _p(2,'};') 589 end 590 end 591 }, true) 592 593 if not table.isempty(settings) then 594 _p('/* Begin PBXGroup section */') 595 printSettingsTable(2, settings) 596 _p('/* End PBXGroup section */') 597 _p('') 598 end 599 end 600 601 602 function xcode.PBXNativeTarget(tr) 603 _p('/* Begin PBXNativeTarget section */') 604 for _, node in ipairs(tr.products.children) do 605 local name = tr.project.name 606 607 -- This function checks whether there are build commands of a specific 608 -- type to be executed; they will be generated correctly, but the project 609 -- commands will not contain any per-configuration commands, so the logic 610 -- has to be extended a bit to account for that. 611 local function hasBuildCommands(which) 612 -- standard check...this is what existed before 613 if #tr.project[which] > 0 then 614 return true 615 end 616 -- what if there are no project-level commands? check configs... 617 for _, cfg in ipairs(tr.configs) do 618 if #cfg[which] > 0 then 619 return true 620 end 621 end 622 end 623 624 _p(2,'%s /* %s */ = {', node.targetid, name) 625 _p(3,'isa = PBXNativeTarget;') 626 _p(3,'buildConfigurationList = %s /* Build configuration list for PBXNativeTarget "%s" */;', node.cfgsection, escapeSetting(name)) 627 _p(3,'buildPhases = (') 628 if hasBuildCommands('prebuildcommands') then 629 _p(4,'9607AE1010C857E500CD1376 /* Prebuild */,') 630 end 631 _p(4,'%s /* Resources */,', node.resstageid) 632 _p(4,'%s /* Sources */,', node.sourcesid) 633 if hasBuildCommands('prelinkcommands') then 634 _p(4,'9607AE3510C85E7E00CD1376 /* Prelink */,') 635 end 636 _p(4,'%s /* Frameworks */,', node.fxstageid) 637 if hasBuildCommands('postbuildcommands') then 638 _p(4,'9607AE3710C85E8F00CD1376 /* Postbuild */,') 639 end 640 _p(3,');') 641 _p(3,'buildRules = (') 642 _p(3,');') 643 644 _p(3,'dependencies = (') 645 for _, node in ipairs(tr.projects.children) do 646 _p(4,'%s /* PBXTargetDependency */,', node.targetdependid) 647 end 648 _p(3,');') 649 650 _p(3,'name = %s;', stringifySetting(name)) 651 652 local p 653 if node.cfg.kind == "ConsoleApp" then 654 p = "$(HOME)/bin" 655 elseif node.cfg.kind == "WindowedApp" then 656 p = "$(HOME)/Applications" 657 end 658 if p then 659 _p(3,'productInstallPath = %s;', stringifySetting(p)) 660 end 661 662 _p(3,'productName = %s;', stringifySetting(name)) 663 _p(3,'productReference = %s /* %s */;', node.id, node.name) 664 _p(3,'productType = %s;', stringifySetting(xcode.getproducttype(node))) 665 _p(2,'};') 666 end 667 _p('/* End PBXNativeTarget section */') 668 _p('') 669 end 670 671 672 function xcode.PBXProject(tr) 673 _p('/* Begin PBXProject section */') 674 _p(2,'08FB7793FE84155DC02AAC07 /* Project object */ = {') 675 _p(3,'isa = PBXProject;') 676 _p(3,'buildConfigurationList = 1DEB928908733DD80010E9CD /* Build configuration list for PBXProject "%s" */;', tr.name) 677 _p(3,'compatibilityVersion = "Xcode 3.2";') 678 _p(3,'hasScannedForEncodings = 1;') 679 _p(3,'mainGroup = %s /* %s */;', tr.id, tr.name) 680 _p(3,'projectDirPath = "";') 681 682 if #tr.projects.children > 0 then 683 _p(3,'projectReferences = (') 684 for _, node in ipairs(tr.projects.children) do 685 _p(4,'{') 686 _p(5,'ProductGroup = %s /* Products */;', node.productgroupid) 687 _p(5,'ProjectRef = %s /* %s */;', node.id, path.getname(node.path)) 688 _p(4,'},') 689 end 690 _p(3,');') 691 end 692 693 _p(3,'projectRoot = "";') 694 _p(3,'targets = (') 695 for _, node in ipairs(tr.products.children) do 696 _p(4,'%s /* %s */,', node.targetid, node.name) 697 end 698 _p(3,');') 699 _p(2,'};') 700 _p('/* End PBXProject section */') 701 _p('') 702 end 703 704 705 function xcode.PBXReferenceProxy(tr) 706 local settings = {} 707 708 tree.traverse(tr.projects, { 709 onleaf = function(node) 710 settings[node.id] = function() 711 _p(2,'%s /* %s */ = {', node.id, node.name) 712 _p(3,'isa = PBXReferenceProxy;') 713 _p(3,'fileType = %s;', xcode.gettargettype(node)) 714 _p(3,'path = %s;', stringifySetting(node.name)) 715 _p(3,'remoteRef = %s /* PBXContainerItemProxy */;', node.parent.productproxyid) 716 _p(3,'sourceTree = BUILT_PRODUCTS_DIR;') 717 _p(2,'};') 718 end 719 end 720 }) 721 722 if not table.isempty(settings) then 723 _p('/* Begin PBXReferenceProxy section */') 724 printSettingsTable(2, settings) 725 _p('/* End PBXReferenceProxy section */') 726 _p('') 727 end 728 end 729 730 731 function xcode.PBXResourcesBuildPhase(tr) 732 _p('/* Begin PBXResourcesBuildPhase section */') 733 for _, target in ipairs(tr.products.children) do 734 _p(2,'%s /* Resources */ = {', target.resstageid) 735 _p(3,'isa = PBXResourcesBuildPhase;') 736 _p(3,'buildActionMask = 2147483647;') 737 _p(3,'files = (') 738 tree.traverse(tr, { 739 onnode = function(node) 740 if xcode.getbuildcategory(node) == "Resources" then 741 _p(4,'%s /* %s in Resources */,', node.buildid, node.name) 742 end 743 end 744 }) 745 _p(3,');') 746 _p(3,'runOnlyForDeploymentPostprocessing = 0;') 747 _p(2,'};') 748 end 749 _p('/* End PBXResourcesBuildPhase section */') 750 _p('') 751 end 752 753 function xcode.PBXShellScriptBuildPhase(tr) 754 local wrapperWritten = false 755 756 local function doblock(id, name, which) 757 -- start with the project-level commands (most common) 758 local prjcmds = tr.project[which] 759 local commands = table.join(prjcmds, {}) 760 761 -- see if there are any config-specific commands to add 762 for _, cfg in ipairs(tr.configs) do 763 local cfgcmds = cfg[which] 764 if #cfgcmds > #prjcmds then 765 table.insert(commands, 'if [ "${CONFIGURATION}" = "' .. cfg.buildcfg .. '" ]; then') 766 for i = #prjcmds + 1, #cfgcmds do 767 table.insert(commands, cfgcmds[i]) 768 end 769 table.insert(commands, 'fi') 770 end 771 end 772 773 if #commands > 0 then 774 commands = os.translateCommands(commands, p.MACOSX) 775 if not wrapperWritten then 776 _p('/* Begin PBXShellScriptBuildPhase section */') 777 wrapperWritten = true 778 end 779 _p(2,'%s /* %s */ = {', id, name) 780 _p(3,'isa = PBXShellScriptBuildPhase;') 781 _p(3,'buildActionMask = 2147483647;') 782 _p(3,'files = (') 783 _p(3,');') 784 _p(3,'inputPaths = ('); 785 _p(3,');'); 786 _p(3,'name = %s;', name); 787 _p(3,'outputPaths = ('); 788 _p(3,');'); 789 _p(3,'runOnlyForDeploymentPostprocessing = 0;'); 790 _p(3,'shellPath = /bin/sh;'); 791 _p(3,'shellScript = %s;', stringifySetting(table.concat(commands, '\n'))) 792 _p(2,'};') 793 end 794 end 795 796 doblock("9607AE1010C857E500CD1376", "Prebuild", "prebuildcommands") 797 doblock("9607AE3510C85E7E00CD1376", "Prelink", "prelinkcommands") 798 doblock("9607AE3710C85E8F00CD1376", "Postbuild", "postbuildcommands") 799 800 if wrapperWritten then 801 _p('/* End PBXShellScriptBuildPhase section */') 802 end 803 end 804 805 806 function xcode.PBXSourcesBuildPhase(tr) 807 _p('/* Begin PBXSourcesBuildPhase section */') 808 for _, target in ipairs(tr.products.children) do 809 _p(2,'%s /* Sources */ = {', target.sourcesid) 810 _p(3,'isa = PBXSourcesBuildPhase;') 811 _p(3,'buildActionMask = 2147483647;') 812 _p(3,'files = (') 813 tree.traverse(tr, { 814 onleaf = function(node) 815 if xcode.getbuildcategory(node) == "Sources" then 816 _p(4,'%s /* %s in Sources */,', node.buildid, node.name) 817 end 818 end 819 }) 820 _p(3,');') 821 _p(3,'runOnlyForDeploymentPostprocessing = 0;') 822 _p(2,'};') 823 end 824 _p('/* End PBXSourcesBuildPhase section */') 825 _p('') 826 end 827 828 829 function xcode.PBXVariantGroup(tr) 830 local settings = {} 831 tree.traverse(tr, { 832 onbranch = function(node) 833 settings[node.id] = function() 834 if node.kind == "vgroup" then 835 _p(2,'%s /* %s */ = {', node.id, node.name) 836 _p(3,'isa = PBXVariantGroup;') 837 _p(3,'children = (') 838 for _, lang in ipairs(node.children) do 839 _p(4,'%s /* %s */,', lang.id, lang.name) 840 end 841 _p(3,');') 842 _p(3,'name = %s;', node.name) 843 _p(3,'sourceTree = "<group>";') 844 _p(2,'};') 845 end 846 end 847 end 848 }) 849 850 if not table.isempty(settings) then 851 _p('/* Begin PBXVariantGroup section */') 852 printSettingsTable(2, settings) 853 _p('/* End PBXVariantGroup section */') 854 _p('') 855 end 856 end 857 858 859 function xcode.PBXTargetDependency(tr) 860 local settings = {} 861 tree.traverse(tr.projects, { 862 onleaf = function(node) 863 settings[node.parent.targetdependid] = function() 864 _p(2,'%s /* PBXTargetDependency */ = {', node.parent.targetdependid) 865 _p(3,'isa = PBXTargetDependency;') 866 _p(3,'name = %s;', stringifySetting(node.name)) 867 _p(3,'targetProxy = %s /* PBXContainerItemProxy */;', node.parent.targetproxyid) 868 _p(2,'};') 869 end 870 end 871 }) 872 873 if not table.isempty(settings) then 874 _p('/* Begin PBXTargetDependency section */') 875 printSettingsTable(2, settings) 876 _p('/* End PBXTargetDependency section */') 877 _p('') 878 end 879 end 880 881 882 function xcode.XCBuildConfiguration_Target(tr, target, cfg) 883 local settings = {} 884 885 settings['ALWAYS_SEARCH_USER_PATHS'] = 'NO' 886 887 if cfg.symbols ~= p.OFF then 888 settings['DEBUG_INFORMATION_FORMAT'] = 'dwarf-with-dsym' 889 end 890 891 if cfg.kind ~= "StaticLib" and cfg.buildtarget.prefix ~= '' then 892 settings['EXECUTABLE_PREFIX'] = cfg.buildtarget.prefix 893 end 894 895 --[[if cfg.targetextension then 896 local ext = cfg.targetextension 897 ext = iif(ext:startswith('.'), ext:sub(2), ext) 898 settings['EXECUTABLE_EXTENSION'] = ext 899 end]] 900 901 local outdir = path.getrelative(tr.project.location, path.getdirectory(cfg.buildtarget.relpath)) 902 if outdir ~= "." then 903 settings['CONFIGURATION_BUILD_DIR'] = outdir 904 end 905 906 settings['GCC_DYNAMIC_NO_PIC'] = 'NO' 907 908 if tr.infoplist then 909 settings['INFOPLIST_FILE'] = config.findfile(cfg, path.getextension(tr.infoplist.name)) 910 end 911 912 installpaths = { 913 ConsoleApp = '/usr/local/bin', 914 WindowedApp = '"$(HOME)/Applications"', 915 SharedLib = '/usr/local/lib', 916 StaticLib = '/usr/local/lib', 917 } 918 settings['INSTALL_PATH'] = installpaths[cfg.kind] 919 920 local fileNameList = {} 921 local file_tree = project.getsourcetree(tr.project) 922 tree.traverse(tr, { 923 onnode = function(node) 924 if node.buildid and not node.isResource and node.abspath then 925 -- ms this seems to work on visual studio !!! 926 -- why not in xcode ?? 927 local filecfg = fileconfig.getconfig(node, cfg) 928 if filecfg and filecfg.flags.ExcludeFromBuild then 929 --fileNameList = fileNameList .. " " ..filecfg.name 930 table.insert(fileNameList, escapeArg(node.name)) 931 end 932 933 --ms new way 934 -- if the file is not in this config file list excluded it from build !!! 935 --if not cfg.files[node.abspath] then 936 -- table.insert(fileNameList, escapeArg(node.name)) 937 --end 938 end 939 end 940 }) 941 942 if not table.isempty(fileNameList) then 943 settings['EXCLUDED_SOURCE_FILE_NAMES'] = fileNameList 944 end 945 settings['PRODUCT_NAME'] = cfg.buildtarget.basename 946 947 --ms not by default ...add it manually if you need it 948 --settings['COMBINE_HIDPI_IMAGES'] = 'YES' 949 950 overrideSettings(settings, cfg.xcodebuildsettings) 951 952 _p(2,'%s /* %s */ = {', cfg.xcode.targetid, cfg.buildcfg) 953 _p(3,'isa = XCBuildConfiguration;') 954 _p(3,'buildSettings = {') 955 printSettingsTable(4, settings) 956 _p(3,'};') 957 printSetting(3, 'name', cfg.buildcfg); 958 _p(2,'};') 959 end 960 961 962 function xcode.XCBuildConfiguration_CLanguageStandard(settings, cfg) 963 if cfg.cdialect and cfg.cdialect ~= "Default" then 964 settings['GCC_C_LANGUAGE_STANDARD'] = cfg.cdialect 965 else 966 settings['GCC_C_LANGUAGE_STANDARD'] = 'gnu99' 967 end 968 end 969 970 971 function xcode.XCBuildConfiguration_CppLanguageStandard(settings, cfg) 972 if cfg.cppdialect and cfg.cppdialect ~= "Default" then 973 settings['CLANG_CXX_LANGUAGE_STANDARD'] = cfg.cppdialect 974 end 975 end 976 977 978 function xcode.XCBuildConfiguration_Project(tr, cfg) 979 local settings = {} 980 981 local archs = { 982 Native = "$(NATIVE_ARCH_ACTUAL)", 983 x86 = "i386", 984 x86_64 = "x86_64", 985 Universal32 = "$(ARCHS_STANDARD_32_BIT)", 986 Universal64 = "$(ARCHS_STANDARD_64_BIT)", 987 Universal = "$(ARCHS_STANDARD_32_64_BIT)", 988 } 989 990 settings['ARCHS'] = archs[cfg.platform or "Native"] 991 992 --ms This is the default so don;t write it 993 --settings['SDKROOT'] = 'macosx' 994 995 local targetdir = path.getdirectory(cfg.buildtarget.relpath) 996 if targetdir ~= "." then 997 settings['CONFIGURATION_BUILD_DIR'] = '$(SYMROOT)' 998 end 999 1000 settings['CONFIGURATION_TEMP_DIR'] = '$(OBJROOT)' 1001 1002 if config.isDebugBuild(cfg) then 1003 settings['COPY_PHASE_STRIP'] = 'NO' 1004 end 1005 1006 xcode.XCBuildConfiguration_CLanguageStandard(settings, cfg) 1007 xcode.XCBuildConfiguration_CppLanguageStandard(settings, cfg) 1008 1009 if cfg.exceptionhandling == p.OFF then 1010 settings['GCC_ENABLE_CPP_EXCEPTIONS'] = 'NO' 1011 end 1012 1013 if cfg.rtti == p.OFF then 1014 settings['GCC_ENABLE_CPP_RTTI'] = 'NO' 1015 end 1016 1017 if cfg.symbols == p.ON and cfg.editandcontinue ~= "Off" then 1018 settings['GCC_ENABLE_FIX_AND_CONTINUE'] = 'YES' 1019 end 1020 1021 if cfg.exceptionhandling == p.OFF then 1022 settings['GCC_ENABLE_OBJC_EXCEPTIONS'] = 'NO' 1023 end 1024 1025 local optimizeMap = { On = 3, Size = 's', Speed = 3, Full = 'fast', Debug = 1 } 1026 settings['GCC_OPTIMIZATION_LEVEL'] = optimizeMap[cfg.optimize] or 0 1027 1028 if cfg.pchheader and not cfg.flags.NoPCH then 1029 settings['GCC_PRECOMPILE_PREFIX_HEADER'] = 'YES' 1030 settings['GCC_PREFIX_HEADER'] = cfg.pchheader 1031 end 1032 1033 local escapedDefines = { } 1034 for i,v in ipairs(cfg.defines) do 1035 escapedDefines[i] = escapeArg(v) 1036 end 1037 settings['GCC_PREPROCESSOR_DEFINITIONS'] = escapedDefines 1038 1039 settings["GCC_SYMBOLS_PRIVATE_EXTERN"] = 'NO' 1040 1041 if cfg.flags.FatalWarnings then 1042 settings['GCC_TREAT_WARNINGS_AS_ERRORS'] = 'YES' 1043 end 1044 1045 settings['GCC_WARN_ABOUT_RETURN_TYPE'] = 'YES' 1046 settings['GCC_WARN_UNUSED_VARIABLE'] = 'YES' 1047 1048 local includedirs = project.getrelative(cfg.project, cfg.includedirs) 1049 for i,v in ipairs(includedirs) do 1050 cfg.includedirs[i] = p.quoted(v) 1051 end 1052 settings['USER_HEADER_SEARCH_PATHS'] = cfg.includedirs 1053 1054 local sysincludedirs = project.getrelative(cfg.project, cfg.sysincludedirs) 1055 for i,v in ipairs(sysincludedirs) do 1056 cfg.sysincludedirs[i] = p.quoted(v) 1057 end 1058 if not table.isempty(cfg.sysincludedirs) then 1059 table.insert(cfg.sysincludedirs, "$(inherited)") 1060 end 1061 settings['HEADER_SEARCH_PATHS'] = cfg.sysincludedirs 1062 1063 for i,v in ipairs(cfg.libdirs) do 1064 cfg.libdirs[i] = p.project.getrelative(cfg.project, cfg.libdirs[i]) 1065 end 1066 settings['LIBRARY_SEARCH_PATHS'] = cfg.libdirs 1067 1068 for i,v in ipairs(cfg.frameworkdirs) do 1069 cfg.frameworkdirs[i] = p.project.getrelative(cfg.project, cfg.frameworkdirs[i]) 1070 end 1071 settings['FRAMEWORK_SEARCH_PATHS'] = cfg.frameworkdirs 1072 1073 local objDir = path.getrelative(tr.project.location, cfg.objdir) 1074 settings['OBJROOT'] = objDir 1075 1076 settings['ONLY_ACTIVE_ARCH'] = iif(p.config.isDebugBuild(cfg), 'YES', 'NO') 1077 1078 -- build list of "other" C/C++ flags 1079 local checks = { 1080 ["-ffast-math"] = cfg.floatingpoint == "Fast", 1081 ["-ffloat-store"] = cfg.floatingpoint == "Strict", 1082 ["-fomit-frame-pointer"] = cfg.flags.NoFramePointer, 1083 } 1084 1085 local flags = { } 1086 for flag, check in pairs(checks) do 1087 if check then 1088 table.insert(flags, flag) 1089 end 1090 end 1091 1092 1093 --[[if (type(cfg.buildoptions) == "table") then 1094 for k,v in pairs(cfg.buildoptions) do 1095 table.insertflat(flags, string.explode(v," -")) 1096 end 1097 end 1098 ]] 1099 1100 settings['OTHER_CFLAGS'] = table.join(flags, cfg.buildoptions) 1101 1102 -- build list of "other" linked flags. All libraries that aren't frameworks 1103 -- are listed here, so I don't have to try and figure out if they are ".a" 1104 -- or ".dylib", which Xcode requires to list in the Frameworks section 1105 flags = { } 1106 for _, lib in ipairs(config.getlinks(cfg, "system")) do 1107 if not xcode.isframework(lib) then 1108 table.insert(flags, "-l" .. lib) 1109 end 1110 end 1111 1112 --ms this step is for reference projects only 1113 for _, lib in ipairs(config.getlinks(cfg, "dependencies", "object")) do 1114 if (lib.external) then 1115 if not xcode.isframework(lib.linktarget.basename) then 1116 table.insert(flags, "-l" .. escapeArg(lib.linktarget.basename)) 1117 end 1118 end 1119 end 1120 1121 settings['OTHER_LDFLAGS'] = table.join(flags, cfg.linkoptions) 1122 1123 if cfg.flags.StaticRuntime then 1124 settings['STANDARD_C_PLUS_PLUS_LIBRARY_TYPE'] = 'static' 1125 end 1126 1127 if targetdir ~= "." then 1128 settings['SYMROOT'] = path.getrelative(tr.project.location, targetdir) 1129 end 1130 1131 if cfg.warnings == "Extra" then 1132 settings['WARNING_CFLAGS'] = '-Wall -Wextra' 1133 end 1134 1135 overrideSettings(settings, cfg.xcodebuildsettings) 1136 1137 _p(2,'%s /* %s */ = {', cfg.xcode.projectid, cfg.buildcfg) 1138 _p(3,'isa = XCBuildConfiguration;') 1139 _p(3,'buildSettings = {') 1140 printSettingsTable(4, settings) 1141 _p(3,'};') 1142 printSetting(3, 'name', cfg.buildcfg); 1143 _p(2,'};') 1144 end 1145 1146 1147 function xcode.XCBuildConfiguration(tr) 1148 local settings = {} 1149 1150 for _, target in ipairs(tr.products.children) do 1151 for _, cfg in ipairs(tr.configs) do 1152 settings[cfg.xcode.targetid] = function() 1153 xcode.XCBuildConfiguration_Target(tr, target, cfg) 1154 end 1155 end 1156 end 1157 for _, cfg in ipairs(tr.configs) do 1158 settings[cfg.xcode.projectid] = function() 1159 xcode.XCBuildConfiguration_Project(tr, cfg) 1160 end 1161 end 1162 1163 if not table.isempty(settings) then 1164 _p('/* Begin XCBuildConfiguration section */') 1165 printSettingsTable(0, settings) 1166 _p('/* End XCBuildConfiguration section */') 1167 _p('') 1168 end 1169 end 1170 1171 1172 function xcode.XCBuildConfigurationList(tr) 1173 local wks = tr.project.workspace 1174 local defaultCfgName = stringifySetting(tr.configs[1].buildcfg) 1175 local settings = {} 1176 1177 for _, target in ipairs(tr.products.children) do 1178 settings[target.cfgsection] = function() 1179 _p(2,'%s /* Build configuration list for PBXNativeTarget "%s" */ = {', target.cfgsection, target.name) 1180 _p(3,'isa = XCConfigurationList;') 1181 _p(3,'buildConfigurations = (') 1182 for _, cfg in ipairs(tr.configs) do 1183 _p(4,'%s /* %s */,', cfg.xcode.targetid, cfg.buildcfg) 1184 end 1185 _p(3,');') 1186 _p(3,'defaultConfigurationIsVisible = 0;') 1187 _p(3,'defaultConfigurationName = %s;', defaultCfgName) 1188 _p(2,'};') 1189 end 1190 end 1191 settings['1DEB928908733DD80010E9CD'] = function() 1192 _p(2,'1DEB928908733DD80010E9CD /* Build configuration list for PBXProject "%s" */ = {', tr.name) 1193 _p(3,'isa = XCConfigurationList;') 1194 _p(3,'buildConfigurations = (') 1195 for _, cfg in ipairs(tr.configs) do 1196 _p(4,'%s /* %s */,', cfg.xcode.projectid, cfg.buildcfg) 1197 end 1198 _p(3,');') 1199 _p(3,'defaultConfigurationIsVisible = 0;') 1200 _p(3,'defaultConfigurationName = %s;', defaultCfgName) 1201 _p(2,'};') 1202 end 1203 1204 _p('/* Begin XCConfigurationList section */') 1205 printSettingsTable(2, settings) 1206 _p('/* End XCConfigurationList section */') 1207 end 1208