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_SOURCE_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_SOURCE_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, source_names, property_name, values, sep): 154 """Given a set of source files, sets the given property on them.""" 155 output.write('set_source_files_properties(\n') 156 for source_name in source_names: 157 output.write(' ') 158 output.write(source_name) 159 output.write('\n') 160 output.write(' PROPERTIES\n ') 161 output.write(property_name) 162 output.write(' "') 163 for value in values: 164 output.write(CMakeStringEscape(value)) 165 output.write(sep) 166 output.write('"\n)\n') 167 168 169 def SetTargetProperty(output, target_name, property_name, values, sep=''): 170 """Given a target, sets the given property.""" 171 output.write('set_target_properties(') 172 output.write(target_name) 173 output.write(' PROPERTIES ') 174 output.write(property_name) 175 output.write(' "') 176 for value in values: 177 output.write(CMakeStringEscape(value)) 178 output.write(sep) 179 output.write('")\n') 180 181 182 def SetVariable(output, variable_name, value): 183 """Sets a CMake variable.""" 184 output.write('set(') 185 output.write(variable_name) 186 output.write(' "') 187 output.write(CMakeStringEscape(value)) 188 output.write('")\n') 189 190 191 def SetVariableList(output, variable_name, values): 192 """Sets a CMake variable to a list.""" 193 if not values: 194 return SetVariable(output, variable_name, "") 195 if len(values) == 1: 196 return SetVariable(output, variable_name, values[0]) 197 output.write('list(APPEND ') 198 output.write(variable_name) 199 output.write('\n "') 200 output.write('"\n "'.join([CMakeStringEscape(value) for value in values])) 201 output.write('")\n') 202 203 204 def UnsetVariable(output, variable_name): 205 """Unsets a CMake variable.""" 206 output.write('unset(') 207 output.write(variable_name) 208 output.write(')\n') 209 210 211 def WriteVariable(output, variable_name, prepend=None): 212 if prepend: 213 output.write(prepend) 214 output.write('${') 215 output.write(variable_name) 216 output.write('}') 217 218 219 class CMakeTargetType: 220 def __init__(self, command, modifier, property_modifier): 221 self.command = command 222 self.modifier = modifier 223 self.property_modifier = property_modifier 224 225 226 cmake_target_type_from_gyp_target_type = { 227 'executable': CMakeTargetType('add_executable', None, 'RUNTIME'), 228 'static_library': CMakeTargetType('add_library', 'STATIC', 'ARCHIVE'), 229 'shared_library': CMakeTargetType('add_library', 'SHARED', 'LIBRARY'), 230 'loadable_module': CMakeTargetType('add_library', 'MODULE', 'LIBRARY'), 231 'none': CMakeTargetType('add_custom_target', 'SOURCES', None), 232 } 233 234 235 def StringToCMakeTargetName(a): 236 """Converts the given string 'a' to a valid CMake target name. 237 238 All invalid characters are replaced by '_'. 239 Invalid for cmake: ' ', '/', '(', ')' 240 Invalid for make: ':' 241 Invalid for unknown reasons but cause failures: '.' 242 """ 243 return a.translate(string.maketrans(' /():.', '______')) 244 245 246 def WriteActions(target_name, actions, extra_sources, extra_deps, 247 path_to_gyp, output): 248 """Write CMake for the 'actions' in the target. 249 250 Args: 251 target_name: the name of the CMake target being generated. 252 actions: the Gyp 'actions' dict for this target. 253 extra_sources: [(<cmake_src>, <src>)] to append with generated source files. 254 extra_deps: [<cmake_taget>] to append with generated targets. 255 path_to_gyp: relative path from CMakeLists.txt being generated to 256 the Gyp file in which the target being generated is defined. 257 """ 258 for action in actions: 259 action_name = StringToCMakeTargetName(action['action_name']) 260 action_target_name = '%s__%s' % (target_name, action_name) 261 262 inputs = action['inputs'] 263 inputs_name = action_target_name + '__input' 264 SetVariableList(output, inputs_name, 265 [NormjoinPathForceCMakeSource(path_to_gyp, dep) for dep in inputs]) 266 267 outputs = action['outputs'] 268 cmake_outputs = [NormjoinPathForceCMakeSource(path_to_gyp, out) 269 for out in outputs] 270 outputs_name = action_target_name + '__output' 271 SetVariableList(output, outputs_name, cmake_outputs) 272 273 # Build up a list of outputs. 274 # Collect the output dirs we'll need. 275 dirs = set(dir for dir in (os.path.dirname(o) for o in outputs) if dir) 276 277 if int(action.get('process_outputs_as_sources', False)): 278 extra_sources.extend(zip(cmake_outputs, outputs)) 279 280 # add_custom_command 281 output.write('add_custom_command(OUTPUT ') 282 WriteVariable(output, outputs_name) 283 output.write('\n') 284 285 if len(dirs) > 0: 286 for directory in dirs: 287 output.write(' COMMAND ${CMAKE_COMMAND} -E make_directory ') 288 output.write(directory) 289 output.write('\n') 290 291 output.write(' COMMAND ') 292 output.write(gyp.common.EncodePOSIXShellList(action['action'])) 293 output.write('\n') 294 295 output.write(' DEPENDS ') 296 WriteVariable(output, inputs_name) 297 output.write('\n') 298 299 output.write(' WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/') 300 output.write(path_to_gyp) 301 output.write('\n') 302 303 output.write(' COMMENT ') 304 if 'message' in action: 305 output.write(action['message']) 306 else: 307 output.write(action_target_name) 308 output.write('\n') 309 310 output.write(' VERBATIM\n') 311 output.write(')\n') 312 313 # add_custom_target 314 output.write('add_custom_target(') 315 output.write(action_target_name) 316 output.write('\n DEPENDS ') 317 WriteVariable(output, outputs_name) 318 output.write('\n SOURCES ') 319 WriteVariable(output, inputs_name) 320 output.write('\n)\n') 321 322 extra_deps.append(action_target_name) 323 324 325 def NormjoinRulePathForceCMakeSource(base_path, rel_path, rule_source): 326 if rel_path.startswith(("${RULE_INPUT_PATH}","${RULE_INPUT_DIRNAME}")): 327 if any([rule_source.startswith(var) for var in FULL_PATH_VARS]): 328 return rel_path 329 return NormjoinPathForceCMakeSource(base_path, rel_path) 330 331 332 def WriteRules(target_name, rules, extra_sources, extra_deps, 333 path_to_gyp, output): 334 """Write CMake for the 'rules' in the target. 335 336 Args: 337 target_name: the name of the CMake target being generated. 338 actions: the Gyp 'actions' dict for this target. 339 extra_sources: [(<cmake_src>, <src>)] to append with generated source files. 340 extra_deps: [<cmake_taget>] to append with generated targets. 341 path_to_gyp: relative path from CMakeLists.txt being generated to 342 the Gyp file in which the target being generated is defined. 343 """ 344 for rule in rules: 345 rule_name = StringToCMakeTargetName(target_name + '__' + rule['rule_name']) 346 347 inputs = rule.get('inputs', []) 348 inputs_name = rule_name + '__input' 349 SetVariableList(output, inputs_name, 350 [NormjoinPathForceCMakeSource(path_to_gyp, dep) for dep in inputs]) 351 outputs = rule['outputs'] 352 var_outputs = [] 353 354 for count, rule_source in enumerate(rule.get('rule_sources', [])): 355 action_name = rule_name + '_' + str(count) 356 357 rule_source_dirname, rule_source_basename = os.path.split(rule_source) 358 rule_source_root, rule_source_ext = os.path.splitext(rule_source_basename) 359 360 SetVariable(output, 'RULE_INPUT_PATH', rule_source) 361 SetVariable(output, 'RULE_INPUT_DIRNAME', rule_source_dirname) 362 SetVariable(output, 'RULE_INPUT_NAME', rule_source_basename) 363 SetVariable(output, 'RULE_INPUT_ROOT', rule_source_root) 364 SetVariable(output, 'RULE_INPUT_EXT', rule_source_ext) 365 366 # Build up a list of outputs. 367 # Collect the output dirs we'll need. 368 dirs = set(dir for dir in (os.path.dirname(o) for o in outputs) if dir) 369 370 # Create variables for the output, as 'local' variable will be unset. 371 these_outputs = [] 372 for output_index, out in enumerate(outputs): 373 output_name = action_name + '_' + str(output_index) 374 SetVariable(output, output_name, 375 NormjoinRulePathForceCMakeSource(path_to_gyp, out, 376 rule_source)) 377 if int(rule.get('process_outputs_as_sources', False)): 378 extra_sources.append(('${' + output_name + '}', out)) 379 these_outputs.append('${' + output_name + '}') 380 var_outputs.append('${' + output_name + '}') 381 382 # add_custom_command 383 output.write('add_custom_command(OUTPUT\n') 384 for out in these_outputs: 385 output.write(' ') 386 output.write(out) 387 output.write('\n') 388 389 for directory in dirs: 390 output.write(' COMMAND ${CMAKE_COMMAND} -E make_directory ') 391 output.write(directory) 392 output.write('\n') 393 394 output.write(' COMMAND ') 395 output.write(gyp.common.EncodePOSIXShellList(rule['action'])) 396 output.write('\n') 397 398 output.write(' DEPENDS ') 399 WriteVariable(output, inputs_name) 400 output.write(' ') 401 output.write(NormjoinPath(path_to_gyp, rule_source)) 402 output.write('\n') 403 404 # CMAKE_SOURCE_DIR is where the CMakeLists.txt lives. 405 # The cwd is the current build directory. 406 output.write(' WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/') 407 output.write(path_to_gyp) 408 output.write('\n') 409 410 output.write(' COMMENT ') 411 if 'message' in rule: 412 output.write(rule['message']) 413 else: 414 output.write(action_name) 415 output.write('\n') 416 417 output.write(' VERBATIM\n') 418 output.write(')\n') 419 420 UnsetVariable(output, 'RULE_INPUT_PATH') 421 UnsetVariable(output, 'RULE_INPUT_DIRNAME') 422 UnsetVariable(output, 'RULE_INPUT_NAME') 423 UnsetVariable(output, 'RULE_INPUT_ROOT') 424 UnsetVariable(output, 'RULE_INPUT_EXT') 425 426 # add_custom_target 427 output.write('add_custom_target(') 428 output.write(rule_name) 429 output.write(' DEPENDS\n') 430 for out in var_outputs: 431 output.write(' ') 432 output.write(out) 433 output.write('\n') 434 output.write('SOURCES ') 435 WriteVariable(output, inputs_name) 436 output.write('\n') 437 for rule_source in rule.get('rule_sources', []): 438 output.write(' ') 439 output.write(NormjoinPath(path_to_gyp, rule_source)) 440 output.write('\n') 441 output.write(')\n') 442 443 extra_deps.append(rule_name) 444 445 446 def WriteCopies(target_name, copies, extra_deps, path_to_gyp, output): 447 """Write CMake for the 'copies' in the target. 448 449 Args: 450 target_name: the name of the CMake target being generated. 451 actions: the Gyp 'actions' dict for this target. 452 extra_deps: [<cmake_taget>] to append with generated targets. 453 path_to_gyp: relative path from CMakeLists.txt being generated to 454 the Gyp file in which the target being generated is defined. 455 """ 456 copy_name = target_name + '__copies' 457 458 # CMake gets upset with custom targets with OUTPUT which specify no output. 459 have_copies = any(copy['files'] for copy in copies) 460 if not have_copies: 461 output.write('add_custom_target(') 462 output.write(copy_name) 463 output.write(')\n') 464 extra_deps.append(copy_name) 465 return 466 467 class Copy: 468 def __init__(self, ext, command): 469 self.cmake_inputs = [] 470 self.cmake_outputs = [] 471 self.gyp_inputs = [] 472 self.gyp_outputs = [] 473 self.ext = ext 474 self.inputs_name = None 475 self.outputs_name = None 476 self.command = command 477 478 file_copy = Copy('', 'copy') 479 dir_copy = Copy('_dirs', 'copy_directory') 480 481 for copy in copies: 482 files = copy['files'] 483 destination = copy['destination'] 484 for src in files: 485 path = os.path.normpath(src) 486 basename = os.path.split(path)[1] 487 dst = os.path.join(destination, basename) 488 489 copy = file_copy if os.path.basename(src) else dir_copy 490 491 copy.cmake_inputs.append(NormjoinPath(path_to_gyp, src)) 492 copy.cmake_outputs.append(NormjoinPathForceCMakeSource(path_to_gyp, dst)) 493 copy.gyp_inputs.append(src) 494 copy.gyp_outputs.append(dst) 495 496 for copy in (file_copy, dir_copy): 497 if copy.cmake_inputs: 498 copy.inputs_name = copy_name + '__input' + copy.ext 499 SetVariableList(output, copy.inputs_name, copy.cmake_inputs) 500 501 copy.outputs_name = copy_name + '__output' + copy.ext 502 SetVariableList(output, copy.outputs_name, copy.cmake_outputs) 503 504 # add_custom_command 505 output.write('add_custom_command(\n') 506 507 output.write('OUTPUT') 508 for copy in (file_copy, dir_copy): 509 if copy.outputs_name: 510 WriteVariable(output, copy.outputs_name, ' ') 511 output.write('\n') 512 513 for copy in (file_copy, dir_copy): 514 for src, dst in zip(copy.gyp_inputs, copy.gyp_outputs): 515 # 'cmake -E copy src dst' will create the 'dst' directory if needed. 516 output.write('COMMAND ${CMAKE_COMMAND} -E %s ' % copy.command) 517 output.write(src) 518 output.write(' ') 519 output.write(dst) 520 output.write("\n") 521 522 output.write('DEPENDS') 523 for copy in (file_copy, dir_copy): 524 if copy.inputs_name: 525 WriteVariable(output, copy.inputs_name, ' ') 526 output.write('\n') 527 528 output.write('WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/') 529 output.write(path_to_gyp) 530 output.write('\n') 531 532 output.write('COMMENT Copying for ') 533 output.write(target_name) 534 output.write('\n') 535 536 output.write('VERBATIM\n') 537 output.write(')\n') 538 539 # add_custom_target 540 output.write('add_custom_target(') 541 output.write(copy_name) 542 output.write('\n DEPENDS') 543 for copy in (file_copy, dir_copy): 544 if copy.outputs_name: 545 WriteVariable(output, copy.outputs_name, ' ') 546 output.write('\n SOURCES') 547 if file_copy.inputs_name: 548 WriteVariable(output, file_copy.inputs_name, ' ') 549 output.write('\n)\n') 550 551 extra_deps.append(copy_name) 552 553 554 def CreateCMakeTargetBaseName(qualified_target): 555 """This is the name we would like the target to have.""" 556 _, gyp_target_name, gyp_target_toolset = ( 557 gyp.common.ParseQualifiedTarget(qualified_target)) 558 cmake_target_base_name = gyp_target_name 559 if gyp_target_toolset and gyp_target_toolset != 'target': 560 cmake_target_base_name += '_' + gyp_target_toolset 561 return StringToCMakeTargetName(cmake_target_base_name) 562 563 564 def CreateCMakeTargetFullName(qualified_target): 565 """An unambiguous name for the target.""" 566 gyp_file, gyp_target_name, gyp_target_toolset = ( 567 gyp.common.ParseQualifiedTarget(qualified_target)) 568 cmake_target_full_name = gyp_file + ':' + gyp_target_name 569 if gyp_target_toolset and gyp_target_toolset != 'target': 570 cmake_target_full_name += '_' + gyp_target_toolset 571 return StringToCMakeTargetName(cmake_target_full_name) 572 573 574 class CMakeNamer(object): 575 """Converts Gyp target names into CMake target names. 576 577 CMake requires that target names be globally unique. One way to ensure 578 this is to fully qualify the names of the targets. Unfortunatly, this 579 ends up with all targets looking like "chrome_chrome_gyp_chrome" instead 580 of just "chrome". If this generator were only interested in building, it 581 would be possible to fully qualify all target names, then create 582 unqualified target names which depend on all qualified targets which 583 should have had that name. This is more or less what the 'make' generator 584 does with aliases. However, one goal of this generator is to create CMake 585 files for use with IDEs, and fully qualified names are not as user 586 friendly. 587 588 Since target name collision is rare, we do the above only when required. 589 590 Toolset variants are always qualified from the base, as this is required for 591 building. However, it also makes sense for an IDE, as it is possible for 592 defines to be different. 593 """ 594 def __init__(self, target_list): 595 self.cmake_target_base_names_conficting = set() 596 597 cmake_target_base_names_seen = set() 598 for qualified_target in target_list: 599 cmake_target_base_name = CreateCMakeTargetBaseName(qualified_target) 600 601 if cmake_target_base_name not in cmake_target_base_names_seen: 602 cmake_target_base_names_seen.add(cmake_target_base_name) 603 else: 604 self.cmake_target_base_names_conficting.add(cmake_target_base_name) 605 606 def CreateCMakeTargetName(self, qualified_target): 607 base_name = CreateCMakeTargetBaseName(qualified_target) 608 if base_name in self.cmake_target_base_names_conficting: 609 return CreateCMakeTargetFullName(qualified_target) 610 return base_name 611 612 613 def WriteTarget(namer, qualified_target, target_dicts, build_dir, config_to_use, 614 options, generator_flags, all_qualified_targets, output): 615 616 # The make generator does this always. 617 # TODO: It would be nice to be able to tell CMake all dependencies. 618 circular_libs = generator_flags.get('circular', True) 619 620 if not generator_flags.get('standalone', False): 621 output.write('\n#') 622 output.write(qualified_target) 623 output.write('\n') 624 625 gyp_file, _, _ = gyp.common.ParseQualifiedTarget(qualified_target) 626 rel_gyp_file = gyp.common.RelativePath(gyp_file, options.toplevel_dir) 627 rel_gyp_dir = os.path.dirname(rel_gyp_file) 628 629 # Relative path from build dir to top dir. 630 build_to_top = gyp.common.InvertRelativePath(build_dir, options.toplevel_dir) 631 # Relative path from build dir to gyp dir. 632 build_to_gyp = os.path.join(build_to_top, rel_gyp_dir) 633 634 path_from_cmakelists_to_gyp = build_to_gyp 635 636 spec = target_dicts.get(qualified_target, {}) 637 config = spec.get('configurations', {}).get(config_to_use, {}) 638 639 target_name = spec.get('target_name', '<missing target name>') 640 target_type = spec.get('type', '<missing target type>') 641 target_toolset = spec.get('toolset') 642 643 SetVariable(output, 'TARGET', target_name) 644 SetVariable(output, 'TOOLSET', target_toolset) 645 646 cmake_target_name = namer.CreateCMakeTargetName(qualified_target) 647 648 extra_sources = [] 649 extra_deps = [] 650 651 # Actions must come first, since they can generate more OBJs for use below. 652 if 'actions' in spec: 653 WriteActions(cmake_target_name, spec['actions'], extra_sources, extra_deps, 654 path_from_cmakelists_to_gyp, output) 655 656 # Rules must be early like actions. 657 if 'rules' in spec: 658 WriteRules(cmake_target_name, spec['rules'], extra_sources, extra_deps, 659 path_from_cmakelists_to_gyp, output) 660 661 # Copies 662 if 'copies' in spec: 663 WriteCopies(cmake_target_name, spec['copies'], extra_deps, 664 path_from_cmakelists_to_gyp, output) 665 666 # Target and sources 667 srcs = spec.get('sources', []) 668 669 # Gyp separates the sheep from the goats based on file extensions. 670 def partition(l, p): 671 return reduce(lambda x, e: x[not p(e)].append(e) or x, l, ([], [])) 672 compilable_srcs, other_srcs = partition(srcs, Compilable) 673 674 # CMake gets upset when executable targets provide no sources. 675 if target_type == 'executable' and not compilable_srcs and not extra_sources: 676 print ('Executable %s has no complilable sources, treating as "none".' % 677 target_name ) 678 target_type = 'none' 679 680 cmake_target_type = cmake_target_type_from_gyp_target_type.get(target_type) 681 if cmake_target_type is None: 682 print ('Target %s has unknown target type %s, skipping.' % 683 ( target_name, target_type ) ) 684 return 685 686 other_srcs_name = None 687 if other_srcs: 688 other_srcs_name = cmake_target_name + '__other_srcs' 689 SetVariableList(output, other_srcs_name, 690 [NormjoinPath(path_from_cmakelists_to_gyp, src) for src in other_srcs]) 691 692 # CMake is opposed to setting linker directories and considers the practice 693 # of setting linker directories dangerous. Instead, it favors the use of 694 # find_library and passing absolute paths to target_link_libraries. 695 # However, CMake does provide the command link_directories, which adds 696 # link directories to targets defined after it is called. 697 # As a result, link_directories must come before the target definition. 698 # CMake unfortunately has no means of removing entries from LINK_DIRECTORIES. 699 library_dirs = config.get('library_dirs') 700 if library_dirs is not None: 701 output.write('link_directories(') 702 for library_dir in library_dirs: 703 output.write(' ') 704 output.write(NormjoinPath(path_from_cmakelists_to_gyp, library_dir)) 705 output.write('\n') 706 output.write(')\n') 707 708 output.write(cmake_target_type.command) 709 output.write('(') 710 output.write(cmake_target_name) 711 712 if cmake_target_type.modifier is not None: 713 output.write(' ') 714 output.write(cmake_target_type.modifier) 715 716 if other_srcs_name: 717 WriteVariable(output, other_srcs_name, ' ') 718 719 output.write('\n') 720 721 for src in compilable_srcs: 722 output.write(' ') 723 output.write(NormjoinPath(path_from_cmakelists_to_gyp, src)) 724 output.write('\n') 725 for extra_source in extra_sources: 726 output.write(' ') 727 src, _ = extra_source 728 output.write(NormjoinPath(path_from_cmakelists_to_gyp, src)) 729 output.write('\n') 730 731 output.write(')\n') 732 733 # Output name and location. 734 if target_type != 'none': 735 # Mark uncompiled sources as uncompiled. 736 if other_srcs_name: 737 output.write('set_source_files_properties(') 738 WriteVariable(output, other_srcs_name, '') 739 output.write(' PROPERTIES HEADER_FILE_ONLY "TRUE")\n') 740 741 # Output directory 742 target_output_directory = spec.get('product_dir') 743 if target_output_directory is None: 744 if target_type in ('executable', 'loadable_module'): 745 target_output_directory = generator_default_variables['PRODUCT_DIR'] 746 elif target_type in ('shared_library'): 747 target_output_directory = '${builddir}/lib.${TOOLSET}' 748 elif spec.get('standalone_static_library', False): 749 target_output_directory = generator_default_variables['PRODUCT_DIR'] 750 else: 751 base_path = gyp.common.RelativePath(os.path.dirname(gyp_file), 752 options.toplevel_dir) 753 target_output_directory = '${obj}.${TOOLSET}' 754 target_output_directory = ( 755 os.path.join(target_output_directory, base_path)) 756 757 cmake_target_output_directory = NormjoinPathForceCMakeSource( 758 path_from_cmakelists_to_gyp, 759 target_output_directory) 760 SetTargetProperty(output, 761 cmake_target_name, 762 cmake_target_type.property_modifier + '_OUTPUT_DIRECTORY', 763 cmake_target_output_directory) 764 765 # Output name 766 default_product_prefix = '' 767 default_product_name = target_name 768 default_product_ext = '' 769 if target_type == 'static_library': 770 static_library_prefix = generator_default_variables['STATIC_LIB_PREFIX'] 771 default_product_name = RemovePrefix(default_product_name, 772 static_library_prefix) 773 default_product_prefix = static_library_prefix 774 default_product_ext = generator_default_variables['STATIC_LIB_SUFFIX'] 775 776 elif target_type in ('loadable_module', 'shared_library'): 777 shared_library_prefix = generator_default_variables['SHARED_LIB_PREFIX'] 778 default_product_name = RemovePrefix(default_product_name, 779 shared_library_prefix) 780 default_product_prefix = shared_library_prefix 781 default_product_ext = generator_default_variables['SHARED_LIB_SUFFIX'] 782 783 elif target_type != 'executable': 784 print ('ERROR: What output file should be generated?', 785 'type', target_type, 'target', target_name) 786 787 product_prefix = spec.get('product_prefix', default_product_prefix) 788 product_name = spec.get('product_name', default_product_name) 789 product_ext = spec.get('product_extension') 790 if product_ext: 791 product_ext = '.' + product_ext 792 else: 793 product_ext = default_product_ext 794 795 SetTargetProperty(output, cmake_target_name, 'PREFIX', product_prefix) 796 SetTargetProperty(output, cmake_target_name, 797 cmake_target_type.property_modifier + '_OUTPUT_NAME', 798 product_name) 799 SetTargetProperty(output, cmake_target_name, 'SUFFIX', product_ext) 800 801 # Make the output of this target referenceable as a source. 802 cmake_target_output_basename = product_prefix + product_name + product_ext 803 cmake_target_output = os.path.join(cmake_target_output_directory, 804 cmake_target_output_basename) 805 SetFileProperty(output, cmake_target_output, 'GENERATED', ['TRUE'], '') 806 807 # Let CMake know if the 'all' target should depend on this target. 808 exclude_from_all = ('TRUE' if qualified_target not in all_qualified_targets 809 else 'FALSE') 810 SetTargetProperty(output, cmake_target_name, 811 'EXCLUDE_FROM_ALL', exclude_from_all) 812 for extra_target_name in extra_deps: 813 SetTargetProperty(output, extra_target_name, 814 'EXCLUDE_FROM_ALL', exclude_from_all) 815 816 # Includes 817 includes = config.get('include_dirs') 818 if includes: 819 # This (target include directories) is what requires CMake 2.8.8 820 includes_name = cmake_target_name + '__include_dirs' 821 SetVariableList(output, includes_name, 822 [NormjoinPathForceCMakeSource(path_from_cmakelists_to_gyp, include) 823 for include in includes]) 824 output.write('set_property(TARGET ') 825 output.write(cmake_target_name) 826 output.write(' APPEND PROPERTY INCLUDE_DIRECTORIES ') 827 WriteVariable(output, includes_name, '') 828 output.write(')\n') 829 830 # Defines 831 defines = config.get('defines') 832 if defines is not None: 833 SetTargetProperty(output, 834 cmake_target_name, 835 'COMPILE_DEFINITIONS', 836 defines, 837 ';') 838 839 # Compile Flags - http://www.cmake.org/Bug/view.php?id=6493 840 # CMake currently does not have target C and CXX flags. 841 # So, instead of doing... 842 843 # cflags_c = config.get('cflags_c') 844 # if cflags_c is not None: 845 # SetTargetProperty(output, cmake_target_name, 846 # 'C_COMPILE_FLAGS', cflags_c, ' ') 847 848 # cflags_cc = config.get('cflags_cc') 849 # if cflags_cc is not None: 850 # SetTargetProperty(output, cmake_target_name, 851 # 'CXX_COMPILE_FLAGS', cflags_cc, ' ') 852 853 # Instead we must... 854 s_sources = [] 855 c_sources = [] 856 cxx_sources = [] 857 for src in srcs: 858 _, ext = os.path.splitext(src) 859 src_type = COMPILABLE_EXTENSIONS.get(ext, None) 860 861 if src_type == 's': 862 s_sources.append(NormjoinPath(path_from_cmakelists_to_gyp, src)) 863 864 if src_type == 'cc': 865 c_sources.append(NormjoinPath(path_from_cmakelists_to_gyp, src)) 866 867 if src_type == 'cxx': 868 cxx_sources.append(NormjoinPath(path_from_cmakelists_to_gyp, src)) 869 870 for extra_source in extra_sources: 871 src, real_source = extra_source 872 _, ext = os.path.splitext(real_source) 873 src_type = COMPILABLE_EXTENSIONS.get(ext, None) 874 875 if src_type == 's': 876 s_sources.append(NormjoinPath(path_from_cmakelists_to_gyp, src)) 877 878 if src_type == 'cc': 879 c_sources.append(NormjoinPath(path_from_cmakelists_to_gyp, src)) 880 881 if src_type == 'cxx': 882 cxx_sources.append(NormjoinPath(path_from_cmakelists_to_gyp, src)) 883 884 cflags = config.get('cflags', []) 885 cflags_c = config.get('cflags_c', []) 886 cflags_cxx = config.get('cflags_cc', []) 887 if c_sources and not (s_sources or cxx_sources): 888 flags = [] 889 flags.extend(cflags) 890 flags.extend(cflags_c) 891 SetTargetProperty(output, cmake_target_name, 'COMPILE_FLAGS', flags, ' ') 892 893 elif cxx_sources and not (s_sources or c_sources): 894 flags = [] 895 flags.extend(cflags) 896 flags.extend(cflags_cxx) 897 SetTargetProperty(output, cmake_target_name, 'COMPILE_FLAGS', flags, ' ') 898 899 else: 900 if s_sources and cflags: 901 SetFilesProperty(output, s_sources, 'COMPILE_FLAGS', cflags, ' ') 902 903 if c_sources and (cflags or cflags_c): 904 flags = [] 905 flags.extend(cflags) 906 flags.extend(cflags_c) 907 SetFilesProperty(output, c_sources, 'COMPILE_FLAGS', flags, ' ') 908 909 if cxx_sources and (cflags or cflags_cxx): 910 flags = [] 911 flags.extend(cflags) 912 flags.extend(cflags_cxx) 913 SetFilesProperty(output, cxx_sources, 'COMPILE_FLAGS', flags, ' ') 914 915 # Have assembly link as c if there are no other files 916 if not c_sources and not cxx_sources and s_sources: 917 SetTargetProperty(output, cmake_target_name, 'LINKER_LANGUAGE', ['C']) 918 919 # Linker flags 920 ldflags = config.get('ldflags') 921 if ldflags is not None: 922 SetTargetProperty(output, cmake_target_name, 'LINK_FLAGS', ldflags, ' ') 923 924 # Note on Dependencies and Libraries: 925 # CMake wants to handle link order, resolving the link line up front. 926 # Gyp does not retain or enforce specifying enough information to do so. 927 # So do as other gyp generators and use --start-group and --end-group. 928 # Give CMake as little information as possible so that it doesn't mess it up. 929 930 # Dependencies 931 rawDeps = spec.get('dependencies', []) 932 933 static_deps = [] 934 shared_deps = [] 935 other_deps = [] 936 for rawDep in rawDeps: 937 dep_cmake_name = namer.CreateCMakeTargetName(rawDep) 938 dep_spec = target_dicts.get(rawDep, {}) 939 dep_target_type = dep_spec.get('type', None) 940 941 if dep_target_type == 'static_library': 942 static_deps.append(dep_cmake_name) 943 elif dep_target_type == 'shared_library': 944 shared_deps.append(dep_cmake_name) 945 else: 946 other_deps.append(dep_cmake_name) 947 948 # ensure all external dependencies are complete before internal dependencies 949 # extra_deps currently only depend on their own deps, so otherwise run early 950 if static_deps or shared_deps or other_deps: 951 for extra_dep in extra_deps: 952 output.write('add_dependencies(') 953 output.write(extra_dep) 954 output.write('\n') 955 for deps in (static_deps, shared_deps, other_deps): 956 for dep in gyp.common.uniquer(deps): 957 output.write(' ') 958 output.write(dep) 959 output.write('\n') 960 output.write(')\n') 961 962 linkable = target_type in ('executable', 'loadable_module', 'shared_library') 963 other_deps.extend(extra_deps) 964 if other_deps or (not linkable and (static_deps or shared_deps)): 965 output.write('add_dependencies(') 966 output.write(cmake_target_name) 967 output.write('\n') 968 for dep in gyp.common.uniquer(other_deps): 969 output.write(' ') 970 output.write(dep) 971 output.write('\n') 972 if not linkable: 973 for deps in (static_deps, shared_deps): 974 for lib_dep in gyp.common.uniquer(deps): 975 output.write(' ') 976 output.write(lib_dep) 977 output.write('\n') 978 output.write(')\n') 979 980 # Libraries 981 if linkable: 982 external_libs = [lib for lib in spec.get('libraries', []) if len(lib) > 0] 983 if external_libs or static_deps or shared_deps: 984 output.write('target_link_libraries(') 985 output.write(cmake_target_name) 986 output.write('\n') 987 if static_deps: 988 write_group = circular_libs and len(static_deps) > 1 989 if write_group: 990 output.write('-Wl,--start-group\n') 991 for dep in gyp.common.uniquer(static_deps): 992 output.write(' ') 993 output.write(dep) 994 output.write('\n') 995 if write_group: 996 output.write('-Wl,--end-group\n') 997 if shared_deps: 998 for dep in gyp.common.uniquer(shared_deps): 999 output.write(' ') 1000 output.write(dep) 1001 output.write('\n') 1002 if external_libs: 1003 for lib in gyp.common.uniquer(external_libs): 1004 output.write(' ') 1005 output.write(lib) 1006 output.write('\n') 1007 1008 output.write(')\n') 1009 1010 UnsetVariable(output, 'TOOLSET') 1011 UnsetVariable(output, 'TARGET') 1012 1013 1014 def GenerateOutputForConfig(target_list, target_dicts, data, 1015 params, config_to_use): 1016 options = params['options'] 1017 generator_flags = params['generator_flags'] 1018 1019 # generator_dir: relative path from pwd to where make puts build files. 1020 # Makes migrating from make to cmake easier, cmake doesn't put anything here. 1021 # Each Gyp configuration creates a different CMakeLists.txt file 1022 # to avoid incompatibilities between Gyp and CMake configurations. 1023 generator_dir = os.path.relpath(options.generator_output or '.') 1024 1025 # output_dir: relative path from generator_dir to the build directory. 1026 output_dir = generator_flags.get('output_dir', 'out') 1027 1028 # build_dir: relative path from source root to our output files. 1029 # e.g. "out/Debug" 1030 build_dir = os.path.normpath(os.path.join(generator_dir, 1031 output_dir, 1032 config_to_use)) 1033 1034 toplevel_build = os.path.join(options.toplevel_dir, build_dir) 1035 1036 output_file = os.path.join(toplevel_build, 'CMakeLists.txt') 1037 gyp.common.EnsureDirExists(output_file) 1038 1039 output = open(output_file, 'w') 1040 output.write('cmake_minimum_required(VERSION 2.8.8 FATAL_ERROR)\n') 1041 output.write('cmake_policy(VERSION 2.8.8)\n') 1042 1043 _, project_target, _ = gyp.common.ParseQualifiedTarget(target_list[-1]) 1044 output.write('project(') 1045 output.write(project_target) 1046 output.write(')\n') 1047 1048 SetVariable(output, 'configuration', config_to_use) 1049 1050 # The following appears to be as-yet undocumented. 1051 # http://public.kitware.com/Bug/view.php?id=8392 1052 output.write('enable_language(ASM)\n') 1053 # ASM-ATT does not support .S files. 1054 # output.write('enable_language(ASM-ATT)\n') 1055 1056 SetVariable(output, 'builddir', '${CMAKE_BINARY_DIR}') 1057 SetVariable(output, 'obj', '${builddir}/obj') 1058 output.write('\n') 1059 1060 # TODO: Undocumented/unsupported (the CMake Java generator depends on it). 1061 # CMake by default names the object resulting from foo.c to be foo.c.o. 1062 # Gyp traditionally names the object resulting from foo.c foo.o. 1063 # This should be irrelevant, but some targets extract .o files from .a 1064 # and depend on the name of the extracted .o files. 1065 output.write('set(CMAKE_C_OUTPUT_EXTENSION_REPLACE 1)\n') 1066 output.write('set(CMAKE_CXX_OUTPUT_EXTENSION_REPLACE 1)\n') 1067 output.write('\n') 1068 1069 namer = CMakeNamer(target_list) 1070 1071 # The list of targets upon which the 'all' target should depend. 1072 # CMake has it's own implicit 'all' target, one is not created explicitly. 1073 all_qualified_targets = set() 1074 for build_file in params['build_files']: 1075 for qualified_target in gyp.common.AllTargets(target_list, 1076 target_dicts, 1077 os.path.normpath(build_file)): 1078 all_qualified_targets.add(qualified_target) 1079 1080 for qualified_target in target_list: 1081 WriteTarget(namer, qualified_target, target_dicts, build_dir, config_to_use, 1082 options, generator_flags, all_qualified_targets, output) 1083 1084 output.close() 1085 1086 1087 def PerformBuild(data, configurations, params): 1088 options = params['options'] 1089 generator_flags = params['generator_flags'] 1090 1091 # generator_dir: relative path from pwd to where make puts build files. 1092 # Makes migrating from make to cmake easier, cmake doesn't put anything here. 1093 generator_dir = os.path.relpath(options.generator_output or '.') 1094 1095 # output_dir: relative path from generator_dir to the build directory. 1096 output_dir = generator_flags.get('output_dir', 'out') 1097 1098 for config_name in configurations: 1099 # build_dir: relative path from source root to our output files. 1100 # e.g. "out/Debug" 1101 build_dir = os.path.normpath(os.path.join(generator_dir, 1102 output_dir, 1103 config_name)) 1104 arguments = ['cmake', '-G', 'Ninja'] 1105 print 'Generating [%s]: %s' % (config_name, arguments) 1106 subprocess.check_call(arguments, cwd=build_dir) 1107 1108 arguments = ['ninja', '-C', build_dir] 1109 print 'Building [%s]: %s' % (config_name, arguments) 1110 subprocess.check_call(arguments) 1111 1112 1113 def CallGenerateOutputForConfig(arglist): 1114 # Ignore the interrupt signal so that the parent process catches it and 1115 # kills all multiprocessing children. 1116 signal.signal(signal.SIGINT, signal.SIG_IGN) 1117 1118 target_list, target_dicts, data, params, config_name = arglist 1119 GenerateOutputForConfig(target_list, target_dicts, data, params, config_name) 1120 1121 1122 def GenerateOutput(target_list, target_dicts, data, params): 1123 user_config = params.get('generator_flags', {}).get('config', None) 1124 if user_config: 1125 GenerateOutputForConfig(target_list, target_dicts, data, 1126 params, user_config) 1127 else: 1128 config_names = target_dicts[target_list[0]]['configurations'].keys() 1129 if params['parallel']: 1130 try: 1131 pool = multiprocessing.Pool(len(config_names)) 1132 arglists = [] 1133 for config_name in config_names: 1134 arglists.append((target_list, target_dicts, data, 1135 params, config_name)) 1136 pool.map(CallGenerateOutputForConfig, arglists) 1137 except KeyboardInterrupt, e: 1138 pool.terminate() 1139 raise e 1140 else: 1141 for config_name in config_names: 1142 GenerateOutputForConfig(target_list, target_dicts, data, 1143 params, config_name) 1144