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