mirror of
https://github.com/RGBCube/serenity
synced 2025-07-28 14:07:45 +00:00
Meta: Add the start of a gn build based on the LLVM gn build
"based on" in this context means "largely copied from"
This commit is contained in:
parent
4a9a1d1656
commit
4bfb146181
19 changed files with 1225 additions and 0 deletions
170
Meta/gn/build/BUILD.gn
Normal file
170
Meta/gn/build/BUILD.gn
Normal file
|
@ -0,0 +1,170 @@
|
|||
import("//Meta/gn/build/buildflags.gni")
|
||||
import("//Meta/gn/build/mac_sdk.gni")
|
||||
import("//Meta/gn/build/sysroot.gni")
|
||||
import("//Meta/gn/build/toolchain/compiler.gni")
|
||||
|
||||
declare_args() {
|
||||
# If set, puts relative paths in debug info.
|
||||
# Makes the build output independent of the build directory, but makes
|
||||
# most debuggers harder to use. See "Getting to local determinism" and
|
||||
# "Getting debuggers to work well with locally deterministic builds" in
|
||||
# http://blog.llvm.org/2019/11/deterministic-builds-with-clang-and-lld.html
|
||||
# for more information.
|
||||
use_relative_paths_in_debug_info = false
|
||||
|
||||
# The version of host gcc. Ignored if is_clang is true.
|
||||
gcc_version = 12
|
||||
}
|
||||
|
||||
config("compiler_defaults") {
|
||||
defines = []
|
||||
asmflags = []
|
||||
cflags = []
|
||||
cflags_cc = []
|
||||
ldflags = []
|
||||
|
||||
if (symbol_level == 2) {
|
||||
cflags += [ "-g" ]
|
||||
|
||||
# For full debug-info -g builds, --gdb-index makes links ~15% slower, and
|
||||
# gdb symbol reading time 1500% faster (lld links in 4.4 instead of 3.9s,
|
||||
# and gdb loads and runs it in 2s instead of in 30s). It's likely that
|
||||
# people doing symbol_level=2 want to run a debugger (since
|
||||
# symbol_level=2 isn't the default). So this seems like the right
|
||||
# tradeoff.
|
||||
if (current_os != "mac" && use_lld) {
|
||||
cflags += [ "-ggnu-pubnames" ] # llvm PR34820
|
||||
ldflags += [ "-Wl,--gdb-index" ]
|
||||
|
||||
# Use debug fission. In this mode, detailed debug information is
|
||||
# written to a .dwo file next to each .o file instead of into the .o
|
||||
# file directly. The linker then only links the .o files, which contain
|
||||
# a pointer to each .dwo file. The debugger then reads debug info out
|
||||
# of all the .dwo files instead of from the binary.
|
||||
#
|
||||
# (The dwp tool can link all the debug info together into a single
|
||||
# "debug info binary", but that's not done as part of the build.)
|
||||
#
|
||||
# This requires `-Wl,--gdb-index` (above) to work well.
|
||||
#
|
||||
# With lld, this reduces link time:
|
||||
# - in release + symbol_level=2 builds: From 2.3s to 1.3s
|
||||
# - in debug builds: From 5.2s to 4.6s
|
||||
#
|
||||
# Time needed for gdb startup and setting a breakpoint is comparable,
|
||||
# the time from from `r` to hititng a breakpoint on main goes from 4s
|
||||
# to 2s.
|
||||
#
|
||||
# (macOS's linker always keeps debug info out of its output executables
|
||||
# and debuggers there also know to load debug info from the .o files.
|
||||
# macOS also has a debug info linker like dwp, it's called dsymutil.
|
||||
# This happens by default, so there's no need to pass a flag there.)
|
||||
cflags += [ "-gsplit-dwarf" ]
|
||||
ldflags += [ "-gsplit-dwarf" ] # Needed for ThinLTO builds.
|
||||
}
|
||||
} else if (symbol_level == 1) {
|
||||
cflags += [ "-g1" ]
|
||||
# For linetable-only -g1 builds, --gdb-index makes links ~8% slower, but
|
||||
# links are 4x faster than -g builds so it's a fairly small absolute cost.
|
||||
# On the other hand, gdb startup is well below 1s with and without the
|
||||
# index, and people using -g1 likely don't use a debugger. So don't use
|
||||
# the flag here.
|
||||
# Linetables always go in the .o file, even with -gsplit-dwarf, so there's
|
||||
# no point in passing -gsplit-dwarf here.
|
||||
}
|
||||
if (is_optimized) {
|
||||
cflags += [ "-O3" ]
|
||||
}
|
||||
cflags += [ "-fdiagnostics-color" ]
|
||||
|
||||
if (use_lld) {
|
||||
ldflags += [ "-Wl,--color-diagnostics" ]
|
||||
}
|
||||
cflags_cc += [
|
||||
"-std=c++20",
|
||||
"-fvisibility-inlines-hidden",
|
||||
]
|
||||
|
||||
# Warning setup.
|
||||
cflags += [
|
||||
"-Wall",
|
||||
"-Wextra",
|
||||
]
|
||||
cflags += [ "-Wno-unused-parameter" ]
|
||||
if (is_clang) {
|
||||
cflags += [
|
||||
"-Wdelete-non-virtual-dtor",
|
||||
"-Wstring-conversion",
|
||||
"-Wno-user-defined-literals",
|
||||
"-fconstexpr-steps=16777216",
|
||||
"-Wno-unused-private-field",
|
||||
]
|
||||
} else {
|
||||
cflags += [
|
||||
# Disable gcc's potentially uninitialized use analysis as it presents
|
||||
# lots of false positives.
|
||||
"-Wno-maybe-uninitialized",
|
||||
|
||||
# Disable -Wredundant-move on GCC>=9. GCC wants to remove std::move
|
||||
# in code like "A foo(ConvertibleToA a) { return std::move(a); }",
|
||||
# but this code does not compile (or uses the copy constructor
|
||||
# instead) on clang<=3.8. Clang also has a -Wredundant-move, but it
|
||||
# only fires when the types match exactly, so we can keep it here.
|
||||
"-Wno-redundant-move",
|
||||
]
|
||||
}
|
||||
|
||||
if (use_lld) {
|
||||
ldflags += [ "-fuse-ld=lld" ]
|
||||
}
|
||||
|
||||
# Deterministic build setup, see
|
||||
# http://blog.llvm.org/2019/11/deterministic-builds-with-clang-and-lld.html
|
||||
if (is_clang) {
|
||||
cflags += [
|
||||
"-no-canonical-prefixes",
|
||||
"-Werror=date-time",
|
||||
]
|
||||
if (use_relative_paths_in_debug_info) {
|
||||
cflags += [ "-fdebug-compilation-dir=." ]
|
||||
}
|
||||
}
|
||||
if (sysroot != "") {
|
||||
if (current_os != "mac" && current_os != "android") {
|
||||
cflags += [ "--sysroot=" + rebase_path(sysroot, root_build_dir) ]
|
||||
}
|
||||
}
|
||||
|
||||
if (current_os == "mac" && sysroot != "") {
|
||||
sdk_path = mac_sdk_path
|
||||
cflags += [
|
||||
"-isysroot",
|
||||
rebase_path(sdk_path, root_build_dir),
|
||||
]
|
||||
ldflags += [
|
||||
"-isysroot",
|
||||
rebase_path(sdk_path, root_build_dir),
|
||||
]
|
||||
}
|
||||
if (sysroot != "" && current_os != "win" && is_clang) {
|
||||
cflags += [ "-Wpoison-system-directories" ]
|
||||
}
|
||||
cflags_objcc = cflags_cc
|
||||
}
|
||||
|
||||
config("no_exceptions") {
|
||||
cflags_cc = [ "-fno-exceptions" ]
|
||||
cflags_objcc = cflags_cc
|
||||
}
|
||||
|
||||
config("zdefs") {
|
||||
if (current_os != "mac" && current_os != "android") {
|
||||
ldflags = [ "-Wl,-z,defs" ]
|
||||
}
|
||||
}
|
||||
|
||||
config("pic") {
|
||||
if (current_os != "mac") {
|
||||
cflags_cc = [ "-fPIC" ]
|
||||
}
|
||||
}
|
44
Meta/gn/build/BUILDCONFIG.gn
Normal file
44
Meta/gn/build/BUILDCONFIG.gn
Normal file
|
@ -0,0 +1,44 @@
|
|||
# All targets will get this list of configs by default.
|
||||
# Targets can opt out of a config by removing it from their local configs list.
|
||||
# If you're adding global flags and don't need targets to be able to opt out,
|
||||
# add the flags to compiler_defaults, not to a new config.
|
||||
shared_binary_target_configs = [
|
||||
"//Meta/gn/build:compiler_defaults",
|
||||
"//Meta/gn/build:no_exceptions",
|
||||
"//Meta/gn/build:pic",
|
||||
]
|
||||
|
||||
# Apply that default list to the binary target types.
|
||||
set_defaults("executable") {
|
||||
configs = shared_binary_target_configs
|
||||
}
|
||||
set_defaults("loadable_module") {
|
||||
configs = shared_binary_target_configs
|
||||
}
|
||||
set_defaults("static_library") {
|
||||
configs = shared_binary_target_configs
|
||||
}
|
||||
set_defaults("shared_library") {
|
||||
configs = shared_binary_target_configs + [ "//Meta/gn/build:zdefs" ]
|
||||
}
|
||||
set_defaults("source_set") {
|
||||
configs = shared_binary_target_configs
|
||||
}
|
||||
|
||||
if (target_os == "") {
|
||||
target_os = host_os
|
||||
}
|
||||
if (current_os == "") {
|
||||
current_os = target_os
|
||||
}
|
||||
|
||||
if (target_cpu == "") {
|
||||
target_cpu = host_cpu
|
||||
}
|
||||
if (current_cpu == "") {
|
||||
current_cpu = target_cpu
|
||||
}
|
||||
|
||||
host_toolchain = "//Meta/gn/build/toolchain:unix"
|
||||
|
||||
set_default_toolchain(host_toolchain)
|
18
Meta/gn/build/buildflags.gni
Normal file
18
Meta/gn/build/buildflags.gni
Normal file
|
@ -0,0 +1,18 @@
|
|||
declare_args() {
|
||||
# Build for debugging. Equivalent to is_optimized=false symbol_level=2.
|
||||
is_debug = false
|
||||
}
|
||||
|
||||
# args that depend on other args must live in a later declare_args() block.
|
||||
declare_args() {
|
||||
# Whether to build with optimizations.
|
||||
is_optimized = !is_debug
|
||||
|
||||
if (is_debug) {
|
||||
# Debug info symbol level. 0: No symbols; 1: Line numbers; 2: Full symbols.
|
||||
symbol_level = 2
|
||||
} else {
|
||||
# Debug info symbol level. 0: No symbols; 1: Line numbers; 2: Full symbols.
|
||||
symbol_level = 1
|
||||
}
|
||||
}
|
10
Meta/gn/build/libs/crypt/BUILD.gn
Normal file
10
Meta/gn/build/libs/crypt/BUILD.gn
Normal file
|
@ -0,0 +1,10 @@
|
|||
config("crypt_config") {
|
||||
visibility = [ ":crypt" ]
|
||||
libs = [ "crypt" ]
|
||||
}
|
||||
|
||||
group("crypt") {
|
||||
if (current_os == "linux") {
|
||||
public_configs = [ ":crypt_config" ]
|
||||
}
|
||||
}
|
11
Meta/gn/build/libs/pthread/BUILD.gn
Normal file
11
Meta/gn/build/libs/pthread/BUILD.gn
Normal file
|
@ -0,0 +1,11 @@
|
|||
config("pthread_config") {
|
||||
visibility = [ ":pthread" ]
|
||||
libs = [ "pthread" ]
|
||||
}
|
||||
|
||||
group("pthread") {
|
||||
# On Android, bionic has built-in support for pthreads.
|
||||
if (current_os != "android") {
|
||||
public_configs = [ ":pthread_config" ]
|
||||
}
|
||||
}
|
22
Meta/gn/build/mac_sdk.gni
Normal file
22
Meta/gn/build/mac_sdk.gni
Normal file
|
@ -0,0 +1,22 @@
|
|||
import("//Meta/gn/build/sysroot.gni")
|
||||
|
||||
if (sysroot == "") {
|
||||
declare_args() {
|
||||
# Set to true if you don't have Xcode installed, but do have the commandline
|
||||
# tools.
|
||||
mac_use_commandline_tools_sdk = false
|
||||
}
|
||||
|
||||
# Location of the mac sdk.
|
||||
# The correct way to do this is to call xcrun
|
||||
# (https://reviews.llvm.org/D70835), but that makes `gn gen` take twice as
|
||||
# long and almost everyone has Xcode installed. So require that people who
|
||||
# don't have it installed set a gn arg.
|
||||
if (mac_use_commandline_tools_sdk) {
|
||||
mac_sdk_path = "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk"
|
||||
} else {
|
||||
mac_sdk_path = "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk"
|
||||
}
|
||||
} else {
|
||||
mac_sdk_path = sysroot + "/MacOSX.sdk"
|
||||
}
|
4
Meta/gn/build/sysroot.gni
Normal file
4
Meta/gn/build/sysroot.gni
Normal file
|
@ -0,0 +1,4 @@
|
|||
declare_args() {
|
||||
# Path of sysroot to use.
|
||||
sysroot = ""
|
||||
}
|
161
Meta/gn/build/toolchain/BUILD.gn
Normal file
161
Meta/gn/build/toolchain/BUILD.gn
Normal file
|
@ -0,0 +1,161 @@
|
|||
unix_copy_command = "ln -f {{source}} {{output}} 2>/dev/null || (rm -rf {{output}} && cp -af {{source}} {{output}})"
|
||||
|
||||
template("unix_toolchain") {
|
||||
toolchain(target_name) {
|
||||
ar = "ar"
|
||||
cc = "cc"
|
||||
cxx = "c++"
|
||||
ld = cxx
|
||||
|
||||
forward_variables_from(invoker.toolchain_args, "*")
|
||||
forward_variables_from(invoker, "*")
|
||||
|
||||
tool("cc") {
|
||||
depfile = "{{output}}.d"
|
||||
command = "$cc -MMD -MF $depfile -o {{output}} -c {{source}} {{defines}} {{include_dirs}} {{cflags}} {{cflags_c}}"
|
||||
depsformat = "gcc"
|
||||
description = "CC {{output}}"
|
||||
outputs = [ "{{source_out_dir}}/{{label_name}}.{{source_name_part}}.o" ]
|
||||
}
|
||||
|
||||
tool("cxx") {
|
||||
depfile = "{{output}}.d"
|
||||
command = "$cxx -MMD -MF $depfile -o {{output}} -c {{source}} {{defines}} {{include_dirs}} {{cflags}} {{cflags_cc}}"
|
||||
depsformat = "gcc"
|
||||
description = "CXX {{output}}"
|
||||
outputs = [ "{{source_out_dir}}/{{label_name}}.{{source_name_part}}.o" ]
|
||||
}
|
||||
|
||||
tool("objcxx") {
|
||||
depfile = "{{output}}.d"
|
||||
command = "$cxx -MMD -MF $depfile -o {{output}} -c {{source}} {{defines}} {{include_dirs}} {{cflags}} {{cflags_objcc}}"
|
||||
depsformat = "gcc"
|
||||
description = "OBJCXX {{output}}"
|
||||
outputs = [ "{{source_out_dir}}/{{label_name}}.{{source_name_part}}.o" ]
|
||||
}
|
||||
|
||||
tool("asm") {
|
||||
depfile = "{{output}}.d"
|
||||
command = "$cc -MMD -MF $depfile -o {{output}} -c {{source}} {{defines}} {{include_dirs}} {{asmflags}}"
|
||||
depsformat = "gcc"
|
||||
description = "ASM {{output}}"
|
||||
outputs = [ "{{source_out_dir}}/{{label_name}}.{{source_name_part}}.o" ]
|
||||
}
|
||||
|
||||
tool("alink") {
|
||||
if (current_os == "ios" || current_os == "mac") {
|
||||
command = "libtool -D -static -no_warning_for_no_symbols {{arflags}} -o {{output}} {{inputs}}"
|
||||
not_needed([ "ar" ])
|
||||
} else {
|
||||
# Remove the output file first so that ar doesn't try to modify the
|
||||
# existing file.
|
||||
command =
|
||||
"rm -f {{output}} && $ar rcsD {{arflags}} {{output}} {{inputs}}"
|
||||
}
|
||||
description = "AR {{output}}"
|
||||
outputs = [ "{{output_dir}}/{{target_output_name}}.a" ]
|
||||
output_prefix = "lib"
|
||||
if (current_os != "serenity") {
|
||||
output_prefix = "liblagom-"
|
||||
}
|
||||
default_output_dir = "{{root_out_dir}}/lib"
|
||||
}
|
||||
|
||||
# Make these apply to all tools below.
|
||||
lib_switch = "-l"
|
||||
lib_dir_switch = "-L"
|
||||
|
||||
tool("solink") {
|
||||
outfile = "{{output_dir}}/{{target_output_name}}{{output_extension}}"
|
||||
if (current_os == "ios" || current_os == "mac") {
|
||||
command = "$ld -shared {{ldflags}} -o $outfile {{inputs}} {{libs}} {{frameworks}}"
|
||||
default_output_extension = ".dylib"
|
||||
} else {
|
||||
command = "$ld -shared {{ldflags}} -Wl,-soname,{{target_output_name}}{{output_extension}} -Wl,-rpath,\\\$ORIGIN -o $outfile {{inputs}} {{libs}}"
|
||||
default_output_extension = ".so"
|
||||
}
|
||||
description = "SOLINK $outfile"
|
||||
outputs = [ outfile ]
|
||||
output_prefix = "lib"
|
||||
if (current_os != "serenity") {
|
||||
output_prefix = "liblagom-"
|
||||
}
|
||||
default_output_dir = "{{root_out_dir}}/lib"
|
||||
}
|
||||
|
||||
tool("solink_module") {
|
||||
outfile = "{{output_dir}}/{{target_output_name}}{{output_extension}}"
|
||||
if (current_os == "ios" || current_os == "mac") {
|
||||
command = "$ld -shared {{ldflags}} -Wl,-flat_namespace -Wl,-undefined,suppress -o $outfile {{inputs}} {{libs}} {{frameworks}}"
|
||||
default_output_extension = ".dylib"
|
||||
} else {
|
||||
command = "$ld -shared {{ldflags}} -Wl,-soname,{{target_output_name}}{{output_extension}} -Wl,-rpath,\\\$ORIGIN -o $outfile {{inputs}} {{libs}}"
|
||||
default_output_extension = ".so"
|
||||
}
|
||||
description = "SOLINK $outfile"
|
||||
outputs = [ outfile ]
|
||||
if (current_os != "serenity") {
|
||||
output_prefix = "lagom-"
|
||||
}
|
||||
default_output_dir = "{{root_out_dir}}/lib"
|
||||
}
|
||||
|
||||
tool("link") {
|
||||
outfile = "{{output_dir}}/{{target_output_name}}{{output_extension}}"
|
||||
if (current_os == "ios" || current_os == "mac") {
|
||||
command =
|
||||
"$ld {{ldflags}} -o $outfile {{inputs}} {{libs}} {{frameworks}}"
|
||||
} else {
|
||||
command = "$ld {{ldflags}} -o $outfile -Wl,--start-group {{inputs}} -Wl,--end-group -Wl,-rpath,\\\$ORIGIN/../lib {{libs}}"
|
||||
}
|
||||
description = "LINK $outfile"
|
||||
outputs = [ outfile ]
|
||||
|
||||
# Setting this allows targets to override the default executable output by
|
||||
# setting output_dir.
|
||||
default_output_dir = "{{root_out_dir}}/bin"
|
||||
}
|
||||
|
||||
tool("copy") {
|
||||
command = unix_copy_command
|
||||
description = "COPY {{source}} {{output}}"
|
||||
}
|
||||
|
||||
if (current_os == "ios" || current_os == "mac") {
|
||||
tool("copy_bundle_data") {
|
||||
# https://github.com/nico/hack/blob/master/notes/copydir.md
|
||||
_copydir = "cd {{source}} && " +
|
||||
"find . | cpio -pdl \"\$OLDPWD\"/{{output}} 2>/dev/null"
|
||||
command = "rm -rf {{output}} && if [[ -d {{source}} ]]; then " +
|
||||
_copydir + "; else " + unix_copy_command + "; fi"
|
||||
description = "COPY_BUNDLE_DATA {{source}} {{output}}"
|
||||
}
|
||||
tool("compile_xcassets") {
|
||||
command = "false"
|
||||
description = "The serenity build doesn't use any xcasset files"
|
||||
}
|
||||
}
|
||||
|
||||
tool("stamp") {
|
||||
command = "touch {{output}}"
|
||||
description = "STAMP {{output}}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
declare_args() {
|
||||
# C compiler for native builds
|
||||
host_cc = "cc"
|
||||
|
||||
# C++ compiler for native builds
|
||||
host_cxx = "c++"
|
||||
}
|
||||
|
||||
unix_toolchain("unix") {
|
||||
toolchain_args = {
|
||||
current_os = host_os
|
||||
cc = host_cc
|
||||
cxx = host_cxx
|
||||
}
|
||||
}
|
||||
# Note: For serenity, we can override cc and cxx etc in toolchain_args
|
8
Meta/gn/build/toolchain/compiler.gni
Normal file
8
Meta/gn/build/toolchain/compiler.gni
Normal file
|
@ -0,0 +1,8 @@
|
|||
declare_args() {
|
||||
# Set to true when host compiler is clang so that clang flags will be passed to it
|
||||
is_clang = host_os == "mac"
|
||||
|
||||
# Enable setting -fuse-ld and other flags for using the lld linker.
|
||||
# Should not be set on macOS when using the default system compiler.
|
||||
use_lld = false
|
||||
}
|
50
Meta/gn/build/write_cmake_config.gni
Normal file
50
Meta/gn/build/write_cmake_config.gni
Normal file
|
@ -0,0 +1,50 @@
|
|||
# This file introduces a template for calling write_cmake_config.py.
|
||||
#
|
||||
# write_cmake_config behaves like CMake's configure_file(), but runs at build
|
||||
# time, not at generator time. See write_cmake_config.py for details.
|
||||
#
|
||||
# Parameters:
|
||||
#
|
||||
# input (required) [string]
|
||||
#
|
||||
# output (required) [string]
|
||||
#
|
||||
# values (required) [list of strings]
|
||||
# Each entry is a '='-separated key-value pair used for substitution.
|
||||
#
|
||||
# Example use:
|
||||
#
|
||||
# write_cmake_config("attributes_compat_func_gen") {
|
||||
# input = "Version.inc.in"
|
||||
# output = "$root_gen_dir/clang/include/clang/Basic/Version.inc",
|
||||
# values = [
|
||||
# "CLANG_VERSION=$llvm_version",
|
||||
# ]
|
||||
# }
|
||||
|
||||
template("write_cmake_config") {
|
||||
assert(defined(invoker.input), "must set 'input' in $target_name")
|
||||
assert(defined(invoker.output), "must set 'output' in $target_name")
|
||||
assert(defined(invoker.values), "must set 'values' in $target_name")
|
||||
|
||||
action(target_name) {
|
||||
script = "//Meta/gn/build/write_cmake_config.py"
|
||||
|
||||
sources = [ invoker.input ]
|
||||
outputs = [ invoker.output ]
|
||||
args = [
|
||||
"-o",
|
||||
rebase_path(outputs[0], root_build_dir),
|
||||
rebase_path(sources[0], root_build_dir),
|
||||
] + invoker.values
|
||||
|
||||
forward_variables_from(invoker,
|
||||
[
|
||||
"configs",
|
||||
"deps",
|
||||
"public_configs",
|
||||
"public_deps",
|
||||
"visibility",
|
||||
])
|
||||
}
|
||||
}
|
121
Meta/gn/build/write_cmake_config.py
Normal file
121
Meta/gn/build/write_cmake_config.py
Normal file
|
@ -0,0 +1,121 @@
|
|||
#!/usr/bin/env python3
|
||||
r"""Emulates the bits of CMake's configure_file() function needed in serenity.
|
||||
|
||||
The CMake build uses configure_file() for several things. This emulates that
|
||||
function for the GN build. In the GN build, this runs at build time instead
|
||||
of at generator time.
|
||||
|
||||
Takes a list of KEY=VALUE pairs (where VALUE can be empty).
|
||||
|
||||
The sequence `\` `n` in each VALUE is replaced by a newline character.
|
||||
|
||||
On each line, replaces '${KEY}' or '@KEY@' with VALUE.
|
||||
|
||||
Then, handles these special cases (note that FOO= sets the value of FOO to the
|
||||
empty string, which is falsy, but FOO=0 sets it to '0' which is truthy):
|
||||
|
||||
1.) #cmakedefine01 FOO
|
||||
Checks if key FOO is set to a truthy value, and depending on that prints
|
||||
one of the following two lines:
|
||||
|
||||
#define FOO 1
|
||||
#define FOO 0
|
||||
|
||||
2.) #cmakedefine FOO [...]
|
||||
Checks if key FOO is set to a truthy value, and depending on that prints
|
||||
one of the following two lines:
|
||||
|
||||
#define FOO [...]
|
||||
/* #undef FOO */
|
||||
|
||||
Fails if any of the KEY=VALUE arguments aren't needed for processing the
|
||||
input file, or if the input file references keys that weren't passed in.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
epilog=__doc__,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter)
|
||||
parser.add_argument('input', help='input file')
|
||||
parser.add_argument('values', nargs='*', help='several KEY=VALUE pairs')
|
||||
parser.add_argument('-o', '--output', required=True,
|
||||
help='output file')
|
||||
args = parser.parse_args()
|
||||
|
||||
values = {}
|
||||
for value in args.values:
|
||||
key, val = value.split('=', 1)
|
||||
if key in values:
|
||||
print('duplicate key "%s" in args' % key, file=sys.stderr)
|
||||
return 1
|
||||
values[key] = val.replace('\\n', '\n')
|
||||
unused_values = set(values.keys())
|
||||
|
||||
# Matches e.g. '${FOO}' or '@FOO@' and captures FOO in group 1 or 2.
|
||||
var_re = re.compile(r'\$\{([^}]*)\}|@([^@]*)@')
|
||||
|
||||
with open(args.input) as f:
|
||||
in_lines = f.readlines()
|
||||
out_lines = []
|
||||
for in_line in in_lines:
|
||||
def repl(m):
|
||||
key = m.group(1) or m.group(2)
|
||||
unused_values.discard(key)
|
||||
return values[key]
|
||||
in_line = var_re.sub(repl, in_line)
|
||||
if in_line.startswith('#cmakedefine01 ') or in_line.startswith("# cmakedefine01"):
|
||||
in_line = in_line.replace('# cmakedefine01', '#cmakedefine01')
|
||||
_, var = in_line.split()
|
||||
if values[var] == '0':
|
||||
print('error: "%s=0" used with #cmakedefine01 %s' % (var, var))
|
||||
print(" '0' evaluates as truthy with #cmakedefine01")
|
||||
print(' use "%s=" instead' % var)
|
||||
return 1
|
||||
in_line = '#define %s %d\n' % (var, 1 if values[var] else 0)
|
||||
unused_values.discard(var)
|
||||
elif in_line.startswith('#cmakedefine '):
|
||||
_, var = in_line.split(None, 1)
|
||||
try:
|
||||
var, val = var.split(None, 1)
|
||||
in_line = '#define %s %s' % (var, val) # val ends in \n.
|
||||
except _:
|
||||
var = var.rstrip()
|
||||
in_line = '#define %s\n' % var
|
||||
if not values[var]:
|
||||
in_line = '/* #undef %s */\n' % var
|
||||
unused_values.discard(var)
|
||||
out_lines.append(in_line)
|
||||
|
||||
if unused_values:
|
||||
print('unused values args:', file=sys.stderr)
|
||||
print(' ' + '\n '.join(unused_values), file=sys.stderr)
|
||||
return 1
|
||||
|
||||
output = ''.join(out_lines)
|
||||
|
||||
leftovers = var_re.findall(output)
|
||||
if leftovers:
|
||||
print(
|
||||
'unprocessed values:\n',
|
||||
'\n'.join([x[0] or x[1] for x in leftovers]),
|
||||
file=sys.stderr)
|
||||
return 1
|
||||
|
||||
def read(filename):
|
||||
with open(filename) as f:
|
||||
return f.read()
|
||||
|
||||
if not os.path.exists(args.output) or read(args.output) != output:
|
||||
with open(args.output, 'w') as f:
|
||||
f.write(output)
|
||||
os.chmod(args.output, os.stat(args.input).st_mode & 0o777)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
Loading…
Add table
Add a link
Reference in a new issue