aboutsummaryrefslogtreecommitdiffhomepage
path: root/gn/gn_to_cmake.py
diff options
context:
space:
mode:
authorGravatar bungeman <bungeman@google.com>2016-09-28 16:45:35 -0400
committerGravatar Skia Commit-Bot <skia-commit-bot@chromium.org>2016-09-28 21:20:29 +0000
commite95ea08cf4134eff0fa1f7afe624b4a49e3c5ba4 (patch)
treed11041d0f68a8ff6800eb274175d2cea1c0d5a35 /gn/gn_to_cmake.py
parent2331a5f2e061254206510cf8ab59e16f8c4921a2 (diff)
Improve GN to CMake translation for building.
This adds proper target types, dependencies, and library handling. This is enough to build and run dm on Linux and Mac. Change-Id: I5220f67f7dd3dbada7ad03ef83fff8fd80158fad Reviewed-on: https://skia-review.googlesource.com/2664 Commit-Queue: Ben Wagner <bungeman@google.com> Reviewed-by: Mike Klein <mtklein@google.com>
Diffstat (limited to 'gn/gn_to_cmake.py')
-rw-r--r--gn/gn_to_cmake.py395
1 files changed, 339 insertions, 56 deletions
diff --git a/gn/gn_to_cmake.py b/gn/gn_to_cmake.py
index 07720c6e37..98926964ce 100644
--- a/gn/gn_to_cmake.py
+++ b/gn/gn_to_cmake.py
@@ -5,6 +5,7 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
+
"""
Usage: gn_to_cmake.py <json_file_name>
@@ -16,39 +17,345 @@ gn gen out/config --ide=json
python gn/gn_to_cmake.py out/config/project.json
"""
+
import json
import posixpath
import os
import sys
-def write_project(project):
- def get_base_name(target_name):
- base_name = posixpath.basename(target_name)
- sep = base_name.rfind(":")
- if sep != -1:
- base_name = base_name[sep+1:]
- return base_name
+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
+ """
+ return a.replace('\\', '\\\\').replace(';', '\\;').replace('"', '\\"')
+
+
+def SetVariable(out, variable_name, value):
+ """Sets a CMake variable."""
+ out.write('set(')
+ out.write(variable_name)
+ out.write(' "')
+ out.write(CMakeStringEscape(value))
+ out.write('")\n')
+
+
+def SetVariableList(out, variable_name, values):
+ """Sets a CMake variable to a list."""
+ if not values:
+ return SetVariable(out, variable_name, "")
+ if len(values) == 1:
+ return SetVariable(out, variable_name, values[0])
+ out.write('list(APPEND ')
+ out.write(variable_name)
+ out.write('\n "')
+ out.write('"\n "'.join([CMakeStringEscape(value) for value in values]))
+ out.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(out, target_name, property_name, values, sep=''):
+ """Given a target, sets the given property."""
+ out.write('set_target_properties(')
+ out.write(target_name)
+ out.write(' PROPERTIES ')
+ out.write(property_name)
+ out.write(' "')
+ for value in values:
+ out.write(CMakeStringEscape(value))
+ out.write(sep)
+ out.write('")\n')
+
+
+def WriteVariable(output, variable_name, prepend=None):
+ if prepend:
+ output.write(prepend)
+ output.write('${')
+ output.write(variable_name)
+ output.write('}')
+
+
+def GetBaseName(target_name):
+ base_name = posixpath.basename(target_name)
+ sep = base_name.rfind(":")
+ if sep != -1:
+ base_name = base_name[sep+1:]
+ return base_name
- def get_output_name(target_name, target_properties):
- output_name = target_properties.get("output_name", None)
- if output_name is None:
- output_name = get_base_name(target_name)
- output_extension = target_properties.get("output_extension", None)
- if output_extension is not None:
- output_name = posixpath.splitext(output_name)[0]
- if len(output_extension):
- output_name += "." + output_extension
+def GetOutputName(target_name, target_properties):
+ output_name = target_properties.get("output_name", None)
+ if output_name is None:
+ output_name = GetBaseName(target_name)
+ output_extension = target_properties.get("output_extension", None)
+ if output_extension is not None:
+ output_name = posixpath.splitext(output_name)[0]
+ if len(output_extension):
+ output_name += "." + output_extension
+ return output_name
- return output_name
- def get_absolute_path(root_path, path):
- if path.startswith("//"):
- return root_path + "/" + path[2:]
- else:
- return path
+def GetAbsolutePath(root_path, path):
+ if path.startswith("//"):
+ return root_path + "/" + path[2:]
+ else:
+ return path
+
+
+# See GetSourceFileType in gn
+source_file_types = {
+ '.cc': 'cxx',
+ '.cpp': 'cxx',
+ '.cxx': 'cxx',
+ '.c': 'c',
+ '.s': 'asm',
+ '.S': 'asm',
+ '.asm': 'asm',
+ '.o': 'obj',
+ '.obj': 'obj',
+}
+
+
+class CMakeTargetType(object):
+ def __init__(self, command, modifier, property_modifier, is_linkable):
+ self.command = command
+ self.modifier = modifier
+ self.property_modifier = property_modifier
+ self.is_linkable = is_linkable
+CMakeTargetType.custom = CMakeTargetType('add_custom_target', 'SOURCES',
+ None, False)
+
+# See GetStringForOutputType in gn
+cmake_target_types = {
+ 'unknown': CMakeTargetType.custom,
+ 'group': CMakeTargetType.custom,
+ 'executable': CMakeTargetType('add_executable', None, 'RUNTIME', True),
+ 'loadable_module': CMakeTargetType('add_library', 'MODULE', 'LIBRARY', True),
+ 'shared_library': CMakeTargetType('add_library', 'SHARED', 'LIBRARY', True),
+ 'static_library': CMakeTargetType('add_library', 'STATIC', 'ARCHIVE', False),
+ 'source_set': CMakeTargetType('add_library', 'OBJECT', None, False),
+ 'action': CMakeTargetType.custom,
+ 'action_foreach': CMakeTargetType.custom,
+ 'bundle_data': CMakeTargetType.custom,
+ 'create_bundle': CMakeTargetType.custom,
+}
+
+
+class Target(object):
+ def __init__(self, gn_name, targets):
+ self.gn_name = gn_name
+ self.properties = targets[self.gn_name]
+ self.cmake_name = GetOutputName(self.gn_name, self.properties)
+ self.gn_type = self.properties.get('type', None)
+ self.cmake_type = cmake_target_types.get(self.gn_type, None)
+
+
+def WriteCompilerFlags(out, target, targets, root_path, sources):
+ # Hack, set linker language to c if no c or cxx files present.
+ if not 'c' in sources and not 'cxx' in sources:
+ SetTargetProperty(out, target.cmake_name, 'LINKER_LANGUAGE', ['C'])
+
+ # Mark uncompiled sources as uncompiled.
+ if 'other' in sources:
+ out.write('set_source_files_properties(')
+ WriteVariable(out, sources['other'], '')
+ out.write(' PROPERTIES HEADER_FILE_ONLY "TRUE")\n')
+
+ # Mark object sources as linkable.
+ if 'obj' in sources:
+ out.write('set_source_files_properties(')
+ WriteVariable(out, sources['obj'], '')
+ out.write(' PROPERTIES EXTERNAL_OBJECT "TRUE")\n')
+
+ # TODO: 'output_name', 'output_dir', 'output_extension'
+
+ # Includes
+ includes = target.properties.get('include_dirs', [])
+ if includes:
+ out.write('set_property(TARGET ')
+ out.write(target.cmake_name)
+ out.write(' APPEND PROPERTY INCLUDE_DIRECTORIES')
+ for include_dir in includes:
+ out.write('\n "')
+ out.write(GetAbsolutePath(root_path, include_dir))
+ out.write('"')
+ out.write(')\n')
+ # Defines
+ defines = target.properties.get('defines', [])
+ if defines:
+ SetTargetProperty(out, target.cmake_name,
+ 'COMPILE_DEFINITIONS', defines, ';')
+
+ # Compile flags
+ # "arflags", "asmflags", "cflags",
+ # "cflags_c", "clfags_cc", "cflags_objc", "clfags_objcc"
+ # CMake does not have per target lang compile flags.
+ # TODO: $<$<COMPILE_LANGUAGE:CXX>:cflags_cc style generator expression.
+ # http://public.kitware.com/Bug/view.php?id=14857
+ flags = []
+ flags.extend(target.properties.get('cflags', []))
+ cflags_asm = target.properties.get('asmflags', [])
+ cflags_c = target.properties.get('cflags_c', [])
+ cflags_cxx = target.properties.get('cflags_cc', [])
+ if 'c' in sources and not any(k in sources for k in ('asm', 'cxx')):
+ flags.extend(cflags_c)
+ elif 'cxx' in sources and not any(k in sources for k in ('asm', 'c')):
+ flags.extend(cflags_cxx)
+ else:
+ # TODO: This is broken, one cannot generally set properties on files,
+ # as other targets may require different properties on the same files.
+ if 'asm' in sources and cflags_asm:
+ SetFilesProperty(out, sources['asm'], 'COMPILE_FLAGS', cflags_asm, ' ')
+ if 'c' in sources and cflags_c:
+ SetFilesProperty(out, sources['c'], 'COMPILE_FLAGS', cflags_c, ' ')
+ if 'cxx' in sources and cflags_cxx:
+ SetFilesProperty(out, sources['cxx'], 'COMPILE_FLAGS', cflags_cxx, ' ')
+ if flags:
+ SetTargetProperty(out, target.cmake_name, 'COMPILE_FLAGS', flags, ' ')
+
+ # Linker flags
+ ldflags = target.properties.get('ldflags', [])
+ if ldflags:
+ SetTargetProperty(out, target.cmake_name, 'LINK_FLAGS', ldflags, ' ')
+
+
+def GetObjectDependencies(object_dependencies, target_name, targets):
+ dependencies = targets[target_name].get('deps', [])
+ for dependency in dependencies:
+ if targets[dependency].get('type', None) == 'source_set':
+ object_dependencies.add(dependency)
+ GetObjectDependencies(object_dependencies, dependency, targets)
+
+
+def WriteSourceVariables(out, target, targets, root_path):
+ raw_sources = target.properties.get('sources', [])
+
+ # gn separates the sheep from the goats based on file extensions.
+ # A full separation is done here because of flag handing (see Compile flags).
+ source_types = {'cxx':[], 'c':[], 'asm':[],
+ 'obj':[], 'obj_target':[], 'other':[]}
+ for source in raw_sources:
+ _, ext = posixpath.splitext(source)
+ source_abs_path = GetAbsolutePath(root_path, source)
+ source_types[source_file_types.get(ext, 'other')].append(source_abs_path)
+
+ # OBJECT library dependencies need to be listed as sources.
+ # Only executables and non-OBJECT libraries may reference an OBJECT library.
+ # https://gitlab.kitware.com/cmake/cmake/issues/14778
+ if target.cmake_type.modifier != 'OBJECT':
+ object_dependencies = set()
+ GetObjectDependencies(object_dependencies, target.gn_name, targets)
+ for dependency in object_dependencies:
+ cmake_dependency_name = GetOutputName(dependency, targets[dependency])
+ obj_target_sources = '$<TARGET_OBJECTS:' + cmake_dependency_name + '>'
+ source_types['obj_target'].append(obj_target_sources)
+
+ sources = {}
+ for source_type, sources_of_type in source_types.items():
+ if sources_of_type:
+ sources[source_type] = target.cmake_name + '__' + source_type + '_srcs'
+ SetVariableList(out, sources[source_type], sources_of_type)
+ return sources
+
+
+def WriteTarget(out, target_name, root_path, targets):
+ out.write('\n#')
+ out.write(target_name)
+ out.write('\n')
+
+ target = Target(target_name, targets)
+
+ if target.cmake_type is None:
+ print ('Target %s has unknown target type %s, skipping.' %
+ ( target_name, target.gn_type ) )
+ return
+
+ sources = WriteSourceVariables(out, target, targets, root_path)
+
+ out.write(target.cmake_type.command)
+ out.write('(')
+ out.write(target.cmake_name)
+ if target.cmake_type.modifier is not None:
+ out.write(' ')
+ out.write(target.cmake_type.modifier)
+ for sources_type_name in sources.values():
+ WriteVariable(out, sources_type_name, ' ')
+ out.write(')\n')
+
+ if target.cmake_type.command != 'add_custom_target':
+ WriteCompilerFlags(out, target, targets, root_path, sources)
+
+ dependencies = target.properties.get('deps', [])
+ libraries = []
+ nonlibraries = []
+ for dependency in dependencies:
+ gn_dependency_type = targets.get(dependency, {}).get('type', None)
+ cmake_dependency_type = cmake_target_types.get(gn_dependency_type, None)
+ if cmake_dependency_type.command != 'add_library':
+ nonlibraries.append(dependency)
+ elif cmake_dependency_type.modifier != 'OBJECT':
+ libraries.append(GetOutputName(dependency, targets[dependency]))
+
+ # Non-library dependencies.
+ if nonlibraries:
+ out.write('add_dependencies(')
+ out.write(target.cmake_name)
+ out.write('\n')
+ for nonlibrary in nonlibraries:
+ out.write(' ')
+ out.write(GetOutputName(nonlibrary, targets[nonlibrary]))
+ out.write('\n')
+ out.write(')\n')
+
+ # Non-OBJECT library dependencies.
+ external_libraries = target.properties.get('libs', [])
+ if target.cmake_type.is_linkable and (external_libraries or libraries):
+ system_libraries = []
+ for external_library in external_libraries:
+ if '/' in external_library:
+ libraries.append(GetAbsolutePath(root_path, external_library))
+ else:
+ if external_library.endswith('.framework'):
+ external_library = external_library[:-len('.framework')]
+ system_library = external_library + '__library'
+ out.write('find_library (')
+ out.write(system_library)
+ out.write(' ')
+ out.write(external_library)
+ out.write(')\n')
+ system_libraries.append(system_library)
+ out.write('target_link_libraries(')
+ out.write(target.cmake_name)
+ for library in libraries:
+ out.write('\n "')
+ out.write(CMakeStringEscape(library))
+ out.write('"')
+ for system_library in system_libraries:
+ WriteVariable(out, system_library, '\n ')
+ out.write(')\n')
+
+
+def WriteProject(project):
build_settings = project['build_settings']
root_path = build_settings['root_path']
build_path = os.path.join(root_path, build_settings['build_dir'][2:])
@@ -57,42 +364,17 @@ def write_project(project):
out.write('cmake_minimum_required(VERSION 2.8.8 FATAL_ERROR)\n')
out.write('cmake_policy(VERSION 2.8.8)\n')
- for target_name, target_properties in project['targets'].items():
- sources = target_properties.get('sources', [])
- if not sources:
- continue
+ # The following appears to be as-yet undocumented.
+ # http://public.kitware.com/Bug/view.php?id=8392
+ out.write('enable_language(ASM)\n')
+ # ASM-ATT does not support .S files.
+ # output.write('enable_language(ASM-ATT)\n')
- target_output_name = get_output_name(target_name, target_properties)
+ targets = project['targets']
+ for target_name in targets.keys():
out.write('\n')
- out.write('add_library(')
- out.write(target_output_name)
- out.write(' STATIC\n')
- for source in sources:
- out.write(' "')
- out.write(get_absolute_path(root_path, source))
- out.write('"\n')
- out.write(')\n')
+ WriteTarget(out, target_name, root_path, targets)
- out.write('set_target_properties(')
- out.write(target_output_name)
- out.write(' PROPERTIES EXCLUDE_FROM_ALL "TRUE")\n')
-
- out.write('set_property(TARGET ')
- out.write(target_output_name)
- out.write(' APPEND PROPERTY INCLUDE_DIRECTORIES\n')
- for include_dir in target_properties.get('include_dirs', []):
- out.write(' "')
- out.write(get_absolute_path(root_path, include_dir))
- out.write('"\n')
- out.write(')\n')
-
- out.write('set_target_properties(')
- out.write(target_output_name)
- out.write(' PROPERTIES COMPILE_DEFINITIONS "')
- for define in target_properties.get('defines', []):
- out.write(define)
- out.write(';')
- out.write('")\n')
def main():
if len(sys.argv) != 2:
@@ -104,7 +386,8 @@ def main():
with open(json_path, 'r') as json_file:
project = json.loads(json_file.read())
- write_project(project)
+ WriteProject(project)
+
if __name__ == "__main__":
main()