blob: 3eb3da6d24cce247a339300a7180fa13b7edc4d0 [file] [log] [blame]
#!/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 datetime
import os
import re
import subprocess
# All other paths are relative to here (main changes to this directory on startup).
ROOT_PATH = os.path.join(os.path.dirname(__file__), "..", "..")
RUSTFMT_PATH = "prebuilt/third_party/rust/linux-x64/bin/rustfmt"
BINDGEN_PATH = "prebuilt/third_party/rust_bindgen/linux-x64/bindgen"
FX_PATH = "scripts/fx"
FUCHSIA_NOTICE_HEADER = (
"""// Copyright %d 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.
"""
% datetime.datetime.now().year
)
GENERATED_FILE_HEADER = """#![allow(dead_code)]
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
#![allow(non_upper_case_globals)]
#![allow(clippy::missing_safety_doc)]
"""
# Replacements to add to these defined by the user.
BASE_REPLACEMENTS = [
# Remove alignment field that use u8
(re.compile(r"pub _bitfield_align_[0-9]*: \[u8; 0\],\n"), ""),
]
# The `HEAD` API level.
#
# NOTE(hjfreyer): It's not totally clear that the value of HEAD is the
# right value here. If some parts of the C structures are missing,
# we may want to investigate setting this to a different value like
# `PLATFORM`.
FUCHSIA_API_LEVEL_HEAD = 4292870144
class Bindgen:
def __init__(self):
# Clang: Compilation target (`--target`)
self.clang_target = "x86_64-unknown-linux-gnu"
# Use the given PREFIX before raw types instead of ::std::os::raw.
self.c_types_prefix = ""
# Notice header to place at the beginning of the file.
self.notice_header = FUCHSIA_NOTICE_HEADER
# Whether to generate !#[allow(...)] directives.
self.generate_allows = True
# Additional raw lines of Rust code to add to the beginning of the generated output.
self.raw_lines = ""
# Whether to generate explicit padding fields in structs.
self.explicit_padding = True
# Whether size_t must be converted to usize
self.size_t_is_usize = True
# Mark types as an an opaque blob of bytes with a size and alignment.
self.opaque_types = []
# Clang: Include directories (`-I`)
self.include_dirs = []
# Generate implementations for standard traits when not auto-derivable (`--impl-foo`)
self.std_impls = []
# Standard derivations (`--with-derive-foo`)
self.std_derives = []
# Add extra traits to derive on generated structs/unions.
# Only applies to `pub struct`/`pub union` that already have a #[derive()] line.
self.auto_derive_traits = []
# Pairs of (regex, str) replacements to apply to generated output.
self.replacements = BASE_REPLACEMENTS
# Do not generate bindings for given functions or methods.
self.ignore_functions = False
# Allowlist all the free-standing functions matching regexes.
# Other non-allowlisted functions will not be generated.
self.function_allowlist = []
# Allowlist all the free-standing variables matching regexes.
# Other non-allowlisted variables will not be generated.
self.var_allowlist = []
# Allowlist all the free-standing types matching regexes.
# Other non-allowlisted types will not be generated.
self.type_allowlist = []
# Mark functions as hidden, to omit them from generated code.
self.function_blocklist = []
# Mark variables as hidden, to omit them from generated code.
self.var_blocklist = []
# Mark types as hidden, to omit them from generated code.
self.type_blocklist = []
# Avoid deriving/implementing Debug for types matching regexes.
self.no_debug_types = []
# Avoid deriving/implementing Copy for types matching regexes.
self.no_copy_types = []
# Avoid deriving/implementing Default for types matching regexes.
self.no_default_types = []
# Use types from Rust core instead of std.
self.use_core = False
# Additional flags to pass directly to bindgen.
self.additional_bindgen_flags = []
# Clang: Enable standard #include directories for the C++ standard library
self.enable_stdlib_include_dirs = True
# Clang: Define `__Fuchsia_API_level__`.
self.fuchsia_api_level = ""
# Clang: Additional command line flags to pass to clang.
self.additional_clang_flags = []
def set_auto_derive_traits(self, traits_map):
self.auto_derive_traits = [(re.compile(x[0]), x[1]) for x in traits_map]
def set_replacements(self, replacements):
self.replacements = [
(re.compile(x[0]), x[1]) for x in replacements
] + BASE_REPLACEMENTS
def run_bindgen(self, input_file, output_file):
# Bindgen arguments.
raw_lines = self.notice_header
if self.generate_allows:
raw_lines += GENERATED_FILE_HEADER
raw_lines += self.raw_lines
args = [
BINDGEN_PATH,
"--no-layout-tests",
"--raw-line",
raw_lines,
"-o",
output_file,
]
if self.explicit_padding:
args += ["--explicit-padding"]
if self.ignore_functions:
args.append("--ignore-functions")
if self.c_types_prefix:
args.append("--ctypes-prefix=" + self.c_types_prefix)
if self.use_core:
args.append("--use-core")
if not self.size_t_is_usize:
args.append("--no-size_t-is-usize")
args += ["--allowlist-function=" + x for x in self.function_allowlist]
args += ["--allowlist-var=" + x for x in self.var_allowlist]
args += ["--allowlist-type=" + x for x in self.type_allowlist]
args += ["--blocklist-function=" + x for x in self.function_blocklist]
args += ["--blocklist-var=" + x for x in self.var_blocklist]
args += ["--blocklist-type=" + x for x in self.type_blocklist]
args += ["--opaque-type=" + x for x in self.opaque_types]
args += ["--no-debug=" + x for x in self.no_debug_types]
args += ["--no-copy=" + x for x in self.no_copy_types]
args += ["--no-default=" + x for x in self.no_default_types]
args += ["--impl-" + x for x in self.std_impls]
args += ["--with-derive-" + x for x in self.std_derives]
args += self.additional_bindgen_flags
args += [input_file]
# Clang arguments (after the "--").
args += [
"--",
"-target",
self.clang_target,
"-DIS_BINDGEN=1",
]
if not self.enable_stdlib_include_dirs:
args += ["-nostdlibinc"]
if self.fuchsia_api_level:
args += [f"-D__Fuchsia_API_level__={self.fuchsia_api_level}"]
for i in self.include_dirs:
args += ["-I", i]
args += ["-I", "."]
args += self.additional_clang_flags
subprocess.check_call(
args,
env={"RUSTFMT": os.path.abspath(RUSTFMT_PATH)},
)
def get_auto_derive_traits(self, line):
"""Returns true if the given line defines a Rust structure with a name
matching any of the types we need to add FromBytes."""
if not (
line.startswith("pub struct ") or line.startswith("pub union ")
):
return None
# The third word (after the "pub struct") is the type name.
split = re.split(r"[ <\(]", line)
if len(split) < 3:
return None
type_name = split[2]
results = set()
for t, traits in self.auto_derive_traits:
if t.match(type_name):
results.update(traits)
if len(results) == 0:
return None
else:
return sorted(results)
def post_process_rust_file(self, rust_file_name):
with open(rust_file_name, "r+") as source_file:
input_lines = source_file.readlines()
output_lines = []
for line in input_lines:
extra_traits = self.get_auto_derive_traits(line)
if extra_traits:
# Parse existing traits, if any.
if len(output_lines) > 0 and output_lines[-1].startswith(
"#[derive(",
):
traits = output_lines[-1][9:-3].split(", ")
traits.extend(
x for x in extra_traits if x not in traits
)
output_lines.pop()
else:
traits = extra_traits
output_lines.append(
"#[derive(" + ", ".join(traits) + ")]\n",
)
output_lines.append(line)
text = "".join(output_lines)
for regexp, replacement in self.replacements:
text = regexp.sub(replacement, text)
source_file.seek(0)
source_file.truncate()
source_file.write(text)
def run(self, input_file, rust_file):
os.chdir(ROOT_PATH)
self.run_bindgen(input_file, rust_file)
self.post_process_rust_file(rust_file)
subprocess.check_call([FX_PATH, "format-code", "--files=" + rust_file])