| #!/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()) |