blob: b2b3fd82af36779f697436b5bfb226871787efa6 [file] [log] [blame]
Alexander Afanasyevd1a75b82013-09-25 16:50:04 -07001#! /usr/bin/env python
2# encoding: utf-8
3# XCode 3/XCode 4 generator for Waf
4# Nicolas Mercier 2011
5
6"""
7Usage:
8
9def options(opt):
10 opt.load('xcode')
11
12$ waf configure xcode
13"""
14
15# TODO: support iOS projects
16
17from waflib import Context, TaskGen, Build, Utils
18import os, sys, random, time
19
20HEADERS_GLOB = '**/(*.h|*.hpp|*.H|*.inl)'
21
22MAP_EXT = {
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
54part1 = 0
55part2 = 10000
56part3 = 0
57id = 562000999
58def newid():
59 global id
60 id = id + 1
61 return "%04X%04X%04X%012d" % (0, 10000, 0, id)
62
63class XCodeNode:
64 def __init__(self):
65 self._id = newid()
66
67 def tostring(self, value):
68 if isinstance(value, dict):
69 result = "{\n"
70 for k,v in value.items():
71 result = result + "\t\t\t%s = %s;\n" % (k, self.tostring(v))
72 result = result + "\t\t}"
73 return result
74 elif isinstance(value, str):
75 return "\"%s\"" % value
76 elif isinstance(value, list):
77 result = "(\n"
78 for i in value:
79 result = result + "\t\t\t%s,\n" % self.tostring(i)
80 result = result + "\t\t)"
81 return result
82 elif isinstance(value, XCodeNode):
83 return value._id
84 else:
85 return str(value)
86
87 def write_recursive(self, value, file):
88 if isinstance(value, dict):
89 for k,v in value.items():
90 self.write_recursive(v, file)
91 elif isinstance(value, list):
92 for i in value:
93 self.write_recursive(i, file)
94 elif isinstance(value, XCodeNode):
95 value.write(file)
96
97 def write(self, file):
98 for attribute,value in self.__dict__.items():
99 if attribute[0] != '_':
100 self.write_recursive(value, file)
101
102 w = file.write
103 w("\t%s = {\n" % self._id)
104 w("\t\tisa = %s;\n" % self.__class__.__name__)
105 for attribute,value in self.__dict__.items():
106 if attribute[0] != '_':
107 w("\t\t%s = %s;\n" % (attribute, self.tostring(value)))
108 w("\t};\n\n")
109
110
111
112# Configurations
113class XCBuildConfiguration(XCodeNode):
114 def __init__(self, name, settings = {}, env=None):
115 XCodeNode.__init__(self)
116 self.baseConfigurationReference = ""
117 self.buildSettings = settings
118 self.name = name
119 if env and env.ARCH:
120 settings['ARCHS'] = " ".join(env.ARCH)
121
122
123class XCConfigurationList(XCodeNode):
124 def __init__(self, settings):
125 XCodeNode.__init__(self)
126 self.buildConfigurations = settings
127 self.defaultConfigurationIsVisible = 0
128 self.defaultConfigurationName = settings and settings[0].name or ""
129
130# Group/Files
131class PBXFileReference(XCodeNode):
132 def __init__(self, name, path, filetype = '', sourcetree = "SOURCE_ROOT"):
133 XCodeNode.__init__(self)
134 self.fileEncoding = 4
135 if not filetype:
136 _, ext = os.path.splitext(name)
137 filetype = MAP_EXT.get(ext, 'text')
138 self.lastKnownFileType = filetype
139 self.name = name
140 self.path = path
141 self.sourceTree = sourcetree
142
143class PBXGroup(XCodeNode):
144 def __init__(self, name, sourcetree = "<group>"):
145 XCodeNode.__init__(self)
146 self.children = []
147 self.name = name
148 self.sourceTree = sourcetree
149
150 def add(self, root, sources):
151 folders = {}
152 def folder(n):
Alexander Afanasyevb6392e32014-05-12 23:43:50 -0700153 if not n.is_child_of(root):
Alexander Afanasyevd1a75b82013-09-25 16:50:04 -0700154 return self
155 try:
156 return folders[n]
157 except KeyError:
158 f = PBXGroup(n.name)
159 p = folder(n.parent)
160 folders[n] = f
161 p.children.append(f)
162 return f
163 for s in sources:
164 f = folder(s.parent)
165 source = PBXFileReference(s.name, s.abspath())
166 f.children.append(source)
167
168
169# Targets
170class PBXLegacyTarget(XCodeNode):
171 def __init__(self, action, target=''):
172 XCodeNode.__init__(self)
173 self.buildConfigurationList = XCConfigurationList([XCBuildConfiguration('waf', {})])
174 if not target:
175 self.buildArgumentsString = "%s %s" % (sys.argv[0], action)
176 else:
177 self.buildArgumentsString = "%s %s --targets='%s'" % (sys.argv[0], action, target)
178 self.buildPhases = []
179 self.buildToolPath = sys.executable
180 self.buildWorkingDirectory = ""
181 self.dependencies = []
182 self.name = target or action
183 self.productName = target or action
184 self.passBuildSettingsInEnvironment = 0
185
186class PBXShellScriptBuildPhase(XCodeNode):
187 def __init__(self, action, target):
188 XCodeNode.__init__(self)
189 self.buildActionMask = 2147483647
190 self.files = []
191 self.inputPaths = []
192 self.outputPaths = []
193 self.runOnlyForDeploymentPostProcessing = 0
194 self.shellPath = "/bin/sh"
195 self.shellScript = "%s %s %s --targets='%s'" % (sys.executable, sys.argv[0], action, target)
196
197class PBXNativeTarget(XCodeNode):
198 def __init__(self, action, target, node, env):
199 XCodeNode.__init__(self)
200 conf = XCBuildConfiguration('waf', {'PRODUCT_NAME':target, 'CONFIGURATION_BUILD_DIR':node.parent.abspath()}, env)
201 self.buildConfigurationList = XCConfigurationList([conf])
202 self.buildPhases = [PBXShellScriptBuildPhase(action, target)]
203 self.buildRules = []
204 self.dependencies = []
205 self.name = target
206 self.productName = target
207 self.productType = "com.apple.product-type.application"
208 self.productReference = PBXFileReference(target, node.abspath(), 'wrapper.application', 'BUILT_PRODUCTS_DIR')
209
210# Root project object
211class PBXProject(XCodeNode):
212 def __init__(self, name, version):
213 XCodeNode.__init__(self)
214 self.buildConfigurationList = XCConfigurationList([XCBuildConfiguration('waf', {})])
215 self.compatibilityVersion = version[0]
216 self.hasScannedForEncodings = 1;
217 self.mainGroup = PBXGroup(name)
218 self.projectRoot = ""
219 self.projectDirPath = ""
220 self.targets = []
221 self._objectVersion = version[1]
222 self._output = PBXGroup('out')
223 self.mainGroup.children.append(self._output)
224
225 def write(self, file):
226 w = file.write
227 w("// !$*UTF8*$!\n")
228 w("{\n")
229 w("\tarchiveVersion = 1;\n")
230 w("\tclasses = {\n")
231 w("\t};\n")
232 w("\tobjectVersion = %d;\n" % self._objectVersion)
233 w("\tobjects = {\n\n")
234
235 XCodeNode.write(self, file)
236
237 w("\t};\n")
238 w("\trootObject = %s;\n" % self._id)
239 w("}\n")
240
241 def add_task_gen(self, tg):
242 if not getattr(tg, 'mac_app', False):
243 self.targets.append(PBXLegacyTarget('build', tg.name))
244 else:
245 target = PBXNativeTarget('build', tg.name, tg.link_task.outputs[0].change_ext('.app'), tg.env)
246 self.targets.append(target)
247 self._output.children.append(target.productReference)
248
249class xcode(Build.BuildContext):
250 cmd = 'xcode'
251 fun = 'build'
252
253 def collect_source(self, tg):
254 source_files = tg.to_nodes(getattr(tg, 'source', []))
255 plist_files = tg.to_nodes(getattr(tg, 'mac_plist', []))
256 resource_files = [tg.path.find_node(i) for i in Utils.to_list(getattr(tg, 'mac_resources', []))]
257 include_dirs = Utils.to_list(getattr(tg, 'includes', [])) + Utils.to_list(getattr(tg, 'export_dirs', []))
258 include_files = []
259 for x in include_dirs:
260 if not isinstance(x, str):
261 include_files.append(x)
262 continue
263 d = tg.path.find_node(x)
264 if d:
265 lst = [y for y in d.ant_glob(HEADERS_GLOB, flat=False)]
266 include_files.extend(lst)
267
268 # remove duplicates
269 source = list(set(source_files + plist_files + resource_files + include_files))
270 source.sort(key=lambda x: x.abspath())
271 return source
272
273 def execute(self):
274 """
275 Entry point
276 """
277 self.restore()
278 if not self.all_envs:
279 self.load_envs()
280 self.recurse([self.run_dir])
281
282 appname = getattr(Context.g_module, Context.APPNAME, os.path.basename(self.srcnode.abspath()))
283 p = PBXProject(appname, ('Xcode 3.2', 46))
284
285 for g in self.groups:
286 for tg in g:
287 if not isinstance(tg, TaskGen.task_gen):
288 continue
289
290 tg.post()
291
292 features = Utils.to_list(getattr(tg, 'features', ''))
293
294 group = PBXGroup(tg.name)
295 group.add(tg.path, self.collect_source(tg))
296 p.mainGroup.children.append(group)
297
298 if 'cprogram' or 'cxxprogram' in features:
299 p.add_task_gen(tg)
300
301
302 # targets that don't produce the executable but that you might want to run
303 p.targets.append(PBXLegacyTarget('configure'))
304 p.targets.append(PBXLegacyTarget('dist'))
305 p.targets.append(PBXLegacyTarget('install'))
306 p.targets.append(PBXLegacyTarget('check'))
307 node = self.srcnode.make_node('%s.xcodeproj' % appname)
308 node.mkdir()
309 node = node.make_node('project.pbxproj')
310 p.write(open(node.abspath(), 'w'))