1
|
|
|
# Copyright (c) 2013 Google Inc. All rights reserved. |
2
|
|
|
# Use of this source code is governed by a BSD-style license that can be |
3
|
|
|
# found in the LICENSE file. |
4
|
|
|
|
5
|
|
|
"""cmake output module |
6
|
|
|
|
7
|
|
|
This module is under development and should be considered experimental. |
8
|
|
|
|
9
|
|
|
This module produces cmake (2.8.8+) input as its output. One CMakeLists.txt is |
10
|
|
|
created for each configuration. |
11
|
|
|
|
12
|
|
|
This module's original purpose was to support editing in IDEs like KDevelop |
13
|
|
|
which use CMake for project management. It is also possible to use CMake to |
14
|
|
|
generate projects for other IDEs such as eclipse cdt and code::blocks. QtCreator |
15
|
|
|
will convert the CMakeLists.txt to a code::blocks cbp for the editor to read, |
16
|
|
|
but build using CMake. As a result QtCreator editor is unaware of compiler |
17
|
|
|
defines. The generated CMakeLists.txt can also be used to build on Linux. There |
18
|
|
|
is currently no support for building on platforms other than Linux. |
19
|
|
|
|
20
|
|
|
The generated CMakeLists.txt should properly compile all projects. However, |
21
|
|
|
there is a mismatch between gyp and cmake with regard to linking. All attempts |
22
|
|
|
are made to work around this, but CMake sometimes sees -Wl,--start-group as a |
23
|
|
|
library and incorrectly repeats it. As a result the output of this generator |
24
|
|
|
should not be relied on for building. |
25
|
|
|
|
26
|
|
|
When using with kdevelop, use version 4.4+. Previous versions of kdevelop will |
27
|
|
|
not be able to find the header file directories described in the generated |
28
|
|
|
CMakeLists.txt file. |
29
|
|
|
""" |
30
|
|
|
|
31
|
|
|
import multiprocessing |
32
|
|
|
import os |
33
|
|
|
import signal |
34
|
|
|
import string |
35
|
|
|
import subprocess |
36
|
|
|
import gyp.common |
37
|
|
|
|
38
|
|
|
generator_default_variables = { |
39
|
|
|
'EXECUTABLE_PREFIX': '', |
40
|
|
|
'EXECUTABLE_SUFFIX': '', |
41
|
|
|
'STATIC_LIB_PREFIX': 'lib', |
42
|
|
|
'STATIC_LIB_SUFFIX': '.a', |
43
|
|
|
'SHARED_LIB_PREFIX': 'lib', |
44
|
|
|
'SHARED_LIB_SUFFIX': '.so', |
45
|
|
|
'SHARED_LIB_DIR': '${builddir}/lib.${TOOLSET}', |
46
|
|
|
'LIB_DIR': '${obj}.${TOOLSET}', |
47
|
|
|
'INTERMEDIATE_DIR': '${obj}.${TOOLSET}/${TARGET}/geni', |
48
|
|
|
'SHARED_INTERMEDIATE_DIR': '${obj}/gen', |
49
|
|
|
'PRODUCT_DIR': '${builddir}', |
50
|
|
|
'RULE_INPUT_PATH': '${RULE_INPUT_PATH}', |
51
|
|
|
'RULE_INPUT_DIRNAME': '${RULE_INPUT_DIRNAME}', |
52
|
|
|
'RULE_INPUT_NAME': '${RULE_INPUT_NAME}', |
53
|
|
|
'RULE_INPUT_ROOT': '${RULE_INPUT_ROOT}', |
54
|
|
|
'RULE_INPUT_EXT': '${RULE_INPUT_EXT}', |
55
|
|
|
'CONFIGURATION_NAME': '${configuration}', |
56
|
|
|
} |
57
|
|
|
|
58
|
|
|
FULL_PATH_VARS = ('${CMAKE_CURRENT_LIST_DIR}', '${builddir}', '${obj}') |
59
|
|
|
|
60
|
|
|
generator_supports_multiple_toolsets = True |
61
|
|
|
generator_wants_static_library_dependencies_adjusted = True |
62
|
|
|
|
63
|
|
|
COMPILABLE_EXTENSIONS = { |
64
|
|
|
'.c': 'cc', |
65
|
|
|
'.cc': 'cxx', |
66
|
|
|
'.cpp': 'cxx', |
67
|
|
|
'.cxx': 'cxx', |
68
|
|
|
'.s': 's', # cc |
69
|
|
|
'.S': 's', # cc |
70
|
|
|
} |
71
|
|
|
|
72
|
|
|
|
73
|
|
|
def RemovePrefix(a, prefix): |
74
|
|
|
"""Returns 'a' without 'prefix' if it starts with 'prefix'.""" |
75
|
|
|
return a[len(prefix):] if a.startswith(prefix) else a |
76
|
|
|
|
77
|
|
|
|
78
|
|
|
def CalculateVariables(default_variables, params): |
79
|
|
|
"""Calculate additional variables for use in the build (called by gyp).""" |
80
|
|
|
default_variables.setdefault('OS', gyp.common.GetFlavor(params)) |
81
|
|
|
|
82
|
|
|
|
83
|
|
|
def Compilable(filename): |
84
|
|
|
"""Return true if the file is compilable (should be in OBJS).""" |
85
|
|
|
return any(filename.endswith(e) for e in COMPILABLE_EXTENSIONS) |
86
|
|
|
|
87
|
|
|
|
88
|
|
|
def Linkable(filename): |
89
|
|
|
"""Return true if the file is linkable (should be on the link line).""" |
90
|
|
|
return filename.endswith('.o') |
91
|
|
|
|
92
|
|
|
|
93
|
|
|
def NormjoinPathForceCMakeSource(base_path, rel_path): |
94
|
|
|
"""Resolves rel_path against base_path and returns the result. |
95
|
|
|
|
96
|
|
|
If rel_path is an absolute path it is returned unchanged. |
97
|
|
|
Otherwise it is resolved against base_path and normalized. |
98
|
|
|
If the result is a relative path, it is forced to be relative to the |
99
|
|
|
CMakeLists.txt. |
100
|
|
|
""" |
101
|
|
|
if os.path.isabs(rel_path): |
102
|
|
|
return rel_path |
103
|
|
|
if any([rel_path.startswith(var) for var in FULL_PATH_VARS]): |
104
|
|
|
return rel_path |
105
|
|
|
# TODO: do we need to check base_path for absolute variables as well? |
106
|
|
|
return os.path.join('${CMAKE_CURRENT_LIST_DIR}', |
107
|
|
|
os.path.normpath(os.path.join(base_path, rel_path))) |
108
|
|
|
|
109
|
|
|
|
110
|
|
|
def NormjoinPath(base_path, rel_path): |
111
|
|
|
"""Resolves rel_path against base_path and returns the result. |
112
|
|
|
TODO: what is this really used for? |
113
|
|
|
If rel_path begins with '$' it is returned unchanged. |
114
|
|
|
Otherwise it is resolved against base_path if relative, then normalized. |
115
|
|
|
""" |
116
|
|
|
if rel_path.startswith('$') and not rel_path.startswith('${configuration}'): |
117
|
|
|
return rel_path |
118
|
|
|
return os.path.normpath(os.path.join(base_path, rel_path)) |
119
|
|
|
|
120
|
|
|
|
121
|
|
|
def CMakeStringEscape(a): |
122
|
|
|
"""Escapes the string 'a' for use inside a CMake string. |
123
|
|
|
|
124
|
|
|
This means escaping |
125
|
|
|
'\' otherwise it may be seen as modifying the next character |
126
|
|
|
'"' otherwise it will end the string |
127
|
|
|
';' otherwise the string becomes a list |
128
|
|
|
|
129
|
|
|
The following do not need to be escaped |
130
|
|
|
'#' when the lexer is in string state, this does not start a comment |
131
|
|
|
|
132
|
|
|
The following are yet unknown |
133
|
|
|
'$' generator variables (like ${obj}) must not be escaped, |
134
|
|
|
but text $ should be escaped |
135
|
|
|
what is wanted is to know which $ come from generator variables |
136
|
|
|
""" |
137
|
|
|
return a.replace('\\', '\\\\').replace(';', '\\;').replace('"', '\\"') |
138
|
|
|
|
139
|
|
|
|
140
|
|
|
def SetFileProperty(output, source_name, property_name, values, sep): |
141
|
|
|
"""Given a set of source file, sets the given property on them.""" |
142
|
|
|
output.write('set_source_files_properties(') |
143
|
|
|
output.write(source_name) |
144
|
|
|
output.write(' PROPERTIES ') |
145
|
|
|
output.write(property_name) |
146
|
|
|
output.write(' "') |
147
|
|
|
for value in values: |
148
|
|
|
output.write(CMakeStringEscape(value)) |
149
|
|
|
output.write(sep) |
150
|
|
|
output.write('")\n') |
151
|
|
|
|
152
|
|
|
|
153
|
|
|
def SetFilesProperty(output, variable, property_name, values, sep): |
154
|
|
|
"""Given a set of source files, sets the given property on them.""" |
155
|
|
|
output.write('set_source_files_properties(') |
156
|
|
|
WriteVariable(output, variable) |
157
|
|
|
output.write(' PROPERTIES ') |
158
|
|
|
output.write(property_name) |
159
|
|
|
output.write(' "') |
160
|
|
|
for value in values: |
161
|
|
|
output.write(CMakeStringEscape(value)) |
162
|
|
|
output.write(sep) |
163
|
|
|
output.write('")\n') |
164
|
|
|
|
165
|
|
|
|
166
|
|
|
def SetTargetProperty(output, target_name, property_name, values, sep=''): |
167
|
|
|
"""Given a target, sets the given property.""" |
168
|
|
|
output.write('set_target_properties(') |
169
|
|
|
output.write(target_name) |
170
|
|
|
output.write(' PROPERTIES ') |
171
|
|
|
output.write(property_name) |
172
|
|
|
output.write(' "') |
173
|
|
|
for value in values: |
174
|
|
|
output.write(CMakeStringEscape(value)) |
175
|
|
|
output.write(sep) |
176
|
|
|
output.write('")\n') |
177
|
|
|
|
178
|
|
|
|
179
|
|
|
def SetVariable(output, variable_name, value): |
180
|
|
|
"""Sets a CMake variable.""" |
181
|
|
|
output.write('set(') |
182
|
|
|
output.write(variable_name) |
183
|
|
|
output.write(' "') |
184
|
|
|
output.write(CMakeStringEscape(value)) |
185
|
|
|
output.write('")\n') |
186
|
|
|
|
187
|
|
|
|
188
|
|
|
def SetVariableList(output, variable_name, values): |
189
|
|
|
"""Sets a CMake variable to a list.""" |
190
|
|
|
if not values: |
191
|
|
|
return SetVariable(output, variable_name, "") |
192
|
|
|
if len(values) == 1: |
193
|
|
|
return SetVariable(output, variable_name, values[0]) |
194
|
|
|
output.write('list(APPEND ') |
195
|
|
|
output.write(variable_name) |
196
|
|
|
output.write('\n "') |
197
|
|
|
output.write('"\n "'.join([CMakeStringEscape(value) for value in values])) |
198
|
|
|
output.write('")\n') |
199
|
|
|
|
200
|
|
|
|
201
|
|
|
def UnsetVariable(output, variable_name): |
202
|
|
|
"""Unsets a CMake variable.""" |
203
|
|
|
output.write('unset(') |
204
|
|
|
output.write(variable_name) |
205
|
|
|
output.write(')\n') |
206
|
|
|
|
207
|
|
|
|
208
|
|
|
def WriteVariable(output, variable_name, prepend=None): |
209
|
|
|
if prepend: |
210
|
|
|
output.write(prepend) |
211
|
|
|
output.write('${') |
212
|
|
|
output.write(variable_name) |
213
|
|
|
output.write('}') |
214
|
|
|
|
215
|
|
|
|
216
|
|
|
class CMakeTargetType(object): |
217
|
|
|
def __init__(self, command, modifier, property_modifier): |
218
|
|
|
self.command = command |
219
|
|
|
self.modifier = modifier |
220
|
|
|
self.property_modifier = property_modifier |
221
|
|
|
|
222
|
|
|
|
223
|
|
|
cmake_target_type_from_gyp_target_type = { |
224
|
|
|
'executable': CMakeTargetType('add_executable', None, 'RUNTIME'), |
225
|
|
|
'static_library': CMakeTargetType('add_library', 'STATIC', 'ARCHIVE'), |
226
|
|
|
'shared_library': CMakeTargetType('add_library', 'SHARED', 'LIBRARY'), |
227
|
|
|
'loadable_module': CMakeTargetType('add_library', 'MODULE', 'LIBRARY'), |
228
|
|
|
'none': CMakeTargetType('add_custom_target', 'SOURCES', None), |
229
|
|
|
} |
230
|
|
|
|
231
|
|
|
|
232
|
|
|
def StringToCMakeTargetName(a): |
233
|
|
|
"""Converts the given string 'a' to a valid CMake target name. |
234
|
|
|
|
235
|
|
|
All invalid characters are replaced by '_'. |
236
|
|
|
Invalid for cmake: ' ', '/', '(', ')', '"' |
237
|
|
|
Invalid for make: ':' |
238
|
|
|
Invalid for unknown reasons but cause failures: '.' |
239
|
|
|
""" |
240
|
|
|
return a.translate(string.maketrans(' /():."', '_______')) |
241
|
|
|
|
242
|
|
|
|
243
|
|
|
def WriteActions(target_name, actions, extra_sources, extra_deps, |
244
|
|
|
path_to_gyp, output): |
245
|
|
|
"""Write CMake for the 'actions' in the target. |
246
|
|
|
|
247
|
|
|
Args: |
248
|
|
|
target_name: the name of the CMake target being generated. |
249
|
|
|
actions: the Gyp 'actions' dict for this target. |
250
|
|
|
extra_sources: [(<cmake_src>, <src>)] to append with generated source files. |
251
|
|
|
extra_deps: [<cmake_taget>] to append with generated targets. |
252
|
|
|
path_to_gyp: relative path from CMakeLists.txt being generated to |
253
|
|
|
the Gyp file in which the target being generated is defined. |
254
|
|
|
""" |
255
|
|
|
for action in actions: |
256
|
|
|
action_name = StringToCMakeTargetName(action['action_name']) |
257
|
|
|
action_target_name = '%s__%s' % (target_name, action_name) |
258
|
|
|
|
259
|
|
|
inputs = action['inputs'] |
260
|
|
|
inputs_name = action_target_name + '__input' |
261
|
|
|
SetVariableList(output, inputs_name, |
262
|
|
|
[NormjoinPathForceCMakeSource(path_to_gyp, dep) for dep in inputs]) |
263
|
|
|
|
264
|
|
|
outputs = action['outputs'] |
265
|
|
|
cmake_outputs = [NormjoinPathForceCMakeSource(path_to_gyp, out) |
266
|
|
|
for out in outputs] |
267
|
|
|
outputs_name = action_target_name + '__output' |
268
|
|
|
SetVariableList(output, outputs_name, cmake_outputs) |
269
|
|
|
|
270
|
|
|
# Build up a list of outputs. |
271
|
|
|
# Collect the output dirs we'll need. |
272
|
|
|
dirs = set(dir for dir in (os.path.dirname(o) for o in outputs) if dir) |
273
|
|
|
|
274
|
|
|
if int(action.get('process_outputs_as_sources', False)): |
275
|
|
|
extra_sources.extend(zip(cmake_outputs, outputs)) |
276
|
|
|
|
277
|
|
|
# add_custom_command |
278
|
|
|
output.write('add_custom_command(OUTPUT ') |
279
|
|
|
WriteVariable(output, outputs_name) |
280
|
|
|
output.write('\n') |
281
|
|
|
|
282
|
|
|
if len(dirs) > 0: |
283
|
|
|
for directory in dirs: |
284
|
|
|
output.write(' COMMAND ${CMAKE_COMMAND} -E make_directory ') |
285
|
|
|
output.write(directory) |
286
|
|
|
output.write('\n') |
287
|
|
|
|
288
|
|
|
output.write(' COMMAND ') |
289
|
|
|
output.write(gyp.common.EncodePOSIXShellList(action['action'])) |
290
|
|
|
output.write('\n') |
291
|
|
|
|
292
|
|
|
output.write(' DEPENDS ') |
293
|
|
|
WriteVariable(output, inputs_name) |
294
|
|
|
output.write('\n') |
295
|
|
|
|
296
|
|
|
output.write(' WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/') |
297
|
|
|
output.write(path_to_gyp) |
298
|
|
|
output.write('\n') |
299
|
|
|
|
300
|
|
|
output.write(' COMMENT ') |
301
|
|
|
if 'message' in action: |
302
|
|
|
output.write(action['message']) |
303
|
|
|
else: |
304
|
|
|
output.write(action_target_name) |
305
|
|
|
output.write('\n') |
306
|
|
|
|
307
|
|
|
output.write(' VERBATIM\n') |
308
|
|
|
output.write(')\n') |
309
|
|
|
|
310
|
|
|
# add_custom_target |
311
|
|
|
output.write('add_custom_target(') |
312
|
|
|
output.write(action_target_name) |
313
|
|
|
output.write('\n DEPENDS ') |
314
|
|
|
WriteVariable(output, outputs_name) |
315
|
|
|
output.write('\n SOURCES ') |
316
|
|
|
WriteVariable(output, inputs_name) |
317
|
|
|
output.write('\n)\n') |
318
|
|
|
|
319
|
|
|
extra_deps.append(action_target_name) |
320
|
|
|
|
321
|
|
|
|
322
|
|
|
def NormjoinRulePathForceCMakeSource(base_path, rel_path, rule_source): |
323
|
|
|
if rel_path.startswith(("${RULE_INPUT_PATH}","${RULE_INPUT_DIRNAME}")): |
324
|
|
|
if any([rule_source.startswith(var) for var in FULL_PATH_VARS]): |
325
|
|
|
return rel_path |
326
|
|
|
return NormjoinPathForceCMakeSource(base_path, rel_path) |
327
|
|
|
|
328
|
|
|
|
329
|
|
|
def WriteRules(target_name, rules, extra_sources, extra_deps, |
330
|
|
|
path_to_gyp, output): |
331
|
|
|
"""Write CMake for the 'rules' in the target. |
332
|
|
|
|
333
|
|
|
Args: |
334
|
|
|
target_name: the name of the CMake target being generated. |
335
|
|
|
actions: the Gyp 'actions' dict for this target. |
336
|
|
|
extra_sources: [(<cmake_src>, <src>)] to append with generated source files. |
337
|
|
|
extra_deps: [<cmake_taget>] to append with generated targets. |
338
|
|
|
path_to_gyp: relative path from CMakeLists.txt being generated to |
339
|
|
|
the Gyp file in which the target being generated is defined. |
340
|
|
|
""" |
341
|
|
|
for rule in rules: |
342
|
|
|
rule_name = StringToCMakeTargetName(target_name + '__' + rule['rule_name']) |
343
|
|
|
|
344
|
|
|
inputs = rule.get('inputs', []) |
345
|
|
|
inputs_name = rule_name + '__input' |
346
|
|
|
SetVariableList(output, inputs_name, |
347
|
|
|
[NormjoinPathForceCMakeSource(path_to_gyp, dep) for dep in inputs]) |
348
|
|
|
outputs = rule['outputs'] |
349
|
|
|
var_outputs = [] |
350
|
|
|
|
351
|
|
|
for count, rule_source in enumerate(rule.get('rule_sources', [])): |
352
|
|
|
action_name = rule_name + '_' + str(count) |
353
|
|
|
|
354
|
|
|
rule_source_dirname, rule_source_basename = os.path.split(rule_source) |
355
|
|
|
rule_source_root, rule_source_ext = os.path.splitext(rule_source_basename) |
356
|
|
|
|
357
|
|
|
SetVariable(output, 'RULE_INPUT_PATH', rule_source) |
358
|
|
|
SetVariable(output, 'RULE_INPUT_DIRNAME', rule_source_dirname) |
359
|
|
|
SetVariable(output, 'RULE_INPUT_NAME', rule_source_basename) |
360
|
|
|
SetVariable(output, 'RULE_INPUT_ROOT', rule_source_root) |
361
|
|
|
SetVariable(output, 'RULE_INPUT_EXT', rule_source_ext) |
362
|
|
|
|
363
|
|
|
# Build up a list of outputs. |
364
|
|
|
# Collect the output dirs we'll need. |
365
|
|
|
dirs = set(dir for dir in (os.path.dirname(o) for o in outputs) if dir) |
366
|
|
|
|
367
|
|
|
# Create variables for the output, as 'local' variable will be unset. |
368
|
|
|
these_outputs = [] |
369
|
|
|
for output_index, out in enumerate(outputs): |
370
|
|
|
output_name = action_name + '_' + str(output_index) |
371
|
|
|
SetVariable(output, output_name, |
372
|
|
|
NormjoinRulePathForceCMakeSource(path_to_gyp, out, |
373
|
|
|
rule_source)) |
374
|
|
|
if int(rule.get('process_outputs_as_sources', False)): |
375
|
|
|
extra_sources.append(('${' + output_name + '}', out)) |
376
|
|
|
these_outputs.append('${' + output_name + '}') |
377
|
|
|
var_outputs.append('${' + output_name + '}') |
378
|
|
|
|
379
|
|
|
# add_custom_command |
380
|
|
|
output.write('add_custom_command(OUTPUT\n') |
381
|
|
|
for out in these_outputs: |
382
|
|
|
output.write(' ') |
383
|
|
|
output.write(out) |
384
|
|
|
output.write('\n') |
385
|
|
|
|
386
|
|
|
for directory in dirs: |
387
|
|
|
output.write(' COMMAND ${CMAKE_COMMAND} -E make_directory ') |
388
|
|
|
output.write(directory) |
389
|
|
|
output.write('\n') |
390
|
|
|
|
391
|
|
|
output.write(' COMMAND ') |
392
|
|
|
output.write(gyp.common.EncodePOSIXShellList(rule['action'])) |
393
|
|
|
output.write('\n') |
394
|
|
|
|
395
|
|
|
output.write(' DEPENDS ') |
396
|
|
|
WriteVariable(output, inputs_name) |
397
|
|
|
output.write(' ') |
398
|
|
|
output.write(NormjoinPath(path_to_gyp, rule_source)) |
399
|
|
|
output.write('\n') |
400
|
|
|
|
401
|
|
|
# CMAKE_CURRENT_LIST_DIR is where the CMakeLists.txt lives. |
402
|
|
|
# The cwd is the current build directory. |
403
|
|
|
output.write(' WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/') |
404
|
|
|
output.write(path_to_gyp) |
405
|
|
|
output.write('\n') |
406
|
|
|
|
407
|
|
|
output.write(' COMMENT ') |
408
|
|
|
if 'message' in rule: |
409
|
|
|
output.write(rule['message']) |
410
|
|
|
else: |
411
|
|
|
output.write(action_name) |
412
|
|
|
output.write('\n') |
413
|
|
|
|
414
|
|
|
output.write(' VERBATIM\n') |
415
|
|
|
output.write(')\n') |
416
|
|
|
|
417
|
|
|
UnsetVariable(output, 'RULE_INPUT_PATH') |
418
|
|
|
UnsetVariable(output, 'RULE_INPUT_DIRNAME') |
419
|
|
|
UnsetVariable(output, 'RULE_INPUT_NAME') |
420
|
|
|
UnsetVariable(output, 'RULE_INPUT_ROOT') |
421
|
|
|
UnsetVariable(output, 'RULE_INPUT_EXT') |
422
|
|
|
|
423
|
|
|
# add_custom_target |
424
|
|
|
output.write('add_custom_target(') |
425
|
|
|
output.write(rule_name) |
426
|
|
|
output.write(' DEPENDS\n') |
427
|
|
|
for out in var_outputs: |
428
|
|
|
output.write(' ') |
429
|
|
|
output.write(out) |
430
|
|
|
output.write('\n') |
431
|
|
|
output.write('SOURCES ') |
432
|
|
|
WriteVariable(output, inputs_name) |
433
|
|
|
output.write('\n') |
434
|
|
|
for rule_source in rule.get('rule_sources', []): |
435
|
|
|
output.write(' ') |
436
|
|
|
output.write(NormjoinPath(path_to_gyp, rule_source)) |
437
|
|
|
output.write('\n') |
438
|
|
|
output.write(')\n') |
439
|
|
|
|
440
|
|
|
extra_deps.append(rule_name) |
441
|
|
|
|
442
|
|
|
|
443
|
|
|
def WriteCopies(target_name, copies, extra_deps, path_to_gyp, output): |
444
|
|
|
"""Write CMake for the 'copies' in the target. |
445
|
|
|
|
446
|
|
|
Args: |
447
|
|
|
target_name: the name of the CMake target being generated. |
448
|
|
|
actions: the Gyp 'actions' dict for this target. |
449
|
|
|
extra_deps: [<cmake_taget>] to append with generated targets. |
450
|
|
|
path_to_gyp: relative path from CMakeLists.txt being generated to |
451
|
|
|
the Gyp file in which the target being generated is defined. |
452
|
|
|
""" |
453
|
|
|
copy_name = target_name + '__copies' |
454
|
|
|
|
455
|
|
|
# CMake gets upset with custom targets with OUTPUT which specify no output. |
456
|
|
|
have_copies = any(copy['files'] for copy in copies) |
457
|
|
|
if not have_copies: |
458
|
|
|
output.write('add_custom_target(') |
459
|
|
|
output.write(copy_name) |
460
|
|
|
output.write(')\n') |
461
|
|
|
extra_deps.append(copy_name) |
462
|
|
|
return |
463
|
|
|
|
464
|
|
|
class Copy(object): |
465
|
|
|
def __init__(self, ext, command): |
466
|
|
|
self.cmake_inputs = [] |
467
|
|
|
self.cmake_outputs = [] |
468
|
|
|
self.gyp_inputs = [] |
469
|
|
|
self.gyp_outputs = [] |
470
|
|
|
self.ext = ext |
471
|
|
|
self.inputs_name = None |
472
|
|
|
self.outputs_name = None |
473
|
|
|
self.command = command |
474
|
|
|
|
475
|
|
|
file_copy = Copy('', 'copy') |
476
|
|
|
dir_copy = Copy('_dirs', 'copy_directory') |
477
|
|
|
|
478
|
|
|
for copy in copies: |
479
|
|
|
files = copy['files'] |
480
|
|
|
destination = copy['destination'] |
481
|
|
|
for src in files: |
482
|
|
|
path = os.path.normpath(src) |
483
|
|
|
basename = os.path.split(path)[1] |
484
|
|
|
dst = os.path.join(destination, basename) |
485
|
|
|
|
486
|
|
|
copy = file_copy if os.path.basename(src) else dir_copy |
487
|
|
|
|
488
|
|
|
copy.cmake_inputs.append(NormjoinPathForceCMakeSource(path_to_gyp, src)) |
489
|
|
|
copy.cmake_outputs.append(NormjoinPathForceCMakeSource(path_to_gyp, dst)) |
490
|
|
|
copy.gyp_inputs.append(src) |
491
|
|
|
copy.gyp_outputs.append(dst) |
492
|
|
|
|
493
|
|
|
for copy in (file_copy, dir_copy): |
494
|
|
|
if copy.cmake_inputs: |
495
|
|
|
copy.inputs_name = copy_name + '__input' + copy.ext |
496
|
|
|
SetVariableList(output, copy.inputs_name, copy.cmake_inputs) |
497
|
|
|
|
498
|
|
|
copy.outputs_name = copy_name + '__output' + copy.ext |
499
|
|
|
SetVariableList(output, copy.outputs_name, copy.cmake_outputs) |
500
|
|
|
|
501
|
|
|
# add_custom_command |
502
|
|
|
output.write('add_custom_command(\n') |
503
|
|
|
|
504
|
|
|
output.write('OUTPUT') |
505
|
|
|
for copy in (file_copy, dir_copy): |
506
|
|
|
if copy.outputs_name: |
507
|
|
|
WriteVariable(output, copy.outputs_name, ' ') |
508
|
|
|
output.write('\n') |
509
|
|
|
|
510
|
|
|
for copy in (file_copy, dir_copy): |
511
|
|
|
for src, dst in zip(copy.gyp_inputs, copy.gyp_outputs): |
512
|
|
|
# 'cmake -E copy src dst' will create the 'dst' directory if needed. |
513
|
|
|
output.write('COMMAND ${CMAKE_COMMAND} -E %s ' % copy.command) |
514
|
|
|
output.write(src) |
515
|
|
|
output.write(' ') |
516
|
|
|
output.write(dst) |
517
|
|
|
output.write("\n") |
518
|
|
|
|
519
|
|
|
output.write('DEPENDS') |
520
|
|
|
for copy in (file_copy, dir_copy): |
521
|
|
|
if copy.inputs_name: |
522
|
|
|
WriteVariable(output, copy.inputs_name, ' ') |
523
|
|
|
output.write('\n') |
524
|
|
|
|
525
|
|
|
output.write('WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/') |
526
|
|
|
output.write(path_to_gyp) |
527
|
|
|
output.write('\n') |
528
|
|
|
|
529
|
|
|
output.write('COMMENT Copying for ') |
530
|
|
|
output.write(target_name) |
531
|
|
|
output.write('\n') |
532
|
|
|
|
533
|
|
|
output.write('VERBATIM\n') |
534
|
|
|
output.write(')\n') |
535
|
|
|
|
536
|
|
|
# add_custom_target |
537
|
|
|
output.write('add_custom_target(') |
538
|
|
|
output.write(copy_name) |
539
|
|
|
output.write('\n DEPENDS') |
540
|
|
|
for copy in (file_copy, dir_copy): |
541
|
|
|
if copy.outputs_name: |
542
|
|
|
WriteVariable(output, copy.outputs_name, ' ') |
543
|
|
|
output.write('\n SOURCES') |
544
|
|
|
if file_copy.inputs_name: |
545
|
|
|
WriteVariable(output, file_copy.inputs_name, ' ') |
546
|
|
|
output.write('\n)\n') |
547
|
|
|
|
548
|
|
|
extra_deps.append(copy_name) |
549
|
|
|
|
550
|
|
|
|
551
|
|
|
def CreateCMakeTargetBaseName(qualified_target): |
552
|
|
|
"""This is the name we would like the target to have.""" |
553
|
|
|
_, gyp_target_name, gyp_target_toolset = ( |
554
|
|
|
gyp.common.ParseQualifiedTarget(qualified_target)) |
555
|
|
|
cmake_target_base_name = gyp_target_name |
556
|
|
|
if gyp_target_toolset and gyp_target_toolset != 'target': |
557
|
|
|
cmake_target_base_name += '_' + gyp_target_toolset |
558
|
|
|
return StringToCMakeTargetName(cmake_target_base_name) |
559
|
|
|
|
560
|
|
|
|
561
|
|
|
def CreateCMakeTargetFullName(qualified_target): |
562
|
|
|
"""An unambiguous name for the target.""" |
563
|
|
|
gyp_file, gyp_target_name, gyp_target_toolset = ( |
564
|
|
|
gyp.common.ParseQualifiedTarget(qualified_target)) |
565
|
|
|
cmake_target_full_name = gyp_file + ':' + gyp_target_name |
566
|
|
|
if gyp_target_toolset and gyp_target_toolset != 'target': |
567
|
|
|
cmake_target_full_name += '_' + gyp_target_toolset |
568
|
|
|
return StringToCMakeTargetName(cmake_target_full_name) |
569
|
|
|
|
570
|
|
|
|
571
|
|
|
class CMakeNamer(object): |
572
|
|
|
"""Converts Gyp target names into CMake target names. |
573
|
|
|
|
574
|
|
|
CMake requires that target names be globally unique. One way to ensure |
575
|
|
|
this is to fully qualify the names of the targets. Unfortunatly, this |
576
|
|
|
ends up with all targets looking like "chrome_chrome_gyp_chrome" instead |
577
|
|
|
of just "chrome". If this generator were only interested in building, it |
578
|
|
|
would be possible to fully qualify all target names, then create |
579
|
|
|
unqualified target names which depend on all qualified targets which |
580
|
|
|
should have had that name. This is more or less what the 'make' generator |
581
|
|
|
does with aliases. However, one goal of this generator is to create CMake |
582
|
|
|
files for use with IDEs, and fully qualified names are not as user |
583
|
|
|
friendly. |
584
|
|
|
|
585
|
|
|
Since target name collision is rare, we do the above only when required. |
586
|
|
|
|
587
|
|
|
Toolset variants are always qualified from the base, as this is required for |
588
|
|
|
building. However, it also makes sense for an IDE, as it is possible for |
589
|
|
|
defines to be different. |
590
|
|
|
""" |
591
|
|
|
def __init__(self, target_list): |
592
|
|
|
self.cmake_target_base_names_conficting = set() |
593
|
|
|
|
594
|
|
|
cmake_target_base_names_seen = set() |
595
|
|
|
for qualified_target in target_list: |
596
|
|
|
cmake_target_base_name = CreateCMakeTargetBaseName(qualified_target) |
597
|
|
|
|
598
|
|
|
if cmake_target_base_name not in cmake_target_base_names_seen: |
599
|
|
|
cmake_target_base_names_seen.add(cmake_target_base_name) |
600
|
|
|
else: |
601
|
|
|
self.cmake_target_base_names_conficting.add(cmake_target_base_name) |
602
|
|
|
|
603
|
|
|
def CreateCMakeTargetName(self, qualified_target): |
604
|
|
|
base_name = CreateCMakeTargetBaseName(qualified_target) |
605
|
|
|
if base_name in self.cmake_target_base_names_conficting: |
606
|
|
|
return CreateCMakeTargetFullName(qualified_target) |
607
|
|
|
return base_name |
608
|
|
|
|
609
|
|
|
|
610
|
|
|
def WriteTarget(namer, qualified_target, target_dicts, build_dir, config_to_use, |
611
|
|
|
options, generator_flags, all_qualified_targets, output): |
612
|
|
|
|
613
|
|
|
# The make generator does this always. |
614
|
|
|
# TODO: It would be nice to be able to tell CMake all dependencies. |
615
|
|
|
circular_libs = generator_flags.get('circular', True) |
616
|
|
|
|
617
|
|
|
if not generator_flags.get('standalone', False): |
618
|
|
|
output.write('\n#') |
619
|
|
|
output.write(qualified_target) |
620
|
|
|
output.write('\n') |
621
|
|
|
|
622
|
|
|
gyp_file, _, _ = gyp.common.ParseQualifiedTarget(qualified_target) |
623
|
|
|
rel_gyp_file = gyp.common.RelativePath(gyp_file, options.toplevel_dir) |
624
|
|
|
rel_gyp_dir = os.path.dirname(rel_gyp_file) |
625
|
|
|
|
626
|
|
|
# Relative path from build dir to top dir. |
627
|
|
|
build_to_top = gyp.common.InvertRelativePath(build_dir, options.toplevel_dir) |
628
|
|
|
# Relative path from build dir to gyp dir. |
629
|
|
|
build_to_gyp = os.path.join(build_to_top, rel_gyp_dir) |
630
|
|
|
|
631
|
|
|
path_from_cmakelists_to_gyp = build_to_gyp |
632
|
|
|
|
633
|
|
|
spec = target_dicts.get(qualified_target, {}) |
634
|
|
|
config = spec.get('configurations', {}).get(config_to_use, {}) |
635
|
|
|
|
636
|
|
|
target_name = spec.get('target_name', '<missing target name>') |
637
|
|
|
target_type = spec.get('type', '<missing target type>') |
638
|
|
|
target_toolset = spec.get('toolset') |
639
|
|
|
|
640
|
|
|
cmake_target_type = cmake_target_type_from_gyp_target_type.get(target_type) |
641
|
|
|
if cmake_target_type is None: |
642
|
|
|
print ('Target %s has unknown target type %s, skipping.' % |
643
|
|
|
( target_name, target_type ) ) |
644
|
|
|
return |
645
|
|
|
|
646
|
|
|
SetVariable(output, 'TARGET', target_name) |
647
|
|
|
SetVariable(output, 'TOOLSET', target_toolset) |
648
|
|
|
|
649
|
|
|
cmake_target_name = namer.CreateCMakeTargetName(qualified_target) |
650
|
|
|
|
651
|
|
|
extra_sources = [] |
652
|
|
|
extra_deps = [] |
653
|
|
|
|
654
|
|
|
# Actions must come first, since they can generate more OBJs for use below. |
655
|
|
|
if 'actions' in spec: |
656
|
|
|
WriteActions(cmake_target_name, spec['actions'], extra_sources, extra_deps, |
657
|
|
|
path_from_cmakelists_to_gyp, output) |
658
|
|
|
|
659
|
|
|
# Rules must be early like actions. |
660
|
|
|
if 'rules' in spec: |
661
|
|
|
WriteRules(cmake_target_name, spec['rules'], extra_sources, extra_deps, |
662
|
|
|
path_from_cmakelists_to_gyp, output) |
663
|
|
|
|
664
|
|
|
# Copies |
665
|
|
|
if 'copies' in spec: |
666
|
|
|
WriteCopies(cmake_target_name, spec['copies'], extra_deps, |
667
|
|
|
path_from_cmakelists_to_gyp, output) |
668
|
|
|
|
669
|
|
|
# Target and sources |
670
|
|
|
srcs = spec.get('sources', []) |
671
|
|
|
|
672
|
|
|
# Gyp separates the sheep from the goats based on file extensions. |
673
|
|
|
# A full separation is done here because of flag handing (see below). |
674
|
|
|
s_sources = [] |
675
|
|
|
c_sources = [] |
676
|
|
|
cxx_sources = [] |
677
|
|
|
linkable_sources = [] |
678
|
|
|
other_sources = [] |
679
|
|
|
for src in srcs: |
680
|
|
|
_, ext = os.path.splitext(src) |
681
|
|
|
src_type = COMPILABLE_EXTENSIONS.get(ext, None) |
682
|
|
|
src_norm_path = NormjoinPath(path_from_cmakelists_to_gyp, src); |
683
|
|
|
|
684
|
|
|
if src_type == 's': |
685
|
|
|
s_sources.append(src_norm_path) |
686
|
|
|
elif src_type == 'cc': |
687
|
|
|
c_sources.append(src_norm_path) |
688
|
|
|
elif src_type == 'cxx': |
689
|
|
|
cxx_sources.append(src_norm_path) |
690
|
|
|
elif Linkable(ext): |
691
|
|
|
linkable_sources.append(src_norm_path) |
692
|
|
|
else: |
693
|
|
|
other_sources.append(src_norm_path) |
694
|
|
|
|
695
|
|
|
for extra_source in extra_sources: |
696
|
|
|
src, real_source = extra_source |
697
|
|
|
_, ext = os.path.splitext(real_source) |
698
|
|
|
src_type = COMPILABLE_EXTENSIONS.get(ext, None) |
699
|
|
|
|
700
|
|
|
if src_type == 's': |
701
|
|
|
s_sources.append(src) |
702
|
|
|
elif src_type == 'cc': |
703
|
|
|
c_sources.append(src) |
704
|
|
|
elif src_type == 'cxx': |
705
|
|
|
cxx_sources.append(src) |
706
|
|
|
elif Linkable(ext): |
707
|
|
|
linkable_sources.append(src) |
708
|
|
|
else: |
709
|
|
|
other_sources.append(src) |
710
|
|
|
|
711
|
|
|
s_sources_name = None |
712
|
|
|
if s_sources: |
713
|
|
|
s_sources_name = cmake_target_name + '__asm_srcs' |
714
|
|
|
SetVariableList(output, s_sources_name, s_sources) |
715
|
|
|
|
716
|
|
|
c_sources_name = None |
717
|
|
|
if c_sources: |
718
|
|
|
c_sources_name = cmake_target_name + '__c_srcs' |
719
|
|
|
SetVariableList(output, c_sources_name, c_sources) |
720
|
|
|
|
721
|
|
|
cxx_sources_name = None |
722
|
|
|
if cxx_sources: |
723
|
|
|
cxx_sources_name = cmake_target_name + '__cxx_srcs' |
724
|
|
|
SetVariableList(output, cxx_sources_name, cxx_sources) |
725
|
|
|
|
726
|
|
|
linkable_sources_name = None |
727
|
|
|
if linkable_sources: |
728
|
|
|
linkable_sources_name = cmake_target_name + '__linkable_srcs' |
729
|
|
|
SetVariableList(output, linkable_sources_name, linkable_sources) |
730
|
|
|
|
731
|
|
|
other_sources_name = None |
732
|
|
|
if other_sources: |
733
|
|
|
other_sources_name = cmake_target_name + '__other_srcs' |
734
|
|
|
SetVariableList(output, other_sources_name, other_sources) |
735
|
|
|
|
736
|
|
|
# CMake gets upset when executable targets provide no sources. |
737
|
|
|
# http://www.cmake.org/pipermail/cmake/2010-July/038461.html |
738
|
|
|
dummy_sources_name = None |
739
|
|
|
has_sources = (s_sources_name or |
740
|
|
|
c_sources_name or |
741
|
|
|
cxx_sources_name or |
742
|
|
|
linkable_sources_name or |
743
|
|
|
other_sources_name) |
744
|
|
|
if target_type == 'executable' and not has_sources: |
745
|
|
|
dummy_sources_name = cmake_target_name + '__dummy_srcs' |
746
|
|
|
SetVariable(output, dummy_sources_name, |
747
|
|
|
"${obj}.${TOOLSET}/${TARGET}/genc/dummy.c") |
748
|
|
|
output.write('if(NOT EXISTS "') |
749
|
|
|
WriteVariable(output, dummy_sources_name) |
750
|
|
|
output.write('")\n') |
751
|
|
|
output.write(' file(WRITE "') |
752
|
|
|
WriteVariable(output, dummy_sources_name) |
753
|
|
|
output.write('" "")\n') |
754
|
|
|
output.write("endif()\n") |
755
|
|
|
|
756
|
|
|
|
757
|
|
|
# CMake is opposed to setting linker directories and considers the practice |
758
|
|
|
# of setting linker directories dangerous. Instead, it favors the use of |
759
|
|
|
# find_library and passing absolute paths to target_link_libraries. |
760
|
|
|
# However, CMake does provide the command link_directories, which adds |
761
|
|
|
# link directories to targets defined after it is called. |
762
|
|
|
# As a result, link_directories must come before the target definition. |
763
|
|
|
# CMake unfortunately has no means of removing entries from LINK_DIRECTORIES. |
764
|
|
|
library_dirs = config.get('library_dirs') |
765
|
|
|
if library_dirs is not None: |
766
|
|
|
output.write('link_directories(') |
767
|
|
|
for library_dir in library_dirs: |
768
|
|
|
output.write(' ') |
769
|
|
|
output.write(NormjoinPath(path_from_cmakelists_to_gyp, library_dir)) |
770
|
|
|
output.write('\n') |
771
|
|
|
output.write(')\n') |
772
|
|
|
|
773
|
|
|
output.write(cmake_target_type.command) |
774
|
|
|
output.write('(') |
775
|
|
|
output.write(cmake_target_name) |
776
|
|
|
|
777
|
|
|
if cmake_target_type.modifier is not None: |
778
|
|
|
output.write(' ') |
779
|
|
|
output.write(cmake_target_type.modifier) |
780
|
|
|
|
781
|
|
|
if s_sources_name: |
782
|
|
|
WriteVariable(output, s_sources_name, ' ') |
783
|
|
|
if c_sources_name: |
784
|
|
|
WriteVariable(output, c_sources_name, ' ') |
785
|
|
|
if cxx_sources_name: |
786
|
|
|
WriteVariable(output, cxx_sources_name, ' ') |
787
|
|
|
if linkable_sources_name: |
788
|
|
|
WriteVariable(output, linkable_sources_name, ' ') |
789
|
|
|
if other_sources_name: |
790
|
|
|
WriteVariable(output, other_sources_name, ' ') |
791
|
|
|
if dummy_sources_name: |
792
|
|
|
WriteVariable(output, dummy_sources_name, ' ') |
793
|
|
|
|
794
|
|
|
output.write(')\n') |
795
|
|
|
|
796
|
|
|
# Let CMake know if the 'all' target should depend on this target. |
797
|
|
|
exclude_from_all = ('TRUE' if qualified_target not in all_qualified_targets |
798
|
|
|
else 'FALSE') |
799
|
|
|
SetTargetProperty(output, cmake_target_name, |
800
|
|
|
'EXCLUDE_FROM_ALL', exclude_from_all) |
801
|
|
|
for extra_target_name in extra_deps: |
802
|
|
|
SetTargetProperty(output, extra_target_name, |
803
|
|
|
'EXCLUDE_FROM_ALL', exclude_from_all) |
804
|
|
|
|
805
|
|
|
# Output name and location. |
806
|
|
|
if target_type != 'none': |
807
|
|
|
# Link as 'C' if there are no other files |
808
|
|
|
if not c_sources and not cxx_sources: |
809
|
|
|
SetTargetProperty(output, cmake_target_name, 'LINKER_LANGUAGE', ['C']) |
810
|
|
|
|
811
|
|
|
# Mark uncompiled sources as uncompiled. |
812
|
|
|
if other_sources_name: |
813
|
|
|
output.write('set_source_files_properties(') |
814
|
|
|
WriteVariable(output, other_sources_name, '') |
815
|
|
|
output.write(' PROPERTIES HEADER_FILE_ONLY "TRUE")\n') |
816
|
|
|
|
817
|
|
|
# Mark object sources as linkable. |
818
|
|
|
if linkable_sources_name: |
819
|
|
|
output.write('set_source_files_properties(') |
820
|
|
|
WriteVariable(output, other_sources_name, '') |
821
|
|
|
output.write(' PROPERTIES EXTERNAL_OBJECT "TRUE")\n') |
822
|
|
|
|
823
|
|
|
# Output directory |
824
|
|
|
target_output_directory = spec.get('product_dir') |
825
|
|
|
if target_output_directory is None: |
826
|
|
|
if target_type in ('executable', 'loadable_module'): |
827
|
|
|
target_output_directory = generator_default_variables['PRODUCT_DIR'] |
828
|
|
|
elif target_type == 'shared_library': |
829
|
|
|
target_output_directory = '${builddir}/lib.${TOOLSET}' |
830
|
|
|
elif spec.get('standalone_static_library', False): |
831
|
|
|
target_output_directory = generator_default_variables['PRODUCT_DIR'] |
832
|
|
|
else: |
833
|
|
|
base_path = gyp.common.RelativePath(os.path.dirname(gyp_file), |
834
|
|
|
options.toplevel_dir) |
835
|
|
|
target_output_directory = '${obj}.${TOOLSET}' |
836
|
|
|
target_output_directory = ( |
837
|
|
|
os.path.join(target_output_directory, base_path)) |
838
|
|
|
|
839
|
|
|
cmake_target_output_directory = NormjoinPathForceCMakeSource( |
840
|
|
|
path_from_cmakelists_to_gyp, |
841
|
|
|
target_output_directory) |
842
|
|
|
SetTargetProperty(output, |
843
|
|
|
cmake_target_name, |
844
|
|
|
cmake_target_type.property_modifier + '_OUTPUT_DIRECTORY', |
845
|
|
|
cmake_target_output_directory) |
846
|
|
|
|
847
|
|
|
# Output name |
848
|
|
|
default_product_prefix = '' |
849
|
|
|
default_product_name = target_name |
850
|
|
|
default_product_ext = '' |
851
|
|
|
if target_type == 'static_library': |
852
|
|
|
static_library_prefix = generator_default_variables['STATIC_LIB_PREFIX'] |
853
|
|
|
default_product_name = RemovePrefix(default_product_name, |
854
|
|
|
static_library_prefix) |
855
|
|
|
default_product_prefix = static_library_prefix |
856
|
|
|
default_product_ext = generator_default_variables['STATIC_LIB_SUFFIX'] |
857
|
|
|
|
858
|
|
|
elif target_type in ('loadable_module', 'shared_library'): |
859
|
|
|
shared_library_prefix = generator_default_variables['SHARED_LIB_PREFIX'] |
860
|
|
|
default_product_name = RemovePrefix(default_product_name, |
861
|
|
|
shared_library_prefix) |
862
|
|
|
default_product_prefix = shared_library_prefix |
863
|
|
|
default_product_ext = generator_default_variables['SHARED_LIB_SUFFIX'] |
864
|
|
|
|
865
|
|
|
elif target_type != 'executable': |
866
|
|
|
print ('ERROR: What output file should be generated?', |
867
|
|
|
'type', target_type, 'target', target_name) |
868
|
|
|
|
869
|
|
|
product_prefix = spec.get('product_prefix', default_product_prefix) |
870
|
|
|
product_name = spec.get('product_name', default_product_name) |
871
|
|
|
product_ext = spec.get('product_extension') |
872
|
|
|
if product_ext: |
873
|
|
|
product_ext = '.' + product_ext |
874
|
|
|
else: |
875
|
|
|
product_ext = default_product_ext |
876
|
|
|
|
877
|
|
|
SetTargetProperty(output, cmake_target_name, 'PREFIX', product_prefix) |
878
|
|
|
SetTargetProperty(output, cmake_target_name, |
879
|
|
|
cmake_target_type.property_modifier + '_OUTPUT_NAME', |
880
|
|
|
product_name) |
881
|
|
|
SetTargetProperty(output, cmake_target_name, 'SUFFIX', product_ext) |
882
|
|
|
|
883
|
|
|
# Make the output of this target referenceable as a source. |
884
|
|
|
cmake_target_output_basename = product_prefix + product_name + product_ext |
885
|
|
|
cmake_target_output = os.path.join(cmake_target_output_directory, |
886
|
|
|
cmake_target_output_basename) |
887
|
|
|
SetFileProperty(output, cmake_target_output, 'GENERATED', ['TRUE'], '') |
888
|
|
|
|
889
|
|
|
# Includes |
890
|
|
|
includes = config.get('include_dirs') |
891
|
|
|
if includes: |
892
|
|
|
# This (target include directories) is what requires CMake 2.8.8 |
893
|
|
|
includes_name = cmake_target_name + '__include_dirs' |
894
|
|
|
SetVariableList(output, includes_name, |
895
|
|
|
[NormjoinPathForceCMakeSource(path_from_cmakelists_to_gyp, include) |
896
|
|
|
for include in includes]) |
897
|
|
|
output.write('set_property(TARGET ') |
898
|
|
|
output.write(cmake_target_name) |
899
|
|
|
output.write(' APPEND PROPERTY INCLUDE_DIRECTORIES ') |
900
|
|
|
WriteVariable(output, includes_name, '') |
901
|
|
|
output.write(')\n') |
902
|
|
|
|
903
|
|
|
# Defines |
904
|
|
|
defines = config.get('defines') |
905
|
|
|
if defines is not None: |
906
|
|
|
SetTargetProperty(output, |
907
|
|
|
cmake_target_name, |
908
|
|
|
'COMPILE_DEFINITIONS', |
909
|
|
|
defines, |
910
|
|
|
';') |
911
|
|
|
|
912
|
|
|
# Compile Flags - http://www.cmake.org/Bug/view.php?id=6493 |
913
|
|
|
# CMake currently does not have target C and CXX flags. |
914
|
|
|
# So, instead of doing... |
915
|
|
|
|
916
|
|
|
# cflags_c = config.get('cflags_c') |
917
|
|
|
# if cflags_c is not None: |
918
|
|
|
# SetTargetProperty(output, cmake_target_name, |
919
|
|
|
# 'C_COMPILE_FLAGS', cflags_c, ' ') |
920
|
|
|
|
921
|
|
|
# cflags_cc = config.get('cflags_cc') |
922
|
|
|
# if cflags_cc is not None: |
923
|
|
|
# SetTargetProperty(output, cmake_target_name, |
924
|
|
|
# 'CXX_COMPILE_FLAGS', cflags_cc, ' ') |
925
|
|
|
|
926
|
|
|
# Instead we must... |
927
|
|
|
cflags = config.get('cflags', []) |
928
|
|
|
cflags_c = config.get('cflags_c', []) |
929
|
|
|
cflags_cxx = config.get('cflags_cc', []) |
930
|
|
|
if (not cflags_c or not c_sources) and (not cflags_cxx or not cxx_sources): |
931
|
|
|
SetTargetProperty(output, cmake_target_name, 'COMPILE_FLAGS', cflags, ' ') |
932
|
|
|
|
933
|
|
|
elif c_sources and not (s_sources or cxx_sources): |
934
|
|
|
flags = [] |
935
|
|
|
flags.extend(cflags) |
936
|
|
|
flags.extend(cflags_c) |
937
|
|
|
SetTargetProperty(output, cmake_target_name, 'COMPILE_FLAGS', flags, ' ') |
938
|
|
|
|
939
|
|
|
elif cxx_sources and not (s_sources or c_sources): |
940
|
|
|
flags = [] |
941
|
|
|
flags.extend(cflags) |
942
|
|
|
flags.extend(cflags_cxx) |
943
|
|
|
SetTargetProperty(output, cmake_target_name, 'COMPILE_FLAGS', flags, ' ') |
944
|
|
|
|
945
|
|
|
else: |
946
|
|
|
# TODO: This is broken, one cannot generally set properties on files, |
947
|
|
|
# as other targets may require different properties on the same files. |
948
|
|
|
if s_sources and cflags: |
949
|
|
|
SetFilesProperty(output, s_sources_name, 'COMPILE_FLAGS', cflags, ' ') |
950
|
|
|
|
951
|
|
|
if c_sources and (cflags or cflags_c): |
952
|
|
|
flags = [] |
953
|
|
|
flags.extend(cflags) |
954
|
|
|
flags.extend(cflags_c) |
955
|
|
|
SetFilesProperty(output, c_sources_name, 'COMPILE_FLAGS', flags, ' ') |
956
|
|
|
|
957
|
|
|
if cxx_sources and (cflags or cflags_cxx): |
958
|
|
|
flags = [] |
959
|
|
|
flags.extend(cflags) |
960
|
|
|
flags.extend(cflags_cxx) |
961
|
|
|
SetFilesProperty(output, cxx_sources_name, 'COMPILE_FLAGS', flags, ' ') |
962
|
|
|
|
963
|
|
|
# Linker flags |
964
|
|
|
ldflags = config.get('ldflags') |
965
|
|
|
if ldflags is not None: |
966
|
|
|
SetTargetProperty(output, cmake_target_name, 'LINK_FLAGS', ldflags, ' ') |
967
|
|
|
|
968
|
|
|
# Note on Dependencies and Libraries: |
969
|
|
|
# CMake wants to handle link order, resolving the link line up front. |
970
|
|
|
# Gyp does not retain or enforce specifying enough information to do so. |
971
|
|
|
# So do as other gyp generators and use --start-group and --end-group. |
972
|
|
|
# Give CMake as little information as possible so that it doesn't mess it up. |
973
|
|
|
|
974
|
|
|
# Dependencies |
975
|
|
|
rawDeps = spec.get('dependencies', []) |
976
|
|
|
|
977
|
|
|
static_deps = [] |
978
|
|
|
shared_deps = [] |
979
|
|
|
other_deps = [] |
980
|
|
|
for rawDep in rawDeps: |
981
|
|
|
dep_cmake_name = namer.CreateCMakeTargetName(rawDep) |
982
|
|
|
dep_spec = target_dicts.get(rawDep, {}) |
983
|
|
|
dep_target_type = dep_spec.get('type', None) |
984
|
|
|
|
985
|
|
|
if dep_target_type == 'static_library': |
986
|
|
|
static_deps.append(dep_cmake_name) |
987
|
|
|
elif dep_target_type == 'shared_library': |
988
|
|
|
shared_deps.append(dep_cmake_name) |
989
|
|
|
else: |
990
|
|
|
other_deps.append(dep_cmake_name) |
991
|
|
|
|
992
|
|
|
# ensure all external dependencies are complete before internal dependencies |
993
|
|
|
# extra_deps currently only depend on their own deps, so otherwise run early |
994
|
|
|
if static_deps or shared_deps or other_deps: |
995
|
|
|
for extra_dep in extra_deps: |
996
|
|
|
output.write('add_dependencies(') |
997
|
|
|
output.write(extra_dep) |
998
|
|
|
output.write('\n') |
999
|
|
|
for deps in (static_deps, shared_deps, other_deps): |
1000
|
|
|
for dep in gyp.common.uniquer(deps): |
1001
|
|
|
output.write(' ') |
1002
|
|
|
output.write(dep) |
1003
|
|
|
output.write('\n') |
1004
|
|
|
output.write(')\n') |
1005
|
|
|
|
1006
|
|
|
linkable = target_type in ('executable', 'loadable_module', 'shared_library') |
1007
|
|
|
other_deps.extend(extra_deps) |
1008
|
|
|
if other_deps or (not linkable and (static_deps or shared_deps)): |
1009
|
|
|
output.write('add_dependencies(') |
1010
|
|
|
output.write(cmake_target_name) |
1011
|
|
|
output.write('\n') |
1012
|
|
|
for dep in gyp.common.uniquer(other_deps): |
1013
|
|
|
output.write(' ') |
1014
|
|
|
output.write(dep) |
1015
|
|
|
output.write('\n') |
1016
|
|
|
if not linkable: |
1017
|
|
|
for deps in (static_deps, shared_deps): |
1018
|
|
|
for lib_dep in gyp.common.uniquer(deps): |
1019
|
|
|
output.write(' ') |
1020
|
|
|
output.write(lib_dep) |
1021
|
|
|
output.write('\n') |
1022
|
|
|
output.write(')\n') |
1023
|
|
|
|
1024
|
|
|
# Libraries |
1025
|
|
|
if linkable: |
1026
|
|
|
external_libs = [lib for lib in spec.get('libraries', []) if len(lib) > 0] |
1027
|
|
|
if external_libs or static_deps or shared_deps: |
1028
|
|
|
output.write('target_link_libraries(') |
1029
|
|
|
output.write(cmake_target_name) |
1030
|
|
|
output.write('\n') |
1031
|
|
|
if static_deps: |
1032
|
|
|
write_group = circular_libs and len(static_deps) > 1 |
1033
|
|
|
if write_group: |
1034
|
|
|
output.write('-Wl,--start-group\n') |
1035
|
|
|
for dep in gyp.common.uniquer(static_deps): |
1036
|
|
|
output.write(' ') |
1037
|
|
|
output.write(dep) |
1038
|
|
|
output.write('\n') |
1039
|
|
|
if write_group: |
1040
|
|
|
output.write('-Wl,--end-group\n') |
1041
|
|
|
if shared_deps: |
1042
|
|
|
for dep in gyp.common.uniquer(shared_deps): |
1043
|
|
|
output.write(' ') |
1044
|
|
|
output.write(dep) |
1045
|
|
|
output.write('\n') |
1046
|
|
|
if external_libs: |
1047
|
|
|
for lib in gyp.common.uniquer(external_libs): |
1048
|
|
|
output.write(' ') |
1049
|
|
|
output.write(lib) |
1050
|
|
|
output.write('\n') |
1051
|
|
|
|
1052
|
|
|
output.write(')\n') |
1053
|
|
|
|
1054
|
|
|
UnsetVariable(output, 'TOOLSET') |
1055
|
|
|
UnsetVariable(output, 'TARGET') |
1056
|
|
|
|
1057
|
|
|
|
1058
|
|
|
def GenerateOutputForConfig(target_list, target_dicts, data, |
1059
|
|
|
params, config_to_use): |
1060
|
|
|
options = params['options'] |
1061
|
|
|
generator_flags = params['generator_flags'] |
1062
|
|
|
|
1063
|
|
|
# generator_dir: relative path from pwd to where make puts build files. |
1064
|
|
|
# Makes migrating from make to cmake easier, cmake doesn't put anything here. |
1065
|
|
|
# Each Gyp configuration creates a different CMakeLists.txt file |
1066
|
|
|
# to avoid incompatibilities between Gyp and CMake configurations. |
1067
|
|
|
generator_dir = os.path.relpath(options.generator_output or '.') |
1068
|
|
|
|
1069
|
|
|
# output_dir: relative path from generator_dir to the build directory. |
1070
|
|
|
output_dir = generator_flags.get('output_dir', 'out') |
1071
|
|
|
|
1072
|
|
|
# build_dir: relative path from source root to our output files. |
1073
|
|
|
# e.g. "out/Debug" |
1074
|
|
|
build_dir = os.path.normpath(os.path.join(generator_dir, |
1075
|
|
|
output_dir, |
1076
|
|
|
config_to_use)) |
1077
|
|
|
|
1078
|
|
|
toplevel_build = os.path.join(options.toplevel_dir, build_dir) |
1079
|
|
|
|
1080
|
|
|
output_file = os.path.join(toplevel_build, 'CMakeLists.txt') |
1081
|
|
|
gyp.common.EnsureDirExists(output_file) |
1082
|
|
|
|
1083
|
|
|
output = open(output_file, 'w') |
1084
|
|
|
output.write('cmake_minimum_required(VERSION 2.8.8 FATAL_ERROR)\n') |
1085
|
|
|
output.write('cmake_policy(VERSION 2.8.8)\n') |
1086
|
|
|
|
1087
|
|
|
gyp_file, project_target, _ = gyp.common.ParseQualifiedTarget(target_list[-1]) |
1088
|
|
|
output.write('project(') |
1089
|
|
|
output.write(project_target) |
1090
|
|
|
output.write(')\n') |
1091
|
|
|
|
1092
|
|
|
SetVariable(output, 'configuration', config_to_use) |
1093
|
|
|
|
1094
|
|
|
ar = None |
1095
|
|
|
cc = None |
1096
|
|
|
cxx = None |
1097
|
|
|
|
1098
|
|
|
make_global_settings = data[gyp_file].get('make_global_settings', []) |
1099
|
|
|
build_to_top = gyp.common.InvertRelativePath(build_dir, |
1100
|
|
|
options.toplevel_dir) |
1101
|
|
|
for key, value in make_global_settings: |
1102
|
|
|
if key == 'AR': |
1103
|
|
|
ar = os.path.join(build_to_top, value) |
1104
|
|
|
if key == 'CC': |
1105
|
|
|
cc = os.path.join(build_to_top, value) |
1106
|
|
|
if key == 'CXX': |
1107
|
|
|
cxx = os.path.join(build_to_top, value) |
1108
|
|
|
|
1109
|
|
|
ar = gyp.common.GetEnvironFallback(['AR_target', 'AR'], ar) |
1110
|
|
|
cc = gyp.common.GetEnvironFallback(['CC_target', 'CC'], cc) |
1111
|
|
|
cxx = gyp.common.GetEnvironFallback(['CXX_target', 'CXX'], cxx) |
1112
|
|
|
|
1113
|
|
|
if ar: |
1114
|
|
|
SetVariable(output, 'CMAKE_AR', ar) |
1115
|
|
|
if cc: |
1116
|
|
|
SetVariable(output, 'CMAKE_C_COMPILER', cc) |
1117
|
|
|
if cxx: |
1118
|
|
|
SetVariable(output, 'CMAKE_CXX_COMPILER', cxx) |
1119
|
|
|
|
1120
|
|
|
# The following appears to be as-yet undocumented. |
1121
|
|
|
# http://public.kitware.com/Bug/view.php?id=8392 |
1122
|
|
|
output.write('enable_language(ASM)\n') |
1123
|
|
|
# ASM-ATT does not support .S files. |
1124
|
|
|
# output.write('enable_language(ASM-ATT)\n') |
1125
|
|
|
|
1126
|
|
|
if cc: |
1127
|
|
|
SetVariable(output, 'CMAKE_ASM_COMPILER', cc) |
1128
|
|
|
|
1129
|
|
|
SetVariable(output, 'builddir', '${CMAKE_CURRENT_BINARY_DIR}') |
1130
|
|
|
SetVariable(output, 'obj', '${builddir}/obj') |
1131
|
|
|
output.write('\n') |
1132
|
|
|
|
1133
|
|
|
# TODO: Undocumented/unsupported (the CMake Java generator depends on it). |
1134
|
|
|
# CMake by default names the object resulting from foo.c to be foo.c.o. |
1135
|
|
|
# Gyp traditionally names the object resulting from foo.c foo.o. |
1136
|
|
|
# This should be irrelevant, but some targets extract .o files from .a |
1137
|
|
|
# and depend on the name of the extracted .o files. |
1138
|
|
|
output.write('set(CMAKE_C_OUTPUT_EXTENSION_REPLACE 1)\n') |
1139
|
|
|
output.write('set(CMAKE_CXX_OUTPUT_EXTENSION_REPLACE 1)\n') |
1140
|
|
|
output.write('\n') |
1141
|
|
|
|
1142
|
|
|
# Force ninja to use rsp files. Otherwise link and ar lines can get too long, |
1143
|
|
|
# resulting in 'Argument list too long' errors. |
1144
|
|
|
output.write('set(CMAKE_NINJA_FORCE_RESPONSE_FILE 1)\n') |
1145
|
|
|
output.write('\n') |
1146
|
|
|
|
1147
|
|
|
namer = CMakeNamer(target_list) |
1148
|
|
|
|
1149
|
|
|
# The list of targets upon which the 'all' target should depend. |
1150
|
|
|
# CMake has it's own implicit 'all' target, one is not created explicitly. |
1151
|
|
|
all_qualified_targets = set() |
1152
|
|
|
for build_file in params['build_files']: |
1153
|
|
|
for qualified_target in gyp.common.AllTargets(target_list, |
1154
|
|
|
target_dicts, |
1155
|
|
|
os.path.normpath(build_file)): |
1156
|
|
|
all_qualified_targets.add(qualified_target) |
1157
|
|
|
|
1158
|
|
|
for qualified_target in target_list: |
1159
|
|
|
WriteTarget(namer, qualified_target, target_dicts, build_dir, config_to_use, |
1160
|
|
|
options, generator_flags, all_qualified_targets, output) |
1161
|
|
|
|
1162
|
|
|
output.close() |
1163
|
|
|
|
1164
|
|
|
|
1165
|
|
|
def PerformBuild(data, configurations, params): |
1166
|
|
|
options = params['options'] |
1167
|
|
|
generator_flags = params['generator_flags'] |
1168
|
|
|
|
1169
|
|
|
# generator_dir: relative path from pwd to where make puts build files. |
1170
|
|
|
# Makes migrating from make to cmake easier, cmake doesn't put anything here. |
1171
|
|
|
generator_dir = os.path.relpath(options.generator_output or '.') |
1172
|
|
|
|
1173
|
|
|
# output_dir: relative path from generator_dir to the build directory. |
1174
|
|
|
output_dir = generator_flags.get('output_dir', 'out') |
1175
|
|
|
|
1176
|
|
|
for config_name in configurations: |
1177
|
|
|
# build_dir: relative path from source root to our output files. |
1178
|
|
|
# e.g. "out/Debug" |
1179
|
|
|
build_dir = os.path.normpath(os.path.join(generator_dir, |
1180
|
|
|
output_dir, |
1181
|
|
|
config_name)) |
1182
|
|
|
arguments = ['cmake', '-G', 'Ninja'] |
1183
|
|
|
print 'Generating [%s]: %s' % (config_name, arguments) |
1184
|
|
|
subprocess.check_call(arguments, cwd=build_dir) |
1185
|
|
|
|
1186
|
|
|
arguments = ['ninja', '-C', build_dir] |
1187
|
|
|
print 'Building [%s]: %s' % (config_name, arguments) |
1188
|
|
|
subprocess.check_call(arguments) |
1189
|
|
|
|
1190
|
|
|
|
1191
|
|
|
def CallGenerateOutputForConfig(arglist): |
1192
|
|
|
# Ignore the interrupt signal so that the parent process catches it and |
1193
|
|
|
# kills all multiprocessing children. |
1194
|
|
|
signal.signal(signal.SIGINT, signal.SIG_IGN) |
1195
|
|
|
|
1196
|
|
|
target_list, target_dicts, data, params, config_name = arglist |
1197
|
|
|
GenerateOutputForConfig(target_list, target_dicts, data, params, config_name) |
1198
|
|
|
|
1199
|
|
|
|
1200
|
|
|
def GenerateOutput(target_list, target_dicts, data, params): |
1201
|
|
|
user_config = params.get('generator_flags', {}).get('config', None) |
1202
|
|
|
if user_config: |
1203
|
|
|
GenerateOutputForConfig(target_list, target_dicts, data, |
1204
|
|
|
params, user_config) |
1205
|
|
|
else: |
1206
|
|
|
config_names = target_dicts[target_list[0]]['configurations'].keys() |
1207
|
|
|
if params['parallel']: |
1208
|
|
|
try: |
1209
|
|
|
pool = multiprocessing.Pool(len(config_names)) |
1210
|
|
|
arglists = [] |
1211
|
|
|
for config_name in config_names: |
1212
|
|
|
arglists.append((target_list, target_dicts, data, |
1213
|
|
|
params, config_name)) |
1214
|
|
|
pool.map(CallGenerateOutputForConfig, arglists) |
1215
|
|
|
except KeyboardInterrupt, e: |
1216
|
|
|
pool.terminate() |
1217
|
|
|
raise e |
1218
|
|
|
else: |
1219
|
|
|
for config_name in config_names: |
1220
|
|
|
GenerateOutputForConfig(target_list, target_dicts, data, |
1221
|
|
|
params, config_name) |
1222
|
|
|
|