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