Generatory źródeł

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

Wszystkie generatory źródeł zapewniają 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 być używany jako crate, tak jakby 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 crate. Makro include! jest obsługiwane w przypadku wygenerowanego źródła, ale jego głównym celem jest obsługa kodu zewnętrznego znajdującego się w elementach external/.

W niektórych przypadkach kod platformy może nadal używać źródła wygenerowanego za pomocą makra include!(), np. gdy do generowania źródła w niestandardowy sposób używasz modułu genrule.

Używanie funkcji include!() do uwzględniania wygenerowanego źródła

Korzystanie z wygenerowanego źródła jako kontenera jest opisane na stronach poszczególnych modułów. Z 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 zakłada, że masz zdefiniowany moduł rust_bindgen (libbuzz_bindgen) i możesz przejść do instrukcji dołączania wygenerowanego źródła, aby użyć 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ł.

Włączanie 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, który reprezentuje punkt wejścia do pliku binarnego lub biblioteki. Oczekuje się, że struktura drzewa źródeł umożliwia automatyczne wykrywanie wszystkich wymaganych plików źródłowych. Oznacza to, że wygenerowane źródło musi być umieszczone w drzewie źródłowym lub podane 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ż dane wyjściowe każdego modułu są umieszczane w osobnym katalogu 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 w skrzynkę, którą można zaimportować, z kilku powodów:

  • zapobieganie kolizjom nazw wygenerowanych plików źródłowych;
  • Zmniejsz ilość kodu stałego w całym drzewie, który wymaga konserwacji. Wszelkie elementy szablonowe, które są wymagane do skompilowania wygenerowanego źródła w pakiet, można zarządzać 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 kodu źródłowego Rust w Androidzie generują kod, który można skompilować i używać jako paczki. Soong nadal obsługuje zewnętrzne paczki bez zmian, jeśli wszystkie wygenerowane zależności źródłowe modułu zostaną skopiowane do pojedynczej katalogu na moduł, podobnie jak w przypadku Cargo. W takich przypadkach podczas kompilowania modułu Soong ustawia zmienną środowiskową OUT_DIR na ten katalog, aby można było znaleźć wygenerowany kod źródłowy. Ze względu na już podane powody zalecamy jednak używanie tego mechanizmu w kodzie platformy tylko wtedy, gdy jest to absolutnie konieczne.


  1. Nie powoduje to żadnych problemów w przypadku języków C/C++ i podobnych, 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.