2019.2.2 change title

For a list of documents, see: Rust Mobile Complex Graphics Rendering Project Development Series summary (table of Contents)

Actions for the body project before compilation (build.rs)

Build. Rs can implement additional operations before compilation of this project, such as code generation, calling Cmake /clang/ GCC/ndK-build to compile the C/C++ libraries on which it depends, reading C/C++ header files to generate FFI files for use in Rust projects, etc., which is equivalent to shell scripts written by Rust. To make the build process more manageable, Cargo allows different types of build.rs statements, such as warning, error, and so on, to be logged to indicate that a certain phase passed or an error was encountered.

println!("cargo:warning=Error failed with {:? }.", some_reason);
Copy the code

Currently there is no way to output info level logsprintln! ("cargo:info={:? }, some_status);Unable to output information on console.

Rs pulls git submodule

The following code is taken from GLSL-to-SPIRv.

use std::process::Command;

// Try to initialize submodules. Don't care if it fails, since this code also runs for
// the crates.io package.
let _ = Command::new("git")
    .arg("submodule")
    .arg("update")
    .arg("--init")
    .status();
Copy the code

Cargo calls Clang to compile the third-party C/C++ libraries it relies on

The most complete reference I’ve seen so far is the official libstd/build.rs command, which is almost always the “inspiration” for compiling third-party libraries for our business. The core code is posted below, and the key operation is build_libbacktrace(). C/C++ code that needs to be compiled is declared by cc::Build instances, theoretically supporting regular matching of file names and paths.

#! [deny(warnings)]

extern crate build_helper;
extern crate cc;

use build_helper::native_lib_boilerplate;
use std::env;
use std::fs::File;

fn main() {
    let target = env::var("TARGET").expect("TARGET was not set");
    if cfg!(feature = "backtrace") &&
        !target.contains("cloudabi") 
        / /... More conditions
    {
        let _ = build_libbacktrace(&target);
    }

    if target.contains("linux") {
        / /... A series of operating system judgments and println!}}fn build_libbacktrace(target: &str) - >Result< () () > {let native = native_lib_boilerplate("libbacktrace"."libbacktrace"."backtrace"."")? ;let mut build = cc::Build::new();
    build
        .flag("-fvisibility=hidden")
        .include(".. /libbacktrace")
        .include(&native.out_dir)
        .out_dir(&native.out_dir)
        .warnings(false)
        .file(".. /libbacktrace/alloc.c")
        .file(".. /libbacktrace/backtrace.c")
        / /... A bunch of.c files

    let any_debug = env::var("RUSTC_DEBUGINFO").unwrap_or_default() == "true" ||
        env::var("RUSTC_DEBUGINFO_LINES").unwrap_or_default() == "true";
    build.debug(any_debug);

    if target.contains("darwin") {
        build.file(".. /libbacktrace/macho.c");
    } else if target.contains("windows") {
        build.file(".. /libbacktrace/pecoff.c");
    } else {
        build.file(".. /libbacktrace/elf.c");

        let pointer_width = env::var("CARGO_CFG_TARGET_POINTER_WIDTH").unwrap();
        if pointer_width == "64" {
            build.define("BACKTRACE_ELF_SIZE"."64");
        } else {
            build.define("BACKTRACE_ELF_SIZE"."32");
        }
    }

    File::create(native.out_dir.join("backtrace-supported.h")).unwrap();
    build.define("BACKTRACE_SUPPORTED"."1");
    build.define("BACKTRACE_USES_MALLOC"."1");
    build.define("BACKTRACE_SUPPORTS_THREADS"."0");
    build.define("BACKTRACE_SUPPORTS_DATA"."0");

    File::create(native.out_dir.join("config.h")).unwrap();
    if! target.contains("apple-ios") &&
       !target.contains("solaris") &&
       !target.contains("redox") &&
       !target.contains("android") &&
       !target.contains("haiku") {
        build.define("HAVE_DL_ITERATE_PHDR"."1");
    }
    build.define("_GNU_SOURCE"."1");
    build.define("_LARGE_FILES"."1");

    build.compile("backtrace");
    Ok(())}Copy the code

Cargo calls dk-build to compile third-party C/C++ libraries

The following code is referenced from Rustdroid-native

use std::{env, path::PathBuf, process};

fn main() {
    establish_ndk();
    establish_ndk_toolchain();
}

fn establish_ndk() {
    match find_ndk_path() {
        None= >println!("cargo:warning=NDK path not found"),
        Some(path) => println!("cargo:warning=NDK path found at {}", path.to_string_lossy()),
    };
}

fn establish_ndk_toolchain() {
    match find_ndk_toolchain_path() {
        None= >println!("cargo:warning=NDK_TOOLCHAIN path not found"),
        Some(path) => println!(
            "cargo:warning=NDK_TOOLCHAIN path found at {}",
            path.to_string_lossy()
        ),
    };
}

fn command_which_ndk_build_path() - >Option<PathBuf> {
    let mut cmd = process::Command::new("sh"); // mut due to API limitation
    cmd.arg("-c").arg("which ndk-build");
    match cmd.output() {
        Err(e) => {
            println!(
                "cargo:warning=Error executing process command <{:? } > : {}",
                cmd, e
            );
            None
        }
        Ok(o) => match String::from_utf8(o.stdout) {
            Err(e) => {
                println!("cargo:warning=Error parsing command output as UTF-8: {}", e);
                None
            }
            Ok(s) => PathBuf::from(&s)
                .parent()
                .and_then(|p| Some(p.to_path_buf())),
        },
    }
}

fn path_from_string(pathname: &str) - >Option<PathBuf> {
    // TODO: @@@ FUTURE RUST FEATURE
    //Some(PathBuf::from(pathname)).filter(|p| p.exists())
    let path = PathBuf::from(&pathname);
    if path.exists() {
        Some(path)
    } else {
        None}}fn path_from_env_var(varname: &'static str) - >Option<PathBuf> {
    match env::var(varname) {
        Ok(s) => path_from_string(&s),
        Err(_) = >None,}}fn path_with_ndk_build(path: &PathBuf) -> Option<PathBuf> {
    // TODO: @@@ FUTURE RUST FEATURE
    //path.filter(|p| p.join("ndk-build").exists())
    if path.join("ndk-build").exists() {
        Some(path.clone())
    } else {
        None}}fn path_with_ndk_bundle_ndk_build(path: &PathBuf) -> Option<PathBuf> {
    path_with_ndk_build(&path.join("ndk-bundle"))}fn path_with_ndk_build_from_env_var(varname: &'static str) - >Option<PathBuf> {
    path_from_env_var(&varname).and_then(|p| path_with_ndk_build(&p))
}

fn path_with_ndk_bundle_ndk_build_from_env_var(varname: &'static str) - >Option<PathBuf> {
    path_from_env_var(&varname).and_then(|p| path_with_ndk_bundle_ndk_build(&p))
}

fn find_ndk_path_from_ndk_env_vars() - >Option<PathBuf> {
    // TODO: @@@ REFACTOR INTO ITERATION OF COLLECTION
    path_with_ndk_build_from_env_var("ANDROID_NDK_HOME").or_else(|| {
        path_with_ndk_build_from_env_var("ANDROID_NDK_ROOT").or_else(|| {
            path_with_ndk_build_from_env_var("NDK_HOME").or_else(|| {
                path_with_ndk_build_from_env_var("NDK_ROOT") // NVIDIA CodeWorks
                    .or_else(|| path_with_ndk_build_from_env_var("NDKROOT"))})})})// NVIDIA CodeWorks
}

fn find_ndk_path_from_sdk_env_vars() - >Option<PathBuf> {
    // TODO: @@@ REFACTOR INTO ITERATION OF COLLECTION
    path_with_ndk_bundle_ndk_build_from_env_var("ANDROID_SDK_HOME")
        .or_else(|| path_with_ndk_bundle_ndk_build_from_env_var("ANDROID_SDK_ROOT"))
        .or_else(|| path_with_ndk_bundle_ndk_build_from_env_var("ANDROID_HOME"))}fn find_ndk_path_from_env_vars() - >Option<PathBuf> {
    find_ndk_path_from_ndk_env_vars().or_else(|| find_ndk_path_from_sdk_env_vars())
}

fn find_ndk_version_build_path(path: &PathBuf) -> Option<PathBuf> {
    //println! ("cargo:warning=find_ndk_version_build_path() pathname: {:? }", pathname);
    if let Ok(iter) = path.read_dir() {
        for entry in iter {
            if let Ok(entry) = entry {
                let path = entry.path();
                //println! ("cargo:warning=searching path: {:? }", path);
                if path.join("ndk-build").exists() {
                    return Some(path); }}}}None
}

fn find_ndk_path_from_known_installations() - >Option<PathBuf> {
    env::home_dir().and_then(|home| {
        path_with_ndk_bundle_ndk_build(
            // Android Studio on GNU/Linux
            &home.join(".android").join("sdk"),
        )
        .or_else(|| {
            path_with_ndk_bundle_ndk_build(
                // Android Studio on macOS
                &home.join("Library").join("Android").join("sdk"),
            )
        })
        .or_else(|| {
            find_ndk_version_build_path(
                // NVIDIA CodeWorks
                &home.join("NVPACK"),)})})}fn find_ndk_path() - >Option<PathBuf> {
    command_which_ndk_build_path()
        .or_else(|| find_ndk_path_from_env_vars())
        .or_else(|| find_ndk_path_from_known_installations())
}

fn find_ndk_toolchain_path() - >Option<PathBuf> {
    path_from_env_var("NDK_TOOLCHAIN")}Copy the code

Graphics open source project build.rs reference compilation scripts

Cargo compile glslang

glslang-sys/build.rs

Cons: Does not correspond to the latest GLslang project. Advantages: Use file suffixes to match files to be compiled, avoiding hard coding. Gossip: The author of this project is a Google employee who also developed the cargo lipo project, which greatly made it easy for Rust to compile iOS libraries. When I first got to know Rust, I didn’t understand anything and even asked him a wrong issue, which led Josh to discuss with him for a while.

Glsl-to-spirv uses Cmakelist.txt directly from GLSL-to-spirV, which is a good choice for quick iteration and continuous maintenance of open source projects, reducing build.rs writing and maintenance costs.

Cargo compile SPIRV – Cross

spirv_cross/build.rs

Disadvantages: Hardcoded list of files involved in compilation. Pros: This is Josh’s project, and it’s more engineered and organized than the previous Glslang-SYS project, so it’s worth considering.

Cargo compiles the Metal Shader file to.metallib

metal/build.rs

Compile Metal’s. Shader file as. Metallib to avoid runtime compilation and improve performance. It is worth considering how to call the XCode build chain in build.rs.

Create a directory with build.rs

use std::fs;

fn main() {
    fs::create_dir_all("./dir1/dir2/dir3"); / / 1
    fs::create_dir_all(". /.. /lib"); / / 2
}
Copy the code
  • / / 1Create all directories required for dir1/dir2/dir3 in the build.rs sibling directory. For example, dir1 and dir2 do not existfs::create_dir_all()They are automatically created, and dir3 is created.
  • / / 2Create the lib directory in the build.rs parent directory.

Fs ::create_dir_all() note the difference between paths.

How to check if a directory exists and create a new one if it doesn’t in Rust?

The operation of the project after compilation

For example, the Rust project does not currently support compiling directly into the. Framework supported by iOS/macOS, so we have to script. A and. Cargo /issue has already been discussed.

Conditional compilation

All conditional compilation is implemented through the CFG configuration, which supports the combination of any, all, and NOT logical predicates.

Basic usage

Add the [features] section to Cargo. Toml and list the feature names that need to be combined. .

[features]
default = []
metal = ["gfx-backend-metal"]
vulkan = ["gfx-backend-vulkan"]
dx12 = ["gfx-backend-dx12"]
Copy the code

Conditional compilation at the mod level

For an implementation example, see GL-rs /gl_generator/lib.rs

#[cfg(feature = "unstable_generator_utils")]
pub mod generators;
#[cfg(not(feature = "unstable_generator_utils")))
mod generators;
Copy the code

Compile specific CPU architectures

Specify target_arch + CPU architecture name The value is a character string, such as #[CFG (target_arch= “x86”)], #[CFG (any(target_arch = “arm”, target_arch= “x86”))].

Reference libstd/OS/android/raw. Rs

#[cfg(any(target_arch = "arm", target_arch = "x86")))
mod arch {
    use os::raw::{c_uint, c_uchar, c_ulonglong, c_longlong, c_ulong};
    use os::unix::raw::{uid_t, gid_t};

    #[stable(feature = "raw_ext", since = "1.1.0")]
    pub type dev_t = u64;
    #[stable(feature = "raw_ext", since = "1.1.0")]
    pub type mode_t = u32;

    #[stable(feature = "raw_ext", since = "1.1.0")]
    pub type blkcnt_t = u64;
    #[stable(feature = "raw_ext", since = "1.1.0")]
    pub type blksize_t = u64;
    #[stable(feature = "raw_ext", since = "1.1.0")]
    pub type ino_t = u64;
    #[stable(feature = "raw_ext", since = "1.1.0")]
    pub type nlink_t = u64;
    #[stable(feature = "raw_ext", since = "1.1.0")]
    pub type off_t = u64;
    #[stable(feature = "raw_ext", since = "1.1.0")]
    pub type time_t = i64;
Copy the code
#[doc(include = "os/raw/char.md")]
#[cfg(any(all(target_os = "linux", any(target_arch = "aarch64",
                                       target_arch = "arm",
                                       target_arch = "powerpc",
                                       target_arch = "powerpc64",
                                       target_arch = "s390x")),
