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