blob: 727e40def9218e355f099bdeea387c5853405c9a [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
bungemane95ea082016-09-28 16:45:35 -040097# See GetSourceFileType in gn
98source_file_types = {
99 '.cc': 'cxx',
100 '.cpp': 'cxx',
101 '.cxx': 'cxx',
102 '.c': 'c',
103 '.s': 'asm',
104 '.S': 'asm',
105 '.asm': 'asm',
106 '.o': 'obj',
107 '.obj': 'obj',
108}
109
110
111class CMakeTargetType(object):
112 def __init__(self, command, modifier, property_modifier, is_linkable):
113 self.command = command
114 self.modifier = modifier
115 self.property_modifier = property_modifier
116 self.is_linkable = is_linkable
117CMakeTargetType.custom = CMakeTargetType('add_custom_target', 'SOURCES',
118 None, False)
119
120# See GetStringForOutputType in gn
121cmake_target_types = {
122 'unknown': CMakeTargetType.custom,
123 'group': CMakeTargetType.custom,
124 'executable': CMakeTargetType('add_executable', None, 'RUNTIME', True),
125 'loadable_module': CMakeTargetType('add_library', 'MODULE', 'LIBRARY', True),
126 'shared_library': CMakeTargetType('add_library', 'SHARED', 'LIBRARY', True),
127 'static_library': CMakeTargetType('add_library', 'STATIC', 'ARCHIVE', False),
128 'source_set': CMakeTargetType('add_library', 'OBJECT', None, False),
Ben Wagnerbc344042016-09-29 15:41:53 -0400129 'copy': CMakeTargetType.custom,
bungemane95ea082016-09-28 16:45:35 -0400130 'action': CMakeTargetType.custom,
131 'action_foreach': CMakeTargetType.custom,
132 'bundle_data': CMakeTargetType.custom,
133 'create_bundle': CMakeTargetType.custom,
134}
135
136
Ben Wagnerbc344042016-09-29 15:41:53 -0400137def GetBaseName(gn_target_name):
138 base_name = posixpath.basename(gn_target_name)
139 sep = base_name.rfind(":")
140 if sep != -1:
141 base_name = base_name[sep+1:]
142 return base_name
143
144
145class Project(object):
146 def __init__(self, project_json):
147 self.targets = project_json['targets']
148 build_settings = project_json['build_settings']
149 self.root_path = build_settings['root_path']
150 self.build_path = posixpath.join(self.root_path,
151 build_settings['build_dir'][2:])
152
153 def GetAbsolutePath(self, path):
154 if path.startswith("//"):
155 return self.root_path + "/" + path[2:]
156 else:
157 return path
158
159 def GetObjectDependencies(self, gn_target_name, object_dependencies):
160 dependencies = self.targets[gn_target_name].get('deps', [])
161 for dependency in dependencies:
162 dependency_type = self.targets[dependency].get('type', None)
163 if dependency_type == 'source_set':
164 object_dependencies.add(dependency)
165 if dependency_type not in gn_target_types_that_absorb_objects:
166 self.GetObjectDependencies(dependency, object_dependencies)
167
168 def GetCMakeTargetName(self, gn_target_name):
169 target_properties = self.targets[gn_target_name]
170 output_name = target_properties.get("output_name", None)
171 if output_name is None:
172 output_name = GetBaseName(gn_target_name)
173 output_extension = target_properties.get("output_extension", None)
174 if output_extension is not None:
175 output_name = posixpath.splitext(output_name)[0]
176 if len(output_extension):
177 output_name += "." + output_extension
178 return output_name
179
180
bungemane95ea082016-09-28 16:45:35 -0400181class Target(object):
Ben Wagnerbc344042016-09-29 15:41:53 -0400182 def __init__(self, gn_target_name, project):
183 self.gn_name = gn_target_name
184 self.properties = project.targets[self.gn_name]
185 self.cmake_name = project.GetCMakeTargetName(self.gn_name)
bungemane95ea082016-09-28 16:45:35 -0400186 self.gn_type = self.properties.get('type', None)
187 self.cmake_type = cmake_target_types.get(self.gn_type, None)
188
189
Ben Wagnerbc344042016-09-29 15:41:53 -0400190def WriteAction(out, target, project, sources, synthetic_dependencies):
191 outputs = []
192 output_directories = set()
193 for output in target.properties.get('outputs', []):
194 output_abs_path = project.GetAbsolutePath(output)
195 outputs.append(output_abs_path)
196 output_directory = posixpath.dirname(output_abs_path)
197 if output_directory:
198 output_directories.add(output_directory)
199 outputs_name = target.cmake_name + '__output'
200 SetVariableList(out, outputs_name, outputs)
201
202 out.write('add_custom_command(OUTPUT ')
203 WriteVariable(out, outputs_name)
204 out.write('\n')
205
206 for directory in output_directories:
207 out.write(' COMMAND ${CMAKE_COMMAND} -E make_directory ')
208 out.write(directory)
209 out.write('\n')
210
211 out.write(' COMMAND python ')
212 out.write(project.GetAbsolutePath(target.properties['script']))
213 out.write(' ')
214 out.write(' '.join(target.properties['args']))
215 out.write('\n')
216
217 out.write(' DEPENDS ')
218 for sources_type_name in sources.values():
219 WriteVariable(out, sources_type_name, ' ')
220 out.write('\n')
221
222 out.write(' WORKING_DIRECTORY ')
223 out.write(project.build_path)
224 out.write('\n')
225
226 out.write(' COMMENT ')
227 out.write(target.cmake_name)
228 out.write('\n')
229
230 out.write(' VERBATIM)\n')
231
232 synthetic_dependencies.add(outputs_name)
233
234
235def WriteCompilerFlags(out, target, project, sources):
bungemane95ea082016-09-28 16:45:35 -0400236 # Hack, set linker language to c if no c or cxx files present.
237 if not 'c' in sources and not 'cxx' in sources:
238 SetTargetProperty(out, target.cmake_name, 'LINKER_LANGUAGE', ['C'])
239
240 # Mark uncompiled sources as uncompiled.
Ben Wagnerbc344042016-09-29 15:41:53 -0400241 if 'input' in sources:
242 SetFilesProperty(out, sources['input'], 'HEADER_FILE_ONLY', ('True',), '')
bungemane95ea082016-09-28 16:45:35 -0400243 if 'other' in sources:
Ben Wagnerbc344042016-09-29 15:41:53 -0400244 SetFilesProperty(out, sources['other'], 'HEADER_FILE_ONLY', ('True',), '')
bungemane95ea082016-09-28 16:45:35 -0400245
246 # Mark object sources as linkable.
247 if 'obj' in sources:
Ben Wagnerbc344042016-09-29 15:41:53 -0400248 SetFilesProperty(out, sources['obj'], 'EXTERNAL_OBJECT', ('True',), '')
bungemane95ea082016-09-28 16:45:35 -0400249
250 # TODO: 'output_name', 'output_dir', 'output_extension'
Ben Wagnerbc344042016-09-29 15:41:53 -0400251 # This includes using 'source_outputs' to direct compiler output.
bungemane95ea082016-09-28 16:45:35 -0400252
253 # Includes
254 includes = target.properties.get('include_dirs', [])
255 if includes:
256 out.write('set_property(TARGET ')
257 out.write(target.cmake_name)
258 out.write(' APPEND PROPERTY INCLUDE_DIRECTORIES')
259 for include_dir in includes:
260 out.write('\n "')
Ben Wagnerbc344042016-09-29 15:41:53 -0400261 out.write(project.GetAbsolutePath(include_dir))
bungemane95ea082016-09-28 16:45:35 -0400262 out.write('"')
263 out.write(')\n')
264
265 # Defines
266 defines = target.properties.get('defines', [])
267 if defines:
268 SetTargetProperty(out, target.cmake_name,
269 'COMPILE_DEFINITIONS', defines, ';')
270
271 # Compile flags
272 # "arflags", "asmflags", "cflags",
273 # "cflags_c", "clfags_cc", "cflags_objc", "clfags_objcc"
274 # CMake does not have per target lang compile flags.
275 # TODO: $<$<COMPILE_LANGUAGE:CXX>:cflags_cc style generator expression.
276 # http://public.kitware.com/Bug/view.php?id=14857
277 flags = []
278 flags.extend(target.properties.get('cflags', []))
279 cflags_asm = target.properties.get('asmflags', [])
280 cflags_c = target.properties.get('cflags_c', [])
281 cflags_cxx = target.properties.get('cflags_cc', [])
282 if 'c' in sources and not any(k in sources for k in ('asm', 'cxx')):
283 flags.extend(cflags_c)
284 elif 'cxx' in sources and not any(k in sources for k in ('asm', 'c')):
285 flags.extend(cflags_cxx)
286 else:
287 # TODO: This is broken, one cannot generally set properties on files,
288 # as other targets may require different properties on the same files.
289 if 'asm' in sources and cflags_asm:
290 SetFilesProperty(out, sources['asm'], 'COMPILE_FLAGS', cflags_asm, ' ')
291 if 'c' in sources and cflags_c:
292 SetFilesProperty(out, sources['c'], 'COMPILE_FLAGS', cflags_c, ' ')
293 if 'cxx' in sources and cflags_cxx:
294 SetFilesProperty(out, sources['cxx'], 'COMPILE_FLAGS', cflags_cxx, ' ')
295 if flags:
296 SetTargetProperty(out, target.cmake_name, 'COMPILE_FLAGS', flags, ' ')
297
298 # Linker flags
299 ldflags = target.properties.get('ldflags', [])
300 if ldflags:
301 SetTargetProperty(out, target.cmake_name, 'LINK_FLAGS', ldflags, ' ')
302
303
Ben Wagnerbc344042016-09-29 15:41:53 -0400304gn_target_types_that_absorb_objects = (
305 'executable',
306 'loadable_module',
307 'shared_library',
308 'static_library'
309)
bungemane95ea082016-09-28 16:45:35 -0400310
311
Ben Wagnerbc344042016-09-29 15:41:53 -0400312def WriteSourceVariables(out, target, project):
bungemane95ea082016-09-28 16:45:35 -0400313 # gn separates the sheep from the goats based on file extensions.
314 # A full separation is done here because of flag handing (see Compile flags).
315 source_types = {'cxx':[], 'c':[], 'asm':[],
Ben Wagnerbc344042016-09-29 15:41:53 -0400316 'obj':[], 'obj_target':[], 'input':[], 'other':[]}
317
318 # TODO .def files on Windows
319 for source in target.properties.get('sources', []):
bungemane95ea082016-09-28 16:45:35 -0400320 _, ext = posixpath.splitext(source)
Ben Wagnerbc344042016-09-29 15:41:53 -0400321 source_abs_path = project.GetAbsolutePath(source)
bungemane95ea082016-09-28 16:45:35 -0400322 source_types[source_file_types.get(ext, 'other')].append(source_abs_path)
323
Ben Wagnerbc344042016-09-29 15:41:53 -0400324 for input_path in target.properties.get('inputs', []):
325 input_abs_path = project.GetAbsolutePath(input_path)
326 source_types['input'].append(input_abs_path)
327
bungemane95ea082016-09-28 16:45:35 -0400328 # OBJECT library dependencies need to be listed as sources.
329 # Only executables and non-OBJECT libraries may reference an OBJECT library.
330 # https://gitlab.kitware.com/cmake/cmake/issues/14778
Ben Wagnerbc344042016-09-29 15:41:53 -0400331 if target.gn_type in gn_target_types_that_absorb_objects:
bungemane95ea082016-09-28 16:45:35 -0400332 object_dependencies = set()
Ben Wagnerbc344042016-09-29 15:41:53 -0400333 project.GetObjectDependencies(target.gn_name, object_dependencies)
bungemane95ea082016-09-28 16:45:35 -0400334 for dependency in object_dependencies:
Ben Wagnerbc344042016-09-29 15:41:53 -0400335 cmake_dependency_name = project.GetCMakeTargetName(dependency)
bungemane95ea082016-09-28 16:45:35 -0400336 obj_target_sources = '$<TARGET_OBJECTS:' + cmake_dependency_name + '>'
337 source_types['obj_target'].append(obj_target_sources)
338
339 sources = {}
340 for source_type, sources_of_type in source_types.items():
341 if sources_of_type:
342 sources[source_type] = target.cmake_name + '__' + source_type + '_srcs'
343 SetVariableList(out, sources[source_type], sources_of_type)
344 return sources
345
346
Ben Wagnerbc344042016-09-29 15:41:53 -0400347def WriteTarget(out, target, project):
bungemane95ea082016-09-28 16:45:35 -0400348 out.write('\n#')
Ben Wagnerbc344042016-09-29 15:41:53 -0400349 out.write(target.gn_name)
bungemane95ea082016-09-28 16:45:35 -0400350 out.write('\n')
351
bungemane95ea082016-09-28 16:45:35 -0400352 if target.cmake_type is None:
353 print ('Target %s has unknown target type %s, skipping.' %
Ben Wagnerbc344042016-09-29 15:41:53 -0400354 ( target.gn_name, target.gn_type ) )
bungemane95ea082016-09-28 16:45:35 -0400355 return
356
Ben Wagnerbc344042016-09-29 15:41:53 -0400357 sources = WriteSourceVariables(out, target, project)
358
359 synthetic_dependencies = set()
360 if target.gn_type == 'action':
361 WriteAction(out, target, project, sources, synthetic_dependencies)
bungemane95ea082016-09-28 16:45:35 -0400362
363 out.write(target.cmake_type.command)
364 out.write('(')
365 out.write(target.cmake_name)
366 if target.cmake_type.modifier is not None:
367 out.write(' ')
368 out.write(target.cmake_type.modifier)
369 for sources_type_name in sources.values():
370 WriteVariable(out, sources_type_name, ' ')
Ben Wagnerbc344042016-09-29 15:41:53 -0400371 if synthetic_dependencies:
372 out.write(' DEPENDS')
373 for synthetic_dependencie in synthetic_dependencies:
374 WriteVariable(out, synthetic_dependencie, ' ')
bungemane95ea082016-09-28 16:45:35 -0400375 out.write(')\n')
376
377 if target.cmake_type.command != 'add_custom_target':
Ben Wagnerbc344042016-09-29 15:41:53 -0400378 WriteCompilerFlags(out, target, project, sources)
bungemane95ea082016-09-28 16:45:35 -0400379
380 dependencies = target.properties.get('deps', [])
381 libraries = []
382 nonlibraries = []
383 for dependency in dependencies:
Ben Wagnerbc344042016-09-29 15:41:53 -0400384 gn_dependency_type = project.targets.get(dependency, {}).get('type', None)
bungemane95ea082016-09-28 16:45:35 -0400385 cmake_dependency_type = cmake_target_types.get(gn_dependency_type, None)
Ben Wagnerbc344042016-09-29 15:41:53 -0400386 cmake_dependency_name = project.GetCMakeTargetName(dependency)
bungemane95ea082016-09-28 16:45:35 -0400387 if cmake_dependency_type.command != 'add_library':
Ben Wagnerbc344042016-09-29 15:41:53 -0400388 nonlibraries.append(cmake_dependency_name)
bungemane95ea082016-09-28 16:45:35 -0400389 elif cmake_dependency_type.modifier != 'OBJECT':
Ben Wagnerbc344042016-09-29 15:41:53 -0400390 if target.cmake_type.is_linkable:
391 libraries.append(cmake_dependency_name)
392 else:
393 nonlibraries.append(cmake_dependency_name)
bungemane95ea082016-09-28 16:45:35 -0400394
395 # Non-library dependencies.
396 if nonlibraries:
397 out.write('add_dependencies(')
398 out.write(target.cmake_name)
bungemane95ea082016-09-28 16:45:35 -0400399 for nonlibrary in nonlibraries:
Ben Wagnerbc344042016-09-29 15:41:53 -0400400 out.write('\n ')
401 out.write(nonlibrary)
bungemane95ea082016-09-28 16:45:35 -0400402 out.write(')\n')
403
404 # Non-OBJECT library dependencies.
405 external_libraries = target.properties.get('libs', [])
406 if target.cmake_type.is_linkable and (external_libraries or libraries):
407 system_libraries = []
408 for external_library in external_libraries:
409 if '/' in external_library:
Ben Wagnerbc344042016-09-29 15:41:53 -0400410 libraries.append(project.GetAbsolutePath(external_library))
bungemane95ea082016-09-28 16:45:35 -0400411 else:
412 if external_library.endswith('.framework'):
413 external_library = external_library[:-len('.framework')]
414 system_library = external_library + '__library'
415 out.write('find_library (')
416 out.write(system_library)
417 out.write(' ')
418 out.write(external_library)
419 out.write(')\n')
420 system_libraries.append(system_library)
421 out.write('target_link_libraries(')
422 out.write(target.cmake_name)
423 for library in libraries:
424 out.write('\n "')
425 out.write(CMakeStringEscape(library))
426 out.write('"')
427 for system_library in system_libraries:
428 WriteVariable(out, system_library, '\n ')
429 out.write(')\n')
430
431
432def WriteProject(project):
Ben Wagnerbc344042016-09-29 15:41:53 -0400433 out = open(posixpath.join(project.build_path, 'CMakeLists.txt'), 'w+')
bungeman623ef922016-09-23 08:16:04 -0700434 out.write('cmake_minimum_required(VERSION 2.8.8 FATAL_ERROR)\n')
435 out.write('cmake_policy(VERSION 2.8.8)\n')
436
bungemane95ea082016-09-28 16:45:35 -0400437 # The following appears to be as-yet undocumented.
438 # http://public.kitware.com/Bug/view.php?id=8392
439 out.write('enable_language(ASM)\n')
440 # ASM-ATT does not support .S files.
441 # output.write('enable_language(ASM-ATT)\n')
bungeman623ef922016-09-23 08:16:04 -0700442
Ben Wagnerbc344042016-09-29 15:41:53 -0400443 for target_name in project.targets.keys():
bungeman623ef922016-09-23 08:16:04 -0700444 out.write('\n')
Ben Wagnerbc344042016-09-29 15:41:53 -0400445 WriteTarget(out, Target(target_name, project), project)
bungeman623ef922016-09-23 08:16:04 -0700446
bungeman623ef922016-09-23 08:16:04 -0700447
448def main():
449 if len(sys.argv) != 2:
450 print('Usage: ' + sys.argv[0] + ' <json_file_name>')
451 exit(1)
452
453 json_path = sys.argv[1]
454 project = None
455 with open(json_path, 'r') as json_file:
456 project = json.loads(json_file.read())
457
Ben Wagnerbc344042016-09-29 15:41:53 -0400458 WriteProject(Project(project))
bungemane95ea082016-09-28 16:45:35 -0400459
bungeman623ef922016-09-23 08:16:04 -0700460
461if __name__ == "__main__":
462 main()