meson-to-hermetic: Rename meson-to-hermetic to -> meson_to_hermetic
Python packages does not allow for the use of hyphen as it's a reserved token. This change needs to be made to be imported by unit-tests in the future This change will require a new venv to be generated locally: `$ rm -rf venv` `$ ./setup-venv.sh` `$ source venv/bin/activate` Test: Run meson_to_hermetic/build-android/fuchsia-turnip.sh Bug: 360173803 Change-Id: Ie6fab4689ef5fc985e15747b0c06bfae662c387c
This commit is contained in:
parent
8e6d10e737
commit
c6cc3b175f
15 changed files with 7 additions and 7 deletions
1
meson_to_hermetic/.gitignore
vendored
Normal file
1
meson_to_hermetic/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
venv/
|
||||
42
meson_to_hermetic/README.md
Normal file
42
meson_to_hermetic/README.md
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
# meson-to-hermetic: automated build system generation
|
||||
|
||||
Goal: ease the integration of Mesa as a component inside larger projects like
|
||||
Android (AOSP).
|
||||
|
||||
How: parse the meson build tree into a python build script, which can be used
|
||||
together with configuration and options to generate other kinds of build scripts.
|
||||
|
||||
Status: useful, but rough. Supports Android (Soong) and Fuchsia (Bazel).
|
||||
|
||||
## Python Dependencies
|
||||
- Python 3.11+
|
||||
- See `requirements.txt`
|
||||
- [](https://github.com/astral-sh/ruff)
|
||||
|
||||
## Environment Setup
|
||||
|
||||
1. Open a terminal within `mesa3d/meson_to_hermetic`
|
||||
2. Run the `setup-venv.sh` file to automatically create a python3 venv and install dependencies.
|
||||
|
||||
## Linting the code
|
||||
1. Before pushing code for review; run `lint.sh` to automatically lint all of the python scripts.
|
||||
- IMPORTANT: Run the lint.sh from the `meson_to_hermetic` directory.
|
||||
|
||||
## 1 - Generate python from meson
|
||||
|
||||
generate_python_build.py: reads meson.build files (following subdir() commands)
|
||||
and uses meson2python to transform the meson into python.
|
||||
|
||||
meson2python.py: passes meson.build input and the meson grammar to
|
||||
[python lark](https://github.com/lark-parser/lark) to perform lexing and parsing; then
|
||||
transforms the parse tree into valid python. The result is one large python script.
|
||||
|
||||
## 2 - Generate Android.bp from python
|
||||
|
||||
meson_android.py defines the meson API entry points and emits Android.bp build
|
||||
constructs. A config file is read to determine some build parameters such as
|
||||
`cpu_family` (similar to meson's cross file).
|
||||
|
||||
## Limitations
|
||||
|
||||
Meson build options must be set by modifying the defaults in meson_options.txt.
|
||||
67
meson_to_hermetic/aosp.toml
Normal file
67
meson_to_hermetic/aosp.toml
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
# Copyright 2024 Google LLC
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
build = 'Soong'
|
||||
|
||||
[[project_config]]
|
||||
name = 'android_aarch64_drivers'
|
||||
|
||||
[project_config.host_machine]
|
||||
cpu_family = 'aarch64'
|
||||
cpu = 'aarch64'
|
||||
host_machine = 'android'
|
||||
build_machine = 'linux'
|
||||
|
||||
[project_config.meson_options]
|
||||
platforms = 'android'
|
||||
android-libbacktrace = 'disabled'
|
||||
gallium-drivers = ''
|
||||
vulkan-drivers = 'freedreno'
|
||||
freedreno-kmds = 'kgsl'
|
||||
platform-sdk-version = 33
|
||||
|
||||
[project_config.header_not_supported]
|
||||
headers = []
|
||||
|
||||
[project_config.symbol_not_supported]
|
||||
symbols = []
|
||||
|
||||
[project_config.function_not_supported]
|
||||
functions = []
|
||||
|
||||
[project_config.link_not_supported]
|
||||
links = []
|
||||
|
||||
[project_config.ext_dependencies]
|
||||
# DependencyTargetType
|
||||
# SHARED_LIBRARY = 1
|
||||
# STATIC_LIBRARY = 2
|
||||
# HEADER_LIBRARY = 3
|
||||
# See meson_impl.py
|
||||
zlib = [
|
||||
{ target_name = 'libz', target_type = 2 }
|
||||
]
|
||||
hardware = [
|
||||
{ target_name = 'libhardware', target_type = 1 },
|
||||
{ target_name = 'hwvulkan_headers', target_type = 3 }
|
||||
]
|
||||
cutils = [
|
||||
{ target_name = 'libcutils', target_type = 1 }
|
||||
]
|
||||
log = [
|
||||
{ target_name = 'liblog', target_type = 1 }
|
||||
]
|
||||
nativewindow = [
|
||||
{ target_name = 'libnativewindow', target_type = 1 }
|
||||
]
|
||||
sync = [
|
||||
{ target_name = 'libsync', target_type = 2 }
|
||||
]
|
||||
'android.hardware.graphics.mapper' = [
|
||||
{ target_name = 'libgralloctypes', target_type = 2 },
|
||||
{ target_name = 'android.hardware.graphics.mapper@4.0', target_type = 2 },
|
||||
{ target_name = 'libhidlbase', target_type = 1 },
|
||||
{ target_name = 'libutils', target_type = 1 }
|
||||
]
|
||||
|
||||
# Define new project configs
|
||||
# [[project_config]]
|
||||
35
meson_to_hermetic/build-android-turnip.sh
Executable file
35
meson_to_hermetic/build-android-turnip.sh
Executable file
|
|
@ -0,0 +1,35 @@
|
|||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
if [ ! -f meson_options.txt ]; then
|
||||
echo "Run this script from the repo root"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
BIN_DIR=$(dirname "$0")
|
||||
ROOT_DIR=$BIN_DIR/../../..
|
||||
|
||||
PYTHON_BUILD=generate_android_build.py
|
||||
|
||||
REGEN=0
|
||||
if [ "$1" == "-regen" ]; then
|
||||
REGEN=1
|
||||
fi
|
||||
if [ ! -f $PYTHON_BUILD ]; then
|
||||
REGEN=1
|
||||
fi
|
||||
|
||||
if [ "$REGEN" == "1" ]; then
|
||||
time python3 $BIN_DIR/generate_python_build.py
|
||||
else
|
||||
echo "Python build found; use -regen to regenerate it"
|
||||
fi
|
||||
|
||||
# Always generate Android.bp because it's fast
|
||||
PYTHONPATH=$BIN_DIR python3 generate_android_build.py --config=meson_to_hermetic/aosp.toml
|
||||
|
||||
source $ROOT_DIR/build/envsetup.sh
|
||||
lunch aosp_trout_arm64-trunk_staging-userdebug
|
||||
|
||||
m vulkan_freedreno
|
||||
57
meson_to_hermetic/build-fuchsia-turnip.sh
Executable file
57
meson_to_hermetic/build-fuchsia-turnip.sh
Executable file
|
|
@ -0,0 +1,57 @@
|
|||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
if [ ! -f meson_options.txt ]; then
|
||||
echo "Run this script from the repo root"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
BIN_DIR=$(dirname "$0")
|
||||
ROOT_DIR=$BIN_DIR/../../..
|
||||
PYTHON_BUILD=generate_fuchsia_build.py
|
||||
|
||||
function unwrap_zlib {
|
||||
rm -rf fuchsia-build/third_party/download fuchsia-build/third_party/zlib-*
|
||||
SOURCE_URL=`grep -Eo 'http://[^ ]+zlib[0-9\.-]+\.tar\.gz' subprojects/zlib.wrap`
|
||||
wget -P fuchsia-build/third_party/download $SOURCE_URL
|
||||
tar -C fuchsia-build/third_party -xf fuchsia-build/third_party/download/zlib-*
|
||||
|
||||
PATCH_URL=`grep -Eo 'https://[^ ]+get_patch' subprojects/zlib.wrap`
|
||||
wget -P fuchsia-build/third_party/download $PATCH_URL
|
||||
# get_patch is a zip file
|
||||
unzip -d fuchsia-build/third_party fuchsia-build/third_party/download/get_patch
|
||||
|
||||
pushd fuchsia-build/third_party/zlib-*
|
||||
# Create an empty workspace
|
||||
touch WORKSPACE.bazel
|
||||
ln -s ../../../meson_to_hermetic meson_to_hermetic
|
||||
python3 meson_to_hermetic/generate_python_build.py --target fuchsia
|
||||
PYTHONPATH=$PWD/meson_to_hermetic python3 generate_fuchsia_build.py --config=meson_to_hermetic/fuchsia.toml
|
||||
popd
|
||||
}
|
||||
|
||||
REGEN=0
|
||||
if [ "$1" == "-regen" ]; then
|
||||
REGEN=1
|
||||
fi
|
||||
if [ ! -f $PYTHON_BUILD ]; then
|
||||
REGEN=1
|
||||
fi
|
||||
if [ ! -d fuchsia-build/third_party/zlib-* ]; then
|
||||
REGEN=1
|
||||
fi
|
||||
|
||||
if [ "$REGEN" == "1" ]; then
|
||||
unwrap_zlib
|
||||
time python3 $BIN_DIR/generate_python_build.py --target fuchsia
|
||||
else
|
||||
echo "Python build found; use -regen to regenerate it"
|
||||
fi
|
||||
|
||||
# Always generate Android.bp because it's fast
|
||||
PYTHONPATH=$BIN_DIR python3 generate_fuchsia_build.py --config=meson_to_hermetic/fuchsia.toml
|
||||
|
||||
$BIN_DIR/../tools/bazel --bazelrc=fuchsia-build/third_party/fuchsia-infra-bazel-rules/config/common_config.bazelrc \
|
||||
build --config=fuchsia_arm64 --platforms=@fuchsia_sdk//fuchsia/constraints/platforms:fuchsia_arm64 \
|
||||
//:vulkan_freedreno
|
||||
60
meson_to_hermetic/fuchsia.toml
Normal file
60
meson_to_hermetic/fuchsia.toml
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
# Copyright 2024 Google LLC
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
build = 'bazel'
|
||||
|
||||
[[project_config]]
|
||||
name = 'fuchsia_aarch64_drivers'
|
||||
|
||||
[project_config.host_machine]
|
||||
cpu_family = 'aarch64'
|
||||
cpu = 'aarch64'
|
||||
host_machine = 'fuchsia'
|
||||
build_machine = 'linux'
|
||||
|
||||
[project_config.meson_options]
|
||||
platforms = 'none'
|
||||
gallium-drivers = ''
|
||||
vulkan-drivers = 'freedreno'
|
||||
freedreno-kmds = 'magma'
|
||||
platform-sdk-version = 33
|
||||
shader-cache = 'disabled'
|
||||
|
||||
[project_config.header_not_supported]
|
||||
headers = [
|
||||
'sys/sysmacros.h',
|
||||
]
|
||||
|
||||
[project_config.symbol_not_supported]
|
||||
symbols = [
|
||||
|
||||
]
|
||||
|
||||
[project_config.function_not_supported]
|
||||
functions = [
|
||||
'getrandom',
|
||||
'memfd_create',
|
||||
]
|
||||
|
||||
[project_config.link_not_supported]
|
||||
links = [
|
||||
'strtod has locale support',
|
||||
]
|
||||
|
||||
[project_config.ext_dependencies]
|
||||
# DependencyTargetType
|
||||
# SHARED_LIBRARY = 1
|
||||
# STATIC_LIBRARY = 2
|
||||
# HEADER_LIBRARY = 3
|
||||
# See meson_impl.py
|
||||
zlib = [
|
||||
{ target_name = '@zlib//:zlib', target_type = 2 }
|
||||
]
|
||||
libmagma = [
|
||||
{ target_name = '@fuchsia_sdk//pkg/magma_client', target_type = 2 }
|
||||
]
|
||||
libmagma_virt = [
|
||||
# No targets
|
||||
]
|
||||
|
||||
# Define new project configs
|
||||
# [[project_config]]
|
||||
141
meson_to_hermetic/generate_python_build.py
Normal file
141
meson_to_hermetic/generate_python_build.py
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
import getopt
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
from meson2python import meson2python
|
||||
from jinja2 import Environment, FileSystemLoader
|
||||
from pathlib import Path
|
||||
|
||||
environment = Environment(
|
||||
loader=FileSystemLoader(Path(__file__).parent.resolve() / 'templates/')
|
||||
)
|
||||
generator_template = environment.get_template('generate_python_build.txt')
|
||||
|
||||
|
||||
# Converts the given |file_name| from meson to python, and writes the python code
|
||||
# to the given |file|. Code is indented by |output_indent|. When a subdir command
|
||||
# is found, the meson.build build in that subdir is converted by recursively invoking
|
||||
# this function.
|
||||
def process_meson(file_name: str, output_indent: str = ''):
|
||||
python_code = ''
|
||||
python_code += (
|
||||
output_indent
|
||||
+ '########################################################################################################################'
|
||||
)
|
||||
python_code += '\n' + output_indent + f'### Begin conversion from: {file_name}'
|
||||
python_code += (
|
||||
'\n'
|
||||
+ output_indent
|
||||
+ '########################################################################################################################'
|
||||
)
|
||||
|
||||
print('Processing: ' + file_name)
|
||||
sys.stdout.flush()
|
||||
|
||||
content = meson2python(file_name)
|
||||
inside_literal = False
|
||||
|
||||
for line in content.splitlines():
|
||||
# Remove line terminator
|
||||
line = line.rstrip()
|
||||
|
||||
# Check for multiline literals.
|
||||
# We ignore literals that start and end on one line, though that may cause
|
||||
# problems for the line processing below.
|
||||
matches = re.findall(r"'''", line)
|
||||
|
||||
literal_delimiter_count = len(matches)
|
||||
|
||||
line_prefix = ''
|
||||
line_suffix = ''
|
||||
if literal_delimiter_count == 1:
|
||||
inside_literal = not inside_literal
|
||||
literal_line_split = line.split(r"'''")
|
||||
if inside_literal:
|
||||
line = literal_line_split[0]
|
||||
line_suffix = r"'''" + literal_line_split[1]
|
||||
else:
|
||||
line_prefix = literal_line_split[0] + r"'''"
|
||||
line = literal_line_split[1]
|
||||
elif literal_delimiter_count == 0 or literal_delimiter_count == 2:
|
||||
if inside_literal:
|
||||
# Don't match anything while inside literal
|
||||
line_prefix = line
|
||||
line = ''
|
||||
else:
|
||||
exit('Unhandled literal in line: ' + line)
|
||||
|
||||
# Recurse into subdirs
|
||||
match = re.match("( *)subdir\('([a-zA-Z0-9_/]+)'\)", line)
|
||||
if match is not None:
|
||||
subdir_output_indent = match.group(1) + output_indent
|
||||
current_dir = os.path.dirname(file_name)
|
||||
next_dir = os.path.join(current_dir, match.group(2))
|
||||
next_file = os.path.join(next_dir, 'meson.build')
|
||||
# Ensure the build definitions are aware of the changing directory
|
||||
python_code += f"\n{subdir_output_indent}set_relative_dir('{next_dir}')"
|
||||
python_code += '\n' + process_meson(next_file, subdir_output_indent)
|
||||
python_code += f"\n{subdir_output_indent}set_relative_dir('{current_dir}')"
|
||||
continue
|
||||
|
||||
python_code += f'\n{output_indent + line_prefix + line + line_suffix}'
|
||||
python_code += (
|
||||
'\n'
|
||||
+ output_indent
|
||||
+ '########################################################################################################################'
|
||||
)
|
||||
python_code += '\n' + output_indent + f'### End conversion from: {file_name}'
|
||||
python_code += (
|
||||
'\n'
|
||||
+ output_indent
|
||||
+ '########################################################################################################################'
|
||||
)
|
||||
return python_code
|
||||
|
||||
|
||||
def generate(target: str):
|
||||
if not (target == 'android' or target == 'fuchsia'):
|
||||
exit('Target must be android or fuchsia')
|
||||
|
||||
output_file_name = 'generate_%s_build.py' % target
|
||||
print('Writing to: ' + output_file_name)
|
||||
|
||||
meson_options = process_meson('meson_options.txt')
|
||||
meson_build = process_meson('meson.build')
|
||||
content = generator_template.render(
|
||||
meson_options=meson_options,
|
||||
meson_build=meson_build,
|
||||
)
|
||||
with open(output_file_name, 'w') as file:
|
||||
file.write(content)
|
||||
|
||||
|
||||
def usage():
|
||||
print('Usage: -t [android|fuchsia]')
|
||||
sys.exit()
|
||||
|
||||
|
||||
def main(argv):
|
||||
target = 'android'
|
||||
try:
|
||||
opts, args = getopt.getopt(
|
||||
argv,
|
||||
'ht:',
|
||||
[
|
||||
'help',
|
||||
'target=',
|
||||
],
|
||||
)
|
||||
for opt, arg in opts:
|
||||
if opt in ('-h', '--help'):
|
||||
usage()
|
||||
elif opt in ('-t', '--target'):
|
||||
target = arg
|
||||
except getopt.GetoptError as _:
|
||||
usage()
|
||||
|
||||
generate(target)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main(sys.argv[1:])
|
||||
11
meson_to_hermetic/lint.sh
Executable file
11
meson_to_hermetic/lint.sh
Executable file
|
|
@ -0,0 +1,11 @@
|
|||
#!/bin/bash
|
||||
if [ ! -d "venv" ]; then
|
||||
echo "A venv folder was not found for this project, try running setup-venv.sh!"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
source venv/bin/activate
|
||||
# automatically fixes formatting that isn't considered 'unsafe'
|
||||
ruff format --config "format.quote-style = 'single'"
|
||||
ruff check # This is for unsafe fixes
|
||||
echo "The above errors displays fixes that must be fixed manually."
|
||||
343
meson_to_hermetic/meson2python.py
Normal file
343
meson_to_hermetic/meson2python.py
Normal file
|
|
@ -0,0 +1,343 @@
|
|||
import re
|
||||
import sys
|
||||
|
||||
from lark import Lark, Tree
|
||||
from lark.visitors import Interpreter
|
||||
|
||||
# This grammar derived from:
|
||||
# https://mesonbuild.com/Syntax.html#grammar
|
||||
meson_grammar = r"""
|
||||
?start: (statement | COMMENT | NEWLINE)*
|
||||
|
||||
?additive_expression: multiplicative_expression | (additive_expression additive_operator multiplicative_expression)
|
||||
additive_operator: PLUS | MINUS
|
||||
argument_list: positional_arguments [COMMA keyword_arguments] [COMMA] | keyword_arguments
|
||||
array_literal: LBRACKET [expression_list] RBRACKET
|
||||
?assignment_statement: assignment_expression
|
||||
assignment_expression: expression assignment_operator expression
|
||||
assignment_operator: EQUALS | PLUS_EQUALS
|
||||
binary_literal: "0b" BINARY_NUMBER
|
||||
BINARY_NUMBER: /[01]+/
|
||||
boolean_literal: TRUE | FALSE
|
||||
build_definition: (NEWLINE | statement)*
|
||||
condition: expression
|
||||
?conditional_expression: logical_or_expression | (logical_or_expression "?" expression ":" expression)
|
||||
decimal_literal: DECIMAL_NUMBER
|
||||
DECIMAL_NUMBER: /[0-9][0-9]*/
|
||||
dictionary_literal: LBRACE [key_value_list] RBRACE
|
||||
?equality_expression: relational_expression | (equality_expression equality_operator relational_expression)
|
||||
equality_operator: DOUBLE_EQUAL | NOT_EQUAL
|
||||
?expression: conditional_expression | logical_or_expression
|
||||
expression_list: expression (COMMA expression)* COMMA?
|
||||
?expression_statement: expression
|
||||
?function_expression: id_expression LPAREN [argument_list] RPAREN
|
||||
hex_literal: "0x" HEX_NUMBER
|
||||
HEX_NUMBER: /[a-fA-F0-9]+/
|
||||
id_expression: IDENTIFIER
|
||||
IDENTIFIER: /[a-zA-Z_][a-zA-Z_0-9]*/
|
||||
identifier_list: id_expression (COMMA id_expression)*
|
||||
integer_literal: decimal_literal | octal_literal | hex_literal
|
||||
iteration_statement: FOREACH identifier_list COLON expression NEWLINE (statement | jump_statement)* ENDFOREACH
|
||||
jump_statement: (BREAK | CONTINUE) NEWLINE
|
||||
key_value_item: expression COLON expression
|
||||
key_value_list: key_value_item (COMMA key_value_item)* COMMA?
|
||||
keyword_item: id_expression ":" expression
|
||||
keyword_arguments: keyword_item (COMMA keyword_item)* COMMA?
|
||||
?literal: integer_literal | string_literal | boolean_literal | array_literal | dictionary_literal
|
||||
?logical_and_expression: equality_expression | (logical_and_expression AND ["\\"] equality_expression)
|
||||
?logical_or_expression: logical_and_expression | (logical_or_expression OR ["\\"] logical_and_expression)
|
||||
?method_expression: postfix_expression ["\\"] DOT function_expression
|
||||
?multiplicative_expression: unary_expression | (multiplicative_expression multiplicative_operator unary_expression)
|
||||
multiplicative_operator: ASTERISK | SLASH | PERCENT
|
||||
octal_literal: "0o" OCTAL_NUMBER
|
||||
OCTAL_NUMBER: /[0-7]+/
|
||||
positional_arguments: expression (COMMA expression)*
|
||||
postfix_expression: primary_expression | subscript_expression | function_expression | method_expression
|
||||
?primary_expression: literal | (LPAREN expression RPAREN) | id_expression
|
||||
?relational_expression: additive_expression | (relational_expression relational_operator additive_expression)
|
||||
relational_operator: GREATER | LESSTHAN | GREATER_OR_EQUAL | LESSTHAN_OR_EQUAL | IN | (NOT IN)
|
||||
selection_statement: IF condition NEWLINE (statement)* (ELIF condition NEWLINE (statement)*)* [ELSE NEWLINE (statement)*] ENDIF
|
||||
statement: (expression_statement | selection_statement | iteration_statement | assignment_statement) NEWLINE
|
||||
string_literal: STRING_SIMPLE_VALUE | STRING_MULTILINE_VALUE
|
||||
?subscript_expression: postfix_expression LBRACKET expression RBRACKET
|
||||
?unary_expression: postfix_expression | (unary_operator unary_expression)
|
||||
unary_operator: NOT | DASH
|
||||
|
||||
AND: /and/
|
||||
ASTERISK: /\*/
|
||||
BREAK: /break/
|
||||
CONTINUE: /continue/
|
||||
COLON: /:/
|
||||
COMMA: /,/
|
||||
DASH: /-/
|
||||
DOT: /\./
|
||||
DOUBLE_EQUAL: /==/
|
||||
EQUALS: /=/
|
||||
FOREACH: /foreach/
|
||||
GREATER: />/
|
||||
GREATER_OR_EQUAL: />=/
|
||||
# Raise priorities to avoid elif parsed as a statement
|
||||
ELIF.1: /elif/
|
||||
ELSE.1: /else/
|
||||
ENDIF.1: /endif/
|
||||
ENDFOREACH: /endforeach/
|
||||
FALSE: /false/
|
||||
IF: /if /
|
||||
IN: / in /
|
||||
LBRACKET: /\[/
|
||||
NOT: /not /
|
||||
NOT_EQUAL: /!=/
|
||||
RBRACKET: /\]/
|
||||
LESSTHAN: /</
|
||||
LESSTHAN_OR_EQUAL: /<=/
|
||||
LBRACE: /{/
|
||||
LPAREN: /\(/
|
||||
RBRACE: /}/
|
||||
RPAREN: /\)/
|
||||
OR: /or/
|
||||
PERCENT: /%/
|
||||
PLUS: /\+/
|
||||
MINUS: /-/
|
||||
PLUS_EQUALS: /\+=/
|
||||
NEWLINE: ( / *\r?\n/ | COMMENT )+
|
||||
COMMENT: / *\#.*\n/
|
||||
SLASH: /\//
|
||||
STRING_SIMPLE_VALUE: /f?'(.*\\')*.*?'/
|
||||
STRING_MULTILINE_VALUE: /f?'''.*?'''/s
|
||||
TRUE: /true/
|
||||
|
||||
%import common.WS
|
||||
|
||||
%ignore WS
|
||||
# Comments would be nice to keep, but parsing fails end-of-line comments
|
||||
%ignore COMMENT
|
||||
"""
|
||||
|
||||
|
||||
class TreeToCode(Interpreter):
|
||||
indent = ''
|
||||
|
||||
def statement(self, tree):
|
||||
string = ''
|
||||
for child in tree.children:
|
||||
if isinstance(child, Tree):
|
||||
string += self.visit(child)
|
||||
elif child is not None:
|
||||
string += child
|
||||
return self.indent + string
|
||||
|
||||
def more_indent(self):
|
||||
self.indent += ' '
|
||||
|
||||
def less_indent(self):
|
||||
self.indent = self.indent[0 : len(self.indent) - 2]
|
||||
|
||||
# Ensure spaces around 'and'
|
||||
def logical_and_expression(self, tree):
|
||||
assert len(tree.children) == 3
|
||||
lhs = self.visit(tree.children[0])
|
||||
rhs = self.visit(tree.children[2])
|
||||
return lhs + ' and ' + rhs
|
||||
|
||||
# Ensure spaces around 'or'
|
||||
def logical_or_expression(self, tree):
|
||||
assert len(tree.children) == 3
|
||||
lhs = self.visit(tree.children[0])
|
||||
rhs = self.visit(tree.children[2])
|
||||
return lhs + ' or ' + rhs
|
||||
|
||||
# A ? B : C becomes B if A else C
|
||||
def conditional_expression(self, tree):
|
||||
assert len(tree.children) == 3
|
||||
expr = self.visit(tree.children[0])
|
||||
first = self.visit(tree.children[1])
|
||||
second = self.visit(tree.children[2])
|
||||
return first + ' if ' + expr + ' else ' + second
|
||||
|
||||
def assignment_expression(self, tree):
|
||||
assert len(tree.children) == 3
|
||||
lhs = self.visit(tree.children[0])
|
||||
operator = self.visit(tree.children[1])
|
||||
rhs = self.visit(tree.children[2])
|
||||
if operator == '+=' and rhs.startswith('{'):
|
||||
# Convert += to |= for dictionaries
|
||||
return lhs + ' |= ' + rhs
|
||||
elif operator == '+=' and rhs.startswith("'"):
|
||||
# Handle literal string append to list or string
|
||||
return (
|
||||
lhs
|
||||
+ ' += '
|
||||
+ '['
|
||||
+ rhs
|
||||
+ '] if isinstance('
|
||||
+ lhs
|
||||
+ ', list) else '
|
||||
+ rhs
|
||||
)
|
||||
return lhs + operator + rhs
|
||||
|
||||
def iteration_statement(self, tree):
|
||||
# foreach = tree.children[0]
|
||||
identifier_list = self.visit(tree.children[1])
|
||||
# colon = tree.children[2]
|
||||
id_expression = self.visit(tree.children[3])
|
||||
# newline = tree.children[4]
|
||||
string = 'for ' + identifier_list + ' in ' + id_expression
|
||||
string += (
|
||||
'.items():\n' if re.search(r',', identifier_list) is not None else ':\n'
|
||||
)
|
||||
self.more_indent()
|
||||
lastindex = len(tree.children) - 1
|
||||
for child in tree.children[5:lastindex]:
|
||||
if isinstance(child, Tree):
|
||||
string += self.visit(child)
|
||||
elif child is not None:
|
||||
string += child
|
||||
self.less_indent()
|
||||
return string
|
||||
|
||||
def selection_statement(self, tree):
|
||||
string = ''
|
||||
index = 0
|
||||
while index < len(tree.children):
|
||||
prefix = tree.children[index]
|
||||
index = index + 1
|
||||
if prefix is None:
|
||||
continue
|
||||
if isinstance(prefix, Tree):
|
||||
exit('unexpected prefix: ' + prefix.pretty())
|
||||
if re.match(r' *endif', prefix) is not None:
|
||||
break
|
||||
|
||||
if re.match(r'if', prefix) is not None:
|
||||
condition = self.visit(tree.children[index])
|
||||
index += 1
|
||||
# Skip indent here because all statements are prepended with the indentation
|
||||
string += 'if ' + condition + ':\n'
|
||||
elif re.match(r'elif', prefix) is not None:
|
||||
condition = self.visit(tree.children[index])
|
||||
index = index + 1
|
||||
string += self.indent + 'elif ' + condition + ':\n'
|
||||
elif re.match(r'else', prefix) is not None:
|
||||
string += self.indent + 'else:\n'
|
||||
else:
|
||||
exit('Not a prefix: ' + prefix)
|
||||
|
||||
# newline = tree.children[index]
|
||||
index += 1
|
||||
|
||||
statement_count = 0
|
||||
self.more_indent()
|
||||
while index < len(tree.children):
|
||||
statement = tree.children[index]
|
||||
if not isinstance(statement, Tree):
|
||||
break
|
||||
string += self.visit(statement)
|
||||
index = index + 1
|
||||
statement_count = statement_count + 1
|
||||
if statement_count == 0:
|
||||
string += self.indent + 'noop()\n'
|
||||
self.less_indent()
|
||||
|
||||
return string
|
||||
|
||||
def postfix_expression(self, tree):
|
||||
string = ''
|
||||
for child in tree.children:
|
||||
if isinstance(child, Tree):
|
||||
subtree = self.visit(child)
|
||||
subtree = re.sub(r'(.+)\.to_int\(\)', r'int(\g<1>)', subtree)
|
||||
subtree = re.sub(r'(.+)\.to_string\(\)', r'str(\g<1>)', subtree)
|
||||
subtree = re.sub(r'(.+)\.length\(\)', r'len(\g<1>)', subtree)
|
||||
subtree = re.sub(r'(.+)\.to_upper\(\)', r'\g<1>.upper()', subtree)
|
||||
subtree = re.sub(
|
||||
r'(.+)\.underscorify\(\)',
|
||||
r"\g<1>.replace('.', '_').replace('/', '_')",
|
||||
subtree,
|
||||
)
|
||||
string += subtree
|
||||
elif child is not None:
|
||||
string += child
|
||||
return string
|
||||
|
||||
def function_expression(self, tree):
|
||||
assert len(tree.children) == 4
|
||||
identifier = self.visit(tree.children[0])
|
||||
if identifier == 'import':
|
||||
identifier = 'module_import'
|
||||
lparen = tree.children[1]
|
||||
args = (
|
||||
self.visit(tree.children[2]) if isinstance(tree.children[2], Tree) else ''
|
||||
)
|
||||
rparen = tree.children[3]
|
||||
if identifier == 'contains':
|
||||
return 'count' + lparen + args + rparen + ' > 0'
|
||||
return identifier + lparen + args + rparen
|
||||
|
||||
def multiplicative_expression(self, tree):
|
||||
assert len(tree.children) == 3
|
||||
lhs = self.visit(tree.children[0])
|
||||
operator = self.visit(tree.children[1])
|
||||
rhs = self.visit(tree.children[2])
|
||||
# Slash used mostly to concatenate strings
|
||||
if operator == '/':
|
||||
return (
|
||||
'('
|
||||
+ lhs
|
||||
+ ' + '
|
||||
+ rhs
|
||||
+ ') if isinstance('
|
||||
+ lhs
|
||||
+ ', str) else ('
|
||||
+ lhs
|
||||
+ ' / '
|
||||
+ rhs
|
||||
+ ')'
|
||||
)
|
||||
return lhs + operator + rhs
|
||||
|
||||
# Switch from colon to equals
|
||||
def keyword_item(self, tree):
|
||||
id_ = self.visit(tree.children[0])
|
||||
args = self.visit(tree.children[1])
|
||||
return id_ + '=' + args
|
||||
|
||||
def boolean_literal(self, tree):
|
||||
assert len(tree.children) == 1
|
||||
value = tree.children[0]
|
||||
if value == 'true':
|
||||
return 'True'
|
||||
elif value == 'false':
|
||||
return 'False'
|
||||
exit('Unhandled value: ' + value)
|
||||
|
||||
def string_literal(self, tree):
|
||||
assert len(tree.children) == 1
|
||||
string = tree.children[0]
|
||||
string = re.sub(r'(@[0-9]@)', r'{}', string)
|
||||
if string.startswith('f'):
|
||||
string = re.sub(r'(@(.+)@)', r'{\g<2>}', string)
|
||||
return string
|
||||
|
||||
def __default__(self, tree):
|
||||
string = ''
|
||||
for child in tree.children:
|
||||
if isinstance(child, Tree):
|
||||
string += self.visit(child)
|
||||
elif child is not None:
|
||||
string += child
|
||||
return string
|
||||
|
||||
|
||||
# Converts the given file from meson to python and returns the content as a string
|
||||
def meson2python(file_name):
|
||||
meson_parser = Lark(meson_grammar, parser='earley')
|
||||
with open(file_name) as f:
|
||||
# Ensure newline before end of file
|
||||
tree = meson_parser.parse(f.read() + '\n')
|
||||
code = TreeToCode().visit(tree)
|
||||
return code
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
meson2python(sys.argv[1])
|
||||
333
meson_to_hermetic/meson_common.py
Normal file
333
meson_to_hermetic/meson_common.py
Normal file
|
|
@ -0,0 +1,333 @@
|
|||
import os
|
||||
import warnings
|
||||
import meson_impl as impl
|
||||
|
||||
_gArrayOptions = []
|
||||
_gFeatureOptions = []
|
||||
_gBooleanOptions = []
|
||||
_gComboOptions = []
|
||||
_gSimpleOptions = []
|
||||
|
||||
|
||||
def noop():
|
||||
return
|
||||
|
||||
|
||||
def message(str):
|
||||
print(str)
|
||||
|
||||
|
||||
def error(message):
|
||||
exit(message)
|
||||
|
||||
|
||||
def warning(message):
|
||||
warnings.warn(message)
|
||||
|
||||
|
||||
def set_relative_dir(dir):
|
||||
impl.set_relative_dir(dir)
|
||||
|
||||
|
||||
def files(*filenames):
|
||||
file_list = []
|
||||
for file in filenames:
|
||||
file_list.append(impl.File(os.path.join(impl.get_relative_dir(), file)))
|
||||
return file_list
|
||||
|
||||
|
||||
def declare_dependency(
|
||||
compile_args=[],
|
||||
d_import_dirs=[],
|
||||
d_module_versions='',
|
||||
dependencies=[],
|
||||
extra_files=[],
|
||||
include_directories=[],
|
||||
link_args=[],
|
||||
link_whole=[],
|
||||
link_with=[],
|
||||
objects=[],
|
||||
sources=[],
|
||||
variables=[],
|
||||
version='',
|
||||
):
|
||||
link_with = impl.get_linear_list([link_with])
|
||||
link_whole = impl.get_linear_list([link_whole])
|
||||
|
||||
return impl.Dependency(
|
||||
'declared',
|
||||
version,
|
||||
found=True,
|
||||
compile_args=compile_args,
|
||||
include_directories=include_directories,
|
||||
dependencies=dependencies,
|
||||
sources=sources,
|
||||
link_with=link_with,
|
||||
link_whole=link_whole,
|
||||
)
|
||||
|
||||
|
||||
def find_program(name: str, required=False, native=False, disabler=False, version=''):
|
||||
if type(required) is impl.FeatureOption:
|
||||
required = required.state == impl.EnableState.ENABLED
|
||||
if type(required) is not bool:
|
||||
exit('Unhandled required type: ' + str(type(required)))
|
||||
|
||||
maybe_filename = impl.get_relative_dir(name)
|
||||
|
||||
# may be a script in the current directory
|
||||
if os.path.isfile(maybe_filename):
|
||||
return impl.Program(maybe_filename, found=True)
|
||||
|
||||
# These are required for building turnip though not tagged as such
|
||||
if name == 'bison' or name == 'flex' or name == 'gzip':
|
||||
return impl.Program(name, found=True)
|
||||
|
||||
if (
|
||||
name == 'byacc'
|
||||
or name == 'glslangValidator'
|
||||
or name == 'install_megadrivers.py'
|
||||
or name == 'nm'
|
||||
or name == 'python'
|
||||
or name == 'symbols-check.py'
|
||||
or name == 'sphinx-build'
|
||||
):
|
||||
return impl.Program(name, found=required)
|
||||
|
||||
exit('Unhandled program check: ' + name)
|
||||
|
||||
|
||||
def add_project_arguments(args, language=[], native=False):
|
||||
impl.add_project_arguments(args, language, native)
|
||||
|
||||
|
||||
def add_project_link_arguments(args, language=[], native=False):
|
||||
return
|
||||
|
||||
|
||||
# Used by meson_options.txt to define an option
|
||||
def option(
|
||||
name: str,
|
||||
type: str,
|
||||
min: int = 0,
|
||||
max: int = 0,
|
||||
value='',
|
||||
choices=[],
|
||||
description='',
|
||||
deprecated=None,
|
||||
):
|
||||
if type == 'array':
|
||||
global _gArrayOptions
|
||||
_gArrayOptions.append(impl.ArrayOption(name, value))
|
||||
return
|
||||
if type == 'feature':
|
||||
global _gFeatureOptions
|
||||
if value == '' or value == 'auto':
|
||||
state = impl.EnableState.AUTO
|
||||
elif value == 'disabled':
|
||||
state = impl.EnableState.DISABLED
|
||||
elif value == 'enabled':
|
||||
state = impl.EnableState.ENABLED
|
||||
else:
|
||||
exit('Unhandled feature option value')
|
||||
_gFeatureOptions.append(impl.FeatureOption(name, state))
|
||||
return
|
||||
if type == 'boolean':
|
||||
global _gBooleanOptions
|
||||
if isinstance(value, str):
|
||||
flag = True if value.lower() == 'true' else False
|
||||
_gBooleanOptions.append(impl.BooleanOption(name, flag))
|
||||
else:
|
||||
_gBooleanOptions.append(impl.BooleanOption(name, value))
|
||||
return
|
||||
if type == 'combo':
|
||||
global _gComboOptions
|
||||
_gComboOptions.append(impl.ComboOption(name, value))
|
||||
return
|
||||
if type == 'string' or type == 'integer':
|
||||
global _gSimpleOptions
|
||||
_gSimpleOptions.append(impl.SimpleOption(name, value))
|
||||
return
|
||||
|
||||
|
||||
def set_option(name, value: str):
|
||||
print('set_option: %s=%s' % (name, value))
|
||||
for option in _gArrayOptions:
|
||||
if option.name == name:
|
||||
option.set(value)
|
||||
|
||||
for option in _gFeatureOptions:
|
||||
if option.name == name:
|
||||
option.set(value)
|
||||
|
||||
for option in _gBooleanOptions:
|
||||
if option.name == name:
|
||||
option.set(value)
|
||||
|
||||
for option in _gComboOptions:
|
||||
if option.name == name:
|
||||
option.set(value)
|
||||
|
||||
for option in _gSimpleOptions:
|
||||
if option.name == name:
|
||||
option.set(value)
|
||||
|
||||
for option in impl.get_project_options():
|
||||
if option.name == name:
|
||||
option.set(value)
|
||||
|
||||
|
||||
def get_option(name):
|
||||
for option in _gArrayOptions:
|
||||
if option.name == name:
|
||||
return option.strings
|
||||
|
||||
for option in _gFeatureOptions:
|
||||
if option.name == name:
|
||||
return option
|
||||
|
||||
for option in _gBooleanOptions:
|
||||
if option.name == name:
|
||||
return option.value
|
||||
|
||||
for option in _gComboOptions:
|
||||
if option.name == name:
|
||||
return option.value
|
||||
|
||||
for option in _gSimpleOptions:
|
||||
if option.name == name:
|
||||
return option.value
|
||||
|
||||
for option in impl.get_project_options():
|
||||
if option.name == name:
|
||||
return option.value
|
||||
|
||||
# built-in options
|
||||
if name == 'layout':
|
||||
return 'mirror'
|
||||
if name == 'prefix':
|
||||
return 'prefix'
|
||||
if name == 'libdir':
|
||||
return 'libdir'
|
||||
if name == 'datadir':
|
||||
return 'datadir'
|
||||
if name == 'sysconfdir':
|
||||
return 'sysconfdir'
|
||||
if name == 'includedir':
|
||||
return 'includedir'
|
||||
if name == 'c_args':
|
||||
return ''
|
||||
if name == 'cpp_rtti':
|
||||
return False
|
||||
if name == 'debug':
|
||||
return True
|
||||
if name == 'b_sanitize':
|
||||
return False
|
||||
if name == 'backend':
|
||||
return 'custom'
|
||||
|
||||
exit('Unhandled option: ' + name)
|
||||
|
||||
|
||||
def project(name, language_list, version, license, meson_version, default_options=[]):
|
||||
impl.project(name, language_list, version, license, meson_version, default_options)
|
||||
|
||||
|
||||
def run_command(program, *commands, check=False):
|
||||
return program.run_command(commands)
|
||||
|
||||
|
||||
def environment():
|
||||
return impl.Environment()
|
||||
|
||||
|
||||
def join_paths(*paths):
|
||||
joined_path = ''
|
||||
for path in paths:
|
||||
joined_path = os.path.join(joined_path, path)
|
||||
return joined_path
|
||||
|
||||
|
||||
def executable(
|
||||
target_name,
|
||||
*source,
|
||||
c_args=[],
|
||||
cpp_args=[],
|
||||
c_pch='',
|
||||
build_by_default=False,
|
||||
build_rpath='',
|
||||
d_debug=[],
|
||||
d_import_dirs=[],
|
||||
d_module_versions=[],
|
||||
d_unittest=False,
|
||||
dependencies=[],
|
||||
export_dynamic=False,
|
||||
extra_files='',
|
||||
gnu_symbol_visibility='',
|
||||
gui_app=False,
|
||||
implib=False,
|
||||
implicit_include_directories=False,
|
||||
include_directories=[],
|
||||
install=False,
|
||||
install_dir='',
|
||||
install_mode=[],
|
||||
install_rpath='',
|
||||
install_tag='',
|
||||
link_args=[],
|
||||
link_depends='',
|
||||
link_language='',
|
||||
link_whole=[],
|
||||
link_with=[],
|
||||
name_prefix='',
|
||||
name_suffix='',
|
||||
native=False,
|
||||
objects=[],
|
||||
override_options=[],
|
||||
pie=False,
|
||||
rust_crate_type='',
|
||||
rust_dependency_map={},
|
||||
sources='',
|
||||
vala_args=[],
|
||||
vs_module_defs='',
|
||||
win_subsystem='',
|
||||
):
|
||||
return impl.Executable(target_name)
|
||||
|
||||
|
||||
def test(
|
||||
name,
|
||||
executable,
|
||||
args=[],
|
||||
depends=[],
|
||||
env=[],
|
||||
is_parallel=False,
|
||||
priority=0,
|
||||
protocol='',
|
||||
should_fail=False,
|
||||
suite='',
|
||||
timeout=0,
|
||||
verbose=False,
|
||||
workdir='',
|
||||
):
|
||||
return
|
||||
|
||||
|
||||
def summary(entry, bool_yn=False, list_sep='', section=''):
|
||||
return
|
||||
|
||||
|
||||
def install_headers(*headers, subdir=''):
|
||||
return
|
||||
|
||||
|
||||
def install_data(
|
||||
*files,
|
||||
follow_symlinks=False,
|
||||
install_dir='',
|
||||
install_mode=[],
|
||||
install_tag='',
|
||||
preserve_path=False,
|
||||
rename=[],
|
||||
sources=[],
|
||||
):
|
||||
return
|
||||
802
meson_to_hermetic/meson_impl.py
Normal file
802
meson_to_hermetic/meson_impl.py
Normal file
|
|
@ -0,0 +1,802 @@
|
|||
from enum import Enum
|
||||
from abc import ABC, abstractmethod
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import tomllib
|
||||
|
||||
# The file used to write output build definitions.
|
||||
_gOutputFile = ''
|
||||
|
||||
# The relative directory that is currently being processed. When files are
|
||||
# referenced they are relative to this path.
|
||||
_gRelativeDir = ''
|
||||
|
||||
# Global compiler flags
|
||||
_gProjectCflags = []
|
||||
_gProjectCppflags = []
|
||||
|
||||
# Parameters set by config file
|
||||
_gCpuFamily = 'unknown'
|
||||
_gCpu = _gCpuFamily
|
||||
|
||||
_gProjectVersion = 'unknown'
|
||||
_gProjectOptions = []
|
||||
|
||||
# Caches the list of dependencies found in .toml config files
|
||||
# Structure:
|
||||
# DependencyTargetType
|
||||
# SHARED_LIBRARY = 1
|
||||
# STATIC_LIBRARY = 2
|
||||
# HEADER_LIBRARY = 3
|
||||
# See meson_impl.py
|
||||
# external_dep = {
|
||||
# 'zlib': {
|
||||
# # target_name: target_type
|
||||
# 'libz': 2
|
||||
# },
|
||||
# }
|
||||
external_dep = {}
|
||||
|
||||
|
||||
class IncludeDirectories:
|
||||
def __init__(self, name: str, dirs: []):
|
||||
self.name = name
|
||||
self.dirs = dirs
|
||||
|
||||
def __iter__(self):
|
||||
return iter([self])
|
||||
|
||||
|
||||
class File:
|
||||
def __init__(self, name: str):
|
||||
self.name = name
|
||||
|
||||
|
||||
class Machine:
|
||||
def __init__(self, system):
|
||||
self._system = system
|
||||
|
||||
def system(self):
|
||||
return self._system
|
||||
|
||||
def set_system(self, system: str):
|
||||
self._system = system
|
||||
|
||||
def cpu_family(self):
|
||||
return _gCpuFamily
|
||||
|
||||
def cpu(self):
|
||||
return _gCpuFamily
|
||||
|
||||
|
||||
class DependencyTargetType(Enum):
|
||||
SHARED_LIBRARY = 1
|
||||
STATIC_LIBRARY = 2
|
||||
HEADER_LIBRARY = 3
|
||||
|
||||
|
||||
class DependencyTarget:
|
||||
def __init__(self, target_name: str, target_type: DependencyTargetType):
|
||||
self.target_name = target_name
|
||||
self.target_type = target_type
|
||||
|
||||
|
||||
class Dependency:
|
||||
_id_generator = 1000
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
version='',
|
||||
found=False,
|
||||
targets=[],
|
||||
compile_args=[],
|
||||
include_directories=[],
|
||||
dependencies=[],
|
||||
sources=[],
|
||||
link_with=[],
|
||||
link_whole=[],
|
||||
):
|
||||
self.name = name
|
||||
self.targets = targets
|
||||
self._version = version
|
||||
self._found = found
|
||||
self.compile_args = compile_args
|
||||
self.include_directories = include_directories
|
||||
self.dependencies = dependencies
|
||||
self.sources = sources
|
||||
self.link_with = link_with
|
||||
self.link_whole = link_whole
|
||||
Dependency._id_generator += 1
|
||||
self.unique_id = Dependency._id_generator
|
||||
|
||||
def version(self):
|
||||
return self._version
|
||||
|
||||
def found(self):
|
||||
return self._found
|
||||
|
||||
def partial_dependency(self, compile_args=''):
|
||||
return self
|
||||
|
||||
def __iter__(self):
|
||||
return iter([self])
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self.unique_id)
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.unique_id == other._unique_id
|
||||
|
||||
|
||||
class CommandReturn:
|
||||
def __init__(self, completed_process):
|
||||
self.completed_process = completed_process
|
||||
|
||||
def returncode(self):
|
||||
return self.completed_process.returncode
|
||||
|
||||
def stdout(self):
|
||||
return self.completed_process.stdout
|
||||
|
||||
|
||||
class Program:
|
||||
def __init__(self, command, found: bool):
|
||||
self.command = command
|
||||
self._found = found
|
||||
|
||||
# Running commands from the ambient system may give wrong/misleading results, since
|
||||
# some build systems use hermetic installations of tools like python.
|
||||
def run_command(self, *commands, capture_output=False):
|
||||
command_line = [self.command]
|
||||
for command in commands:
|
||||
command_line += command
|
||||
|
||||
completed_process = subprocess.run(
|
||||
command_line, check=False, capture_output=capture_output
|
||||
)
|
||||
return CommandReturn(completed_process)
|
||||
|
||||
def found(self):
|
||||
return self._found
|
||||
|
||||
def full_path(self):
|
||||
return 'full_path'
|
||||
|
||||
|
||||
class PythonModule:
|
||||
def find_installation(self, name: str):
|
||||
if name == 'python3':
|
||||
return Program(name, found=True)
|
||||
exit('Unhandled python installation: ' + name)
|
||||
|
||||
|
||||
class EnableState(Enum):
|
||||
ENABLED = 1
|
||||
DISABLED = 2
|
||||
AUTO = 3
|
||||
|
||||
|
||||
class FeatureOption:
|
||||
def __init__(self, name, state=EnableState.AUTO):
|
||||
self.name = name
|
||||
self.state = state
|
||||
|
||||
def allowed(self):
|
||||
return self.state == EnableState.ENABLED or self.state == EnableState.AUTO
|
||||
|
||||
def enabled(self):
|
||||
return self.state == EnableState.ENABLED
|
||||
|
||||
def disabled(self):
|
||||
return self.state == EnableState.DISABLED
|
||||
|
||||
def disable_auto_if(self, value: bool):
|
||||
if value and self.state == EnableState.AUTO:
|
||||
self.state = EnableState.DISABLED
|
||||
return self
|
||||
|
||||
def disable_if(self, value: bool, error_message: str):
|
||||
if not value:
|
||||
return self
|
||||
if self.state == EnableState.ENABLED:
|
||||
exit(error_message)
|
||||
return FeatureOption(self.name, state=EnableState.DISABLED)
|
||||
|
||||
def require(self, value: bool, error_message: str):
|
||||
if value:
|
||||
return self
|
||||
if self.state == EnableState.ENABLED:
|
||||
exit(error_message)
|
||||
return FeatureOption(self.name, state=EnableState.DISABLED)
|
||||
|
||||
def set(self, value: str):
|
||||
value = value.lower()
|
||||
if value == 'auto':
|
||||
self.state = EnableState.AUTO
|
||||
elif value == 'enabled':
|
||||
self.state = EnableState.ENABLED
|
||||
elif value == 'disabled':
|
||||
self.state = EnableState.DISABLED
|
||||
else:
|
||||
exit('Unable to set feature to: %s' % value)
|
||||
|
||||
|
||||
class ArrayOption:
|
||||
def __init__(self, name: str, value: []):
|
||||
self.name = name
|
||||
self.strings = value
|
||||
|
||||
def set(self, value: str):
|
||||
if value == '':
|
||||
self.strings = []
|
||||
else:
|
||||
self.strings = [value]
|
||||
|
||||
|
||||
class ComboOption:
|
||||
def __init__(self, name: str, value: str):
|
||||
self.name = name
|
||||
self.value = value
|
||||
|
||||
def set(self, value: str):
|
||||
self.value = value
|
||||
|
||||
|
||||
class BooleanOption:
|
||||
def __init__(self, name, value: bool):
|
||||
assert type(value) is bool
|
||||
self.name = name
|
||||
self.value = value
|
||||
|
||||
def set(self, value: str):
|
||||
self.value = bool(value)
|
||||
|
||||
|
||||
# Value can be string or other type
|
||||
class SimpleOption:
|
||||
def __init__(self, name, value):
|
||||
self.name = name
|
||||
self.value = value
|
||||
|
||||
def set(self, value: str):
|
||||
if type(self.value) is int:
|
||||
self.value = int(value)
|
||||
else:
|
||||
self.value = value
|
||||
|
||||
|
||||
class Environment:
|
||||
def set(self, var, val):
|
||||
return
|
||||
|
||||
def append(self, var, val):
|
||||
return
|
||||
|
||||
|
||||
class StaticLibrary:
|
||||
def __init__(self, target_name, link_with=[], link_whole=[]):
|
||||
self.target_name = target_name
|
||||
self.link_with = link_with
|
||||
self.link_whole = link_whole
|
||||
|
||||
|
||||
class SharedLibrary:
|
||||
name = ''
|
||||
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
|
||||
|
||||
class Executable:
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
|
||||
|
||||
class CustomTargetItem:
|
||||
def __init__(self, custom_target, index):
|
||||
self.target = custom_target
|
||||
self.index = index
|
||||
|
||||
|
||||
class CustomTarget:
|
||||
def __init__(self, name, outputs=[], generates_h=False, generates_c=False):
|
||||
self._name = name
|
||||
self._outputs = outputs
|
||||
self._generates_h = generates_h
|
||||
self._generates_c = generates_c
|
||||
|
||||
@property
|
||||
def outputs(self):
|
||||
return self._outputs
|
||||
|
||||
def generates_h(self):
|
||||
return self._generates_h
|
||||
|
||||
def generates_c(self):
|
||||
return self._generates_c
|
||||
|
||||
def target_name(self):
|
||||
return self._name
|
||||
|
||||
def target_name_h(self):
|
||||
if self._generates_h and self._generates_c:
|
||||
return self._name + '.h'
|
||||
return self._name
|
||||
|
||||
def target_name_c(self):
|
||||
if self._generates_h and self._generates_c:
|
||||
return self._name + '.c'
|
||||
return self._name
|
||||
|
||||
def header_outputs(self):
|
||||
hdrs = []
|
||||
for out in self._outputs:
|
||||
if out.endswith('.h'):
|
||||
hdrs.append(out)
|
||||
return hdrs
|
||||
|
||||
def __iter__(self):
|
||||
return iter([self])
|
||||
|
||||
def __getitem__(self, index):
|
||||
return CustomTargetItem(self, index)
|
||||
|
||||
def full_path(self):
|
||||
return 'fullpath'
|
||||
|
||||
|
||||
class Meson:
|
||||
def __init__(self, compiler):
|
||||
self._compiler = compiler
|
||||
|
||||
def get_compiler(self, language_string, native=False):
|
||||
return self._compiler
|
||||
|
||||
def set_compiler(self, compiler):
|
||||
self._compiler = compiler
|
||||
|
||||
def project_version(self):
|
||||
return _gProjectVersion
|
||||
|
||||
def project_source_root(self):
|
||||
return os.getcwd()
|
||||
|
||||
def is_cross_build(self):
|
||||
return True
|
||||
|
||||
def can_run_host_binaries(self):
|
||||
return False
|
||||
|
||||
def current_source_dir(self):
|
||||
return os.getcwd()
|
||||
|
||||
def current_build_dir(self):
|
||||
return '@CURRENT_BUILD_DIR@'
|
||||
|
||||
def project_build_root(self):
|
||||
return '@PROJECT_BUILD_ROOT@'
|
||||
|
||||
def add_devenv(self, env):
|
||||
return
|
||||
|
||||
|
||||
class Compiler(ABC):
|
||||
def __init__(self):
|
||||
self._id = 'clang'
|
||||
|
||||
@abstractmethod
|
||||
def has_header_symbol(
|
||||
self,
|
||||
header: str,
|
||||
symbol: str,
|
||||
args=None,
|
||||
dependencies=None,
|
||||
include_directories=None,
|
||||
no_builtin_args: bool = False,
|
||||
prefix=None,
|
||||
required: bool = False,
|
||||
) -> bool:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def check_header(self, header: str, prefix: str = '') -> bool:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def has_function(self, function, args=None, prefix='', dependencies='') -> bool:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def links(self, snippet: str, name: str, args=None, dependencies=None) -> bool:
|
||||
pass
|
||||
|
||||
def get_id(self):
|
||||
return self._id
|
||||
|
||||
def is_symbol_supported(self, header: str, symbol: str):
|
||||
if header == 'sys/mkdev.h' or symbol == 'program_invocation_name':
|
||||
return False
|
||||
return True
|
||||
|
||||
def is_function_supported(self, function: str):
|
||||
if (
|
||||
function == 'qsort_s'
|
||||
or function == 'pthread_setaffinity_np'
|
||||
or function == 'secure_getenv'
|
||||
):
|
||||
return False
|
||||
return True
|
||||
|
||||
def is_link_supported(self, name: str):
|
||||
if name == 'GNU qsort_r' or name == 'BSD qsort_r':
|
||||
return False
|
||||
return True
|
||||
|
||||
def is_header_supported(self, header: str):
|
||||
if (
|
||||
header == 'xlocale.h'
|
||||
or header == 'pthread_np.h'
|
||||
or header == 'renderdoc_app.h'
|
||||
):
|
||||
return False
|
||||
return True
|
||||
|
||||
def get_define(self, define: str, prefix: str):
|
||||
if define == 'ETIME':
|
||||
return define
|
||||
exit('Unhandled define: ' + define)
|
||||
|
||||
def get_supported_function_attributes(self, attributes: list[str]):
|
||||
# Assume all are supported
|
||||
return attributes
|
||||
|
||||
def has_function_attribute(self, attribute: str):
|
||||
return True
|
||||
|
||||
def has_argument(self, name: str):
|
||||
result = True
|
||||
print("has_argument '%s': %s" % (name, str(result)))
|
||||
return result
|
||||
|
||||
def has_link_argument(self, name: str):
|
||||
result = True
|
||||
print("has_link_argument '%s': %s" % (name, str(result)))
|
||||
return result
|
||||
|
||||
def compiles(self, snippet, name: str):
|
||||
# Exclude what is currently not working.
|
||||
result = True
|
||||
if name == '__uint128_t':
|
||||
result = False
|
||||
print("compiles '%s': %s" % (name, str(result)))
|
||||
return result
|
||||
|
||||
def has_member(self, struct, member, prefix):
|
||||
# Assume it does
|
||||
return True
|
||||
|
||||
def get_argument_syntax(self):
|
||||
return 'gcc'
|
||||
|
||||
def get_supported_arguments(self, args):
|
||||
supported_args = []
|
||||
for arg in args:
|
||||
if (
|
||||
arg.startswith('-flifetime-dse')
|
||||
or arg.startswith('-Wno-format-truncation')
|
||||
or arg.startswith('-Wno-nonnull-compare')
|
||||
or arg.startswith('-Wno-class-memaccess')
|
||||
or arg.startswith('-Wno-format-truncation')
|
||||
):
|
||||
continue
|
||||
supported_args.append(arg)
|
||||
return supported_args
|
||||
|
||||
def get_supported_link_arguments(self, args):
|
||||
return args
|
||||
|
||||
def find_library(self, name, required=False):
|
||||
if name == 'ws2_32' or name == 'elf' or name == 'm' or name == 'sensors':
|
||||
return Dependency(name, found=required)
|
||||
exit('Unhandled library: ' + name)
|
||||
|
||||
def sizeof(self, string):
|
||||
table = _get_sizeof_table()
|
||||
|
||||
if string not in table:
|
||||
exit('Unhandled compiler sizeof: ' + string)
|
||||
return table[string]
|
||||
|
||||
|
||||
class PkgConfigModule:
|
||||
def generate(self, lib, name='', description='', extra_cflags=None):
|
||||
pass
|
||||
|
||||
|
||||
###################################################################################################
|
||||
|
||||
|
||||
def fprint(args):
|
||||
print(args, file=_gOutputFile)
|
||||
|
||||
|
||||
def set_relative_dir(dir):
|
||||
global _gRelativeDir
|
||||
_gRelativeDir = dir
|
||||
|
||||
|
||||
def open_output_file(name):
|
||||
global _gOutputFile
|
||||
_gOutputFile = open(name, 'w')
|
||||
|
||||
|
||||
def close_output_file():
|
||||
global _gOutputFile
|
||||
_gOutputFile.close()
|
||||
|
||||
|
||||
def get_relative_dir(path_or_file=''):
|
||||
if isinstance(path_or_file, File):
|
||||
return path_or_file.name
|
||||
|
||||
assert isinstance(path_or_file, str)
|
||||
if path_or_file == '':
|
||||
return _gRelativeDir
|
||||
return os.path.join(_gRelativeDir, path_or_file)
|
||||
|
||||
|
||||
def get_relative_gen_dir(path=''):
|
||||
return os.path.join(_gRelativeDir, path)
|
||||
|
||||
|
||||
def project(name, language_list, version, license, meson_version, default_options):
|
||||
if type(version) is str:
|
||||
_gProjectVersion = version
|
||||
else:
|
||||
assert type(version) is list
|
||||
version_file = version[0]
|
||||
assert type(version_file) is File
|
||||
with open(version_file.name, 'r') as file:
|
||||
for line in file:
|
||||
_gProjectVersion = line.strip()
|
||||
break
|
||||
|
||||
for option in default_options:
|
||||
value_pair = option.split('=')
|
||||
_gProjectOptions.append(SimpleOption(value_pair[0], value_pair[1]))
|
||||
|
||||
|
||||
def get_project_options():
|
||||
return _gProjectOptions
|
||||
|
||||
|
||||
def load_config_file(filename):
|
||||
if not filename.endswith('.toml'):
|
||||
exit('Config file that is not .toml is not supported.')
|
||||
|
||||
with open(filename, 'rb') as f:
|
||||
data = tomllib.load(f)
|
||||
project_configs = data.get('project_config')
|
||||
if project_configs is None:
|
||||
exit(f'meson_options not defined in {filename}.')
|
||||
project_config = project_configs[0]
|
||||
# TODO(bpnguyen): Make so project config isn't hardcoded to pick the first set of configs
|
||||
host_machine_settings = project_config.get('host_machine')
|
||||
for key in host_machine_settings:
|
||||
match key:
|
||||
case 'cpu_family':
|
||||
cpu_fam = host_machine_settings.get(key)
|
||||
global _gCpuFamily
|
||||
_gCpuFamily = cpu_fam
|
||||
print(f'Config: cpu_family={_gCpuFamily}')
|
||||
case 'cpu':
|
||||
cpu = host_machine_settings.get(key)
|
||||
global _gCpu
|
||||
_gCpu = cpu
|
||||
print(f'Config: cpu={_gCpu}')
|
||||
case 'host_machine' | 'build_machine':
|
||||
continue
|
||||
case _: # Default case
|
||||
exit(f'Unhandled config key: {key}')
|
||||
|
||||
|
||||
def add_project_arguments(args, language=[], native=False):
|
||||
global _gProjectCflags, _gProjectCppflags
|
||||
if type(args) is not list:
|
||||
args = [args]
|
||||
for lang in language:
|
||||
for arg in args:
|
||||
if isinstance(arg, list):
|
||||
add_project_arguments(arg, language=language, native=native)
|
||||
continue
|
||||
assert isinstance(arg, str)
|
||||
if lang == 'c':
|
||||
print('cflags: ' + arg)
|
||||
_gProjectCflags.append(arg)
|
||||
elif lang == 'cpp':
|
||||
print('cppflags: ' + arg)
|
||||
_gProjectCppflags.append(arg)
|
||||
else:
|
||||
exit('Unhandle arguments language: ' + lang)
|
||||
|
||||
|
||||
def get_project_cflags():
|
||||
return _gProjectCflags
|
||||
|
||||
|
||||
def get_project_cppflags():
|
||||
return _gProjectCppflags
|
||||
|
||||
|
||||
def _get_sizeof_table():
|
||||
table_32 = {'void*': 4}
|
||||
table_64 = {'void*': 8}
|
||||
if _gCpuFamily == 'arm':
|
||||
table = table_32
|
||||
elif _gCpuFamily == 'aarch64':
|
||||
table = table_64
|
||||
else:
|
||||
exit('sizeof unhandled cpu family: %s' % _gCpuFamily)
|
||||
return table
|
||||
|
||||
|
||||
def get_linear_list(arg_list):
|
||||
args = []
|
||||
for arg in arg_list:
|
||||
if type(arg) is list:
|
||||
args.extend(get_linear_list(arg))
|
||||
else:
|
||||
args.append(arg)
|
||||
return args
|
||||
|
||||
|
||||
def load_dependencies(config):
|
||||
with open(config, 'rb') as f:
|
||||
data = tomllib.load(f)
|
||||
project_configs = data.get('project_config')
|
||||
for project_config in project_configs:
|
||||
dependencies = project_config.get('ext_dependencies')
|
||||
for dep_name, targets in dependencies.items():
|
||||
dep_targets = {
|
||||
t.get('target_name'): t.get('target_type') for t in targets
|
||||
}
|
||||
external_dep[dep_name] = dep_targets
|
||||
|
||||
|
||||
def dependency(*names, required=True, version=''):
|
||||
for name in names:
|
||||
print('dependency: %s' % name)
|
||||
if name == '':
|
||||
return Dependency('null', version, found=False)
|
||||
|
||||
if name in external_dep:
|
||||
targets = external_dep.get(name)
|
||||
return Dependency(
|
||||
name,
|
||||
targets=[
|
||||
DependencyTarget(t, DependencyTargetType(targets[t]))
|
||||
for t in targets
|
||||
],
|
||||
version=version,
|
||||
found=True,
|
||||
)
|
||||
|
||||
if (
|
||||
name == 'backtrace'
|
||||
or name == 'curses'
|
||||
or name == 'expat'
|
||||
or name == 'libconfig'
|
||||
or name == 'libmagma_virt'
|
||||
or name == 'libva'
|
||||
or name == 'libzstd'
|
||||
or name == 'libdrm'
|
||||
or name == 'libglvnd'
|
||||
or name == 'libudev'
|
||||
or name == 'libunwind'
|
||||
or name == 'llvm'
|
||||
or name == 'libxml-2.0'
|
||||
or name == 'lua54'
|
||||
or name == 'valgrind'
|
||||
or name == 'wayland-scanner'
|
||||
):
|
||||
return Dependency(name, version, found=False)
|
||||
|
||||
if (
|
||||
name == 'libarchive'
|
||||
or name == 'libelf'
|
||||
or name == 'threads'
|
||||
or name == 'vdpau'
|
||||
):
|
||||
return Dependency(name, version, found=required)
|
||||
|
||||
exit('Unhandled dependency: ' + name)
|
||||
|
||||
|
||||
def get_set_of_deps(deps, set_of_deps=set()):
|
||||
for dep in deps:
|
||||
if type(dep) is list:
|
||||
set_of_deps = get_set_of_deps(dep, set_of_deps)
|
||||
elif dep not in set_of_deps:
|
||||
set_of_deps.add(dep)
|
||||
set_of_deps = get_set_of_deps(dep.dependencies, set_of_deps)
|
||||
return set_of_deps
|
||||
|
||||
|
||||
def get_include_dirs(paths) -> list[str]:
|
||||
dir_list = []
|
||||
for path in paths:
|
||||
if type(path) is list:
|
||||
dir_list.extend(get_include_dirs(p for p in path))
|
||||
elif type(path) is IncludeDirectories:
|
||||
dir_list.extend(path.dirs)
|
||||
else:
|
||||
assert type(path) is str
|
||||
dir_list.append(get_relative_dir(path))
|
||||
return dir_list
|
||||
|
||||
|
||||
def get_include_directories(includes) -> list[IncludeDirectories]:
|
||||
dirs = []
|
||||
if type(includes) is list:
|
||||
for inc in includes:
|
||||
dirs.extend(get_include_directories(inc))
|
||||
elif type(includes) is IncludeDirectories:
|
||||
dirs.extend(includes)
|
||||
else:
|
||||
assert type(includes) is str
|
||||
exit('get_include_directories got string: %s' % includes)
|
||||
return dirs
|
||||
|
||||
|
||||
def get_static_libs(arg_list):
|
||||
libs = []
|
||||
for arg in arg_list:
|
||||
if type(arg) is list:
|
||||
libs.extend(get_static_libs(arg))
|
||||
else:
|
||||
assert type(arg) is StaticLibrary
|
||||
libs.extend(get_static_libs(arg.link_with))
|
||||
libs.append(arg)
|
||||
return libs
|
||||
|
||||
|
||||
def get_whole_static_libs(arg_list):
|
||||
libs = []
|
||||
for arg in arg_list:
|
||||
if type(arg) is list:
|
||||
libs.extend(get_whole_static_libs(arg))
|
||||
else:
|
||||
assert type(arg) is StaticLibrary
|
||||
libs.extend(get_whole_static_libs(arg._link_whole))
|
||||
libs.append(arg)
|
||||
return libs
|
||||
|
||||
|
||||
def get_list_of_relative_inputs(list_or_string):
|
||||
if isinstance(list_or_string, list):
|
||||
ret = []
|
||||
for item in list_or_string:
|
||||
ret.extend(get_list_of_relative_inputs(item))
|
||||
return ret
|
||||
|
||||
return [get_relative_dir(list_or_string)]
|
||||
|
||||
|
||||
def get_command_line_from_args(args: list):
|
||||
command_line = ''
|
||||
for arg in args:
|
||||
command_line += ' ' + arg
|
||||
# Escape angle brackets
|
||||
command_line = re.sub(r'(<|>)', '\\\\\\\\\g<1>', command_line)
|
||||
return command_line
|
||||
|
||||
|
||||
def replace_wrapped_input_with_target(args, python_script, python_script_target_name):
|
||||
outargs = []
|
||||
for index, arg in enumerate(args):
|
||||
pattern = '(.*?)(' + python_script + ')'
|
||||
replace = '\g<1>' + python_script_target_name
|
||||
outargs.append(re.sub(pattern, replace, arg))
|
||||
return outargs
|
||||
1463
meson_to_hermetic/meson_to_hermetic.py
Normal file
1463
meson_to_hermetic/meson_to_hermetic.py
Normal file
File diff suppressed because it is too large
Load diff
4
meson_to_hermetic/requirements.txt
Normal file
4
meson_to_hermetic/requirements.txt
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
lark>=1.1.9
|
||||
ruff>=0.5.4
|
||||
Mako>=1.3.5
|
||||
Jinja2>=3.1.4
|
||||
16
meson_to_hermetic/setup-venv.sh
Executable file
16
meson_to_hermetic/setup-venv.sh
Executable file
|
|
@ -0,0 +1,16 @@
|
|||
#!/bin/bash
|
||||
if [ -d "venv" ]; then
|
||||
echo "A venv folder already exists in this project!"
|
||||
exit 0
|
||||
fi
|
||||
if command -v python3 >/dev/null 2>&1; then
|
||||
echo "Currently creating a python virtual environment..."
|
||||
python3 -m venv venv
|
||||
source venv/bin/activate
|
||||
echo "Done creating new venv folder and terminal is now using venv."
|
||||
echo "Now attempting to install dependencies..."
|
||||
pip install -r requirements.txt
|
||||
echo "Successfully finished installing dependencies!"
|
||||
else
|
||||
echo "Python 3 is not currently installed on your machine!"
|
||||
fi
|
||||
63
meson_to_hermetic/templates/generate_python_build.txt
Normal file
63
meson_to_hermetic/templates/generate_python_build.txt
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
import sys
|
||||
import tomllib
|
||||
###
|
||||
########################################################################################################################
|
||||
###
|
||||
### Pull in the definitions meson is expecting
|
||||
###
|
||||
from meson_common import *
|
||||
from meson_to_hermetic import *
|
||||
###
|
||||
########################################################################################################################
|
||||
{{ meson_options }}
|
||||
###
|
||||
### Process command line arguments for setting options
|
||||
config_path = ''
|
||||
|
||||
if __name__ == "__main__":
|
||||
for arg in sys.argv[1:]:
|
||||
flag, value = arg.split('=') # args form follows a: -flag=value
|
||||
match flag:
|
||||
case ('--config' | '-c'):
|
||||
if not value.endswith('.toml'):
|
||||
exit(f'File format for {value} not supported. Please provide a valid file (toml).')
|
||||
with open(value, 'rb') as f:
|
||||
data = tomllib.load(f)
|
||||
config_path = value
|
||||
project_config = data.get('project_config')
|
||||
for config in project_config:
|
||||
meson_options = config.get('meson_options')
|
||||
for key in meson_options:
|
||||
set_option(key, meson_options[key])
|
||||
case _:
|
||||
exit(f'Unhandled arg={flag} with value={value}')
|
||||
|
||||
###
|
||||
### These definitions must be inside the module
|
||||
def get_variable(name: str):
|
||||
return globals()[name]
|
||||
|
||||
|
||||
### Load Metadata
|
||||
load_meson_data(config_path)
|
||||
|
||||
meson = impl.Meson(meson_translator.generator)
|
||||
host_machine = impl.Machine(meson_translator.host_machine)
|
||||
build_machine = impl.Machine(meson_translator.build_machine)
|
||||
|
||||
### Open the build definition file
|
||||
open_output_file()
|
||||
|
||||
|
||||
### Load config
|
||||
load_config_file()
|
||||
|
||||
|
||||
### Load dependencies
|
||||
load_dependencies()
|
||||
|
||||
|
||||
{{ meson_build }}
|
||||
### Close the build definition file
|
||||
close_output_file()
|
||||
{# Newline Placeholder #}
|
||||
Loading…
Add table
Add a link
Reference in a new issue