Generatory źródeł

Na tej stronie znajdziesz ogólne informacje o tym, jak obsługiwany jest wygenerowany kod źródłowy i jak można go używać w systemie kompilacji.

Wszystkie generatory źródeł oferują podobne funkcje systemu kompilacji. Trzy obsługiwane przez system kompilacji przypadki użycia generowania kodu źródłowego to generowanie połączeń C za pomocą bindgen, interfejsów AIDL i interfejsów protobuf.

Zbiór danych generowanych przez źródło

Każdy moduł Rust, który generuje kod źródłowy, może służyć jako ramka, dokładnie w takiej postaci, w jakiej byłby zdefiniowany jako rust_library. Oznacza to, że można go zdefiniować jako zależność we właściwościach rustlibs, rlibsdylibs. Najlepszym wzorcem użycia kodu platformy jest użycie wygenerowanego źródła jako pliku crate. Chociaż makro include! jest obsługiwane w przypadku wygenerowanego źródła, jego głównym przeznaczeniem jest obsługa kodu innej firmy znajdującego się w external/.

W niektórych przypadkach kod platformy nadal może używać wygenerowanego źródła za pomocą makra include!() – na przykład gdy używasz modułu genrule do wygenerowania źródła w unikalny sposób.

Użyj funkcji include!(), aby uwzględnić wygenerowane źródło

Korzystanie z wygenerowanego źródła jako kontenera jest opisane na stronach poszczególnych modułów. W tej sekcji dowiesz się, jak odwoływać się do wygenerowanego źródła za pomocą makra include!(). Pamiętaj, że ten proces jest podobny w przypadku wszystkich generatorów źródeł.

Warunek wstępny

Ten przykład opiera się na założeniu, że masz zdefiniowany moduł rust_bindgen (libbuzz_bindgen), więc możesz przejść do instrukcji dołączania wygenerowanego źródła w celu użycia makra include!(). Jeśli nie, przejdź do sekcji Defining a rust bindgen module (Definiowanie modułu Rust bindgen)libbuzz_bindgen, a potem wróć tutaj.

Pamiętaj, że te części pliku kompilacji dotyczą wszystkich generatorów źródeł.

Instrukcje dotyczące uwzględniania wygenerowanego źródła

Utwórz plik external/rust/hello_bindgen/Android.bp z tą zawartością:

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",
    ],
}

Utwórz plik external/rust/hello_bindgen/src/bindings.rs z tą zawartością:

#![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"));

Utwórz plik external/rust/hello_bindgen/src/lib.rs z tą zawartością:

mod bindings;

fn main() {
    let mut x = bindings::foo { x: 2 };
    unsafe { bindings::fizz(1, &mut x as *mut bindings::foo) }
}

Dlaczego warto używać krat do generowanych źródeł

W przeciwieństwie do kompilatorów C/C++ kompilator rustc akceptuje tylko 1 plik źródłowy reprezentujący punkt wejścia do pliku binarnego lub biblioteki. Oczekuje, że drzewo źródeł jest tak sformatowane, aby można było automatycznie wykryć wszystkie wymagane pliki źródłowe. Oznacza to, że wygenerowane źródło należy umieścić w drzewie źródłowym lub udostępnić za pomocą dyrektywy include w źródle:

include!("/path/to/hello.rs");

Społeczność Rust korzysta ze skryptów build.rs i zakładań dotyczących środowiska kompilacji Cargo, aby zarządzać tą różnicą. Podczas kompilacji polecenie cargo ustawia zmienną środowiskową OUT_DIR, w której skrypty build.rs mają umieszczać wygenerowany kod źródłowy. Aby uwzględnić kod źródłowy, użyj tego polecenia:

include!(concat!(env!("OUT_DIR"), "/hello.rs"));

Jest to problem dla Soong, ponieważ wyjścia każdego modułu są umieszczane w oddzielnych katalogach out/1. Nie ma ani jednego OUT_DIR, w którym zależności zwracają wygenerowane źródło.

W przypadku kodu platformy AOSP preferuje pakowanie wygenerowanego źródła do pliku, który można zaimportować, z kilku powodów:

  • Zapobiegaj kolizji wygenerowanych nazw plików źródłowych.
  • Zmniejsz ilość kodu stałego w całym drzewie, który wymaga konserwacji. Wszystkie elementy potrzebne do kompilacji wygenerowanego źródła w skrzynię można utrzymywać centralnie.
  • Unikaj ukrytych2 interakcji między wygenerowanym kodem a otaczającym go kontenerem.
  • Zmniejsz obciążenie pamięci i dysku, dynamicznie łącząc często używane źródła wygenerowane.

W rezultacie wszystkie typy modułów generowania źródła Rust na Androidzie tworzą kod, który można skompilować i użyć jako kodu. Soong nadal obsługuje bez modyfikacji zewnętrzne skrzynki referencyjne, jeśli wszystkie wygenerowane zależności źródłowe modułu zostaną skopiowane do jednego katalogu na moduł, podobnego do Cargo. W takich przypadkach podczas kompilowania modułu Soong ustawia zmienną środowiskową OUT_DIR na ten katalog, aby można było znaleźć wygenerowane źródło. Ze względu na już podane powody zalecamy jednak używanie tego mechanizmu w kodzie platformy tylko wtedy, gdy jest to absolutnie konieczne.


  1. W przypadku języków C/C++ i podobnych nie ma to znaczenia, ponieważ ścieżka do wygenerowanego źródła jest przekazywana bezpośrednio do kompilatora. 

  2. Ponieważ include! działa na zasadzie włączania tekstu, może odwoływać się do wartości z otaczającej przestrzeni nazw, modyfikować tę przestrzeń lub używać konstrukcji takich jak #![foo]. Takie interakcje mogą być trudne do utrzymania. Zawsze wybieraj makro, gdy interakcja z resztą kontenera jest naprawdę wymagana.