Copy the code

IOS/Android/macOS/Windows cross-platform compile the example

[target.'cfg(any(target_os = "macos", all(target_os = "ios", target_arch = "aarch64")))'.dependencies.gfx-backend-metal]
git = "https://github.com/gfx-rs/gfx"
version = "0.1"
optional = true

[target.'cfg(target_os = "android")'.dependencies.gfx-backend-vulkan]
git = "https://github.com/gfx-rs/gfx"
version = "0.1"
optional = true

[target.'cfg(windows)'.dependencies.gfx-backend-dx12]
git = "https://github.com/gfx-rs/gfx"
version = "0.1"
optional = true
Copy the code

Cargo build –features Metal –target AARCH64-apple-ios –release Also package the code with the feature of gfX-backend-metal (the previous features section needs to be configured).

Cargo Build –features Vulkan –target AARCH64-linux-Android –release Relase 64-bit Android static library At the same time, set the feature to gfX-backend-vulkan (the previous features section needs to be configured).

Compile to binary package of specified type (.a/.so/.r)

Not found yet for macOS/iOS compiler support.frameworkMethods.

Add the [lib] section to Cargo. Toml,

  • nameRepresents the library name of the output,The final output file is named lib+name.a or lib+name.so.Such as libportability. So.
  • crate-typeRepresents the binary package type of output, for example
    • staticlib = .a IOS only recognizes Rust output. A,Android can do dot A and dot soAnd configured to["staticlib", "cdylib"]When using cargo-lipo, a warning is not supportedcdylib, ignore it.
    • cdylib = .so
    • rlibStatic libraries for Rust
    • dylib= Dynamic libraries for Rust
  • pathThe entry file that represents the library project, usually SRC /lib.rs. If this location is changed, this can be done with path = the new location, for example:
