| #!/usr/bin/env python3 |
| # |
| # Copyright 2020 the V8 project authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| import sys |
| import lark |
| import argparse |
| from contextlib import suppress |
| from collections import namedtuple |
| from datetime import datetime |
| |
| # GN grammar from https://gn.googlesource.com/gn/+/master/src/gn/parser.cc. |
| GN_GRAMMAR = """ |
| ?file : statement_list |
| |
| ?statement : assignment | call | condition |
| ?lvalue : IDENTIFIER | array_access | scope_access |
| assignment : lvalue assign_op expr |
| call : IDENTIFIER "(" [ expr_list ] ")" [ block ] |
| condition : "if" "(" expr ")" block [ "else" ( condition | block ) ] |
| ?block : "{" statement_list "}" |
| statement_list : statement* |
| |
| array_access : IDENTIFIER "[" expr "]" |
| scope_access : IDENTIFIER "." IDENTIFIER |
| ?primary_expr : IDENTIFIER | INTEGER | STRING | call |
| | array_access | scope_access | block |
| | "(" expr ")" -> par_expr |
| | array |
| array : "[" [ expr ( "," expr? )* ] "]" |
| expr_list : expr ( "," expr )* |
| |
| ?assign_op : "=" -> asgn_op |
| | "+=" -> asgn_add_op |
| | "-=" -> asgn_sub_op |
| |
| ?expr : expr1 |
| ?expr1 : expr1 "||" expr2 -> or |
| | expr2 |
| ?expr2 : expr2 "&&" expr3 -> and |
| | expr3 |
| ?expr3 : expr3 "==" expr4 -> eq |
| | expr3 "!=" expr4 -> ne |
| | expr4 |
| ?expr4 : expr4 "<=" expr5 -> le |
| | expr4 "<" expr5 -> lt |
| | expr4 ">=" expr5 -> ge |
| | expr4 ">" expr5 -> gt |
| | expr5 |
| ?expr5 : expr5 "+" expr6 -> add |
| | expr5 "-" expr6 -> sub |
| | expr6 |
| ?expr6 : "!" primary_expr -> neg |
| | primary_expr |
| |
| COMMENT : /#.*/ |
| |
| %import common.ESCAPED_STRING -> STRING |
| %import common.SIGNED_INT -> INTEGER |
| %import common.CNAME -> IDENTIFIER |
| %import common.WS |
| %ignore WS |
| %ignore COMMENT |
| """ |
| |
| V8_TARGET_TYPES = ( |
| 'v8_component', |
| 'v8_source_set', |
| 'v8_executable', |
| ) |
| |
| OPS = ( |
| 'neg', |
| 'eq', |
| 'ne', |
| 'le', |
| 'lt', |
| 'ge', |
| 'gt', |
| 'and', |
| 'or', |
| ) |
| |
| |
| class UnsupportedOperation(Exception): |
| pass |
| |
| |
| class V8GNTransformer(object): |
| """ |
| Traverse GN parse-tree and build resulting object. |
| """ |
| def __init__(self, builder, filtered_targets): |
| self.builder = builder |
| self.filtered_targets = filtered_targets |
| self.current_target = None |
| |
| def Traverse(self, tree): |
| self.builder.BuildPrologue() |
| self.TraverseTargets(tree) |
| self.builder.BuildEpilogue() |
| |
| def TraverseTargets(self, tree): |
| 'Traverse top level GN targets and call the builder functions' |
| for stmt in tree.children: |
| if stmt.data != 'call': |
| continue |
| target_type = stmt.children[0] |
| if target_type not in V8_TARGET_TYPES: |
| continue |
| target = stmt.children[1].children[0].strip('\"') |
| if target not in self.filtered_targets: |
| continue |
| self.current_target = target |
| self._Target(target_type, target, stmt.children[2].children) |
| |
| def _Target(self, target_type, target, stmts): |
| stmts = self._StatementList(stmts) |
| return self.builder.BuildTarget(target_type, target, stmts) |
| |
| def _StatementList(self, stmts): |
| built_stmts = [] |
| for stmt in stmts: |
| built_stmts.append(self._Statement(stmt)) |
| return [stmt for stmt in built_stmts if stmt] |
| |
| def _Statement(self, stmt): |
| # Handle only interesting gn statements. |
| with suppress(KeyError): |
| return self.STATEMENTS[stmt.data](self, *stmt.children) |
| |
| def _Assignment(self, left, op, right): |
| return self.ASSIGN_TYPES[op.data](self, left, right) |
| |
| def _AssignEq(self, left, right): |
| if left == 'sources': |
| return self.builder.BuildSourcesList( |
| self.current_target, [str(token) for token in right.children]) |
| |
| def _AssignAdd(self, left, right): |
| if left == 'sources': |
| return self.builder.BuildAppendSources( |
| self.current_target, [str(token) for token in right.children]) |
| |
| def _AssignSub(self, left, right): |
| if left == 'sources': |
| return self.builder.BuildRemoveSources( |
| self.current_target, [str(token) for token in right.children]) |
| |
| def _Condition(self, cond_expr, then_stmts, else_stmts=None): |
| 'Visit GN condition: if (cond) {then_stmts} else {else_stmts}' |
| cond_expr = self._Expr(cond_expr) |
| then_stmts = self._StatementList(then_stmts.children) |
| if not then_stmts: |
| # Ignore conditions with empty then stmts. |
| return |
| if else_stmts is None: |
| return self.builder.BuildCondition(cond_expr, then_stmts) |
| elif else_stmts.data == 'condition': |
| else_cond = self._Condition(*else_stmts.children) |
| return self.builder.BuildConditionWithElseCond( |
| cond_expr, then_stmts, else_cond) |
| else: |
| assert 'statement_list' == else_stmts.data |
| else_stmts = self._StatementList(else_stmts.children) |
| return self.builder.BuildConditionWithElseStmts( |
| cond_expr, then_stmts, else_stmts) |
| |
| def _Expr(self, expr): |
| 'Post-order traverse expression trees' |
| if isinstance(expr, lark.Token): |
| if expr.type == 'IDENTIFIER': |
| return self.builder.BuildIdentifier(str(expr)) |
| elif expr.type == 'INTEGER': |
| return self.builder.BuildInteger(str(expr)) |
| else: |
| return self.builder.BuildString(str(expr)) |
| if expr.data == 'par_expr': |
| return self.builder.BuildParenthesizedOperation( |
| self._Expr(*expr.children)) |
| if expr.data not in OPS: |
| raise UnsupportedOperation( |
| f'The operator "{expr.data}" is not supported') |
| if len(expr.children) == 1: |
| return self._UnaryExpr(expr.data, *expr.children) |
| if len(expr.children) == 2: |
| return self._BinaryExpr(expr.data, *expr.children) |
| raise UnsupportedOperation(f'Unsupported arity {len(expr.children)}') |
| |
| def _UnaryExpr(self, op, right): |
| right = self._Expr(right) |
| return self.builder.BuildUnaryOperation(op, right) |
| |
| def _BinaryExpr(self, op, left, right): |
| left = self._Expr(left) |
| right = self._Expr(right) |
| return self.builder.BuildBinaryOperation(left, op, right) |
| |
| STATEMENTS = { |
| 'assignment': _Assignment, |
| 'condition': _Condition, |
| } |
| |
| ASSIGN_TYPES = { |
| 'asgn_op': _AssignEq, |
| 'asgn_add_op': _AssignAdd, |
| 'asgn_sub_op': _AssignSub, |
| } |
| |
| |
| TARGETS = { |
| 'v8_libbase': 'lib', |
| 'v8_cppgc_shared': 'lib', |
| 'cppgc_base': 'lib', |
| 'cppgc_standalone': 'sample', |
| 'cppgc_unittests_sources': 'tests', |
| 'cppgc_unittests': 'tests', |
| } |
| |
| |
| class CMakeBuilder(object): |
| """ |
| Builder that produces the main CMakeLists.txt. |
| """ |
| def __init__(self): |
| self.result = [] |
| self.source_sets = {} |
| |
| def BuildPrologue(self): |
| self.result.append(f""" |
| # Copyright {datetime.now().year} the V8 project authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| # |
| # This file is automatically generated by {__file__}. Do NOT edit it. |
| |
| cmake_minimum_required(VERSION 3.11) |
| project(cppgc CXX) |
| |
| set(CMAKE_CXX_STANDARD 14) |
| set(CMAKE_CXX_STANDARD_REQUIRED ON) |
| |
| option(CPPGC_ENABLE_OBJECT_NAMES "Enable object names in cppgc for debug purposes" OFF) |
| option(CPPGC_ENABLE_CAGED_HEAP "Enable heap reservation of size 4GB, only possible for 64bit archs" OFF) |
| option(CPPGC_ENABLE_YOUNG_GENERATION "Enable young generation in cppgc" OFF) |
| set(CPPGC_TARGET_ARCH "x64" CACHE STRING "Target architecture, possible options: x64, x86, arm, arm64, ppc64, s390x, mipsel, mips64el") |
| |
| set(IS_POSIX ${{UNIX}}) |
| set(IS_MAC ${{APPLE}}) |
| set(IS_WIN ${{WIN32}}) |
| if("${{CMAKE_SYSTEM_NAME}}" STREQUAL "Linux") |
| set(IS_LINUX 1) |
| elseif("${{CMAKE_SYSTEM_NAME}}" STREQUAL "Fuchsia") |
| set(IS_FUCHSIA 1) |
| endif() |
| |
| set(CURRENT_CPU ${{CPPGC_TARGET_ARCH}}) |
| |
| if("${{CPPGC_TARGET_ARCH}}" STREQUAL "x64" OR |
| "${{CPPGC_TARGET_ARCH}}" STREQUAL "arm64" OR |
| "${{CPPGC_TARGET_ARCH}}" STREQUAL "ppc64" OR |
| "${{CPPGC_TARGET_ARCH}}" STREQUAL "mips64el") |
| if(NOT CMAKE_SIZEOF_VOID_P EQUAL 8) |
| message(FATAL_ERROR "64-bit arch specified for 32-bit compiler") |
| endif() |
| set(CPPGC_64_BITS ON) |
| endif() |
| |
| if(CPPGC_ENABLE_CAGED_HEAP AND NOT CPPGC_64_BITS) |
| message(FATAL_ERROR "Caged heap is only supported for 64bit archs") |
| endif() |
| |
| if(CPPGC_64_BITS) |
| # Always enable caged heap for 64bits archs. |
| set(CPPGC_ENABLE_CAGED_HEAP ON CACHE BOOL "Enable caged heap for 64bit" FORCE) |
| endif() |
| |
| if(CPPGC_ENABLE_YOUNG_GENERATION AND NOT CPPGC_ENABLE_CAGED_HEAP) |
| message(FATAL_ERROR "Young generation is only supported for caged heap configuration") |
| endif() |
| |
| if(NOT CPPGC_64_BITS) |
| if(NOT MSVC) |
| set(CMAKE_CXX_FLAGS "${{CMAKE_CXX_FLAGS}} -m32") |
| set(CMAKE_C_FLAGS "${{CMAKE_C_FLAGS}} -m32") |
| set(CMAKE_EXE_LINKER_FLAGS "${{CMAKE_EXE_LINKER_FLAGS}} -m32") |
| set(CMAKE_SHARED_LINKER_FLAGS "${{CMAKE_SHARED_LINKER_FLAGS}} -m32") |
| set(CMAKE_MODULE_LINKER_FLAGS "${{CMAKE_MODULE_LINKER_FLAGS}} -m32") |
| endif() |
| endif() |
| |
| find_package(Threads REQUIRED) |
| |
| include(FetchContent) |
| FetchContent_Declare( |
| googletest |
| GIT_REPOSITORY "https://chromium.googlesource.com/external/github.com/google/googletest.git" |
| GIT_TAG "4fe018038f87675c083d0cfb6a6b57c274fb1753" |
| SOURCE_DIR "${{CMAKE_BINARY_DIR}}/third_party/googletest/src" |
| ) |
| |
| FetchContent_GetProperties(googletest) |
| if(NOT googletest_POPULATED) |
| FetchContent_Populate(googletest) |
| message("Fetched googletest into ${{googletest_SOURCE_DIR}}") |
| add_subdirectory(${{googletest_SOURCE_DIR}} ${{googletest_BINARY_DIR}} EXCLUDE_FROM_ALL) |
| include_directories("${{CMAKE_BINARY_DIR}}") |
| endif() |
| """) |
| |
| def BuildEpilogue(self): |
| self.result.extend( |
| self._GenTargetString(target, sets) |
| for target, sets in self.source_sets.items()) |
| self.result.append("\ninstall(TARGETS cppgc)") |
| |
| def BuildTarget(self, target_type, target, rules): |
| # Don't generate CMake targets yet, defer it to build_epilogue. |
| comment = f""" |
| #=============================================================================== |
| # {self._CMakeTarget(target)} sources. |
| #===============================================================================""" |
| self.result.append(comment) |
| self.result.extend(rules) |
| self.source_sets.setdefault( |
| TARGETS[target], []).append('${' + self._SourceVar(target) + '}') |
| |
| def BuildSourcesList(self, target, sources): |
| sources = self._ExpandSources(target, sources) |
| return f'set({self._SourceVar(target)} {sources})' |
| |
| def BuildAppendSources(self, target, sources): |
| sources = self._ExpandSources(target, sources) |
| return f'list(APPEND {self._SourceVar(target)} {sources})' |
| |
| def BuildRemoveSources(self, target, sources): |
| sources = self._ExpandSources(target, sources) |
| return f'list(REMOVE_ITEM {self._SourceVar(target)} {sources})' |
| |
| def BuildCondition(self, cond, then_stmts): |
| return f""" |
| if({cond}) |
| {' '.join(then_stmts)} |
| endif() |
| """.strip() |
| |
| def BuildConditionWithElseStmts(self, cond, then_stmts, else_stmts): |
| return f""" |
| if({cond}) |
| {' '.join(then_stmts)} |
| {'else() ' + ' '.join(else_stmts)} |
| endif() |
| """.strip() |
| |
| def BuildConditionWithElseCond(self, cond, then_stmts, else_cond): |
| return f""" |
| if({cond}) |
| {' '.join(then_stmts)} |
| else{else_cond} |
| """.strip() |
| |
| def BuildParenthesizedOperation(self, operation): |
| return ''.join(['(', operation, ')']) |
| |
| def BuildUnaryOperation(self, op, right): |
| OPS = { |
| 'neg': 'NOT', |
| } |
| return ' '.join([OPS[op], right]) |
| |
| def BuildBinaryOperation(self, left, op, right): |
| if op == 'ne': |
| neg_result = self.BuildBinaryOperation(left, 'eq', right) |
| return self.BuildUnaryOperation('neg', neg_result) |
| OPS = { |
| 'eq': 'STREQUAL', |
| 'le': 'LESS_EQUAL', |
| 'lt': 'LESS', |
| 'ge': 'GREATER_EQUAL', |
| 'gt': 'GREATER', |
| 'and': 'AND', |
| 'or': 'OR', |
| } |
| return ' '.join([left, OPS[op], right]) |
| |
| def BuildIdentifier(self, token): |
| return self._CMakeVarRef(token) |
| |
| def BuildInteger(self, integer): |
| return integer |
| |
| def BuildString(self, string): |
| return string |
| |
| def GetResult(self): |
| return '\n'.join(self.result) |
| |
| @staticmethod |
| def _GenTargetString(target_type, source_sets): |
| Target = namedtuple('Target', 'name cmake deps desc') |
| CMAKE_TARGETS = { |
| 'lib': |
| Target(name='cppgc', |
| cmake='add_library', |
| deps=['Threads::Threads'], |
| desc='Main library'), |
| 'sample': |
| Target(name='cppgc_sample', |
| cmake='add_executable', |
| deps=['cppgc'], |
| desc='Example'), |
| 'tests': |
| Target(name='cppgc_unittests', |
| cmake='add_executable', |
| deps=['cppgc', 'gtest', 'gmock'], |
| desc='Unittests') |
| } |
| target = CMAKE_TARGETS[target_type] |
| return f""" |
| # {target.desc} target. |
| {target.cmake}({target.name} {' '.join(source_sets)}) |
| |
| {'target_link_libraries(' + target.name + ' ' + ' '.join(target.deps) + ')' if target.deps else ''} |
| |
| target_include_directories({target.name} PRIVATE "${{CMAKE_SOURCE_DIR}}" |
| PRIVATE "${{CMAKE_SOURCE_DIR}}/include") |
| |
| if(CPPGC_ENABLE_OBJECT_NAMES) |
| target_compile_definitions({target.name} PRIVATE "-DCPPGC_SUPPORTS_OBJECT_NAMES") |
| endif() |
| if(CPPGC_ENABLE_CAGED_HEAP) |
| target_compile_definitions({target.name} PRIVATE "-DCPPGC_CAGED_HEAP") |
| endif() |
| if(CPPGC_ENABLE_YOUNG_GENERATION) |
| target_compile_definitions({target.name} PRIVATE "-DCPPGC_YOUNG_GENERATION") |
| endif()""" |
| |
| @staticmethod |
| def _ExpandSources(target, sources): |
| if TARGETS[target] == 'tests': |
| sources = ['\"test/unittests/' + s[1:] for s in sources] |
| return ' '.join(sources) |
| |
| @staticmethod |
| def _SourceVar(target): |
| return CMakeBuilder._CMakeVar(target) + '_SOURCES' |
| |
| @staticmethod |
| def _CMakeVar(var): |
| return var.replace('v8_', '').upper() |
| |
| @staticmethod |
| def _CMakeTarget(var): |
| return var.replace('v8_', '') |
| |
| @staticmethod |
| def _CMakeVarRef(var): |
| return '\"${' + CMakeBuilder._CMakeVar(var) + '}"' |
| |
| |
| def FormatCMake(contents): |
| from cmake_format import configuration, lexer, parse, formatter |
| cfg = configuration.Configuration() |
| tokens = lexer.tokenize(contents) |
| parse_tree = parse.parse(tokens) |
| box_tree = formatter.layout_tree(parse_tree, cfg) |
| return formatter.write_tree(box_tree, cfg, contents) |
| |
| |
| def SaveContents(contents, outfile): |
| if outfile == '-': |
| return print(contents) |
| with open(outfile, 'w+') as ofile: |
| ofile.write(contents) |
| |
| |
| def ParseGN(contents): |
| parser = lark.Lark(GN_GRAMMAR, parser='lalr', start='file') |
| return parser.parse(contents) |
| |
| |
| def ParseGNFile(filename): |
| with open(filename, 'r') as file: |
| contents = file.read() |
| return ParseGN(contents) |
| |
| |
| def GenCMake(main_gn, test_gn, outfile): |
| tree = ParseGNFile(main_gn) |
| tree.children.extend(ParseGNFile(test_gn).children) |
| builder = CMakeBuilder() |
| V8GNTransformer(builder, TARGETS.keys()).Traverse(tree) |
| result = FormatCMake(builder.GetResult()) |
| SaveContents(result, outfile) |
| |
| |
| def Main(): |
| arg_parser = argparse.ArgumentParser( |
| description= |
| 'Generate CMake from the main GN file for targets needed to build CppGC.' |
| ) |
| arg_parser.add_argument('--out', help='output CMake filename', default='-') |
| arg_parser.add_argument('--main-gn', |
| help='main BUILD.gn input file', |
| default='BUILD.gn') |
| arg_parser.add_argument('--test-gn', |
| help='unittest BUILD.gn input file', |
| default='test/unittests/BUILD.gn') |
| args = arg_parser.parse_args() |
| |
| GenCMake(args.main_gn, args.test_gn, args.out) |
| return 0 |
| |
| |
| if __name__ == '__main__': |
| sys.exit(Main()) |