Auf dieser Seite erhalten Sie einen allgemeinen Überblick darüber, wie generierter Quellcode unterstützt wird und wie er im Build-System verwendet werden kann.
Alle Quellgeneratoren bieten ähnliche Buildsystemfunktionen. Die drei vom Build-System unterstützten Anwendungsfälle für die Quellcode-Generierung sind das Generieren von C-Bindungen mit bindgen, AIDL-Schnittstellen und Protobuf-Schnittstellen.
Crates aus generiertem Quellcode
Jedes Rust-Modul, das Quellcode generiert, kann als Crate verwendet werden, genau wie wenn es als rust_library definiert wäre. Das bedeutet, dass sie als Abhängigkeit in den Attributen rustlibs, rlibs und dylibs definiert werden kann. Die beste Verwendung von Plattformcode ist die Verwendung von generiertem Quellcode als Crate. Das Makro include! wird zwar für generierten Quellcode unterstützt, sein Hauptzweck ist jedoch die Unterstützung von Drittanbietercode, der sich in external/ befindet.
Es gibt Fälle, in denen im Plattformcode möglicherweise weiterhin generierter Quellcode über das Makro include!() verwendet wird, z. B. wenn Sie ein genrule-Modul verwenden, um Quellcode auf einzigartige Weise zu generieren.
Mit „include!()“ generierten Quellcode einfügen
Die Verwendung des generierten Quellcodes als Crate wird in den Beispielen auf jeder Modulseite behandelt. In diesem Abschnitt wird gezeigt, wie Sie mit dem Makro include!() auf generierten Quellcode verweisen. Dieses Verfahren ist für alle Quellgeneratoren ähnlich.
Voraussetzung
In diesem Beispiel wird davon ausgegangen, dass Sie ein rust_bindgen-Modul (libbuzz_bindgen) definiert haben und mit den Schritten zum Einbinden der generierten Quelle für die Verwendung desinclude!()-Makros fortfahren können. Falls nicht, gehen Sie zu „Rust-Bindgen-Modul definieren“, erstellen Sie libbuzz_bindgen und kehren Sie dann hierher zurück.
Die Build-Datei-Abschnitte gelten für alle Quellgeneratoren.
Schritte zum Einbinden einer generierten Quelle
Erstellen Sie external/rust/hello_bindgen/Android.bp mit folgendem Inhalt:
rust_binary {
name: "hello_bzip_bindgen_include",
srcs: [
// The primary rust source file must come first in this list.
"src/lib.rs",
// The module providing the bindgen bindings is
// included in srcs prepended by ":".
":libbuzz_bindgen",
],
// Dependencies need to be redeclared when generated source is used via srcs.
shared_libs: [
"libbuzz",
],
}
Erstellen Sie external/rust/hello_bindgen/src/bindings.rs mit folgendem Inhalt:
#![allow(clippy::all)]
#![allow(non_upper_case_globals)]
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
#![allow(unused)]
#![allow(missing_docs)]
// Note that "bzip_bindings.rs" here must match the source_stem property from
// the rust_bindgen module.
include!(concat!(env!("OUT_DIR"), "/bzip_bindings.rs"));
Erstellen Sie external/rust/hello_bindgen/src/lib.rs mit folgendem Inhalt:
mod bindings;
fn main() {
let mut x = bindings::foo { x: 2 };
unsafe { bindings::fizz(1, &mut x as *mut bindings::foo) }
}
Warum Crates für generierten Quellcode?
Im Gegensatz zu C/C++-Compilern akzeptiert rustc nur eine einzelne Quelldatei, die einen Einstiegspunkt für eine Binärdatei oder Bibliothek darstellt. Dabei wird davon ausgegangen, dass der Quellbaum so strukturiert ist, dass alle erforderlichen Quelldateien automatisch erkannt werden können. Das bedeutet, dass die generierte Quelle entweder im Quellbaum platziert oder über eine Include-Anweisung in der Quelle bereitgestellt werden muss:
include!("/path/to/hello.rs");
Die Rust-Community ist auf build.rs-Scripts und Annahmen zur Cargo-Build-Umgebung angewiesen, um mit diesem Unterschied umzugehen.
Beim Erstellen wird mit dem Befehl cargo eine OUT_DIR-Umgebungsvariable festgelegt, in die build.rs-Skripts generierten Quellcode einfügen sollen. Verwenden Sie den folgenden Befehl, um Quellcode einzufügen:
include!(concat!(env!("OUT_DIR"), "/hello.rs"));
Dies stellt eine Herausforderung für Soong dar, da die Ausgaben für jedes Modul in einem eigenen out/-Verzeichnis1 platziert werden. Es gibt kein einzelnes OUT_DIR, in dem Abhängigkeiten ihren generierten Quellcode ausgeben.
Für Plattformcode wird in AOSP aus mehreren Gründen bevorzugt, generierten Quellcode in einem Crate zu verpacken, das importiert werden kann:
- Kollisionen von generierten Quelldateinamen verhindern
- Boilerplate-Code, der im gesamten Baum eingecheckt ist und gewartet werden muss, wird reduziert. Jeglicher Boilerplate-Code, der erforderlich ist, damit der generierte Quellcode in eine Kiste kompiliert werden kann, kann zentral verwaltet werden.
- Vermeiden Sie implizite2 Interaktionen zwischen generiertem Code und dem umgebenden Crate.
- Reduzieren Sie die Belastung von Arbeitsspeicher und Festplatte, indem Sie häufig verwendete generierte Quellen dynamisch verknüpfen.
Daher wird bei allen Android-Modultypen zur Rust-Quellcodegenerierung Code erzeugt, der als Crate kompiliert und verwendet werden kann.
Soong unterstützt weiterhin Drittanbieter-Crates ohne Änderungen, wenn alle generierten Quellabhängigkeiten für ein Modul in ein einzelnes Verzeichnis pro Modul kopiert werden, ähnlich wie bei Cargo. In solchen Fällen legt Soong die Umgebungsvariable OUT_DIR beim Kompilieren des Moduls auf dieses Verzeichnis fest, sodass die generierte Quelle gefunden werden kann.
Aus den bereits beschriebenen Gründen ist es jedoch am besten, diesen Mechanismus nur dann im Plattformcode zu verwenden, wenn es unbedingt erforderlich ist.
-
Für C/C++ und ähnliche Sprachen stellt dies kein Problem dar, da der Pfad zum generierten Quellcode direkt an den Compiler übergeben wird. ↩
-
Da
include!durch textuelle Einbeziehung funktioniert, kann es Werte aus dem einschließenden Namespace referenzieren, den Namespace ändern oder Konstrukte wie#![foo]verwenden. Diese impliziten Interaktionen können schwer aufrechtzuerhalten sein. Verwenden Sie immer Makros, wenn eine Interaktion mit dem Rest des Crate wirklich erforderlich ist. ↩