Skip to content

Commit fb44cd6

Browse files
ai-edge-botcopybara-github
authored andcommitted
Added Rust binding.
LiteRT-PiperOrigin-RevId: 890251237
1 parent 5c5b9ce commit fb44cd6

17 files changed

Lines changed: 2880 additions & 0 deletions

File tree

litert/rust/Cargo.toml

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# Copyright 2025 Google LLC.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
16+
[package]
17+
authors = ["Max Gubin <mgubin@google.com>"]
18+
build = "build/build.rs"
19+
name = "google-ai-edge-litert"
20+
version = "0.1.0"
21+
edition = "2021"
22+
repository = "https://github.com/google-ai-edge/LiteRT/"
23+
24+
# If enabled, the runtime will be build using Docker, otherwise
25+
# a prebuild binary for the target platform will be downloaded.
26+
[features]
27+
build_runtime_with_docker = []
28+
29+
[lib]
30+
name = "litert"
31+
path = "src/lib.rs"
32+
33+
[build-dependencies]
34+
bindgen = "*"
35+
reqwest = { version = "0.11", features = ["blocking"] }
36+
zip = "0.6"
37+
anyhow = "1.0" # For easy error handling in build script
38+
build-print = "1"

litert/rust/README.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Rust Binding
2+
3+
## Build procedure
4+
5+
If LiteRT sources are already installed, set the environment variable
6+
`RUST_LITERT_SOURCE_DIR`. If the variable is not set, a copy of sources will be
7+
downloaded from Github.
8+
9+
If LiteRT binary runtime is available, set the environment variable
10+
`RUST_LITERT_RUNTIME_LIBRARY_DIR`. If the variable is not set, the build script
11+
will download pre-build binaries or, if the feature `build_runtime_with_docker`
12+
is enabled, use Docker to build it from sources.
13+
14+
## Dependencies
15+
16+
The build script depends on a few tools, it checks that the tools are installed
17+
by trying to run them and fails if it can't:
18+
19+
* Clang - is needed for bindgen
20+
* Docker - only used if the script builds Runtime binary library.

litert/rust/build/build.rs

