• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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