OLD | NEW |
(Empty) | |
| 1 #!/usr/bin/env python |
| 2 # Copyright 2016 The Chromium Authors. All rights reserved. |
| 3 # Use of this source code is governed by a BSD-style license that can be |
| 4 # found in the LICENSE file. |
| 5 |
| 6 """ |
| 7 Usage: gn_to_cmake.py <json_file_name> |
| 8 |
| 9 gn gen out/config --ide=json --json-ide-script=../../gn/gn_to_cmake.py |
| 10 |
| 11 or |
| 12 |
| 13 gn gen out/config --ide=json |
| 14 python gn/gn_to_cmake.py out/config/project.json |
| 15 |
| 16 The first is recommended, as it will auto-update. |
| 17 """ |
| 18 |
| 19 import functools |
| 20 import json |
| 21 import posixpath |
| 22 import string |
| 23 import sys |
| 24 |
| 25 |
| 26 def CMakeStringEscape(a): |
| 27 """Escapes the string 'a' for use inside a CMake string. |
| 28 |
| 29 This means escaping |
| 30 '\' otherwise it may be seen as modifying the next character |
| 31 '"' otherwise it will end the string |
| 32 ';' otherwise the string becomes a list |
| 33 |
| 34 The following do not need to be escaped |
| 35 '#' when the lexer is in string state, this does not start a comment |
| 36 """ |
| 37 return a.replace('\\', '\\\\').replace(';', '\\;').replace('"', '\\"') |
| 38 |
| 39 |
| 40 def CMakeTargetEscape(a): |
| 41 """Escapes the string 'a' for use as a CMake target name. |
| 42 |
| 43 CMP0037 in CMake 3.0 restricts target names to "^[A-Za-z0-9_.:+-]+$" |
| 44 The ':' is only allowed for imported targets. |
| 45 """ |
| 46 def Escape(c): |
| 47 if c in string.ascii_letters or c in string.digits or c in '_.+-': |
| 48 return c |
| 49 else: |
| 50 return '__' |
| 51 return ''.join([Escape(c) for c in a]) |
| 52 |
| 53 |
| 54 def SetVariable(out, variable_name, value): |
| 55 """Sets a CMake variable.""" |
| 56 out.write('set("') |
| 57 out.write(CMakeStringEscape(variable_name)) |
| 58 out.write('" "') |
| 59 out.write(CMakeStringEscape(value)) |
| 60 out.write('")\n') |
| 61 |
| 62 |
| 63 def SetVariableList(out, variable_name, values): |
| 64 """Sets a CMake variable to a list.""" |
| 65 if not values: |
| 66 return SetVariable(out, variable_name, "") |
| 67 if len(values) == 1: |
| 68 return SetVariable(out, variable_name, values[0]) |
| 69 out.write('list(APPEND "') |
| 70 out.write(CMakeStringEscape(variable_name)) |
| 71 out.write('"\n "') |
| 72 out.write('"\n "'.join([CMakeStringEscape(value) for value in values])) |
| 73 out.write('")\n') |
| 74 |
| 75 |
| 76 def SetFilesProperty(output, variable, property_name, values, sep): |
| 77 """Given a set of source files, sets the given property on them.""" |
| 78 output.write('set_source_files_properties(') |
| 79 WriteVariable(output, variable) |
| 80 output.write(' PROPERTIES ') |
| 81 output.write(property_name) |
| 82 output.write(' "') |
| 83 for value in values: |
| 84 output.write(CMakeStringEscape(value)) |
| 85 output.write(sep) |
| 86 output.write('")\n') |
| 87 |
| 88 |
| 89 def SetCurrentTargetProperty(out, property_name, values, sep=''): |
| 90 """Given a target, sets the given property.""" |
| 91 out.write('set_target_properties("${target}" PROPERTIES ') |
| 92 out.write(property_name) |
| 93 out.write(' "') |
| 94 for value in values: |
| 95 out.write(CMakeStringEscape(value)) |
| 96 out.write(sep) |
| 97 out.write('")\n') |
| 98 |
| 99 |
| 100 def WriteVariable(output, variable_name, prepend=None): |
| 101 if prepend: |
| 102 output.write(prepend) |
| 103 output.write('${') |
| 104 output.write(variable_name) |
| 105 output.write('}') |
| 106 |
| 107 |
| 108 # See GetSourceFileType in gn |
| 109 source_file_types = { |
| 110 '.cc': 'cxx', |
| 111 '.cpp': 'cxx', |
| 112 '.cxx': 'cxx', |
| 113 '.c': 'c', |
| 114 '.s': 'asm', |
| 115 '.S': 'asm', |
| 116 '.asm': 'asm', |
| 117 '.o': 'obj', |
| 118 '.obj': 'obj', |
| 119 } |
| 120 |
| 121 |
| 122 class CMakeTargetType(object): |
| 123 def __init__(self, command, modifier, property_modifier, is_linkable): |
| 124 self.command = command |
| 125 self.modifier = modifier |
| 126 self.property_modifier = property_modifier |
| 127 self.is_linkable = is_linkable |
| 128 CMakeTargetType.custom = CMakeTargetType('add_custom_target', 'SOURCES', |
| 129 None, False) |
| 130 |
| 131 # See GetStringForOutputType in gn |
| 132 cmake_target_types = { |
| 133 'unknown': CMakeTargetType.custom, |
| 134 'group': CMakeTargetType.custom, |
| 135 'executable': CMakeTargetType('add_executable', None, 'RUNTIME', True), |
| 136 'loadable_module': CMakeTargetType('add_library', 'MODULE', 'LIBRARY', True), |
| 137 'shared_library': CMakeTargetType('add_library', 'SHARED', 'LIBRARY', True), |
| 138 'static_library': CMakeTargetType('add_library', 'STATIC', 'ARCHIVE', False), |
| 139 'source_set': CMakeTargetType('add_library', 'OBJECT', None, False), |
| 140 'copy': CMakeTargetType.custom, |
| 141 'action': CMakeTargetType.custom, |
| 142 'action_foreach': CMakeTargetType.custom, |
| 143 'bundle_data': CMakeTargetType.custom, |
| 144 'create_bundle': CMakeTargetType.custom, |
| 145 } |
| 146 |
| 147 |
| 148 def FindFirstOf(s, a): |
| 149 return min(s.find(i) for i in a if i in s) |
| 150 |
| 151 |
| 152 def GetCMakeTargetName(gn_target_name): |
| 153 # See <chromium>/src/tools/gn/label.cc#Resolve |
| 154 # //base/test:test_support(//build/toolchain/win:msvc) |
| 155 path_separator = FindFirstOf(gn_target_name, (':', '(')) |
| 156 location = None |
| 157 name = None |
| 158 toolchain = None |
| 159 if not path_separator: |
| 160 location = gn_target_name[2:] |
| 161 else: |
| 162 location = gn_target_name[2:path_separator] |
| 163 toolchain_separator = gn_target_name.find('(', path_separator) |
| 164 if toolchain_separator == -1: |
| 165 name = gn_target_name[path_separator + 1:] |
| 166 else: |
| 167 if toolchain_separator > path_separator: |
| 168 name = gn_target_name[path_separator + 1:toolchain_separator] |
| 169 assert gn_target_name.endswith(')') |
| 170 toolchain = gn_target_name[toolchain_separator + 1:-1] |
| 171 assert location or name |
| 172 |
| 173 cmake_target_name = None |
| 174 if location.endswith('/' + name): |
| 175 cmake_target_name = location |
| 176 elif location: |
| 177 cmake_target_name = location + '_' + name |
| 178 else: |
| 179 cmake_target_name = name |
| 180 if toolchain: |
| 181 cmake_target_name += '--' + toolchain |
| 182 return CMakeTargetEscape(cmake_target_name) |
| 183 |
| 184 |
| 185 class Project(object): |
| 186 def __init__(self, project_json): |
| 187 self.targets = project_json['targets'] |
| 188 build_settings = project_json['build_settings'] |
| 189 self.root_path = build_settings['root_path'] |
| 190 self.build_path = posixpath.join(self.root_path, |
| 191 build_settings['build_dir'][2:]) |
| 192 |
| 193 def GetAbsolutePath(self, path): |
| 194 if path.startswith("//"): |
| 195 return self.root_path + "/" + path[2:] |
| 196 else: |
| 197 return path |
| 198 |
| 199 def GetObjectSourceDependencies(self, gn_target_name, object_dependencies): |
| 200 """All OBJECT libraries whose sources have not been absorbed.""" |
| 201 dependencies = self.targets[gn_target_name].get('deps', []) |
| 202 for dependency in dependencies: |
| 203 dependency_type = self.targets[dependency].get('type', None) |
| 204 if dependency_type == 'source_set': |
| 205 object_dependencies.add(dependency) |
| 206 if dependency_type not in gn_target_types_that_absorb_objects: |
| 207 self.GetObjectSourceDependencies(dependency, object_dependencies) |
| 208 |
| 209 def GetObjectLibraryDependencies(self, gn_target_name, object_dependencies): |
| 210 """All OBJECT libraries whose libraries have not been absorbed.""" |
| 211 dependencies = self.targets[gn_target_name].get('deps', []) |
| 212 for dependency in dependencies: |
| 213 dependency_type = self.targets[dependency].get('type', None) |
| 214 if dependency_type == 'source_set': |
| 215 object_dependencies.add(dependency) |
| 216 self.GetObjectLibraryDependencies(dependency, object_dependencies) |
| 217 |
| 218 |
| 219 class Target(object): |
| 220 def __init__(self, gn_target_name, project): |
| 221 self.gn_name = gn_target_name |
| 222 self.properties = project.targets[self.gn_name] |
| 223 self.cmake_name = GetCMakeTargetName(self.gn_name) |
| 224 self.gn_type = self.properties.get('type', None) |
| 225 self.cmake_type = cmake_target_types.get(self.gn_type, None) |
| 226 |
| 227 |
| 228 def WriteAction(out, target, project, sources, synthetic_dependencies): |
| 229 outputs = [] |
| 230 output_directories = set() |
| 231 for output in target.properties.get('outputs', []): |
| 232 output_abs_path = project.GetAbsolutePath(output) |
| 233 outputs.append(output_abs_path) |
| 234 output_directory = posixpath.dirname(output_abs_path) |
| 235 if output_directory: |
| 236 output_directories.add(output_directory) |
| 237 outputs_name = '${target}__output' |
| 238 SetVariableList(out, outputs_name, outputs) |
| 239 |
| 240 out.write('add_custom_command(OUTPUT ') |
| 241 WriteVariable(out, outputs_name) |
| 242 out.write('\n') |
| 243 |
| 244 if output_directories: |
| 245 out.write(' COMMAND ${CMAKE_COMMAND} -E make_directory "') |
| 246 out.write('" "'.join([CMakeStringEscape(d) for d in output_directories])) |
| 247 out.write('"\n') |
| 248 |
| 249 script = target.properties['script'] |
| 250 arguments = target.properties['args'] |
| 251 out.write(' COMMAND python "') |
| 252 out.write(CMakeStringEscape(project.GetAbsolutePath(script))) |
| 253 out.write('"') |
| 254 if arguments: |
| 255 out.write('\n "') |
| 256 out.write('"\n "'.join([CMakeStringEscape(a) for a in arguments])) |
| 257 out.write('"') |
| 258 out.write('\n') |
| 259 |
| 260 out.write(' DEPENDS ') |
| 261 for sources_type_name in sources.values(): |
| 262 WriteVariable(out, sources_type_name, ' ') |
| 263 out.write('\n') |
| 264 |
| 265 #TODO: CMake 3.7 is introducing DEPFILE |
| 266 |
| 267 out.write(' WORKING_DIRECTORY "') |
| 268 out.write(CMakeStringEscape(project.build_path)) |
| 269 out.write('"\n') |
| 270 |
| 271 out.write(' COMMENT "Action: ${target}"\n') |
| 272 |
| 273 out.write(' VERBATIM)\n') |
| 274 |
| 275 synthetic_dependencies.add(outputs_name) |
| 276 |
| 277 |
| 278 def ExpandPlaceholders(source, a): |
| 279 source_dir, source_file_part = posixpath.split(source) |
| 280 source_name_part, _ = posixpath.splitext(source_file_part) |
| 281 #TODO: {{source_gen_dir}}, {{source_out_dir}}, {{response_file_name}} |
| 282 return a.replace('{{source}}', source) \ |
| 283 .replace('{{source_file_part}}', source_file_part) \ |
| 284 .replace('{{source_name_part}}', source_name_part) \ |
| 285 .replace('{{source_dir}}', source_dir) \ |
| 286 .replace('{{source_root_relative_dir}}', source_dir) |
| 287 |
| 288 |
| 289 def WriteActionForEach(out, target, project, sources, synthetic_dependencies): |
| 290 all_outputs = target.properties.get('outputs', []) |
| 291 inputs = target.properties.get('sources', []) |
| 292 # TODO: consider expanding 'output_patterns' instead. |
| 293 outputs_per_input = len(all_outputs) / len(inputs) |
| 294 for count, source in enumerate(inputs): |
| 295 source_abs_path = project.GetAbsolutePath(source) |
| 296 |
| 297 outputs = [] |
| 298 output_directories = set() |
| 299 for output in all_outputs[outputs_per_input * count: |
| 300 outputs_per_input * (count+1)]: |
| 301 output_abs_path = project.GetAbsolutePath(output) |
| 302 outputs.append(output_abs_path) |
| 303 output_directory = posixpath.dirname(output_abs_path) |
| 304 if output_directory: |
| 305 output_directories.add(output_directory) |
| 306 outputs_name = '${target}__output_' + str(count) |
| 307 SetVariableList(out, outputs_name, outputs) |
| 308 |
| 309 out.write('add_custom_command(OUTPUT ') |
| 310 WriteVariable(out, outputs_name) |
| 311 out.write('\n') |
| 312 |
| 313 if output_directories: |
| 314 out.write(' COMMAND ${CMAKE_COMMAND} -E make_directory "') |
| 315 out.write('" "'.join([CMakeStringEscape(d) for d in output_directories])) |
| 316 out.write('"\n') |
| 317 |
| 318 script = target.properties['script'] |
| 319 # TODO: need to expand {{xxx}} in arguments |
| 320 arguments = target.properties['args'] |
| 321 out.write(' COMMAND python "') |
| 322 out.write(CMakeStringEscape(project.GetAbsolutePath(script))) |
| 323 out.write('"') |
| 324 if arguments: |
| 325 out.write('\n "') |
| 326 expand = functools.partial(ExpandPlaceholders, source_abs_path) |
| 327 out.write('"\n "'.join( |
| 328 [CMakeStringEscape(expand(a)) for a in arguments])) |
| 329 out.write('"') |
| 330 out.write('\n') |
| 331 |
| 332 out.write(' DEPENDS') |
| 333 if 'input' in sources: |
| 334 WriteVariable(out, sources['input'], ' ') |
| 335 out.write(' "') |
| 336 out.write(CMakeStringEscape(source_abs_path)) |
| 337 out.write('"\n') |
| 338 |
| 339 #TODO: CMake 3.7 is introducing DEPFILE |
| 340 |
| 341 out.write(' WORKING_DIRECTORY "') |
| 342 out.write(CMakeStringEscape(project.build_path)) |
| 343 out.write('"\n') |
| 344 |
| 345 out.write(' COMMENT "Action ${target} on ') |
| 346 out.write(CMakeStringEscape(source_abs_path)) |
| 347 out.write('"\n') |
| 348 |
| 349 out.write(' VERBATIM)\n') |
| 350 |
| 351 synthetic_dependencies.add(outputs_name) |
| 352 |
| 353 |
| 354 def WriteCopy(out, target, project, sources, synthetic_dependencies): |
| 355 inputs = target.properties.get('sources', []) |
| 356 raw_outputs = target.properties.get('outputs', []) |
| 357 |
| 358 # TODO: consider expanding 'output_patterns' instead. |
| 359 outputs = [] |
| 360 for output in raw_outputs: |
| 361 output_abs_path = project.GetAbsolutePath(output) |
| 362 outputs.append(output_abs_path) |
| 363 outputs_name = '${target}__output' |
| 364 SetVariableList(out, outputs_name, outputs) |
| 365 |
| 366 out.write('add_custom_command(OUTPUT ') |
| 367 WriteVariable(out, outputs_name) |
| 368 out.write('\n') |
| 369 |
| 370 for src, dst in zip(inputs, outputs): |
| 371 out.write(' COMMAND ${CMAKE_COMMAND} -E copy "') |
| 372 out.write(CMakeStringEscape(project.GetAbsolutePath(src))) |
| 373 out.write('" "') |
| 374 out.write(CMakeStringEscape(dst)) |
| 375 out.write('"\n') |
| 376 |
| 377 out.write(' DEPENDS ') |
| 378 for sources_type_name in sources.values(): |
| 379 WriteVariable(out, sources_type_name, ' ') |
| 380 out.write('\n') |
| 381 |
| 382 out.write(' WORKING_DIRECTORY "') |
| 383 out.write(CMakeStringEscape(project.build_path)) |
| 384 out.write('"\n') |
| 385 |
| 386 out.write(' COMMENT "Copy ${target}"\n') |
| 387 |
| 388 out.write(' VERBATIM)\n') |
| 389 |
| 390 synthetic_dependencies.add(outputs_name) |
| 391 |
| 392 |
| 393 def WriteCompilerFlags(out, target, project, sources): |
| 394 # Hack, set linker language to c if no c or cxx files present. |
| 395 if not 'c' in sources and not 'cxx' in sources: |
| 396 SetCurrentTargetProperty(out, 'LINKER_LANGUAGE', ['C']) |
| 397 |
| 398 # Mark uncompiled sources as uncompiled. |
| 399 if 'input' in sources: |
| 400 SetFilesProperty(out, sources['input'], 'HEADER_FILE_ONLY', ('True',), '') |
| 401 if 'other' in sources: |
| 402 SetFilesProperty(out, sources['other'], 'HEADER_FILE_ONLY', ('True',), '') |
| 403 |
| 404 # Mark object sources as linkable. |
| 405 if 'obj' in sources: |
| 406 SetFilesProperty(out, sources['obj'], 'EXTERNAL_OBJECT', ('True',), '') |
| 407 |
| 408 # TODO: 'output_name', 'output_dir', 'output_extension' |
| 409 # This includes using 'source_outputs' to direct compiler output. |
| 410 |
| 411 # Includes |
| 412 includes = target.properties.get('include_dirs', []) |
| 413 if includes: |
| 414 out.write('set_property(TARGET "${target}" ') |
| 415 out.write('APPEND PROPERTY INCLUDE_DIRECTORIES') |
| 416 for include_dir in includes: |
| 417 out.write('\n "') |
| 418 out.write(project.GetAbsolutePath(include_dir)) |
| 419 out.write('"') |
| 420 out.write(')\n') |
| 421 |
| 422 # Defines |
| 423 defines = target.properties.get('defines', []) |
| 424 if defines: |
| 425 SetCurrentTargetProperty(out, 'COMPILE_DEFINITIONS', defines, ';') |
| 426 |
| 427 # Compile flags |
| 428 # "arflags", "asmflags", "cflags", |
| 429 # "cflags_c", "clfags_cc", "cflags_objc", "clfags_objcc" |
| 430 # CMake does not have per target lang compile flags. |
| 431 # TODO: $<$<COMPILE_LANGUAGE:CXX>:cflags_cc style generator expression. |
| 432 # http://public.kitware.com/Bug/view.php?id=14857 |
| 433 flags = [] |
| 434 flags.extend(target.properties.get('cflags', [])) |
| 435 cflags_asm = target.properties.get('asmflags', []) |
| 436 cflags_c = target.properties.get('cflags_c', []) |
| 437 cflags_cxx = target.properties.get('cflags_cc', []) |
| 438 if 'c' in sources and not any(k in sources for k in ('asm', 'cxx')): |
| 439 flags.extend(cflags_c) |
| 440 elif 'cxx' in sources and not any(k in sources for k in ('asm', 'c')): |
| 441 flags.extend(cflags_cxx) |
| 442 else: |
| 443 # TODO: This is broken, one cannot generally set properties on files, |
| 444 # as other targets may require different properties on the same files. |
| 445 if 'asm' in sources and cflags_asm: |
| 446 SetFilesProperty(out, sources['asm'], 'COMPILE_FLAGS', cflags_asm, ' ') |
| 447 if 'c' in sources and cflags_c: |
| 448 SetFilesProperty(out, sources['c'], 'COMPILE_FLAGS', cflags_c, ' ') |
| 449 if 'cxx' in sources and cflags_cxx: |
| 450 SetFilesProperty(out, sources['cxx'], 'COMPILE_FLAGS', cflags_cxx, ' ') |
| 451 if flags: |
| 452 SetCurrentTargetProperty(out, 'COMPILE_FLAGS', flags, ' ') |
| 453 |
| 454 # Linker flags |
| 455 ldflags = target.properties.get('ldflags', []) |
| 456 if ldflags: |
| 457 SetCurrentTargetProperty(out, 'LINK_FLAGS', ldflags, ' ') |
| 458 |
| 459 |
| 460 gn_target_types_that_absorb_objects = ( |
| 461 'executable', |
| 462 'loadable_module', |
| 463 'shared_library', |
| 464 'static_library' |
| 465 ) |
| 466 |
| 467 |
| 468 def WriteSourceVariables(out, target, project): |
| 469 # gn separates the sheep from the goats based on file extensions. |
| 470 # A full separation is done here because of flag handing (see Compile flags). |
| 471 source_types = {'cxx':[], 'c':[], 'asm':[], |
| 472 'obj':[], 'obj_target':[], 'input':[], 'other':[]} |
| 473 |
| 474 # TODO .def files on Windows |
| 475 for source in target.properties.get('sources', []): |
| 476 _, ext = posixpath.splitext(source) |
| 477 source_abs_path = project.GetAbsolutePath(source) |
| 478 source_types[source_file_types.get(ext, 'other')].append(source_abs_path) |
| 479 |
| 480 for input_path in target.properties.get('inputs', []): |
| 481 input_abs_path = project.GetAbsolutePath(input_path) |
| 482 source_types['input'].append(input_abs_path) |
| 483 |
| 484 # OBJECT library dependencies need to be listed as sources. |
| 485 # Only executables and non-OBJECT libraries may reference an OBJECT library. |
| 486 # https://gitlab.kitware.com/cmake/cmake/issues/14778 |
| 487 if target.gn_type in gn_target_types_that_absorb_objects: |
| 488 object_dependencies = set() |
| 489 project.GetObjectSourceDependencies(target.gn_name, object_dependencies) |
| 490 for dependency in object_dependencies: |
| 491 cmake_dependency_name = GetCMakeTargetName(dependency) |
| 492 obj_target_sources = '$<TARGET_OBJECTS:' + cmake_dependency_name + '>' |
| 493 source_types['obj_target'].append(obj_target_sources) |
| 494 |
| 495 sources = {} |
| 496 for source_type, sources_of_type in source_types.items(): |
| 497 if sources_of_type: |
| 498 sources[source_type] = '${target}__' + source_type + '_srcs' |
| 499 SetVariableList(out, sources[source_type], sources_of_type) |
| 500 return sources |
| 501 |
| 502 |
| 503 def WriteTarget(out, target, project): |
| 504 out.write('\n#') |
| 505 out.write(target.gn_name) |
| 506 out.write('\n') |
| 507 |
| 508 if target.cmake_type is None: |
| 509 print 'Target {} has unknown target type {}, skipping.'.format( |
| 510 target.gn_name, target.gn_type) |
| 511 return |
| 512 |
| 513 SetVariable(out, 'target', target.cmake_name) |
| 514 |
| 515 sources = WriteSourceVariables(out, target, project) |
| 516 |
| 517 synthetic_dependencies = set() |
| 518 if target.gn_type == 'action': |
| 519 WriteAction(out, target, project, sources, synthetic_dependencies) |
| 520 if target.gn_type == 'action_foreach': |
| 521 WriteActionForEach(out, target, project, sources, synthetic_dependencies) |
| 522 if target.gn_type == 'copy': |
| 523 WriteCopy(out, target, project, sources, synthetic_dependencies) |
| 524 |
| 525 out.write(target.cmake_type.command) |
| 526 out.write('("${target}"') |
| 527 if target.cmake_type.modifier is not None: |
| 528 out.write(' ') |
| 529 out.write(target.cmake_type.modifier) |
| 530 for sources_type_name in sources.values(): |
| 531 WriteVariable(out, sources_type_name, ' ') |
| 532 if synthetic_dependencies: |
| 533 out.write(' DEPENDS') |
| 534 for synthetic_dependencie in synthetic_dependencies: |
| 535 WriteVariable(out, synthetic_dependencie, ' ') |
| 536 out.write(')\n') |
| 537 |
| 538 if target.cmake_type.command != 'add_custom_target': |
| 539 WriteCompilerFlags(out, target, project, sources) |
| 540 |
| 541 libraries = set() |
| 542 nonlibraries = set() |
| 543 |
| 544 dependencies = set(target.properties.get('deps', [])) |
| 545 # Transitive OBJECT libraries are in sources. |
| 546 # Those sources are dependent on the OBJECT library dependencies. |
| 547 # Those sources cannot bring in library dependencies. |
| 548 object_dependencies = set() |
| 549 if target.gn_type != 'source_set': |
| 550 project.GetObjectLibraryDependencies(target.gn_name, object_dependencies) |
| 551 for object_dependency in object_dependencies: |
| 552 dependencies.update(project.targets.get(object_dependency).get('deps', [])) |
| 553 |
| 554 for dependency in dependencies: |
| 555 gn_dependency_type = project.targets.get(dependency, {}).get('type', None) |
| 556 cmake_dependency_type = cmake_target_types.get(gn_dependency_type, None) |
| 557 cmake_dependency_name = GetCMakeTargetName(dependency) |
| 558 if cmake_dependency_type.command != 'add_library': |
| 559 nonlibraries.add(cmake_dependency_name) |
| 560 elif cmake_dependency_type.modifier != 'OBJECT': |
| 561 if target.cmake_type.is_linkable: |
| 562 libraries.add(cmake_dependency_name) |
| 563 else: |
| 564 nonlibraries.add(cmake_dependency_name) |
| 565 |
| 566 # Non-library dependencies. |
| 567 if nonlibraries: |
| 568 out.write('add_dependencies("${target}"') |
| 569 for nonlibrary in nonlibraries: |
| 570 out.write('\n "') |
| 571 out.write(nonlibrary) |
| 572 out.write('"') |
| 573 out.write(')\n') |
| 574 |
| 575 # Non-OBJECT library dependencies. |
| 576 external_libraries = target.properties.get('libs', []) |
| 577 if target.cmake_type.is_linkable and (external_libraries or libraries): |
| 578 library_dirs = target.properties.get('lib_dirs', []) |
| 579 if library_dirs: |
| 580 SetVariableList(out, '${target}__library_directories', library_dirs) |
| 581 |
| 582 system_libraries = [] |
| 583 for external_library in external_libraries: |
| 584 if '/' in external_library: |
| 585 libraries.add(project.GetAbsolutePath(external_library)) |
| 586 else: |
| 587 if external_library.endswith('.framework'): |
| 588 external_library = external_library[:-len('.framework')] |
| 589 system_library = 'library__' + external_library |
| 590 if library_dirs: |
| 591 system_library = system_library + '__for_${target}' |
| 592 out.write('find_library("') |
| 593 out.write(CMakeStringEscape(system_library)) |
| 594 out.write('" "') |
| 595 out.write(CMakeStringEscape(external_library)) |
| 596 out.write('"') |
| 597 if library_dirs: |
| 598 out.write(' PATHS "') |
| 599 WriteVariable(out, '${target}__library_directories') |
| 600 out.write('"') |
| 601 out.write(')\n') |
| 602 system_libraries.append(system_library) |
| 603 out.write('target_link_libraries("${target}"') |
| 604 for library in libraries: |
| 605 out.write('\n "') |
| 606 out.write(CMakeStringEscape(library)) |
| 607 out.write('"') |
| 608 for system_library in system_libraries: |
| 609 WriteVariable(out, system_library, '\n "') |
| 610 out.write('"') |
| 611 out.write(')\n') |
| 612 |
| 613 |
| 614 def WriteProject(project): |
| 615 out = open(posixpath.join(project.build_path, 'CMakeLists.txt'), 'w+') |
| 616 out.write('# Generated by gn_to_cmake.py.\n') |
| 617 out.write('cmake_minimum_required(VERSION 2.8.8 FATAL_ERROR)\n') |
| 618 out.write('cmake_policy(VERSION 2.8.8)\n\n') |
| 619 |
| 620 # Update the gn generated ninja build. |
| 621 # If a build file has changed, this will update CMakeLists.ext if |
| 622 # gn gen out/config --ide=json --json-ide-script=../../gn/gn_to_cmake.py |
| 623 # style was used to create this config. |
| 624 out.write('execute_process(COMMAND ninja -C "') |
| 625 out.write(CMakeStringEscape(project.build_path)) |
| 626 out.write('" build.ninja)\n') |
| 627 |
| 628 out.write('include(CMakeLists.ext)\n') |
| 629 out.close() |
| 630 |
| 631 out = open(posixpath.join(project.build_path, 'CMakeLists.ext'), 'w+') |
| 632 out.write('# Generated by gn_to_cmake.py.\n') |
| 633 out.write('cmake_minimum_required(VERSION 2.8.8 FATAL_ERROR)\n') |
| 634 out.write('cmake_policy(VERSION 2.8.8)\n') |
| 635 |
| 636 # The following appears to be as-yet undocumented. |
| 637 # http://public.kitware.com/Bug/view.php?id=8392 |
| 638 out.write('enable_language(ASM)\n\n') |
| 639 # ASM-ATT does not support .S files. |
| 640 # output.write('enable_language(ASM-ATT)\n') |
| 641 |
| 642 # Current issues with automatic re-generation: |
| 643 # The gn generated build.ninja target uses build.ninja.d |
| 644 # but build.ninja.d does not contain the ide or gn. |
| 645 # Currently the ide is not run if the project.json file is not changed |
| 646 # but the ide needs to be run anyway if it has itself changed. |
| 647 # This can be worked around by deleting the project.json file. |
| 648 out.write('file(READ "') |
| 649 gn_deps_file = posixpath.join(project.build_path, 'build.ninja.d') |
| 650 out.write(CMakeStringEscape(gn_deps_file)) |
| 651 out.write('" "gn_deps_string" OFFSET ') |
| 652 out.write(str(len('build.ninja: '))) |
| 653 out.write(')\n') |
| 654 # One would think this would need to worry about escaped spaces |
| 655 # but gn doesn't escape spaces here (it generates invalid .d files). |
| 656 out.write('string(REPLACE " " ";" "gn_deps" ${gn_deps_string})\n') |
| 657 out.write('foreach("gn_dep" ${gn_deps})\n') |
| 658 out.write(' configure_file(${gn_dep} "CMakeLists.devnull" COPYONLY)\n') |
| 659 out.write('endforeach("gn_dep")\n') |
| 660 |
| 661 for target_name in project.targets.keys(): |
| 662 out.write('\n') |
| 663 WriteTarget(out, Target(target_name, project), project) |
| 664 |
| 665 |
| 666 def main(): |
| 667 if len(sys.argv) != 2: |
| 668 print 'Usage: ' + sys.argv[0] + ' <json_file_name>' |
| 669 exit(1) |
| 670 |
| 671 json_path = sys.argv[1] |
| 672 project = None |
| 673 with open(json_path, 'r') as json_file: |
| 674 project = json.loads(json_file.read()) |
| 675 |
| 676 WriteProject(Project(project)) |
| 677 |
| 678 |
| 679 if __name__ == "__main__": |
| 680 main() |
OLD | NEW |