Lines changed: 305 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,305 @@
1+
// Copyright 2025 Google LLC.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
use build_print::info;
16+
use std::env;
17+
use std::fs::{self, File};
18+
use std::io::{self, copy};
19+
use std::path::{Path, PathBuf};
20+
use std::process::Command;
21+
22+
// Constansts that are used by the build script.
23+
24+
// The environment variable that contains the output directory where it's possible to write or modify files.
25+
const OUT_DIR_ENV_VAR: &str = "OUT_DIR";
26+
// Cargo environment variable to definbe target os and architecture.
27+
const CARGO_CFG_TARGET_OS_ENV_VAR: &str = "CARGO_CFG_TARGET_OS";
28+
const CARGO_CFG_TARGET_ARCH_ENV_VAR: &str = "CARGO_CFG_TARGET_ARCH";
29+
// If we are generating docuemntation in hermetic environment for docs.rs
30+
const CARGO_DOCS_RS: &str = "DOCS_RS";
31+
32+
const LITERT_RUNTIME_DOWNLOAD_URL: &str = "https://storage.googleapis.com/litert/binaries/latest/";
33+
const LITERT_CC_SDK_DOWNLOAD_URL: &str =
34+
"https://github.com/google-ai-edge/LiteRT/releases/latest/download/litert_cc_sdk.zip";
35+
36+
#[derive(Debug, PartialEq)]
37+
enum TargetPlatform {
38+
AndroidArm64,
39+
AndroidX86,
40+
LinuxArm64,
41+
LinuxX86,
42+
MacosArm64,
43+
WindowsX86,
44+
}
45+
46+
impl TargetPlatform {
47+
// Create target platform from cargo environment variables.
48+
fn from_cargo_env() -> Result<Self, String> {
49+
let os = env::var(CARGO_CFG_TARGET_OS_ENV_VAR).unwrap_or_else(|_| "unknown".to_string());
50+
let arch =
51+
env::var(CARGO_CFG_TARGET_ARCH_ENV_VAR).unwrap_or_else(|_| "unknown".to_string());
52+
info!("Target os {} platform {}", os.as_str(), arch.as_str());
53+
54+
match (os.as_str(), arch.as_str()) {
55+
("android", "aarch64") => Ok(TargetPlatform::AndroidArm64),
56+
("android", "x86_64") => Ok(TargetPlatform::AndroidX86),
57+
("linux", "aarch64") => Ok(TargetPlatform::LinuxArm64),
58+
("linux", "x86_64") => Ok(TargetPlatform::LinuxX86),
59+
("macos", "aarch64") => Ok(TargetPlatform::MacosArm64),
60+
("windows", "x86_64") => Ok(TargetPlatform::WindowsX86),
61+
_ => Err(format!("Unknown target platform os:{} aarch:{}", os, arch)),
62+
}
63+
}
64+
65+
// See https://ai.google.dev/edge/litert/next/cpp_sdk
66+
fn runtime_name(&self) -> String {
67+
match self {
68+
TargetPlatform::AndroidArm64
69+
| TargetPlatform::AndroidX86
70+
| TargetPlatform::LinuxArm64
71+
| TargetPlatform::LinuxX86 => "libLiteRt.so".to_string(),
72+
TargetPlatform::MacosArm64 => "libLiteRt.dylib".to_string(),
73+
TargetPlatform::WindowsX86 => "libLiteRt.dll".to_string(),
74+
}
75+
}
76+
77+
fn runtime_directory(&self) -> String {
78+
match self {
79+
TargetPlatform::AndroidArm64 => "android_arm64".to_string(),
80+
TargetPlatform::AndroidX86 => "android_x86_64".to_string(),
81+
TargetPlatform::LinuxArm64 => "linux_arm64".to_string(),
82+
TargetPlatform::LinuxX86 => "linux_x86_64".to_string(),
83+
TargetPlatform::MacosArm64 => "macos_arm64".to_string(),
84+
TargetPlatform::WindowsX86 => "windows_x86_64".to_string(),
85+
}
86+
}
87+
}
88+
89+
// Helper function to check if a tool is installed
90+
fn check_tool_installed(tool: &str) -> Result<(), String> {
91+
match Command::new(tool).arg("--version").output() {
92+
Ok(output) if output.status.success() => Ok(()),
93+
_ => Err(format!(
94+
"Required tool '{}' is not installed or not in PATH.",
95+
tool
96+
)),
97+
}
98+
}
99+
100+
// Helper function to download a file
101+
fn download_file(url: &str, path: &Path) -> Result<(), Box<dyn std::error::Error>> {
102+
let client = reqwest::blocking::Client::builder()
103+
.timeout(None) // Disable total request timeout
104+
.connect_timeout(std::time::Duration::from_secs(10))
105+
.build()?;
106+
let mut response = client.get(url).send()?;
107+
let mut dest = File::create(path)?;
108+
copy(&mut response, &mut dest)?;
109+
Ok(())
110+
}
111+
112+
fn unzip_archive(
113+
archive_path: &Path,
114+
extract_to: &Path,
115+
) -> Result<PathBuf, Box<dyn std::error::Error>> {
116+
let file = File::open(archive_path)?;
117+
let mut archive = zip::ZipArchive::new(file)?;
118+
// Assume that the directory of the archive has one top directory, that the function return.
119+
let mut root_directory = PathBuf::new();
120+
121+
for i in 0..archive.len() {
122+
let mut file = archive.by_index(i)?;
123+
let file_name = file.mangled_name();
124+
let outpath = extract_to.join(&file_name);
125+
126+
// Extract the root directory of the file.
127+
if let Some(rd) = Path::new(&file_name).iter().next() {
128+
root_directory = Path::new(rd).to_path_buf();
129+
}
130+
if file.name().ends_with('/') {
131+
fs::create_dir_all(&outpath)?;
132+
} else {
133+
if let Some(p) = outpath.parent() {
134+
if !p.exists() {
135+
fs::create_dir_all(p)?;
136+
}
137+
}
138+
let mut outfile = File::create(&outpath)?;
139+
io::copy(&mut file, &mut outfile)?;
140+
}
141+
}
142+
Ok(root_directory)
143+
}
144+
145+
fn download_runtime(
146+
target_plarform: &TargetPlatform,
147+
out_dir: &Path,
148+
) -> Result<(), Box<dyn std::error::Error>> {
149+
info!("Downloading LiteRT ");
150+
151+
let runtime_name = target_plarform.runtime_name();
152+
let runtime_dir = target_plarform.runtime_directory();
153+
let runtime_download_url = String::from(LITERT_RUNTIME_DOWNLOAD_URL)
154+
+ runtime_dir.as_str()
155+
+ "/"
156+
+ runtime_name.as_str();
157+
let runtime_local_name = out_dir.join(runtime_name);
158+
download_file(&runtime_download_url, &runtime_local_name)?;
159+
return Ok(());
160+
}
161+
162+
struct LiteRTSdk {
163+
sdk_root_dir: PathBuf,
164+
sdk_build_dir: PathBuf,
165+
}
166+
167+
impl LiteRTSdk {
168+
fn new(sdk_root_dir: PathBuf, sdk_build_dir: PathBuf) -> Self {
169+
Self {
170+
sdk_root_dir,
171+
sdk_build_dir,
172+
}
173+
}
174+
}
175+
176+
// Following LiteRT C++ SDK build process.
177+
// https://ai.google.dev/edge/litert/next/cpp_sdk
178+
fn download_and_build_cpp_sdk(
179+
target_platform: &TargetPlatform,
180+
out_dir: &Path,
181+
) -> Result<LiteRTSdk, Box<dyn std::error::Error>> {
182+
info!("Downloading LiteRT C++ SDK...");
183+
let sdk_zip_path = out_dir.join("litert_cc_sdk.zip");
184+
download_file(LITERT_CC_SDK_DOWNLOAD_URL, &sdk_zip_path)?;
185+
186+
info!("Unzipping LiteRT C++ SDK...");
187+
let sdk_extract_dir = out_dir.join("litert_cc_sdk_extracted");
188+
if !sdk_extract_dir.exists() {
189+
fs::create_dir_all(&sdk_extract_dir)?;
190+
}
191+
let zip_root_dir = unzip_archive(&sdk_zip_path, &sdk_extract_dir)?;
192+
let sdk_root = sdk_extract_dir.join(zip_root_dir);
193+
194+
info!(
195+
"Downloading prebuilt runtime for SDK to {}",
196+
sdk_root.display()
197+
);
198+
download_runtime(target_platform, &sdk_root)?;
199+
200+
let build_dir = out_dir.join("litert_cc_sdk_build");
201+
info!("Building C++ SDK with CMake in {}", build_dir.display());
202+
203+
let status = Command::new("cmake")
204+
.arg("-S")
205+
.arg(&sdk_root)
206+
.arg("-B")
207+
.arg(&build_dir)
208+
.arg("-DCMAKE_C_COMPILER=clang")
209+
.arg("-DCMAKE_CXX_COMPILER=clang++")
210+
.status()?;
211+
212+
if !status.success() {
213+
return Err(format!("CMake configure failed with status: {}", status).into());
214+
}
215+
216+
let status = Command::new("cmake")
217+
.arg("--build")
218+
.arg(&build_dir)
219+
.arg("-j")
220+
.status()?;
221+
222+
if !status.success() {
223+
return Err(format!("CMake build failed with status: {}", status).into());
224+
}
225+
226+
Ok(LiteRTSdk::new(sdk_root, build_dir))
227+
}
228+
229+
fn dump_all_env_vars() {
230+
for (key, value) in env::vars() {
231+
info!("Environment: {}: {}", key, value);
232+
}
233+
}
234+
235+
fn main() -> Result<(), Box<dyn std::error::Error>> {
236+
dump_all_env_vars();
237+
println!("cargo:rustc-check-cfg=cfg(bindgen_rs_file, cargo_bindgen, docsrs)");
238+
// Check if we are currently generating documentation
239+
let is_doc_gen = env::var(CARGO_DOCS_RS).is_ok();
240+
241+
if is_doc_gen {
242+
info!("Skipping heavy lifting because we are just building docs!");
243+
println!("cargo:rustc-check-cfg=cfg(docsrs)");
244+
println!("cargo:rustc-cfg=docsrs");
245+
return Ok(());
246+
}
247+
println!("cargo::rerun-if-changed=build/build.rs");
248+
println!("cargo::rerun-if-changed=wrapper.h");
249+
250+
if let Ok(manifest_dir) = env::var("CARGO_MANIFEST_DIR") {
251+
info!("Manifest dir {}", manifest_dir);
252+
}
253+
254+
let target_platform = TargetPlatform::from_cargo_env()?;
255+
256+
check_tool_installed("cmake")?;
257+
check_tool_installed("clang")?;
258+
259+
let out_dir = PathBuf::from(env::var("OUT_DIR")?);
260+
let litert_sdk = download_and_build_cpp_sdk(&target_platform, &out_dir)?;
261+
info!("Runtime dir {}", litert_sdk.sdk_root_dir.display());
262+
info!("Build dir {}", litert_sdk.sdk_build_dir.display());
263+
264+
println!(
265+
"cargo::rustc-link-search=native={}",
266+
litert_sdk.sdk_root_dir.display()
267+
);
268+
println!("cargo::rustc-link-lib=dylib=LiteRt");
269+
println!(
270+
"cargo::rustc-link-search=native={}",
271+
litert_sdk.sdk_build_dir.display()
272+
);
273+
println!("cargo::rustc-link-lib=static=litert_cc_api");
274+
275+
let bindings = bindgen::Builder::default()
276+
.header("wrapper.h")
277+
// Add the include path so clang can find dependent headers
278+
.clang_arg(format!("-I{}", litert_sdk.sdk_root_dir.display()))
279+
.clang_arg(format!(
280+
"-I{}",
281+
litert_sdk.sdk_root_dir.join("litert").join("c").display()
282+
))
283+
.clang_arg(format!(
284+
"-I{}",
285+
litert_sdk.sdk_build_dir.join("include").display()
286+
))
287+
.clang_arg("-DLITERT_DISABLE_GPU")
288+
.layout_tests(false)
289+
.derive_default(true)
290+
.generate()
291+
.expect("Unable to generate bindings");
292+
293+
// Write the bindings to the $OUT_DIR/bindings.rs file.
294+
// Get the output directory where cargo wants us to build things
295+
let out_dir = PathBuf::from(env::var(OUT_DIR_ENV_VAR)?);
296+
let bindings_out_path = out_dir.join("bindings.rs");
297+
info!("Writing binding.rs to {}", bindings_out_path.display());
298+
bindings
299+
.write_to_file(bindings_out_path)
300+
.expect("Couldn't write bindings!");
301+
println!("cargo::rustc-check-cfg=cfg(bindgen_rs_file, cargo_bindgen)");
302+
println!("cargo::rustc-cfg=cargo_bindgen");
303+
304+
Ok(())
305+
}

litert/rust/example/Cargo.toml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Copyright 2025 Google LLC.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
[package]
16+
name = "litert_test"
17+
version = "0.1.0"
18+
edition = "2021"
19+
20+
[dependencies]
21+
google-ai-edge-litert = { version = "0.1", path = ".." }
22+
clap = { version = "4.5", features = ["derive"] }
23+
image = "0.25.9"
24+
25+
[[bin]]
26+
name = "segmentation"
27+
path = "segmentation_main.rs"

0 commit comments

Comments
 (0)