v8/tools/cppgc/gen_cmake.py

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

519 lines
16 KiB
Python
Raw Normal View History

#!/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())