1#!/usr/bin/env python3 2# 3# Copyright 2020 the V8 project authors. All rights reserved. 4# Use of this source code is governed by a BSD-style license that can be 5# found in the LICENSE file. 6 7import sys 8import lark 9import argparse 10from contextlib import suppress 11from collections import namedtuple 12from datetime import datetime 13 14# GN grammar from https://gn.googlesource.com/gn/+/master/src/gn/parser.cc. 15GN_GRAMMAR = """ 16 ?file : statement_list 17 18 ?statement : assignment | call | condition 19 ?lvalue : IDENTIFIER | array_access | scope_access 20 assignment : lvalue assign_op expr 21 call : IDENTIFIER "(" [ expr_list ] ")" [ block ] 22 condition : "if" "(" expr ")" block [ "else" ( condition | block ) ] 23 ?block : "{" statement_list "}" 24 statement_list : statement* 25 26 array_access : IDENTIFIER "[" expr "]" 27 scope_access : IDENTIFIER "." IDENTIFIER 28 ?primary_expr : IDENTIFIER | INTEGER | STRING | call 29 | array_access | scope_access | block 30 | "(" expr ")" -> par_expr 31 | array 32 array : "[" [ expr ( "," expr? )* ] "]" 33 expr_list : expr ( "," expr )* 34 35 ?assign_op : "=" -> asgn_op 36 | "+=" -> asgn_add_op 37 | "-=" -> asgn_sub_op 38 39 ?expr : expr1 40 ?expr1 : expr1 "||" expr2 -> or 41 | expr2 42 ?expr2 : expr2 "&&" expr3 -> and 43 | expr3 44 ?expr3 : expr3 "==" expr4 -> eq 45 | expr3 "!=" expr4 -> ne 46 | expr4 47 ?expr4 : expr4 "<=" expr5 -> le 48 | expr4 "<" expr5 -> lt 49 | expr4 ">=" expr5 -> ge 50 | expr4 ">" expr5 -> gt 51 | expr5 52 ?expr5 : expr5 "+" expr6 -> add 53 | expr5 "-" expr6 -> sub 54 | expr6 55 ?expr6 : "!" primary_expr -> neg 56 | primary_expr 57 58 COMMENT : /#.*/ 59 60 %import common.ESCAPED_STRING -> STRING 61 %import common.SIGNED_INT -> INTEGER 62 %import common.CNAME -> IDENTIFIER 63 %import common.WS 64 %ignore WS 65 %ignore COMMENT 66""" 67 68V8_TARGET_TYPES = ( 69 'v8_component', 70 'v8_source_set', 71 'v8_executable', 72) 73 74OPS = ( 75 'neg', 76 'eq', 77 'ne', 78 'le', 79 'lt', 80 'ge', 81 'gt', 82 'and', 83 'or', 84) 85 86 87class UnsupportedOperation(Exception): 88 pass 89 90 91class V8GNTransformer(object): 92 """ 93 Traverse GN parse-tree and build resulting object. 94 """ 95 def __init__(self, builder, filtered_targets): 96 self.builder = builder 97 self.filtered_targets = filtered_targets 98 self.current_target = None 99 100 def Traverse(self, tree): 101 self.builder.BuildPrologue() 102 self.TraverseTargets(tree) 103 self.builder.BuildEpilogue() 104 105 def TraverseTargets(self, tree): 106 'Traverse top level GN targets and call the builder functions' 107 for stmt in tree.children: 108 if stmt.data != 'call': 109 continue 110 target_type = stmt.children[0] 111 if target_type not in V8_TARGET_TYPES: 112 continue 113 target = stmt.children[1].children[0].strip('\"') 114 if target not in self.filtered_targets: 115 continue 116 self.current_target = target 117 self._Target(target_type, target, stmt.children[2].children) 118 119 def _Target(self, target_type, target, stmts): 120 stmts = self._StatementList(stmts) 121 return self.builder.BuildTarget(target_type, target, stmts) 122 123 def _StatementList(self, stmts): 124 built_stmts = [] 125 for stmt in stmts: 126 built_stmts.append(self._Statement(stmt)) 127 return [stmt for stmt in built_stmts if stmt] 128 129 def _Statement(self, stmt): 130 # Handle only interesting gn statements. 131 with suppress(KeyError): 132 return self.STATEMENTS[stmt.data](self, *stmt.children) 133 134 def _Assignment(self, left, op, right): 135 return self.ASSIGN_TYPES[op.data](self, left, right) 136 137 def _AssignEq(self, left, right): 138 if left == 'sources': 139 return self.builder.BuildSourcesList( 140 self.current_target, [str(token) for token in right.children]) 141 142 def _AssignAdd(self, left, right): 143 if left == 'sources': 144 return self.builder.BuildAppendSources( 145 self.current_target, [str(token) for token in right.children]) 146 147 def _AssignSub(self, left, right): 148 if left == 'sources': 149 return self.builder.BuildRemoveSources( 150 self.current_target, [str(token) for token in right.children]) 151 152 def _Condition(self, cond_expr, then_stmts, else_stmts=None): 153 'Visit GN condition: if (cond) {then_stmts} else {else_stmts}' 154 cond_expr = self._Expr(cond_expr) 155 then_stmts = self._StatementList(then_stmts.children) 156 if not then_stmts: 157 # Ignore conditions with empty then stmts. 158 return 159 if else_stmts is None: 160 return self.builder.BuildCondition(cond_expr, then_stmts) 161 elif else_stmts.data == 'condition': 162 else_cond = self._Condition(*else_stmts.children) 163 return self.builder.BuildConditionWithElseCond( 164 cond_expr, then_stmts, else_cond) 165 else: 166 assert 'statement_list' == else_stmts.data 167 else_stmts = self._StatementList(else_stmts.children) 168 return self.builder.BuildConditionWithElseStmts( 169 cond_expr, then_stmts, else_stmts) 170 171 def _Expr(self, expr): 172 'Post-order traverse expression trees' 173 if isinstance(expr, lark.Token): 174 if expr.type == 'IDENTIFIER': 175 return self.builder.BuildIdentifier(str(expr)) 176 elif expr.type == 'INTEGER': 177 return self.builder.BuildInteger(str(expr)) 178 else: 179 return self.builder.BuildString(str(expr)) 180 if expr.data == 'par_expr': 181 return self.builder.BuildParenthesizedOperation( 182 self._Expr(*expr.children)) 183 if expr.data not in OPS: 184 raise UnsupportedOperation( 185 f'The operator "{expr.data}" is not supported') 186 if len(expr.children) == 1: 187 return self._UnaryExpr(expr.data, *expr.children) 188 if len(expr.children) == 2: 189 return self._BinaryExpr(expr.data, *expr.children) 190 raise UnsupportedOperation(f'Unsupported arity {len(expr.children)}') 191 192 def _UnaryExpr(self, op, right): 193 right = self._Expr(right) 194 return self.builder.BuildUnaryOperation(op, right) 195 196 def _BinaryExpr(self, op, left, right): 197 left = self._Expr(left) 198 right = self._Expr(right) 199 return self.builder.BuildBinaryOperation(left, op, right) 200 201 STATEMENTS = { 202 'assignment': _Assignment, 203 'condition': _Condition, 204 } 205 206 ASSIGN_TYPES = { 207 'asgn_op': _AssignEq, 208 'asgn_add_op': _AssignAdd, 209 'asgn_sub_op': _AssignSub, 210 } 211 212 213TARGETS = { 214 'v8_libbase': 'lib', 215 'v8_cppgc_shared': 'lib', 216 'cppgc_base': 'lib', 217 'cppgc_standalone': 'sample', 218 'cppgc_unittests_sources': 'tests', 219 'cppgc_unittests': 'tests', 220} 221 222 223class CMakeBuilder(object): 224 """ 225 Builder that produces the main CMakeLists.txt. 226 """ 227 def __init__(self): 228 self.result = [] 229 self.source_sets = {} 230 231 def BuildPrologue(self): 232 self.result.append(f""" 233# Copyright {datetime.now().year} the V8 project authors. All rights reserved. 234# Use of this source code is governed by a BSD-style license that can be 235# found in the LICENSE file. 236# 237# This file is automatically generated by {__file__}. Do NOT edit it. 238 239cmake_minimum_required(VERSION 3.11) 240project(cppgc CXX) 241 242set(CMAKE_CXX_STANDARD 14) 243set(CMAKE_CXX_STANDARD_REQUIRED ON) 244 245option(CPPGC_ENABLE_OBJECT_NAMES "Enable object names in cppgc for debug purposes" OFF) 246option(CPPGC_ENABLE_CAGED_HEAP "Enable heap reservation of size 4GB, only possible for 64bit archs" OFF) 247option(CPPGC_ENABLE_VERIFY_HEAP "Enables additional heap verification phases and checks" OFF) 248option(CPPGC_ENABLE_YOUNG_GENERATION "Enable young generation in cppgc" OFF) 249set(CPPGC_TARGET_ARCH "x64" CACHE STRING "Target architecture, possible options: x64, x86, arm, arm64, ppc64, s390x, mipsel, mips64el") 250 251set(IS_POSIX ${{UNIX}}) 252set(IS_MAC ${{APPLE}}) 253set(IS_WIN ${{WIN32}}) 254if("${{CMAKE_SYSTEM_NAME}}" STREQUAL "Linux") 255 set(IS_LINUX 1) 256elseif("${{CMAKE_SYSTEM_NAME}}" STREQUAL "Fuchsia") 257 set(IS_FUCHSIA 1) 258endif() 259 260set(CURRENT_CPU ${{CPPGC_TARGET_ARCH}}) 261 262if("${{CPPGC_TARGET_ARCH}}" STREQUAL "x64" OR 263 "${{CPPGC_TARGET_ARCH}}" STREQUAL "arm64" OR 264 "${{CPPGC_TARGET_ARCH}}" STREQUAL "ppc64" OR 265 "${{CPPGC_TARGET_ARCH}}" STREQUAL "mips64el") 266 if(NOT CMAKE_SIZEOF_VOID_P EQUAL 8) 267 message(FATAL_ERROR "64-bit arch specified for 32-bit compiler") 268 endif() 269 set(CPPGC_64_BITS ON) 270endif() 271 272if(CPPGC_ENABLE_CAGED_HEAP AND NOT CPPGC_64_BITS) 273 message(FATAL_ERROR "Caged heap is only supported for 64bit archs") 274endif() 275 276if(CPPGC_64_BITS) 277 # Always enable caged heap for 64bits archs. 278 set(CPPGC_ENABLE_CAGED_HEAP ON CACHE BOOL "Enable caged heap for 64bit" FORCE) 279endif() 280 281if(CPPGC_ENABLE_YOUNG_GENERATION AND NOT CPPGC_ENABLE_CAGED_HEAP) 282 message(FATAL_ERROR "Young generation is only supported for caged heap configuration") 283endif() 284 285if(NOT CPPGC_64_BITS) 286 if(NOT MSVC) 287 set(CMAKE_CXX_FLAGS "${{CMAKE_CXX_FLAGS}} -m32") 288 set(CMAKE_C_FLAGS "${{CMAKE_C_FLAGS}} -m32") 289 set(CMAKE_EXE_LINKER_FLAGS "${{CMAKE_EXE_LINKER_FLAGS}} -m32") 290 set(CMAKE_SHARED_LINKER_FLAGS "${{CMAKE_SHARED_LINKER_FLAGS}} -m32") 291 set(CMAKE_MODULE_LINKER_FLAGS "${{CMAKE_MODULE_LINKER_FLAGS}} -m32") 292 endif() 293endif() 294 295find_package(Threads REQUIRED) 296 297include(FetchContent) 298FetchContent_Declare( 299 googletest 300 GIT_REPOSITORY "https://chromium.googlesource.com/external/github.com/google/googletest.git" 301 GIT_TAG "4fe018038f87675c083d0cfb6a6b57c274fb1753" 302 SOURCE_DIR "${{CMAKE_BINARY_DIR}}/third_party/googletest/src" 303) 304 305FetchContent_GetProperties(googletest) 306if(NOT googletest_POPULATED) 307 FetchContent_Populate(googletest) 308 message("Fetched googletest into ${{googletest_SOURCE_DIR}}") 309 add_subdirectory(${{googletest_SOURCE_DIR}} ${{googletest_BINARY_DIR}} EXCLUDE_FROM_ALL) 310 include_directories("${{CMAKE_BINARY_DIR}}") 311endif() 312""") 313 314 def BuildEpilogue(self): 315 self.result.extend( 316 self._GenTargetString(target, sets) 317 for target, sets in self.source_sets.items()) 318 self.result.append("\ninstall(TARGETS cppgc)") 319 320 def BuildTarget(self, target_type, target, rules): 321 # Don't generate CMake targets yet, defer it to build_epilogue. 322 comment = f""" 323#=============================================================================== 324# {self._CMakeTarget(target)} sources. 325#===============================================================================""" 326 self.result.append(comment) 327 self.result.extend(rules) 328 self.source_sets.setdefault( 329 TARGETS[target], []).append('${' + self._SourceVar(target) + '}') 330 331 def BuildSourcesList(self, target, sources): 332 sources = self._ExpandSources(target, sources) 333 return f'set({self._SourceVar(target)} {sources})' 334 335 def BuildAppendSources(self, target, sources): 336 sources = self._ExpandSources(target, sources) 337 return f'list(APPEND {self._SourceVar(target)} {sources})' 338 339 def BuildRemoveSources(self, target, sources): 340 sources = self._ExpandSources(target, sources) 341 return f'list(REMOVE_ITEM {self._SourceVar(target)} {sources})' 342 343 def BuildCondition(self, cond, then_stmts): 344 return f""" 345if({cond}) 346 {' '.join(then_stmts)} 347endif() 348 """.strip() 349 350 def BuildConditionWithElseStmts(self, cond, then_stmts, else_stmts): 351 return f""" 352if({cond}) 353 {' '.join(then_stmts)} 354{'else() ' + ' '.join(else_stmts)} 355endif() 356 """.strip() 357 358 def BuildConditionWithElseCond(self, cond, then_stmts, else_cond): 359 return f""" 360if({cond}) 361 {' '.join(then_stmts)} 362else{else_cond} 363 """.strip() 364 365 def BuildParenthesizedOperation(self, operation): 366 return ''.join(['(', operation, ')']) 367 368 def BuildUnaryOperation(self, op, right): 369 OPS = { 370 'neg': 'NOT', 371 } 372 return ' '.join([OPS[op], right]) 373 374 def BuildBinaryOperation(self, left, op, right): 375 if op == 'ne': 376 neg_result = self.BuildBinaryOperation(left, 'eq', right) 377 return self.BuildUnaryOperation('neg', neg_result) 378 OPS = { 379 'eq': 'STREQUAL', 380 'le': 'LESS_EQUAL', 381 'lt': 'LESS', 382 'ge': 'GREATER_EQUAL', 383 'gt': 'GREATER', 384 'and': 'AND', 385 'or': 'OR', 386 } 387 return ' '.join([left, OPS[op], right]) 388 389 def BuildIdentifier(self, token): 390 return self._CMakeVarRef(token) 391 392 def BuildInteger(self, integer): 393 return integer 394 395 def BuildString(self, string): 396 return string 397 398 def GetResult(self): 399 return '\n'.join(self.result) 400 401 @staticmethod 402 def _GenTargetString(target_type, source_sets): 403 Target = namedtuple('Target', 'name cmake deps desc') 404 CMAKE_TARGETS = { 405 'lib': 406 Target(name='cppgc', 407 cmake='add_library', 408 deps=['Threads::Threads'], 409 desc='Main library'), 410 'sample': 411 Target(name='cppgc_hello_world', 412 cmake='add_executable', 413 deps=['cppgc'], 414 desc='Example'), 415 'tests': 416 Target(name='cppgc_unittests', 417 cmake='add_executable', 418 deps=['cppgc', 'gtest', 'gmock'], 419 desc='Unittests') 420 } 421 target = CMAKE_TARGETS[target_type] 422 return f""" 423# {target.desc} target. 424{target.cmake}({target.name} {' '.join(source_sets)}) 425 426{'target_link_libraries(' + target.name + ' ' + ' '.join(target.deps) + ')' if target.deps else ''} 427 428target_include_directories({target.name} PRIVATE "${{CMAKE_SOURCE_DIR}}" 429 PRIVATE "${{CMAKE_SOURCE_DIR}}/include") 430 431if(CPPGC_ENABLE_OBJECT_NAMES) 432 target_compile_definitions({target.name} PRIVATE "-DCPPGC_SUPPORTS_OBJECT_NAMES") 433endif() 434if(CPPGC_ENABLE_CAGED_HEAP) 435 target_compile_definitions({target.name} PRIVATE "-DCPPGC_CAGED_HEAP") 436endif() 437if(CPPGC_ENABLE_VERIFY_HEAP) 438 target_compile_definitions({target.name} PRIVATE "-DCPPGC_ENABLE_VERIFY_HEAP") 439endif() 440if(CPPGC_ENABLE_YOUNG_GENERATION) 441 target_compile_definitions({target.name} PRIVATE "-DCPPGC_YOUNG_GENERATION") 442endif()""" 443 444 @staticmethod 445 def _ExpandSources(target, sources): 446 if TARGETS[target] == 'tests': 447 sources = ['\"test/unittests/' + s[1:] for s in sources] 448 return ' '.join(sources) 449 450 @staticmethod 451 def _SourceVar(target): 452 return CMakeBuilder._CMakeVar(target) + '_SOURCES' 453 454 @staticmethod 455 def _CMakeVar(var): 456 return var.replace('v8_', '').upper() 457 458 @staticmethod 459 def _CMakeTarget(var): 460 return var.replace('v8_', '') 461 462 @staticmethod 463 def _CMakeVarRef(var): 464 return '\"${' + CMakeBuilder._CMakeVar(var) + '}"' 465 466 467def FormatCMake(contents): 468 from cmake_format import configuration, lexer, parse, formatter 469 cfg = configuration.Configuration() 470 tokens = lexer.tokenize(contents) 471 parse_tree = parse.parse(tokens) 472 box_tree = formatter.layout_tree(parse_tree, cfg) 473 return formatter.write_tree(box_tree, cfg, contents) 474 475 476def SaveContents(contents, outfile): 477 if outfile == '-': 478 return print(contents) 479 with open(outfile, 'w+') as ofile: 480 ofile.write(contents) 481 482 483def ParseGN(contents): 484 parser = lark.Lark(GN_GRAMMAR, parser='lalr', start='file') 485 return parser.parse(contents) 486 487 488def ParseGNFile(filename): 489 with open(filename, 'r') as file: 490 contents = file.read() 491 return ParseGN(contents) 492 493 494def GenCMake(main_gn, test_gn, outfile): 495 tree = ParseGNFile(main_gn) 496 tree.children.extend(ParseGNFile(test_gn).children) 497 builder = CMakeBuilder() 498 V8GNTransformer(builder, TARGETS.keys()).Traverse(tree) 499 result = FormatCMake(builder.GetResult()) 500 SaveContents(result, outfile) 501 502 503def Main(): 504 arg_parser = argparse.ArgumentParser( 505 description= 506 'Generate CMake from the main GN file for targets needed to build CppGC.' 507 ) 508 arg_parser.add_argument('--out', help='output CMake filename', default='-') 509 arg_parser.add_argument('--main-gn', 510 help='main BUILD.gn input file', 511 default='BUILD.gn') 512 arg_parser.add_argument('--test-gn', 513 help='unittest BUILD.gn input file', 514 default='test/unittests/BUILD.gn') 515 args = arg_parser.parse_args() 516 517 GenCMake(args.main_gn, args.test_gn, args.out) 518 return 0 519 520 521if __name__ == '__main__': 522 sys.exit(Main()) 523