mirror of
https://github.com/lvgl/lvgl.git
synced 2026-05-10 04:37:55 +08:00
arch(cmake): add native Kconfig support to cmake (#7934)
This commit is contained in:
Executable
+109
@@ -0,0 +1,109 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
#
|
||||
# Generate the cmake variables CONFIG_LV_USE_* from the
|
||||
# preprocessed lv_conf_internal.h
|
||||
#
|
||||
# Author: David TRUAN (david.truan@edgemtech.ch)
|
||||
#
|
||||
|
||||
import os
|
||||
import argparse
|
||||
|
||||
def fatal(msg):
|
||||
print()
|
||||
print("ERROR! " + msg)
|
||||
exit(1)
|
||||
|
||||
def get_args():
|
||||
parser = argparse.ArgumentParser(formatter_class=argparse.RawTextHelpFormatter, description=""
|
||||
"Convert the expanded lv_conf_internal.h to cmake variables."
|
||||
"It converts all LV_USE_* configurations."
|
||||
)
|
||||
|
||||
parser.add_argument('--input', type=str, required=True, nargs='?',
|
||||
help='Path of the macro expanded lv_conf_internal.h, which should be generated during a cmake build')
|
||||
|
||||
parser.add_argument('--output', type=str, required=True, nargs='?',
|
||||
help='Path of the output file, where the cmake variables declaration will be written (ex: build/lv_conf.cmake)')
|
||||
|
||||
parser.add_argument("--kconfig", action="store_true", help="Enable kconfig flag")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# The input must exist
|
||||
if not os.path.exists(args.input):
|
||||
fatal(f"Input {args.input} not found")
|
||||
|
||||
return args
|
||||
|
||||
def generate_cmake_variables(path_input: str, path_output: str, kconfig: bool):
|
||||
fin = open(path_input)
|
||||
fout = open(path_output, "w", newline='')
|
||||
|
||||
# If we use Kconfig, we must check the CONFIG_LV_USE_* defines
|
||||
if kconfig:
|
||||
CONFIG_PATTERN="#define CONFIG_LV_USE"
|
||||
CONFIG_PREFIX=""
|
||||
# Otherwise check the LV_USE_* defines
|
||||
else:
|
||||
CONFIG_PATTERN="#define LV_USE"
|
||||
CONFIG_PREFIX="CONFIG_"
|
||||
|
||||
|
||||
# Using the expanded lv_conf_internal, we don't have to deal with regexp,
|
||||
# as all the #define will be aligned on the left with a single space before the value
|
||||
for line in fin.read().splitlines():
|
||||
|
||||
# Treat the LV_USE_STDLIB_* configs in a special way, as we need
|
||||
# to convert the define to full config with 1 value when enabled
|
||||
if line.startswith(f'{CONFIG_PATTERN}_STDLIB'):
|
||||
|
||||
parts = line.split()
|
||||
if len(parts) < 3:
|
||||
continue
|
||||
|
||||
name = parts[1]
|
||||
value = parts[2].strip()
|
||||
|
||||
type = value.split("LV_STDLIB_")[1]
|
||||
|
||||
name = name.replace("STDLIB", type)
|
||||
|
||||
fout.write(f'set({CONFIG_PREFIX}{name} 1)\n')
|
||||
|
||||
# Treat the LV_USE_OS config in a special way, as we need
|
||||
# to convert the define to full config with 1 value when enabled
|
||||
if line.startswith(f'{CONFIG_PATTERN}_OS'):
|
||||
|
||||
parts = line.split()
|
||||
if len(parts) < 3:
|
||||
continue
|
||||
|
||||
name = parts[1]
|
||||
value = parts[2].strip()
|
||||
|
||||
type = value.split("LV_OS")[1]
|
||||
|
||||
name += type
|
||||
|
||||
fout.write(f'set({CONFIG_PREFIX}{name} 1)\n')
|
||||
|
||||
# For the rest of config, simply add CONFIG_ and write
|
||||
# all LV_USE_* configs, as these are the one needed in cmake
|
||||
elif line.startswith(f'{CONFIG_PATTERN}'):
|
||||
|
||||
parts = line.split()
|
||||
if len(parts) < 3:
|
||||
continue
|
||||
|
||||
name = parts[1]
|
||||
value = parts[2].strip()
|
||||
|
||||
fout.write(f'set({CONFIG_PREFIX}{name} {value})\n')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
args = get_args()
|
||||
|
||||
generate_cmake_variables(args.input, args.output, args.kconfig)
|
||||
@@ -1,4 +1,4 @@
|
||||
vcpkg install vcpkg-tool-ninja libpng freetype opengl glfw3 glew
|
||||
if %errorlevel% neq 0 exit /b %errorlevel%
|
||||
pip install pypng lz4 kconfiglib
|
||||
pip install pypng lz4 kconfiglib pcpp
|
||||
if %errorlevel% neq 0 exit /b %errorlevel%
|
||||
|
||||
@@ -14,4 +14,4 @@ sudo apt install gcc gcc-multilib g++-multilib ninja-build \
|
||||
ruby-full gcovr cmake python3 libinput-dev libxkbcommon-dev \
|
||||
libdrm-dev pkg-config wayland-protocols libwayland-dev libwayland-bin \
|
||||
libwayland-dev:i386 libxkbcommon-dev:i386 libudev-dev
|
||||
pip3 install pypng lz4 kconfiglib
|
||||
pip3 install pypng lz4 kconfiglib pcpp
|
||||
|
||||
Executable
+346
@@ -0,0 +1,346 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# Originally modified from:
|
||||
# https://github.com/zephyrproject-rtos/zephyr/blob/main/scripts/kconfig/kconfig.py
|
||||
|
||||
# SPDX-License-Identifier: ISC
|
||||
|
||||
# Writes/updates the lvgl/.config configuration file by merging configuration
|
||||
# files passed as arguments
|
||||
#
|
||||
# When fragments haven't changed, lvgl/.config is both the input and the
|
||||
# output, which just updates it. This is handled in the CMake files.
|
||||
#
|
||||
# Also does various checks (most via Kconfiglib warnings).
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import textwrap
|
||||
|
||||
# Lvgl doesn't use tristate symbols. They're supported here just to make the
|
||||
# script a bit more generic.
|
||||
from kconfiglib import (
|
||||
Kconfig,
|
||||
split_expr,
|
||||
expr_value,
|
||||
expr_str,
|
||||
BOOL,
|
||||
TRISTATE,
|
||||
TRI_TO_STR,
|
||||
AND,
|
||||
OR,
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
print(sys.argv)
|
||||
args = parse_args()
|
||||
|
||||
print("Parsing " + args.kconfig_file)
|
||||
kconf = Kconfig(args.kconfig_file, warn_to_stderr=False, suppress_traceback=True)
|
||||
|
||||
if args.handwritten_input_configs:
|
||||
# Warn for assignments to undefined symbols, but only for handwritten
|
||||
# fragments, to avoid warnings-turned-errors when using an old
|
||||
# configuration file together with updated Kconfig files
|
||||
kconf.warn_assign_undef = True
|
||||
|
||||
# prj.conf may override settings from the board configuration, so
|
||||
# disable warnings about symbols being assigned more than once
|
||||
kconf.warn_assign_override = False
|
||||
kconf.warn_assign_redun = False
|
||||
|
||||
if args.forced_input_configs:
|
||||
# Do not warn on a redundant config.
|
||||
# The reason is that a regular .config will be followed by the forced
|
||||
# config which under normal circumstances should be identical to the
|
||||
# configured setting.
|
||||
# Only if user has modified to a value that gets overruled by the forced
|
||||
# a warning shall be issued.
|
||||
kconf.warn_assign_redun = False
|
||||
|
||||
# Load files
|
||||
print(kconf.load_config(args.configs_in[0]))
|
||||
for config in args.configs_in[1:]:
|
||||
# replace=False creates a merged configuration
|
||||
print(kconf.load_config(config, replace=False))
|
||||
|
||||
if args.handwritten_input_configs:
|
||||
# Check that there are no assignments to promptless symbols, which
|
||||
# have no effect.
|
||||
#
|
||||
# This only makes sense when loading handwritten fragments and not when
|
||||
# loading lvgl/.config, because lvgl/.config is configuration
|
||||
# output and also assigns promptless symbols.
|
||||
check_no_promptless_assign(kconf)
|
||||
|
||||
# Print warnings for symbols that didn't get the assigned value. Only
|
||||
# do this for handwritten input too, to avoid likely unhelpful warnings
|
||||
# when using an old configuration and updating Kconfig files.
|
||||
check_assigned_sym_values(kconf)
|
||||
check_assigned_choice_values(kconf)
|
||||
|
||||
if kconf.syms.get("WARN_DEPRECATED", kconf.y).tri_value == 2:
|
||||
check_deprecated(kconf)
|
||||
|
||||
if kconf.syms.get("WARN_EXPERIMENTAL", kconf.y).tri_value == 2:
|
||||
check_experimental(kconf)
|
||||
|
||||
# Hack: Force all symbols to be evaluated, to catch warnings generated
|
||||
# during evaluation. Wait till the end to write the actual output files, so
|
||||
# that we don't generate any output if there are warnings-turned-errors.
|
||||
#
|
||||
# Kconfiglib caches calculated symbol values internally, so this is still
|
||||
# fast.
|
||||
kconf.write_config(os.devnull)
|
||||
|
||||
warn_only = r"warning:.*set more than once."
|
||||
|
||||
if kconf.warnings:
|
||||
if args.forced_input_configs:
|
||||
error_out = False
|
||||
else:
|
||||
error_out = True
|
||||
|
||||
# Put a blank line between warnings to make them easier to read
|
||||
for warning in kconf.warnings:
|
||||
print("\n" + warning, file=sys.stderr)
|
||||
|
||||
if not error_out and not re.search(warn_only, warning):
|
||||
# The warning is not a warn_only, fail the Kconfig.
|
||||
error_out = True
|
||||
|
||||
# Turn all warnings into errors, so that e.g. assignments to undefined
|
||||
# Kconfig symbols become errors.
|
||||
#
|
||||
# A warning is generated by this script whenever a symbol gets a
|
||||
# different value than the one it was assigned. Keep that one as just a
|
||||
# warning for now.
|
||||
if error_out:
|
||||
err("Aborting due to Kconfig warnings")
|
||||
|
||||
# Write the merged configuration and the C header
|
||||
print(kconf.write_config(args.config_out))
|
||||
print(kconf.write_autoconf(args.header_out))
|
||||
|
||||
# Write the list of parsed Kconfig files to a file
|
||||
write_kconfig_filenames(kconf, args.kconfig_list_out)
|
||||
|
||||
|
||||
def check_no_promptless_assign(kconf):
|
||||
# Checks that no promptless symbols are assigned
|
||||
|
||||
for sym in kconf.unique_defined_syms:
|
||||
if sym.user_value is not None and promptless(sym):
|
||||
err(
|
||||
f"""\
|
||||
{sym.name_and_loc} is assigned in a configuration file, but is not directly
|
||||
user-configurable (has no prompt). It gets its value indirectly from other
|
||||
symbols. """
|
||||
+ SYM_INFO_HINT.format(sym)
|
||||
)
|
||||
|
||||
|
||||
def check_assigned_sym_values(kconf):
|
||||
# Verifies that the values assigned to symbols "took" (matches the value
|
||||
# the symbols actually got), printing warnings otherwise. Choice symbols
|
||||
# are checked separately, in check_assigned_choice_values().
|
||||
|
||||
for sym in kconf.unique_defined_syms:
|
||||
if sym.choice:
|
||||
continue
|
||||
|
||||
user_value = sym.user_value
|
||||
if user_value is None:
|
||||
continue
|
||||
|
||||
# Tristate values are represented as 0, 1, 2. Having them as "n", "m",
|
||||
# "y" is more convenient here, so convert.
|
||||
if sym.type in (BOOL, TRISTATE):
|
||||
user_value = TRI_TO_STR[user_value]
|
||||
|
||||
if user_value != sym.str_value:
|
||||
msg = (
|
||||
f"{sym.name_and_loc} was assigned the value '{user_value}'"
|
||||
f" but got the value '{sym.str_value}'. "
|
||||
)
|
||||
|
||||
# List any unsatisfied 'depends on' dependencies in the warning
|
||||
mdeps = missing_deps(sym)
|
||||
if mdeps:
|
||||
expr_strs = []
|
||||
for expr in mdeps:
|
||||
estr = expr_str(expr)
|
||||
if isinstance(expr, tuple):
|
||||
# Add () around dependencies that aren't plain symbols.
|
||||
# Gives '(FOO || BAR) (=n)' instead of
|
||||
# 'FOO || BAR (=n)', which might be clearer.
|
||||
estr = f"({estr})"
|
||||
expr_strs.append(f"{estr} " f"(={TRI_TO_STR[expr_value(expr)]})")
|
||||
|
||||
msg += (
|
||||
"Check these unsatisfied dependencies: "
|
||||
+ ", ".join(expr_strs)
|
||||
+ ". "
|
||||
)
|
||||
|
||||
warn(msg + SYM_INFO_HINT.format(sym))
|
||||
|
||||
|
||||
def missing_deps(sym):
|
||||
# check_assigned_sym_values() helper for finding unsatisfied dependencies.
|
||||
#
|
||||
# Given direct dependencies
|
||||
#
|
||||
# depends on <expr> && <expr> && ... && <expr>
|
||||
#
|
||||
# on 'sym' (which can also come from e.g. a surrounding 'if'), returns a
|
||||
# list of all <expr>s with a value less than the value 'sym' was assigned
|
||||
# ("less" instead of "not equal" just to be general and handle tristates,
|
||||
# even though lvgl doesn't use them).
|
||||
#
|
||||
# For string/int/hex symbols, just looks for <expr> = n.
|
||||
#
|
||||
# Note that <expr>s can be something more complicated than just a symbol,
|
||||
# like 'FOO || BAR' or 'FOO = "string"'.
|
||||
|
||||
deps = split_expr(sym.direct_dep, AND)
|
||||
|
||||
if sym.type in (BOOL, TRISTATE):
|
||||
return [dep for dep in deps if expr_value(dep) < sym.user_value]
|
||||
# string/int/hex
|
||||
return [dep for dep in deps if expr_value(dep) == 0]
|
||||
|
||||
|
||||
def check_assigned_choice_values(kconf):
|
||||
# Verifies that any choice symbols that were selected (by setting them to
|
||||
# y) ended up as the selection, printing warnings otherwise.
|
||||
#
|
||||
# We check choice symbols separately to avoid warnings when two different
|
||||
# choice symbols within the same choice are set to y. This might happen if
|
||||
# a choice selection from a board defconfig is overridden in a prj.conf,
|
||||
# for example. The last choice symbol set to y becomes the selection (and
|
||||
# all other choice symbols get the value n).
|
||||
#
|
||||
# Without special-casing choices, we'd detect that the first symbol set to
|
||||
# y ended up as n, and print a spurious warning.
|
||||
|
||||
for choice in kconf.unique_choices:
|
||||
if choice.user_selection and choice.user_selection is not choice.selection:
|
||||
|
||||
warn(
|
||||
f"""\
|
||||
The choice symbol {choice.user_selection.name_and_loc} was selected (set =y),
|
||||
but {choice.selection.name_and_loc if choice.selection else "no symbol"} ended
|
||||
up as the choice selection. """
|
||||
+ SYM_INFO_HINT.format(choice.user_selection)
|
||||
)
|
||||
|
||||
|
||||
# Hint on where to find symbol information. Used like
|
||||
# SYM_INFO_HINT.format(sym).
|
||||
SYM_INFO_HINT = """\
|
||||
See https://docs.lvgl.io/master/intro/add-lvgl-to-your-project/configuration.html
|
||||
look up {0.name} in the menuconfig/guiconfig interface. The Application
|
||||
Development Primer, Setting Configuration Values, and Kconfig - Tips and Best
|
||||
Practices sections of the manual might be helpful too.\
|
||||
"""
|
||||
|
||||
|
||||
def check_deprecated(kconf):
|
||||
deprecated = kconf.syms.get("DEPRECATED")
|
||||
dep_expr = kconf.n if deprecated is None else deprecated.rev_dep
|
||||
|
||||
if dep_expr is not kconf.n:
|
||||
selectors = [s for s in split_expr(dep_expr, OR) if expr_value(s) == 2]
|
||||
for selector in selectors:
|
||||
selector_name = split_expr(selector, AND)[0].name
|
||||
warn(f"Deprecated symbol {selector_name} is enabled.")
|
||||
|
||||
|
||||
def check_experimental(kconf):
|
||||
experimental = kconf.syms.get("EXPERIMENTAL")
|
||||
dep_expr = kconf.n if experimental is None else experimental.rev_dep
|
||||
|
||||
if dep_expr is not kconf.n:
|
||||
selectors = [s for s in split_expr(dep_expr, OR) if expr_value(s) == 2]
|
||||
for selector in selectors:
|
||||
selector_name = split_expr(selector, AND)[0].name
|
||||
warn(f"Experimental symbol {selector_name} is enabled.")
|
||||
|
||||
|
||||
def promptless(sym):
|
||||
# Returns True if 'sym' has no prompt. Since the symbol might be defined in
|
||||
# multiple locations, we need to check all locations.
|
||||
|
||||
return not any(node.prompt for node in sym.nodes)
|
||||
|
||||
|
||||
def write_kconfig_filenames(kconf, kconfig_list_path):
|
||||
# Writes a sorted list with the absolute paths of all parsed Kconfig files
|
||||
# to 'kconfig_list_path'. The paths are realpath()'d, and duplicates are
|
||||
# removed. This file is used by CMake to look for changed Kconfig files. It
|
||||
# needs to be deterministic.
|
||||
|
||||
with open(kconfig_list_path, "w") as out:
|
||||
for path in sorted(
|
||||
{
|
||||
os.path.realpath(os.path.join(kconf.srctree, path))
|
||||
for path in kconf.kconfig_filenames
|
||||
}
|
||||
):
|
||||
print(path, file=out)
|
||||
|
||||
|
||||
def parse_args():
|
||||
parser = argparse.ArgumentParser(allow_abbrev=False)
|
||||
|
||||
parser.add_argument(
|
||||
"--handwritten-input-configs",
|
||||
action="store_true",
|
||||
help="Assume the input configuration fragments are "
|
||||
"handwritten fragments and do additional checks "
|
||||
"on them, like no promptless symbols being "
|
||||
"assigned",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--forced-input-configs",
|
||||
action="store_true",
|
||||
help="Indicate the input configuration files are "
|
||||
"followed by an forced configuration file."
|
||||
"The forced configuration is used to forcefully "
|
||||
"set specific configuration settings to a "
|
||||
"pre-defined value and thereby remove any user "
|
||||
" adjustments.",
|
||||
)
|
||||
parser.add_argument("kconfig_file", help="Top-level Kconfig file")
|
||||
parser.add_argument("config_out", help="Output configuration file")
|
||||
parser.add_argument("header_out", help="Output header file")
|
||||
parser.add_argument(
|
||||
"kconfig_list_out", help="Output file for list of parsed Kconfig files"
|
||||
)
|
||||
parser.add_argument(
|
||||
"configs_in",
|
||||
nargs="+",
|
||||
help="Input configuration fragments. Will be merged " "together.",
|
||||
)
|
||||
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def warn(msg):
|
||||
# Use a large fill() width to try to avoid linebreaks in the symbol
|
||||
# reference link, and add some extra newlines to set the message off from
|
||||
# surrounding text (this usually gets printed as part of spammy CMake
|
||||
# output)
|
||||
print("\n" + textwrap.fill("warning: " + msg, 100) + "\n", file=sys.stderr)
|
||||
|
||||
|
||||
def err(msg):
|
||||
sys.exit("\n" + textwrap.fill("error: " + msg, 100) + "\n")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -214,12 +214,21 @@ LV_EXPORT_CONST_INT(LV_DRAW_BUF_ALIGN);
|
||||
#define LV_USE_MEM_MONITOR 0
|
||||
#endif /*LV_USE_SYSMON*/
|
||||
|
||||
|
||||
#ifndef LV_USE_LZ4
|
||||
#define LV_USE_LZ4 (LV_USE_LZ4_INTERNAL || LV_USE_LZ4_EXTERNAL)
|
||||
#if (LV_USE_LZ4_INTERNAL || LV_USE_LZ4_EXTERNAL)
|
||||
#define LV_USE_LZ4 1
|
||||
#else
|
||||
#define LV_USE_LZ4 0
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifndef LV_USE_THORVG
|
||||
#define LV_USE_THORVG (LV_USE_THORVG_INTERNAL || LV_USE_THORVG_EXTERNAL)
|
||||
#if (LV_USE_THORVG_INTERNAL || LV_USE_THORVG_EXTERNAL)
|
||||
#define LV_USE_THORVG 1
|
||||
#else
|
||||
#define LV_USE_THORVG 0
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if LV_USE_OS
|
||||
|
||||
Executable
+96
@@ -0,0 +1,96 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
#
|
||||
# Preprocess the lv_conf_internal.h to generate a header file
|
||||
# containing the evaluated definitions. This output will be used to
|
||||
# generate the cmake variables
|
||||
#
|
||||
# Author: David TRUAN (david.truan@edgemtech.ch)
|
||||
#
|
||||
|
||||
import subprocess
|
||||
import os
|
||||
import argparse
|
||||
import re
|
||||
|
||||
def get_args():
|
||||
parser = argparse.ArgumentParser(description="Preprocess a C header file and remove indentation.")
|
||||
parser.add_argument("--input", help="Path to the input C header file", required=True)
|
||||
parser.add_argument("--tmp_file", help="Path to save the preprocessed output", required=True)
|
||||
parser.add_argument("--output", help="Path to save the cleaned output with removed indentation", required=True)
|
||||
|
||||
parser.add_argument(
|
||||
"--defs",
|
||||
nargs='+',
|
||||
default=[],
|
||||
help="Definitions to be added to pcpp (flag -D)"
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--include",
|
||||
nargs='+',
|
||||
default=[],
|
||||
help="Paths to include directories for the preprocessor (flag -I)"
|
||||
)
|
||||
|
||||
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def preprocess_file(input_file, tmp_file, output_file, include_dirs, defs):
|
||||
|
||||
try:
|
||||
pcpp_command = ["pcpp", "-o", tmp_file, "--passthru-defines", "--line-directive=", input_file]
|
||||
|
||||
for include_path in include_dirs:
|
||||
pcpp_command.append(f"-I{include_path}")
|
||||
|
||||
for definition in defs:
|
||||
pcpp_command.append(f"-D{definition}")
|
||||
|
||||
subprocess.run(pcpp_command, check=True)
|
||||
print(f"Preprocessing completed. Output saved to {tmp_file}")
|
||||
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f"Error during preprocessing: {e}")
|
||||
exit(1)
|
||||
|
||||
|
||||
def remove_indentation(tmp_file, output_file):
|
||||
|
||||
try:
|
||||
with open(tmp_file, "r") as f:
|
||||
lines = f.readlines()
|
||||
|
||||
clean_lines = []
|
||||
for line in lines:
|
||||
stripped = line.lstrip()
|
||||
|
||||
# Remove extra spaces after #
|
||||
if stripped.startswith("#"):
|
||||
stripped = re.sub(r"^#\s+", "#", stripped)
|
||||
|
||||
clean_lines.append(stripped)
|
||||
|
||||
with open(output_file, "w") as f:
|
||||
f.writelines(clean_lines)
|
||||
|
||||
print(f"Indentation removed. Cleaned output saved to {output_file}")
|
||||
|
||||
os.remove(tmp_file)
|
||||
print(f"Temporary preprocessed file {tmp_file} removed.")
|
||||
except Exception as e:
|
||||
print(f"Error during indentation removal: {e}")
|
||||
exit(1)
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
args = get_args()
|
||||
|
||||
preprocess_file(args.input, args.tmp_file, args.output, args.include, args.defs)
|
||||
|
||||
remove_indentation(args.tmp_file, args.output)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user