blob: 7f5a879f49b7c517096e7a2173862d43d327fd37 [file]
#!/usr/bin/env fuchsia-vendored-python
# Copyright 2023 The Fuchsia Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import argparse
import os
import re
import subprocess
import sys
import tempfile
# All other paths are relative to here (main changes to this directory on startup).
ROOT_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", ".."))
INITIAL_CWD = os.getcwd()
FUCHSIA_NOTICE_HEADER = """// Copyright %s The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
"""
def post_process_args(args):
if args.extra_clang_flags:
assert (
args.extra_clang_flags[0] == "--"
), "extra_clang_flags should always be passed after --"
args.extra_clang_flags.pop(0)
# Process complex structures
replacements = []
if args.replacement:
for r in args.replacement:
if len(r) == 1:
replacements.append([r[0], ""])
elif len(r) == 2:
replacements.append([r[0], r[1]])
else:
raise ValueError(
f"Invalid replacement: {r}. Expected 1 or 2 arguments.",
)
args.compiled_replacements = [
(re.compile(x[0]), x[1]) for x in replacements
]
class Bindgen:
def __init__(self, args):
self.args = args
def run_bindgen(self, input_files, output_file, depfile_out=None):
if len(input_files) > 1:
output_dir = os.path.dirname(os.path.abspath(output_file))
with tempfile.TemporaryDirectory(
dir=output_dir,
suffix="_bindgen_tmp",
) as tmpdir:
wrapper_file = os.path.join(tmpdir, "wrapper.h")
with open(wrapper_file, "w") as f:
f.writelines(
f'#include "{header}"\n' for header in input_files
)
abs_wrapper_file = os.path.abspath(wrapper_file)
self._run_bindgen_impl(
abs_wrapper_file,
output_file,
depfile_out,
wrapper_file_to_filter=abs_wrapper_file,
)
else:
self._run_bindgen_impl(input_files[0], output_file, depfile_out)
def _run_bindgen_impl(
self,
input_file_for_bindgen,
output_file,
depfile_out=None,
wrapper_file_to_filter=None,
):
# Bindgen arguments.
if not self.args.notice_year:
raise ValueError("notice_year is required")
raw_lines = FUCHSIA_NOTICE_HEADER % self.args.notice_year
if self.args.generator_label:
raw_lines += f"// This file is automatically generated by {self.args.generator_label}.\n\n"
if self.args.expect_lint:
for lint in self.args.expect_lint:
raw_lines += f"#![expect({lint})]\n"
if self.args.raw_line:
raw_lines += "\n"
if isinstance(self.args.raw_line, list):
raw_lines += "\n".join(self.args.raw_line)
else:
raw_lines += self.args.raw_line
args = [
self.args.bindgen,
"--raw-line",
raw_lines,
"-o",
output_file,
]
if self.args.rust_version:
args += ["--rust-target", self.args.rust_version]
if self.args.rust_edition:
args += ["--rust-edition", self.args.rust_edition]
if not self.args.layout_tests:
args.append("--no-layout-tests")
if self.args.explicit_padding:
args += ["--explicit-padding"]
args.append("--disable-header-comment")
if self.args.use_core:
args.append("--use-core")
args.append("--wrap-unsafe-ops")
# We do not pass --rustfmt or --rustfmt-configuration-file to bindgen
# here because we run rustfmt ourselves in the run() method after
# post-processing. We explicitly tell bindgen to use no formatter.
args += [
"--formatter",
"none",
]
args += [
"--allowlist-function=" + x for x in self.args.allowlist_function
]
args += ["--allowlist-var=" + x for x in self.args.allowlist_var]
args += ["--allowlist-type=" + x for x in self.args.allowlist_type]
args += [
"--blocklist-function=" + x for x in self.args.blocklist_function
]
args += ["--blocklist-type=" + x for x in self.args.blocklist_type]
args += ["--blocklist-var=" + x for x in self.args.blocklist_var]
args += ["--opaque-type=" + x for x in self.args.opaque_type]
args += ["--no-debug=" + x for x in self.args.no_debug]
args += ["--rustified-enum=" + x for x in self.args.rustified_enum]
args += ["--impl-" + x for x in self.args.std_impl]
args += ["--with-derive-" + x for x in self.args.std_derive]
for item in self.args.auto_derive_trait:
args += ["--with-derive-custom", item]
args += self.args.bindgen_flag
args += [input_file_for_bindgen]
if depfile_out:
depfile_path = depfile_out
else:
tmp_depfile = tempfile.NamedTemporaryFile(delete=False)
depfile_path = tmp_depfile.name
tmp_depfile.close()
args += [
"--depfile",
depfile_path,
]
# Clang arguments (after the "--").
args += [
"--",
"-DIS_BINDGEN=1",
]
if self.args.clang_target:
args += ["-target", self.args.clang_target]
if not self.args.clang_resource_dir:
raise ValueError("clang_resource_dir is required")
args += ["-resource-dir", self.args.clang_resource_dir]
if self.args.fuchsia_api_level:
args += [f"-D__Fuchsia_API_level__={self.args.fuchsia_api_level}"]
for i in self.args.include_dir:
args += ["-I", i]
args += self.args.extra_clang_flags
subprocess.check_call(
args,
env={"RUSTFMT": self.args.rustfmt},
)
with open(depfile_path) as f:
depfile_contents = f.read()
if depfile_out:
parts = depfile_contents.split(":")
if len(parts) >= 2:
target = parts[0].strip()
deps = (":".join(parts[1:])).replace("\\\n", " ").split()
fixed_deps = []
for dep in deps:
if dep == "\\":
continue
if os.path.isabs(dep):
abs_dep = os.path.abspath(dep)
else:
abs_dep = os.path.abspath(
os.path.join(INITIAL_CWD, dep),
)
if (
wrapper_file_to_filter
and abs_dep == wrapper_file_to_filter
):
continue
if abs_dep.startswith(ROOT_PATH):
rel_dep = os.path.relpath(abs_dep, INITIAL_CWD)
fixed_deps.append(rel_dep)
if not target.startswith("/"):
abs_target = os.path.abspath(
os.path.join(INITIAL_CWD, target),
)
target = os.path.relpath(abs_target, INITIAL_CWD)
else:
target = os.path.relpath(target, INITIAL_CWD)
with open(depfile_out, "w") as f:
f.write(f"{target}: {' '.join(fixed_deps)}\n")
else:
os.unlink(depfile_path)
def post_process_rust_file(self, rust_file_name):
with open(rust_file_name, "r+") as source_file:
text = source_file.read()
for regexp, replacement in self.args.compiled_replacements:
text = regexp.sub(replacement, text)
source_file.seek(0)
source_file.truncate()
source_file.write(text)
def run(self):
self.run_bindgen(self.args.input, self.args.output, self.args.depfile)
# We must format the file before post-processing because our replacements
# and auto-derive logic expect formatted code layout (e.g. predictable
# spacing and newlines in derives and struct definitions).
cmd = [self.args.rustfmt, self.args.output]
cmd += ["--config-path", os.path.join(ROOT_PATH, "rustfmt.toml")]
cmd += ["--unstable-features", "--skip-children"]
subprocess.check_call(cmd)
self.post_process_rust_file(self.args.output)
# We format the code again here. This ensures that formatting is
# applied to the final file after all our post-processing (like raw_lines
# and replacements) have been applied.
subprocess.check_call(cmd)
def main():
parser = argparse.ArgumentParser(description="Run bindgen")
parser.add_argument(
"--input",
required=True,
action="append",
type=os.path.abspath,
help="Input C header file",
)
parser.add_argument(
"--output",
required=True,
type=os.path.abspath,
help="Output Rust file",
)
parser.add_argument(
"--depfile",
type=os.path.abspath,
help="Output depfile path",
)
parser.add_argument(
"--bindgen",
required=True,
type=os.path.abspath,
help="Path to bindgen binary",
)
parser.add_argument(
"--rustfmt",
required=True,
type=os.path.abspath,
help="Path to rustfmt binary",
)
parser.add_argument(
"--clang-resource-dir",
required=True,
type=os.path.abspath,
help="Path to clang resource directory",
)
# Bindgen configuration options (previously in JSON config)
parser.add_argument(
"--clang-target",
help="Clang: Compilation target (--target)",
)
parser.add_argument(
"--rust-version",
help="Rust version to target (maps to bindgen --rust-target)",
)
parser.add_argument(
"--rust-edition",
help="Rust edition to target (maps to bindgen --rust-edition)",
)
parser.add_argument(
"--expect-lint",
action="append",
default=[],
help="Lints to expect in the generated code.",
)
parser.add_argument(
"--notice-year",
type=int,
help="Notice year to use for the copyright header.",
)
parser.add_argument(
"--generator-label",
help="Label of the GN target that generated this file.",
)
parser.add_argument(
"--raw-line",
action="append",
default=[],
help="Additional raw lines of Rust code to add to the beginning of the generated output.",
)
parser.add_argument(
"--explicit-padding",
action=argparse.BooleanOptionalAction,
default=True,
help="Whether to generate explicit padding fields in structs.",
)
parser.add_argument(
"--layout-tests",
action=argparse.BooleanOptionalAction,
default=False,
help="Whether to generate layout tests.",
)
parser.add_argument(
"--include-dir",
action="append",
type=os.path.abspath,
default=[],
help="Clang: Include directories (-I)",
)
parser.add_argument(
"--std-impl",
action="append",
default=[],
help="Generate implementations for standard traits when not auto-derivable (--impl-foo)",
)
parser.add_argument(
"--std-derive",
action="append",
default=[],
help="Standard derivations (--with-derive-foo)",
)
parser.add_argument(
"--auto-derive-trait",
action="append",
default=[],
help="Add extra traits to derive on generated structs/unions. Only applies to pub struct/pub union that already have a #[derive()] line.",
)
parser.add_argument(
"--replacement",
nargs="+",
action="append",
default=[],
help="Pairs of (regex, str) replacements to apply to generated output.",
)
parser.add_argument(
"--allowlist-function",
action="append",
default=[],
help="Allowlist all the free-standing functions matching regexes. Other non-allowlisted functions will not be generated.",
)
parser.add_argument(
"--allowlist-var",
action="append",
default=[],
help="Allowlist all the free-standing variables matching regexes. Other non-allowlisted variables will not be generated.",
)
parser.add_argument(
"--allowlist-type",
action="append",
default=[],
help="Allowlist all the free-standing types matching regexes. Other non-allowlisted types will not be generated.",
)
parser.add_argument(
"--blocklist-function",
action="append",
default=[],
help="Mark functions as hidden, to omit them from generated code.",
)
parser.add_argument(
"--blocklist-type",
action="append",
default=[],
help="Mark types as hidden, to omit them from generated code.",
)
parser.add_argument(
"--blocklist-var",
action="append",
default=[],
help="Mark variables as hidden, to omit them from generated code.",
)
parser.add_argument(
"--opaque-type",
action="append",
default=[],
help="Mark types as opaque blobs in generated code.",
)
parser.add_argument(
"--no-debug",
action="append",
default=[],
help="Avoid deriving/implementing Debug for types matching regexes.",
)
parser.add_argument(
"--rustified-enum",
action="append",
default=[],
help="Generate enums matching regexes as Rust enums.",
)
parser.add_argument(
"--use-core",
action=argparse.BooleanOptionalAction,
default=False,
help="Use types from Rust core instead of std.",
)
parser.add_argument(
"--bindgen-flag",
action="append",
default=[],
help="Additional flags to pass directly to bindgen.",
)
parser.add_argument(
"--fuchsia-api-level",
default="",
help="Clang: Define __Fuchsia_API_level__.",
)
parser.add_argument(
"extra_clang_flags",
nargs=argparse.REMAINDER,
default=[],
help="Additional arguments forwarded to clang (must begin with --)",
)
args = parser.parse_args()
post_process_args(args)
bindgen = Bindgen(args)
bindgen.run()
if __name__ == "__main__":
sys.exit(main())