| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318 |
- # Copyright (c) 2013 Google Inc. All rights reserved.
- # Use of this source code is governed by a BSD-style license that can be
- # found in the LICENSE file.
- """cmake output module
- This module is under development and should be considered experimental.
- This module produces cmake (2.8.8+) input as its output. One CMakeLists.txt is
- created for each configuration.
- This module's original purpose was to support editing in IDEs like KDevelop
- which use CMake for project management. It is also possible to use CMake to
- generate projects for other IDEs such as eclipse cdt and code::blocks. QtCreator
- will convert the CMakeLists.txt to a code::blocks cbp for the editor to read,
- but build using CMake. As a result QtCreator editor is unaware of compiler
- defines. The generated CMakeLists.txt can also be used to build on Linux. There
- is currently no support for building on platforms other than Linux.
- The generated CMakeLists.txt should properly compile all projects. However,
- there is a mismatch between gyp and cmake with regard to linking. All attempts
- are made to work around this, but CMake sometimes sees -Wl,--start-group as a
- library and incorrectly repeats it. As a result the output of this generator
- should not be relied on for building.
- When using with kdevelop, use version 4.4+. Previous versions of kdevelop will
- not be able to find the header file directories described in the generated
- CMakeLists.txt file.
- """
- import multiprocessing
- import os
- import signal
- import subprocess
- import gyp.common
- import gyp.xcode_emulation
- _maketrans = str.maketrans
- generator_default_variables = {
- "EXECUTABLE_PREFIX": "",
- "EXECUTABLE_SUFFIX": "",
- "STATIC_LIB_PREFIX": "lib",
- "STATIC_LIB_SUFFIX": ".a",
- "SHARED_LIB_PREFIX": "lib",
- "SHARED_LIB_SUFFIX": ".so",
- "SHARED_LIB_DIR": "${builddir}/lib.${TOOLSET}",
- "LIB_DIR": "${obj}.${TOOLSET}",
- "INTERMEDIATE_DIR": "${obj}.${TOOLSET}/${TARGET}/geni",
- "SHARED_INTERMEDIATE_DIR": "${obj}/gen",
- "PRODUCT_DIR": "${builddir}",
- "RULE_INPUT_PATH": "${RULE_INPUT_PATH}",
- "RULE_INPUT_DIRNAME": "${RULE_INPUT_DIRNAME}",
- "RULE_INPUT_NAME": "${RULE_INPUT_NAME}",
- "RULE_INPUT_ROOT": "${RULE_INPUT_ROOT}",
- "RULE_INPUT_EXT": "${RULE_INPUT_EXT}",
- "CONFIGURATION_NAME": "${configuration}",
- }
- FULL_PATH_VARS = ("${CMAKE_CURRENT_LIST_DIR}", "${builddir}", "${obj}")
- generator_supports_multiple_toolsets = True
- generator_wants_static_library_dependencies_adjusted = True
- COMPILABLE_EXTENSIONS = {
- ".c": "cc",
- ".cc": "cxx",
- ".cpp": "cxx",
- ".cxx": "cxx",
- ".s": "s", # cc
- ".S": "s", # cc
- }
- def RemovePrefix(a, prefix):
- """Returns 'a' without 'prefix' if it starts with 'prefix'."""
- return a[len(prefix) :] if a.startswith(prefix) else a
- def CalculateVariables(default_variables, params):
- """Calculate additional variables for use in the build (called by gyp)."""
- default_variables.setdefault("OS", gyp.common.GetFlavor(params))
- def Compilable(filename):
- """Return true if the file is compilable (should be in OBJS)."""
- return any(filename.endswith(e) for e in COMPILABLE_EXTENSIONS)
- def Linkable(filename):
- """Return true if the file is linkable (should be on the link line)."""
- return filename.endswith(".o")
- def NormjoinPathForceCMakeSource(base_path, rel_path):
- """Resolves rel_path against base_path and returns the result.
- If rel_path is an absolute path it is returned unchanged.
- Otherwise it is resolved against base_path and normalized.
- If the result is a relative path, it is forced to be relative to the
- CMakeLists.txt.
- """
- if os.path.isabs(rel_path):
- return rel_path
- if any(rel_path.startswith(var) for var in FULL_PATH_VARS):
- return rel_path
- # TODO: do we need to check base_path for absolute variables as well?
- return os.path.join(
- "${CMAKE_CURRENT_LIST_DIR}", os.path.normpath(os.path.join(base_path, rel_path))
- )
- def NormjoinPath(base_path, rel_path):
- """Resolves rel_path against base_path and returns the result.
- TODO: what is this really used for?
- If rel_path begins with '$' it is returned unchanged.
- Otherwise it is resolved against base_path if relative, then normalized.
- """
- if rel_path.startswith("$") and not rel_path.startswith("${configuration}"):
- return rel_path
- return os.path.normpath(os.path.join(base_path, rel_path))
- def CMakeStringEscape(a):
- """Escapes the string 'a' for use inside a CMake string.
- This means escaping
- '\' otherwise it may be seen as modifying the next character
- '"' otherwise it will end the string
- ';' otherwise the string becomes a list
- The following do not need to be escaped
- '#' when the lexer is in string state, this does not start a comment
- The following are yet unknown
- '$' generator variables (like ${obj}) must not be escaped,
- but text $ should be escaped
- what is wanted is to know which $ come from generator variables
- """
- return a.replace("\\", "\\\\").replace(";", "\\;").replace('"', '\\"')
- def SetFileProperty(output, source_name, property_name, values, sep):
- """Given a set of source file, sets the given property on them."""
- output.write("set_source_files_properties(")
- output.write(source_name)
- output.write(" PROPERTIES ")
- output.write(property_name)
- output.write(' "')
- for value in values:
- output.write(CMakeStringEscape(value))
- output.write(sep)
- output.write('")\n')
- def SetFilesProperty(output, variable, property_name, values, sep):
- """Given a set of source files, sets the given property on them."""
- output.write("set_source_files_properties(")
- WriteVariable(output, variable)
- output.write(" PROPERTIES ")
- output.write(property_name)
- output.write(' "')
- for value in values:
- output.write(CMakeStringEscape(value))
- output.write(sep)
- output.write('")\n')
- def SetTargetProperty(output, target_name, property_name, values, sep=""):
- """Given a target, sets the given property."""
- output.write("set_target_properties(")
- output.write(target_name)
- output.write(" PROPERTIES ")
- output.write(property_name)
- output.write(' "')
- for value in values:
- output.write(CMakeStringEscape(value))
- output.write(sep)
- output.write('")\n')
- def SetVariable(output, variable_name, value):
- """Sets a CMake variable."""
- output.write("set(")
- output.write(variable_name)
- output.write(' "')
- output.write(CMakeStringEscape(value))
- output.write('")\n')
- def SetVariableList(output, variable_name, values):
- """Sets a CMake variable to a list."""
- if not values:
- return SetVariable(output, variable_name, "")
- if len(values) == 1:
- return SetVariable(output, variable_name, values[0])
- output.write("list(APPEND ")
- output.write(variable_name)
- output.write('\n "')
- output.write('"\n "'.join([CMakeStringEscape(value) for value in values]))
- output.write('")\n')
- def UnsetVariable(output, variable_name):
- """Unsets a CMake variable."""
- output.write("unset(")
- output.write(variable_name)
- output.write(")\n")
- def WriteVariable(output, variable_name, prepend=None):
- if prepend:
- output.write(prepend)
- output.write("${")
- output.write(variable_name)
- output.write("}")
- class CMakeTargetType:
- def __init__(self, command, modifier, property_modifier):
- self.command = command
- self.modifier = modifier
- self.property_modifier = property_modifier
- cmake_target_type_from_gyp_target_type = {
- "executable": CMakeTargetType("add_executable", None, "RUNTIME"),
- "static_library": CMakeTargetType("add_library", "STATIC", "ARCHIVE"),
- "shared_library": CMakeTargetType("add_library", "SHARED", "LIBRARY"),
- "loadable_module": CMakeTargetType("add_library", "MODULE", "LIBRARY"),
- "none": CMakeTargetType("add_custom_target", "SOURCES", None),
- }
- def StringToCMakeTargetName(a):
- """Converts the given string 'a' to a valid CMake target name.
- All invalid characters are replaced by '_'.
- Invalid for cmake: ' ', '/', '(', ')', '"'
- Invalid for make: ':'
- Invalid for unknown reasons but cause failures: '.'
- """
- return a.translate(_maketrans(' /():."', "_______"))
- def WriteActions(target_name, actions, extra_sources, extra_deps, path_to_gyp, output):
- """Write CMake for the 'actions' in the target.
- Args:
- target_name: the name of the CMake target being generated.
- actions: the Gyp 'actions' dict for this target.
- extra_sources: [(<cmake_src>, <src>)] to append with generated source files.
- extra_deps: [<cmake_target>] to append with generated targets.
- path_to_gyp: relative path from CMakeLists.txt being generated to
- the Gyp file in which the target being generated is defined.
- """
- for action in actions:
- action_name = StringToCMakeTargetName(action["action_name"])
- action_target_name = f"{target_name}__{action_name}"
- inputs = action["inputs"]
- inputs_name = action_target_name + "__input"
- SetVariableList(
- output,
- inputs_name,
- [NormjoinPathForceCMakeSource(path_to_gyp, dep) for dep in inputs],
- )
- outputs = action["outputs"]
- cmake_outputs = [
- NormjoinPathForceCMakeSource(path_to_gyp, out) for out in outputs
- ]
- outputs_name = action_target_name + "__output"
- SetVariableList(output, outputs_name, cmake_outputs)
- # Build up a list of outputs.
- # Collect the output dirs we'll need.
- dirs = {dir for dir in (os.path.dirname(o) for o in outputs) if dir}
- if int(action.get("process_outputs_as_sources", False)):
- extra_sources.extend(zip(cmake_outputs, outputs))
- # add_custom_command
- output.write("add_custom_command(OUTPUT ")
- WriteVariable(output, outputs_name)
- output.write("\n")
- if len(dirs) > 0:
- for directory in dirs:
- output.write(" COMMAND ${CMAKE_COMMAND} -E make_directory ")
- output.write(directory)
- output.write("\n")
- output.write(" COMMAND ")
- output.write(gyp.common.EncodePOSIXShellList(action["action"]))
- output.write("\n")
- output.write(" DEPENDS ")
- WriteVariable(output, inputs_name)
- output.write("\n")
- output.write(" WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/")
- output.write(path_to_gyp)
- output.write("\n")
- output.write(" COMMENT ")
- if "message" in action:
- output.write(action["message"])
- else:
- output.write(action_target_name)
- output.write("\n")
- output.write(" VERBATIM\n")
- output.write(")\n")
- # add_custom_target
- output.write("add_custom_target(")
- output.write(action_target_name)
- output.write("\n DEPENDS ")
- WriteVariable(output, outputs_name)
- output.write("\n SOURCES ")
- WriteVariable(output, inputs_name)
- output.write("\n)\n")
- extra_deps.append(action_target_name)
- def NormjoinRulePathForceCMakeSource(base_path, rel_path, rule_source):
- if rel_path.startswith(("${RULE_INPUT_PATH}", "${RULE_INPUT_DIRNAME}")):
- if any(rule_source.startswith(var) for var in FULL_PATH_VARS):
- return rel_path
- return NormjoinPathForceCMakeSource(base_path, rel_path)
- def WriteRules(target_name, rules, extra_sources, extra_deps, path_to_gyp, output):
- """Write CMake for the 'rules' in the target.
- Args:
- target_name: the name of the CMake target being generated.
- actions: the Gyp 'actions' dict for this target.
- extra_sources: [(<cmake_src>, <src>)] to append with generated source files.
- extra_deps: [<cmake_target>] to append with generated targets.
- path_to_gyp: relative path from CMakeLists.txt being generated to
- the Gyp file in which the target being generated is defined.
- """
- for rule in rules:
- rule_name = StringToCMakeTargetName(target_name + "__" + rule["rule_name"])
- inputs = rule.get("inputs", [])
- inputs_name = rule_name + "__input"
- SetVariableList(
- output,
- inputs_name,
- [NormjoinPathForceCMakeSource(path_to_gyp, dep) for dep in inputs],
- )
- outputs = rule["outputs"]
- var_outputs = []
- for count, rule_source in enumerate(rule.get("rule_sources", [])):
- action_name = rule_name + "_" + str(count)
- rule_source_dirname, rule_source_basename = os.path.split(rule_source)
- rule_source_root, rule_source_ext = os.path.splitext(rule_source_basename)
- SetVariable(output, "RULE_INPUT_PATH", rule_source)
- SetVariable(output, "RULE_INPUT_DIRNAME", rule_source_dirname)
- SetVariable(output, "RULE_INPUT_NAME", rule_source_basename)
- SetVariable(output, "RULE_INPUT_ROOT", rule_source_root)
- SetVariable(output, "RULE_INPUT_EXT", rule_source_ext)
- # Build up a list of outputs.
- # Collect the output dirs we'll need.
- dirs = {dir for dir in (os.path.dirname(o) for o in outputs) if dir}
- # Create variables for the output, as 'local' variable will be unset.
- these_outputs = []
- for output_index, out in enumerate(outputs):
- output_name = action_name + "_" + str(output_index)
- SetVariable(
- output,
- output_name,
- NormjoinRulePathForceCMakeSource(path_to_gyp, out, rule_source),
- )
- if int(rule.get("process_outputs_as_sources", False)):
- extra_sources.append(("${" + output_name + "}", out))
- these_outputs.append("${" + output_name + "}")
- var_outputs.append("${" + output_name + "}")
- # add_custom_command
- output.write("add_custom_command(OUTPUT\n")
- for out in these_outputs:
- output.write(" ")
- output.write(out)
- output.write("\n")
- for directory in dirs:
- output.write(" COMMAND ${CMAKE_COMMAND} -E make_directory ")
- output.write(directory)
- output.write("\n")
- output.write(" COMMAND ")
- output.write(gyp.common.EncodePOSIXShellList(rule["action"]))
- output.write("\n")
- output.write(" DEPENDS ")
- WriteVariable(output, inputs_name)
- output.write(" ")
- output.write(NormjoinPath(path_to_gyp, rule_source))
- output.write("\n")
- # CMAKE_CURRENT_LIST_DIR is where the CMakeLists.txt lives.
- # The cwd is the current build directory.
- output.write(" WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/")
- output.write(path_to_gyp)
- output.write("\n")
- output.write(" COMMENT ")
- if "message" in rule:
- output.write(rule["message"])
- else:
- output.write(action_name)
- output.write("\n")
- output.write(" VERBATIM\n")
- output.write(")\n")
- UnsetVariable(output, "RULE_INPUT_PATH")
- UnsetVariable(output, "RULE_INPUT_DIRNAME")
- UnsetVariable(output, "RULE_INPUT_NAME")
- UnsetVariable(output, "RULE_INPUT_ROOT")
- UnsetVariable(output, "RULE_INPUT_EXT")
- # add_custom_target
- output.write("add_custom_target(")
- output.write(rule_name)
- output.write(" DEPENDS\n")
- for out in var_outputs:
- output.write(" ")
- output.write(out)
- output.write("\n")
- output.write("SOURCES ")
- WriteVariable(output, inputs_name)
- output.write("\n")
- for rule_source in rule.get("rule_sources", []):
- output.write(" ")
- output.write(NormjoinPath(path_to_gyp, rule_source))
- output.write("\n")
- output.write(")\n")
- extra_deps.append(rule_name)
- def WriteCopies(target_name, copies, extra_deps, path_to_gyp, output):
- """Write CMake for the 'copies' in the target.
- Args:
- target_name: the name of the CMake target being generated.
- actions: the Gyp 'actions' dict for this target.
- extra_deps: [<cmake_target>] to append with generated targets.
- path_to_gyp: relative path from CMakeLists.txt being generated to
- the Gyp file in which the target being generated is defined.
- """
- copy_name = target_name + "__copies"
- # CMake gets upset with custom targets with OUTPUT which specify no output.
- have_copies = any(copy["files"] for copy in copies)
- if not have_copies:
- output.write("add_custom_target(")
- output.write(copy_name)
- output.write(")\n")
- extra_deps.append(copy_name)
- return
- class Copy:
- def __init__(self, ext, command):
- self.cmake_inputs = []
- self.cmake_outputs = []
- self.gyp_inputs = []
- self.gyp_outputs = []
- self.ext = ext
- self.inputs_name = None
- self.outputs_name = None
- self.command = command
- file_copy = Copy("", "copy")
- dir_copy = Copy("_dirs", "copy_directory")
- for copy in copies:
- files = copy["files"]
- destination = copy["destination"]
- for src in files:
- path = os.path.normpath(src)
- basename = os.path.split(path)[1]
- dst = os.path.join(destination, basename)
- copy = file_copy if os.path.basename(src) else dir_copy
- copy.cmake_inputs.append(NormjoinPathForceCMakeSource(path_to_gyp, src))
- copy.cmake_outputs.append(NormjoinPathForceCMakeSource(path_to_gyp, dst))
- copy.gyp_inputs.append(src)
- copy.gyp_outputs.append(dst)
- for copy in (file_copy, dir_copy):
- if copy.cmake_inputs:
- copy.inputs_name = copy_name + "__input" + copy.ext
- SetVariableList(output, copy.inputs_name, copy.cmake_inputs)
- copy.outputs_name = copy_name + "__output" + copy.ext
- SetVariableList(output, copy.outputs_name, copy.cmake_outputs)
- # add_custom_command
- output.write("add_custom_command(\n")
- output.write("OUTPUT")
- for copy in (file_copy, dir_copy):
- if copy.outputs_name:
- WriteVariable(output, copy.outputs_name, " ")
- output.write("\n")
- for copy in (file_copy, dir_copy):
- for src, dst in zip(copy.gyp_inputs, copy.gyp_outputs):
- # 'cmake -E copy src dst' will create the 'dst' directory if needed.
- output.write("COMMAND ${CMAKE_COMMAND} -E %s " % copy.command)
- output.write(src)
- output.write(" ")
- output.write(dst)
- output.write("\n")
- output.write("DEPENDS")
- for copy in (file_copy, dir_copy):
- if copy.inputs_name:
- WriteVariable(output, copy.inputs_name, " ")
- output.write("\n")
- output.write("WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/")
- output.write(path_to_gyp)
- output.write("\n")
- output.write("COMMENT Copying for ")
- output.write(target_name)
- output.write("\n")
- output.write("VERBATIM\n")
- output.write(")\n")
- # add_custom_target
- output.write("add_custom_target(")
- output.write(copy_name)
- output.write("\n DEPENDS")
- for copy in (file_copy, dir_copy):
- if copy.outputs_name:
- WriteVariable(output, copy.outputs_name, " ")
- output.write("\n SOURCES")
- if file_copy.inputs_name:
- WriteVariable(output, file_copy.inputs_name, " ")
- output.write("\n)\n")
- extra_deps.append(copy_name)
- def CreateCMakeTargetBaseName(qualified_target):
- """This is the name we would like the target to have."""
- _, gyp_target_name, gyp_target_toolset = gyp.common.ParseQualifiedTarget(
- qualified_target
- )
- cmake_target_base_name = gyp_target_name
- if gyp_target_toolset and gyp_target_toolset != "target":
- cmake_target_base_name += "_" + gyp_target_toolset
- return StringToCMakeTargetName(cmake_target_base_name)
- def CreateCMakeTargetFullName(qualified_target):
- """An unambiguous name for the target."""
- gyp_file, gyp_target_name, gyp_target_toolset = gyp.common.ParseQualifiedTarget(
- qualified_target
- )
- cmake_target_full_name = gyp_file + ":" + gyp_target_name
- if gyp_target_toolset and gyp_target_toolset != "target":
- cmake_target_full_name += "_" + gyp_target_toolset
- return StringToCMakeTargetName(cmake_target_full_name)
- class CMakeNamer:
- """Converts Gyp target names into CMake target names.
- CMake requires that target names be globally unique. One way to ensure
- this is to fully qualify the names of the targets. Unfortunately, this
- ends up with all targets looking like "chrome_chrome_gyp_chrome" instead
- of just "chrome". If this generator were only interested in building, it
- would be possible to fully qualify all target names, then create
- unqualified target names which depend on all qualified targets which
- should have had that name. This is more or less what the 'make' generator
- does with aliases. However, one goal of this generator is to create CMake
- files for use with IDEs, and fully qualified names are not as user
- friendly.
- Since target name collision is rare, we do the above only when required.
- Toolset variants are always qualified from the base, as this is required for
- building. However, it also makes sense for an IDE, as it is possible for
- defines to be different.
- """
- def __init__(self, target_list):
- self.cmake_target_base_names_conflicting = set()
- cmake_target_base_names_seen = set()
- for qualified_target in target_list:
- cmake_target_base_name = CreateCMakeTargetBaseName(qualified_target)
- if cmake_target_base_name not in cmake_target_base_names_seen:
- cmake_target_base_names_seen.add(cmake_target_base_name)
- else:
- self.cmake_target_base_names_conflicting.add(cmake_target_base_name)
- def CreateCMakeTargetName(self, qualified_target):
- base_name = CreateCMakeTargetBaseName(qualified_target)
- if base_name in self.cmake_target_base_names_conflicting:
- return CreateCMakeTargetFullName(qualified_target)
- return base_name
- def WriteTarget(
- namer,
- qualified_target,
- target_dicts,
- build_dir,
- config_to_use,
- options,
- generator_flags,
- all_qualified_targets,
- flavor,
- output,
- ):
- # The make generator does this always.
- # TODO: It would be nice to be able to tell CMake all dependencies.
- circular_libs = generator_flags.get("circular", True)
- if not generator_flags.get("standalone", False):
- output.write("\n#")
- output.write(qualified_target)
- output.write("\n")
- gyp_file, _, _ = gyp.common.ParseQualifiedTarget(qualified_target)
- rel_gyp_file = gyp.common.RelativePath(gyp_file, options.toplevel_dir)
- rel_gyp_dir = os.path.dirname(rel_gyp_file)
- # Relative path from build dir to top dir.
- build_to_top = gyp.common.InvertRelativePath(build_dir, options.toplevel_dir)
- # Relative path from build dir to gyp dir.
- build_to_gyp = os.path.join(build_to_top, rel_gyp_dir)
- path_from_cmakelists_to_gyp = build_to_gyp
- spec = target_dicts.get(qualified_target, {})
- config = spec.get("configurations", {}).get(config_to_use, {})
- xcode_settings = None
- if flavor == "mac":
- xcode_settings = gyp.xcode_emulation.XcodeSettings(spec)
- target_name = spec.get("target_name", "<missing target name>")
- target_type = spec.get("type", "<missing target type>")
- target_toolset = spec.get("toolset")
- cmake_target_type = cmake_target_type_from_gyp_target_type.get(target_type)
- if cmake_target_type is None:
- print(
- "Target %s has unknown target type %s, skipping."
- % (target_name, target_type)
- )
- return
- SetVariable(output, "TARGET", target_name)
- SetVariable(output, "TOOLSET", target_toolset)
- cmake_target_name = namer.CreateCMakeTargetName(qualified_target)
- extra_sources = []
- extra_deps = []
- # Actions must come first, since they can generate more OBJs for use below.
- if "actions" in spec:
- WriteActions(
- cmake_target_name,
- spec["actions"],
- extra_sources,
- extra_deps,
- path_from_cmakelists_to_gyp,
- output,
- )
- # Rules must be early like actions.
- if "rules" in spec:
- WriteRules(
- cmake_target_name,
- spec["rules"],
- extra_sources,
- extra_deps,
- path_from_cmakelists_to_gyp,
- output,
- )
- # Copies
- if "copies" in spec:
- WriteCopies(
- cmake_target_name,
- spec["copies"],
- extra_deps,
- path_from_cmakelists_to_gyp,
- output,
- )
- # Target and sources
- srcs = spec.get("sources", [])
- # Gyp separates the sheep from the goats based on file extensions.
- # A full separation is done here because of flag handing (see below).
- s_sources = []
- c_sources = []
- cxx_sources = []
- linkable_sources = []
- other_sources = []
- for src in srcs:
- _, ext = os.path.splitext(src)
- src_type = COMPILABLE_EXTENSIONS.get(ext, None)
- src_norm_path = NormjoinPath(path_from_cmakelists_to_gyp, src)
- if src_type == "s":
- s_sources.append(src_norm_path)
- elif src_type == "cc":
- c_sources.append(src_norm_path)
- elif src_type == "cxx":
- cxx_sources.append(src_norm_path)
- elif Linkable(ext):
- linkable_sources.append(src_norm_path)
- else:
- other_sources.append(src_norm_path)
- for extra_source in extra_sources:
- src, real_source = extra_source
- _, ext = os.path.splitext(real_source)
- src_type = COMPILABLE_EXTENSIONS.get(ext, None)
- if src_type == "s":
- s_sources.append(src)
- elif src_type == "cc":
- c_sources.append(src)
- elif src_type == "cxx":
- cxx_sources.append(src)
- elif Linkable(ext):
- linkable_sources.append(src)
- else:
- other_sources.append(src)
- s_sources_name = None
- if s_sources:
- s_sources_name = cmake_target_name + "__asm_srcs"
- SetVariableList(output, s_sources_name, s_sources)
- c_sources_name = None
- if c_sources:
- c_sources_name = cmake_target_name + "__c_srcs"
- SetVariableList(output, c_sources_name, c_sources)
- cxx_sources_name = None
- if cxx_sources:
- cxx_sources_name = cmake_target_name + "__cxx_srcs"
- SetVariableList(output, cxx_sources_name, cxx_sources)
- linkable_sources_name = None
- if linkable_sources:
- linkable_sources_name = cmake_target_name + "__linkable_srcs"
- SetVariableList(output, linkable_sources_name, linkable_sources)
- other_sources_name = None
- if other_sources:
- other_sources_name = cmake_target_name + "__other_srcs"
- SetVariableList(output, other_sources_name, other_sources)
- # CMake gets upset when executable targets provide no sources.
- # http://www.cmake.org/pipermail/cmake/2010-July/038461.html
- dummy_sources_name = None
- has_sources = (
- s_sources_name
- or c_sources_name
- or cxx_sources_name
- or linkable_sources_name
- or other_sources_name
- )
- if target_type == "executable" and not has_sources:
- dummy_sources_name = cmake_target_name + "__dummy_srcs"
- SetVariable(
- output, dummy_sources_name, "${obj}.${TOOLSET}/${TARGET}/genc/dummy.c"
- )
- output.write('if(NOT EXISTS "')
- WriteVariable(output, dummy_sources_name)
- output.write('")\n')
- output.write(' file(WRITE "')
- WriteVariable(output, dummy_sources_name)
- output.write('" "")\n')
- output.write("endif()\n")
- # CMake is opposed to setting linker directories and considers the practice
- # of setting linker directories dangerous. Instead, it favors the use of
- # find_library and passing absolute paths to target_link_libraries.
- # However, CMake does provide the command link_directories, which adds
- # link directories to targets defined after it is called.
- # As a result, link_directories must come before the target definition.
- # CMake unfortunately has no means of removing entries from LINK_DIRECTORIES.
- library_dirs = config.get("library_dirs")
- if library_dirs is not None:
- output.write("link_directories(")
- for library_dir in library_dirs:
- output.write(" ")
- output.write(NormjoinPath(path_from_cmakelists_to_gyp, library_dir))
- output.write("\n")
- output.write(")\n")
- output.write(cmake_target_type.command)
- output.write("(")
- output.write(cmake_target_name)
- if cmake_target_type.modifier is not None:
- output.write(" ")
- output.write(cmake_target_type.modifier)
- if s_sources_name:
- WriteVariable(output, s_sources_name, " ")
- if c_sources_name:
- WriteVariable(output, c_sources_name, " ")
- if cxx_sources_name:
- WriteVariable(output, cxx_sources_name, " ")
- if linkable_sources_name:
- WriteVariable(output, linkable_sources_name, " ")
- if other_sources_name:
- WriteVariable(output, other_sources_name, " ")
- if dummy_sources_name:
- WriteVariable(output, dummy_sources_name, " ")
- output.write(")\n")
- # Let CMake know if the 'all' target should depend on this target.
- exclude_from_all = (
- "TRUE" if qualified_target not in all_qualified_targets else "FALSE"
- )
- SetTargetProperty(output, cmake_target_name, "EXCLUDE_FROM_ALL", exclude_from_all)
- for extra_target_name in extra_deps:
- SetTargetProperty(
- output, extra_target_name, "EXCLUDE_FROM_ALL", exclude_from_all
- )
- # Output name and location.
- if target_type != "none":
- # Link as 'C' if there are no other files
- if not c_sources and not cxx_sources:
- SetTargetProperty(output, cmake_target_name, "LINKER_LANGUAGE", ["C"])
- # Mark uncompiled sources as uncompiled.
- if other_sources_name:
- output.write("set_source_files_properties(")
- WriteVariable(output, other_sources_name, "")
- output.write(' PROPERTIES HEADER_FILE_ONLY "TRUE")\n')
- # Mark object sources as linkable.
- if linkable_sources_name:
- output.write("set_source_files_properties(")
- WriteVariable(output, other_sources_name, "")
- output.write(' PROPERTIES EXTERNAL_OBJECT "TRUE")\n')
- # Output directory
- target_output_directory = spec.get("product_dir")
- if target_output_directory is None:
- if target_type in ("executable", "loadable_module"):
- target_output_directory = generator_default_variables["PRODUCT_DIR"]
- elif target_type == "shared_library":
- target_output_directory = "${builddir}/lib.${TOOLSET}"
- elif spec.get("standalone_static_library", False):
- target_output_directory = generator_default_variables["PRODUCT_DIR"]
- else:
- base_path = gyp.common.RelativePath(
- os.path.dirname(gyp_file), options.toplevel_dir
- )
- target_output_directory = "${obj}.${TOOLSET}"
- target_output_directory = os.path.join(
- target_output_directory, base_path
- )
- cmake_target_output_directory = NormjoinPathForceCMakeSource(
- path_from_cmakelists_to_gyp, target_output_directory
- )
- SetTargetProperty(
- output,
- cmake_target_name,
- cmake_target_type.property_modifier + "_OUTPUT_DIRECTORY",
- cmake_target_output_directory,
- )
- # Output name
- default_product_prefix = ""
- default_product_name = target_name
- default_product_ext = ""
- if target_type == "static_library":
- static_library_prefix = generator_default_variables["STATIC_LIB_PREFIX"]
- default_product_name = RemovePrefix(
- default_product_name, static_library_prefix
- )
- default_product_prefix = static_library_prefix
- default_product_ext = generator_default_variables["STATIC_LIB_SUFFIX"]
- elif target_type in ("loadable_module", "shared_library"):
- shared_library_prefix = generator_default_variables["SHARED_LIB_PREFIX"]
- default_product_name = RemovePrefix(
- default_product_name, shared_library_prefix
- )
- default_product_prefix = shared_library_prefix
- default_product_ext = generator_default_variables["SHARED_LIB_SUFFIX"]
- elif target_type != "executable":
- print(
- "ERROR: What output file should be generated?",
- "type",
- target_type,
- "target",
- target_name,
- )
- product_prefix = spec.get("product_prefix", default_product_prefix)
- product_name = spec.get("product_name", default_product_name)
- product_ext = spec.get("product_extension")
- product_ext = "." + product_ext if product_ext else default_product_ext
- SetTargetProperty(output, cmake_target_name, "PREFIX", product_prefix)
- SetTargetProperty(
- output,
- cmake_target_name,
- cmake_target_type.property_modifier + "_OUTPUT_NAME",
- product_name,
- )
- SetTargetProperty(output, cmake_target_name, "SUFFIX", product_ext)
- # Make the output of this target referenceable as a source.
- cmake_target_output_basename = product_prefix + product_name + product_ext
- cmake_target_output = os.path.join(
- cmake_target_output_directory, cmake_target_output_basename
- )
- SetFileProperty(output, cmake_target_output, "GENERATED", ["TRUE"], "")
- # Includes
- includes = config.get("include_dirs")
- if includes:
- # This (target include directories) is what requires CMake 2.8.8
- includes_name = cmake_target_name + "__include_dirs"
- SetVariableList(
- output,
- includes_name,
- [
- NormjoinPathForceCMakeSource(path_from_cmakelists_to_gyp, include)
- for include in includes
- ],
- )
- output.write("set_property(TARGET ")
- output.write(cmake_target_name)
- output.write(" APPEND PROPERTY INCLUDE_DIRECTORIES ")
- WriteVariable(output, includes_name, "")
- output.write(")\n")
- # Defines
- defines = config.get("defines")
- if defines is not None:
- SetTargetProperty(
- output, cmake_target_name, "COMPILE_DEFINITIONS", defines, ";"
- )
- # Compile Flags - http://www.cmake.org/Bug/view.php?id=6493
- # CMake currently does not have target C and CXX flags.
- # So, instead of doing...
- # cflags_c = config.get('cflags_c')
- # if cflags_c is not None:
- # SetTargetProperty(output, cmake_target_name,
- # 'C_COMPILE_FLAGS', cflags_c, ' ')
- # cflags_cc = config.get('cflags_cc')
- # if cflags_cc is not None:
- # SetTargetProperty(output, cmake_target_name,
- # 'CXX_COMPILE_FLAGS', cflags_cc, ' ')
- # Instead we must...
- cflags = config.get("cflags", [])
- cflags_c = config.get("cflags_c", [])
- cflags_cxx = config.get("cflags_cc", [])
- if xcode_settings:
- cflags = xcode_settings.GetCflags(config_to_use)
- cflags_c = xcode_settings.GetCflagsC(config_to_use)
- cflags_cxx = xcode_settings.GetCflagsCC(config_to_use)
- # cflags_objc = xcode_settings.GetCflagsObjC(config_to_use)
- # cflags_objcc = xcode_settings.GetCflagsObjCC(config_to_use)
- if (not cflags_c or not c_sources) and (not cflags_cxx or not cxx_sources):
- SetTargetProperty(output, cmake_target_name, "COMPILE_FLAGS", cflags, " ")
- elif c_sources and not (s_sources or cxx_sources):
- flags = []
- flags.extend(cflags)
- flags.extend(cflags_c)
- SetTargetProperty(output, cmake_target_name, "COMPILE_FLAGS", flags, " ")
- elif cxx_sources and not (s_sources or c_sources):
- flags = []
- flags.extend(cflags)
- flags.extend(cflags_cxx)
- SetTargetProperty(output, cmake_target_name, "COMPILE_FLAGS", flags, " ")
- else:
- # TODO: This is broken, one cannot generally set properties on files,
- # as other targets may require different properties on the same files.
- if s_sources and cflags:
- SetFilesProperty(output, s_sources_name, "COMPILE_FLAGS", cflags, " ")
- if c_sources and (cflags or cflags_c):
- flags = []
- flags.extend(cflags)
- flags.extend(cflags_c)
- SetFilesProperty(output, c_sources_name, "COMPILE_FLAGS", flags, " ")
- if cxx_sources and (cflags or cflags_cxx):
- flags = []
- flags.extend(cflags)
- flags.extend(cflags_cxx)
- SetFilesProperty(output, cxx_sources_name, "COMPILE_FLAGS", flags, " ")
- # Linker flags
- ldflags = config.get("ldflags")
- if ldflags is not None:
- SetTargetProperty(output, cmake_target_name, "LINK_FLAGS", ldflags, " ")
- # XCode settings
- xcode_settings = config.get("xcode_settings", {})
- for xcode_setting, xcode_value in xcode_settings.items():
- SetTargetProperty(
- output,
- cmake_target_name,
- "XCODE_ATTRIBUTE_%s" % xcode_setting,
- xcode_value,
- "" if isinstance(xcode_value, str) else " ",
- )
- # Note on Dependencies and Libraries:
- # CMake wants to handle link order, resolving the link line up front.
- # Gyp does not retain or enforce specifying enough information to do so.
- # So do as other gyp generators and use --start-group and --end-group.
- # Give CMake as little information as possible so that it doesn't mess it up.
- # Dependencies
- rawDeps = spec.get("dependencies", [])
- static_deps = []
- shared_deps = []
- other_deps = []
- for rawDep in rawDeps:
- dep_cmake_name = namer.CreateCMakeTargetName(rawDep)
- dep_spec = target_dicts.get(rawDep, {})
- dep_target_type = dep_spec.get("type", None)
- if dep_target_type == "static_library":
- static_deps.append(dep_cmake_name)
- elif dep_target_type == "shared_library":
- shared_deps.append(dep_cmake_name)
- else:
- other_deps.append(dep_cmake_name)
- # ensure all external dependencies are complete before internal dependencies
- # extra_deps currently only depend on their own deps, so otherwise run early
- if static_deps or shared_deps or other_deps:
- for extra_dep in extra_deps:
- output.write("add_dependencies(")
- output.write(extra_dep)
- output.write("\n")
- for deps in (static_deps, shared_deps, other_deps):
- for dep in gyp.common.uniquer(deps):
- output.write(" ")
- output.write(dep)
- output.write("\n")
- output.write(")\n")
- linkable = target_type in ("executable", "loadable_module", "shared_library")
- other_deps.extend(extra_deps)
- if other_deps or (not linkable and (static_deps or shared_deps)):
- output.write("add_dependencies(")
- output.write(cmake_target_name)
- output.write("\n")
- for dep in gyp.common.uniquer(other_deps):
- output.write(" ")
- output.write(dep)
- output.write("\n")
- if not linkable:
- for deps in (static_deps, shared_deps):
- for lib_dep in gyp.common.uniquer(deps):
- output.write(" ")
- output.write(lib_dep)
- output.write("\n")
- output.write(")\n")
- # Libraries
- if linkable:
- external_libs = [lib for lib in spec.get("libraries", []) if len(lib) > 0]
- if external_libs or static_deps or shared_deps:
- output.write("target_link_libraries(")
- output.write(cmake_target_name)
- output.write("\n")
- if static_deps:
- write_group = circular_libs and len(static_deps) > 1 and flavor != "mac"
- if write_group:
- output.write("-Wl,--start-group\n")
- for dep in gyp.common.uniquer(static_deps):
- output.write(" ")
- output.write(dep)
- output.write("\n")
- if write_group:
- output.write("-Wl,--end-group\n")
- if shared_deps:
- for dep in gyp.common.uniquer(shared_deps):
- output.write(" ")
- output.write(dep)
- output.write("\n")
- if external_libs:
- for lib in gyp.common.uniquer(external_libs):
- output.write(' "')
- output.write(RemovePrefix(lib, "$(SDKROOT)"))
- output.write('"\n')
- output.write(")\n")
- UnsetVariable(output, "TOOLSET")
- UnsetVariable(output, "TARGET")
- def GenerateOutputForConfig(target_list, target_dicts, data, params, config_to_use):
- options = params["options"]
- generator_flags = params["generator_flags"]
- flavor = gyp.common.GetFlavor(params)
- # generator_dir: relative path from pwd to where make puts build files.
- # Makes migrating from make to cmake easier, cmake doesn't put anything here.
- # Each Gyp configuration creates a different CMakeLists.txt file
- # to avoid incompatibilities between Gyp and CMake configurations.
- generator_dir = os.path.relpath(options.generator_output or ".")
- # output_dir: relative path from generator_dir to the build directory.
- output_dir = generator_flags.get("output_dir", "out")
- # build_dir: relative path from source root to our output files.
- # e.g. "out/Debug"
- build_dir = os.path.normpath(os.path.join(generator_dir, output_dir, config_to_use))
- toplevel_build = os.path.join(options.toplevel_dir, build_dir)
- output_file = os.path.join(toplevel_build, "CMakeLists.txt")
- gyp.common.EnsureDirExists(output_file)
- output = open(output_file, "w")
- output.write("cmake_minimum_required(VERSION 2.8.8 FATAL_ERROR)\n")
- output.write("cmake_policy(VERSION 2.8.8)\n")
- gyp_file, project_target, _ = gyp.common.ParseQualifiedTarget(target_list[-1])
- output.write("project(")
- output.write(project_target)
- output.write(")\n")
- SetVariable(output, "configuration", config_to_use)
- ar = None
- cc = None
- cxx = None
- make_global_settings = data[gyp_file].get("make_global_settings", [])
- build_to_top = gyp.common.InvertRelativePath(build_dir, options.toplevel_dir)
- for key, value in make_global_settings:
- if key == "AR":
- ar = os.path.join(build_to_top, value)
- if key == "CC":
- cc = os.path.join(build_to_top, value)
- if key == "CXX":
- cxx = os.path.join(build_to_top, value)
- ar = gyp.common.GetEnvironFallback(["AR_target", "AR"], ar)
- cc = gyp.common.GetEnvironFallback(["CC_target", "CC"], cc)
- cxx = gyp.common.GetEnvironFallback(["CXX_target", "CXX"], cxx)
- if ar:
- SetVariable(output, "CMAKE_AR", ar)
- if cc:
- SetVariable(output, "CMAKE_C_COMPILER", cc)
- if cxx:
- SetVariable(output, "CMAKE_CXX_COMPILER", cxx)
- # The following appears to be as-yet undocumented.
- # http://public.kitware.com/Bug/view.php?id=8392
- output.write("enable_language(ASM)\n")
- # ASM-ATT does not support .S files.
- # output.write('enable_language(ASM-ATT)\n')
- if cc:
- SetVariable(output, "CMAKE_ASM_COMPILER", cc)
- SetVariable(output, "builddir", "${CMAKE_CURRENT_BINARY_DIR}")
- SetVariable(output, "obj", "${builddir}/obj")
- output.write("\n")
- # TODO: Undocumented/unsupported (the CMake Java generator depends on it).
- # CMake by default names the object resulting from foo.c to be foo.c.o.
- # Gyp traditionally names the object resulting from foo.c foo.o.
- # This should be irrelevant, but some targets extract .o files from .a
- # and depend on the name of the extracted .o files.
- output.write("set(CMAKE_C_OUTPUT_EXTENSION_REPLACE 1)\n")
- output.write("set(CMAKE_CXX_OUTPUT_EXTENSION_REPLACE 1)\n")
- output.write("\n")
- # Force ninja to use rsp files. Otherwise link and ar lines can get too long,
- # resulting in 'Argument list too long' errors.
- # However, rsp files don't work correctly on Mac.
- if flavor != "mac":
- output.write("set(CMAKE_NINJA_FORCE_RESPONSE_FILE 1)\n")
- output.write("\n")
- namer = CMakeNamer(target_list)
- # The list of targets upon which the 'all' target should depend.
- # CMake has it's own implicit 'all' target, one is not created explicitly.
- all_qualified_targets = set()
- for build_file in params["build_files"]:
- for qualified_target in gyp.common.AllTargets(
- target_list, target_dicts, os.path.normpath(build_file)
- ):
- all_qualified_targets.add(qualified_target)
- for qualified_target in target_list:
- if flavor == "mac":
- gyp_file, _, _ = gyp.common.ParseQualifiedTarget(qualified_target)
- spec = target_dicts[qualified_target]
- gyp.xcode_emulation.MergeGlobalXcodeSettingsToSpec(data[gyp_file], spec)
- WriteTarget(
- namer,
- qualified_target,
- target_dicts,
- build_dir,
- config_to_use,
- options,
- generator_flags,
- all_qualified_targets,
- flavor,
- output,
- )
- output.close()
- def PerformBuild(data, configurations, params):
- options = params["options"]
- generator_flags = params["generator_flags"]
- # generator_dir: relative path from pwd to where make puts build files.
- # Makes migrating from make to cmake easier, cmake doesn't put anything here.
- generator_dir = os.path.relpath(options.generator_output or ".")
- # output_dir: relative path from generator_dir to the build directory.
- output_dir = generator_flags.get("output_dir", "out")
- for config_name in configurations:
- # build_dir: relative path from source root to our output files.
- # e.g. "out/Debug"
- build_dir = os.path.normpath(
- os.path.join(generator_dir, output_dir, config_name)
- )
- arguments = ["cmake", "-G", "Ninja"]
- print(f"Generating [{config_name}]: {arguments}")
- subprocess.check_call(arguments, cwd=build_dir)
- arguments = ["ninja", "-C", build_dir]
- print(f"Building [{config_name}]: {arguments}")
- subprocess.check_call(arguments)
- def CallGenerateOutputForConfig(arglist):
- # Ignore the interrupt signal so that the parent process catches it and
- # kills all multiprocessing children.
- signal.signal(signal.SIGINT, signal.SIG_IGN)
- target_list, target_dicts, data, params, config_name = arglist
- GenerateOutputForConfig(target_list, target_dicts, data, params, config_name)
- def GenerateOutput(target_list, target_dicts, data, params):
- user_config = params.get("generator_flags", {}).get("config", None)
- if user_config:
- GenerateOutputForConfig(target_list, target_dicts, data, params, user_config)
- else:
- config_names = target_dicts[target_list[0]]["configurations"]
- if params["parallel"]:
- try:
- pool = multiprocessing.Pool(len(config_names))
- arglists = []
- for config_name in config_names:
- arglists.append(
- (target_list, target_dicts, data, params, config_name)
- )
- pool.map(CallGenerateOutputForConfig, arglists)
- except KeyboardInterrupt as e:
- pool.terminate()
- raise e
- else:
- for config_name in config_names:
- GenerateOutputForConfig(
- target_list, target_dicts, data, params, config_name
- )
|