[lib]
name = "portability"
crate-type = ["staticlib"."cdylib"]
path = "src/ios/lib.rs"
Copy the code

“After-sales Service” developed by SDK

Provide. A /. So to the business team, this process may lead to the failure of people to connect, here are some tips we use.

Read. A Static library iOS version

In macOS terminal, run the following command to query VERSION with /.

otool -lv xyz.a | less
Copy the code

Reference: check – ios – deployment – target – of – a – static – the library

Nm view exported symbols

Sometimes, due to coding negligence, #[no_mangle] and extern and other modifications are not added to the C interface that needs to be exported, or unreasonable optimized attribute is used to cause symbols to be optimized out. In this case, business links to our library will fail. Therefore, it is a qualified engineer’s habit to confirm symbol table with NM before delivering binary package. How do I list the symbols in A. so file. Here is the macOS sample code.

Nm view.so export symbol

nm -D ./target/release/libportability.so  | grep fun_call_exported_to_c
0000000000003190 T fun_call_exported_to_c
Copy the code

Nm view. A Export symbol

nm -g ./target/release/libportability.a  | grep glActiveTexture
000000000000190c T _glActiveTexture
Copy the code

Rust exports the correct posture of interface C

The Rust philosophy is to prefer explicit over implicit. Rust will only export symbols that are publicly accessible from the root crate. This makes it very easy to inspect the public interface of a crate without crawling through all files: just follow the pub from the root. In your case, the symbol rle_new is publicly accessible to anyone having access to the rle module (such as sibling modules), but the rle module itself is not publicly accessible in the root crate.

The simplest solution is to selectively export this symbol:

pub use rle::rle_new;
Copy the code

Stackoverflow.com/questions/4…

Therefore, if a function that identifies #[no_mangle] in a non-lib. rs file forgets to pub use it in lib.rs, it will still not be found as a C library or rlib and will display the following compilation warning. The solution is to either pub use module ::* or pub use module ::{symbol name 1, symbol name 2} in lib.rs.

warning: function is marked #[no_mangle], but not exported
   --> src/portability/gl_es/src/c_abi/mod.rs:785:1
    |
785 | / pub extern "C" fn glViewport(x: GLint, y: GLint, width: GLsizei, height: GLsizei) {
786 | |     unimplemented!(a)787 | | }
    | |_^
    |
    = help: try exporting the item with a `pub use` statement
Copy the code

View a list of systems that the native RUST compiler can compile

rustc --print target-list
Copy the code

Rustc – for example, print target – the list | grep ios without content, use rustup component add ios related CPU architecture, and then you can cross compile ios libraries, other platforms.