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