blob: 98926964ce88f9fa564107f17a74f266dc26f4ee [file] [log] [blame]
bungeman623ef922016-09-23 08:16:04 -07001#!/usr/bin/env python
2#
3# Copyright 2016 Google Inc.
4#
5# Use of this source code is governed by a BSD-style license that can be
6# found in the LICENSE file.
7
bungemane95ea082016-09-28 16:45:35 -04008
bungeman623ef922016-09-23 08:16:04 -07009"""
10Usage: gn_to_cmake.py <json_file_name>
11
12gn gen out/config --ide=json --json-ide-script=../../gn/gn_to_cmake.py
13
14or
15
16gn gen out/config --ide=json
17python gn/gn_to_cmake.py out/config/project.json
18"""
19
bungemane95ea082016-09-28 16:45:35 -040020
bungeman623ef922016-09-23 08:16:04 -070021import json
22import posixpath
23import os
24import sys
25
bungeman623ef922016-09-23 08:16:04 -070026
bungemane95ea082016-09-28 16:45:35 -040027def CMakeStringEscape(a):
28 """Escapes the string 'a' for use inside a CMake string.
bungeman623ef922016-09-23 08:16:04 -070029
bungemane95ea082016-09-28 16:45:35 -040030 This means escaping
31 '\' otherwise it may be seen as modifying the next character
32 '"' otherwise it will end the string
33 ';' otherwise the string becomes a list
bungeman623ef922016-09-23 08:16:04 -070034
bungemane95ea082016-09-28 16:45:35 -040035 The following do not need to be escaped
36 '#' when the lexer is in string state, this does not start a comment
37 """
38 return a.replace('\\', '\\\\').replace(';', '\\;').replace('"', '\\"')
bungeman623ef922016-09-23 08:16:04 -070039
bungeman623ef922016-09-23 08:16:04 -070040
bungemane95ea082016-09-28 16:45:35 -040041def SetVariable(out, variable_name, value):
42 """Sets a CMake variable."""
43 out.write('set(')
44 out.write(variable_name)
45 out.write(' "')
46 out.write(CMakeStringEscape(value))
47 out.write('")\n')
bungeman623ef922016-09-23 08:16:04 -070048
bungemane95ea082016-09-28 16:45:35 -040049
50def SetVariableList(out, variable_name, values):
51 """Sets a CMake variable to a list."""
52 if not values:
53 return SetVariable(out, variable_name, "")
54 if len(values) == 1:
55 return SetVariable(out, variable_name, values[0])
56 out.write('list(APPEND ')
57 out.write(variable_name)
58 out.write('\n "')
59 out.write('"\n "'.join([CMakeStringEscape(value) for value in values]))
60 out.write('")\n')
61
62
63def SetFilesProperty(output, variable, property_name, values, sep):
64 """Given a set of source files, sets the given property on them."""
65 output.write('set_source_files_properties(')
66 WriteVariable(output, variable)
67 output.write(' PROPERTIES ')
68 output.write(property_name)
69 output.write(' "')
70 for value in values:
71 output.write(CMakeStringEscape(value))
72 output.write(sep)
73 output.write('")\n')
74
75
76def SetTargetProperty(out, target_name, property_name, values, sep=''):
77 """Given a target, sets the given property."""
78 out.write('set_target_properties(')
79 out.write(target_name)
80 out.write(' PROPERTIES ')
81 out.write(property_name)
82 out.write(' "')
83 for value in values:
84 out.write(CMakeStringEscape(value))
85 out.write(sep)
86 out.write('")\n')
87
88
89def WriteVariable(output, variable_name, prepend=None):
90 if prepend:
91 output.write(prepend)
92 output.write('${')
93 output.write(variable_name)
94 output.write('}')
95
96
97def GetBaseName(target_name):
98 base_name = posixpath.basename(target_name)
99 sep = base_name.rfind(":")
100 if sep != -1:
101 base_name = base_name[sep+1:]
102 return base_name
103
104
105def GetOutputName(target_name, target_properties):
106 output_name = target_properties.get("output_name", None)
107 if output_name is None:
108 output_name = GetBaseName(target_name)
109 output_extension = target_properties.get("output_extension", None)
110 if output_extension is not None:
111 output_name = posixpath.splitext(output_name)[0]
112 if len(output_extension):
113 output_name += "." + output_extension
114 return output_name
115
116
117def GetAbsolutePath(root_path, path):
118 if path.startswith("//"):
119 return root_path + "/" + path[2:]
120 else:
121 return path
122
123
124# See GetSourceFileType in gn
125source_file_types = {
126 '.cc': 'cxx',
127 '.cpp': 'cxx',
128 '.cxx': 'cxx',
129 '.c': 'c',
130 '.s': 'asm',
131 '.S': 'asm',
132 '.asm': 'asm',
133 '.o': 'obj',
134 '.obj': 'obj',
135}
136
137
138class CMakeTargetType(object):
139 def __init__(self, command, modifier, property_modifier, is_linkable):
140 self.command = command
141 self.modifier = modifier
142 self.property_modifier = property_modifier
143 self.is_linkable = is_linkable
144CMakeTargetType.custom = CMakeTargetType('add_custom_target', 'SOURCES',
145 None, False)
146
147# See GetStringForOutputType in gn
148cmake_target_types = {
149 'unknown': CMakeTargetType.custom,
150 'group': CMakeTargetType.custom,
151 'executable': CMakeTargetType('add_executable', None, 'RUNTIME', True),
152 'loadable_module': CMakeTargetType('add_library', 'MODULE', 'LIBRARY', True),
153 'shared_library': CMakeTargetType('add_library', 'SHARED', 'LIBRARY', True),
154 'static_library': CMakeTargetType('add_library', 'STATIC', 'ARCHIVE', False),
155 'source_set': CMakeTargetType('add_library', 'OBJECT', None, False),
156 'action': CMakeTargetType.custom,
157 'action_foreach': CMakeTargetType.custom,
158 'bundle_data': CMakeTargetType.custom,
159 'create_bundle': CMakeTargetType.custom,
160}
161
162
163class Target(object):
164 def __init__(self, gn_name, targets):
165 self.gn_name = gn_name
166 self.properties = targets[self.gn_name]
167 self.cmake_name = GetOutputName(self.gn_name, self.properties)
168 self.gn_type = self.properties.get('type', None)
169 self.cmake_type = cmake_target_types.get(self.gn_type, None)
170
171
172def WriteCompilerFlags(out, target, targets, root_path, sources):
173 # Hack, set linker language to c if no c or cxx files present.
174 if not 'c' in sources and not 'cxx' in sources:
175 SetTargetProperty(out, target.cmake_name, 'LINKER_LANGUAGE', ['C'])
176
177 # Mark uncompiled sources as uncompiled.
178 if 'other' in sources:
179 out.write('set_source_files_properties(')
180 WriteVariable(out, sources['other'], '')
181 out.write(' PROPERTIES HEADER_FILE_ONLY "TRUE")\n')
182
183 # Mark object sources as linkable.
184 if 'obj' in sources:
185 out.write('set_source_files_properties(')
186 WriteVariable(out, sources['obj'], '')
187 out.write(' PROPERTIES EXTERNAL_OBJECT "TRUE")\n')
188
189 # TODO: 'output_name', 'output_dir', 'output_extension'
190
191 # Includes
192 includes = target.properties.get('include_dirs', [])
193 if includes:
194 out.write('set_property(TARGET ')
195 out.write(target.cmake_name)
196 out.write(' APPEND PROPERTY INCLUDE_DIRECTORIES')
197 for include_dir in includes:
198 out.write('\n "')
199 out.write(GetAbsolutePath(root_path, include_dir))
200 out.write('"')
201 out.write(')\n')
202
203 # Defines
204 defines = target.properties.get('defines', [])
205 if defines:
206 SetTargetProperty(out, target.cmake_name,
207 'COMPILE_DEFINITIONS', defines, ';')
208
209 # Compile flags
210 # "arflags", "asmflags", "cflags",
211 # "cflags_c", "clfags_cc", "cflags_objc", "clfags_objcc"
212 # CMake does not have per target lang compile flags.
213 # TODO: $<$<COMPILE_LANGUAGE:CXX>:cflags_cc style generator expression.
214 # http://public.kitware.com/Bug/view.php?id=14857
215 flags = []
216 flags.extend(target.properties.get('cflags', []))
217 cflags_asm = target.properties.get('asmflags', [])
218 cflags_c = target.properties.get('cflags_c', [])
219 cflags_cxx = target.properties.get('cflags_cc', [])
220 if 'c' in sources and not any(k in sources for k in ('asm', 'cxx')):
221 flags.extend(cflags_c)
222 elif 'cxx' in sources and not any(k in sources for k in ('asm', 'c')):
223 flags.extend(cflags_cxx)
224 else:
225 # TODO: This is broken, one cannot generally set properties on files,
226 # as other targets may require different properties on the same files.
227 if 'asm' in sources and cflags_asm:
228 SetFilesProperty(out, sources['asm'], 'COMPILE_FLAGS', cflags_asm, ' ')
229 if 'c' in sources and cflags_c:
230 SetFilesProperty(out, sources['c'], 'COMPILE_FLAGS', cflags_c, ' ')
231 if 'cxx' in sources and cflags_cxx:
232 SetFilesProperty(out, sources['cxx'], 'COMPILE_FLAGS', cflags_cxx, ' ')
233 if flags:
234 SetTargetProperty(out, target.cmake_name, 'COMPILE_FLAGS', flags, ' ')
235
236 # Linker flags
237 ldflags = target.properties.get('ldflags', [])
238 if ldflags:
239 SetTargetProperty(out, target.cmake_name, 'LINK_FLAGS', ldflags, ' ')
240
241
242def GetObjectDependencies(object_dependencies, target_name, targets):
243 dependencies = targets[target_name].get('deps', [])
244 for dependency in dependencies:
245 if targets[dependency].get('type', None) == 'source_set':
246 object_dependencies.add(dependency)
247 GetObjectDependencies(object_dependencies, dependency, targets)
248
249
250def WriteSourceVariables(out, target, targets, root_path):
251 raw_sources = target.properties.get('sources', [])
252
253 # gn separates the sheep from the goats based on file extensions.
254 # A full separation is done here because of flag handing (see Compile flags).
255 source_types = {'cxx':[], 'c':[], 'asm':[],
256 'obj':[], 'obj_target':[], 'other':[]}
257 for source in raw_sources:
258 _, ext = posixpath.splitext(source)
259 source_abs_path = GetAbsolutePath(root_path, source)
260 source_types[source_file_types.get(ext, 'other')].append(source_abs_path)
261
262 # OBJECT library dependencies need to be listed as sources.
263 # Only executables and non-OBJECT libraries may reference an OBJECT library.
264 # https://gitlab.kitware.com/cmake/cmake/issues/14778
265 if target.cmake_type.modifier != 'OBJECT':
266 object_dependencies = set()
267 GetObjectDependencies(object_dependencies, target.gn_name, targets)
268 for dependency in object_dependencies:
269 cmake_dependency_name = GetOutputName(dependency, targets[dependency])
270 obj_target_sources = '$<TARGET_OBJECTS:' + cmake_dependency_name + '>'
271 source_types['obj_target'].append(obj_target_sources)
272
273 sources = {}
274 for source_type, sources_of_type in source_types.items():
275 if sources_of_type:
276 sources[source_type] = target.cmake_name + '__' + source_type + '_srcs'
277 SetVariableList(out, sources[source_type], sources_of_type)
278 return sources
279
280
281def WriteTarget(out, target_name, root_path, targets):
282 out.write('\n#')
283 out.write(target_name)
284 out.write('\n')
285
286 target = Target(target_name, targets)
287
288 if target.cmake_type is None:
289 print ('Target %s has unknown target type %s, skipping.' %
290 ( target_name, target.gn_type ) )
291 return
292
293 sources = WriteSourceVariables(out, target, targets, root_path)
294
295 out.write(target.cmake_type.command)
296 out.write('(')
297 out.write(target.cmake_name)
298 if target.cmake_type.modifier is not None:
299 out.write(' ')
300 out.write(target.cmake_type.modifier)
301 for sources_type_name in sources.values():
302 WriteVariable(out, sources_type_name, ' ')
303 out.write(')\n')
304
305 if target.cmake_type.command != 'add_custom_target':
306 WriteCompilerFlags(out, target, targets, root_path, sources)
307
308 dependencies = target.properties.get('deps', [])
309 libraries = []
310 nonlibraries = []
311 for dependency in dependencies:
312 gn_dependency_type = targets.get(dependency, {}).get('type', None)
313 cmake_dependency_type = cmake_target_types.get(gn_dependency_type, None)
314 if cmake_dependency_type.command != 'add_library':
315 nonlibraries.append(dependency)
316 elif cmake_dependency_type.modifier != 'OBJECT':
317 libraries.append(GetOutputName(dependency, targets[dependency]))
318
319 # Non-library dependencies.
320 if nonlibraries:
321 out.write('add_dependencies(')
322 out.write(target.cmake_name)
323 out.write('\n')
324 for nonlibrary in nonlibraries:
325 out.write(' ')
326 out.write(GetOutputName(nonlibrary, targets[nonlibrary]))
327 out.write('\n')
328 out.write(')\n')
329
330 # Non-OBJECT library dependencies.
331 external_libraries = target.properties.get('libs', [])
332 if target.cmake_type.is_linkable and (external_libraries or libraries):
333 system_libraries = []
334 for external_library in external_libraries:
335 if '/' in external_library:
336 libraries.append(GetAbsolutePath(root_path, external_library))
337 else:
338 if external_library.endswith('.framework'):
339 external_library = external_library[:-len('.framework')]
340 system_library = external_library + '__library'
341 out.write('find_library (')
342 out.write(system_library)
343 out.write(' ')
344 out.write(external_library)
345 out.write(')\n')
346 system_libraries.append(system_library)
347 out.write('target_link_libraries(')
348 out.write(target.cmake_name)
349 for library in libraries:
350 out.write('\n "')
351 out.write(CMakeStringEscape(library))
352 out.write('"')
353 for system_library in system_libraries:
354 WriteVariable(out, system_library, '\n ')
355 out.write(')\n')
356
357
358def WriteProject(project):
bungeman623ef922016-09-23 08:16:04 -0700359 build_settings = project['build_settings']
360 root_path = build_settings['root_path']
361 build_path = os.path.join(root_path, build_settings['build_dir'][2:])
362
363 out = open(os.path.join(build_path, 'CMakeLists.txt'), 'w+')
364 out.write('cmake_minimum_required(VERSION 2.8.8 FATAL_ERROR)\n')
365 out.write('cmake_policy(VERSION 2.8.8)\n')
366
bungemane95ea082016-09-28 16:45:35 -0400367 # The following appears to be as-yet undocumented.
368 # http://public.kitware.com/Bug/view.php?id=8392
369 out.write('enable_language(ASM)\n')
370 # ASM-ATT does not support .S files.
371 # output.write('enable_language(ASM-ATT)\n')
bungeman623ef922016-09-23 08:16:04 -0700372
bungemane95ea082016-09-28 16:45:35 -0400373 targets = project['targets']
374 for target_name in targets.keys():
bungeman623ef922016-09-23 08:16:04 -0700375 out.write('\n')
bungemane95ea082016-09-28 16:45:35 -0400376 WriteTarget(out, target_name, root_path, targets)
bungeman623ef922016-09-23 08:16:04 -0700377
bungeman623ef922016-09-23 08:16:04 -0700378
379def main():
380 if len(sys.argv) != 2:
381 print('Usage: ' + sys.argv[0] + ' <json_file_name>')
382 exit(1)
383
384 json_path = sys.argv[1]
385 project = None
386 with open(json_path, 'r') as json_file:
387 project = json.loads(json_file.read())
388
bungemane95ea082016-09-28 16:45:35 -0400389 WriteProject(project)
390
bungeman623ef922016-09-23 08:16:04 -0700391
392if __name__ == "__main__":
393 main()