Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Option to compile vendored source from either Samsung or Telegram #58

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,20 @@ jobs:
vendor: TelegramMessenger
- run: cargo test --workspace --all-features

rlottie-sys-vendor-samsung:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- run: cargo test -p rlottie-sys --lib

rlottie-sys-vendor-telegram:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- run: cargo test -p rlottie-sys --lib --features vendor-telegram --no-default-features

rustfmt:
runs-on: ubuntu-latest
steps:
Expand Down
19 changes: 18 additions & 1 deletion rlottie-sys/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,26 @@ license.workspace = true
repository.workspace = true

edition = "2021"
rust-version = "1.64" # bump version in build.rs as well
rust-version = "1.70" # bump version in build.rs as well
links = "rlottie"

[build-dependencies]
bindgen = { version = "0.71", features = ["prettyplease", "runtime"], default-features = false }
#cmake = { version = "0.1.54", optional = true }
pkg-config = "0.3.22"

[features]
default = ["vendor-samsung"]

# If rlottie cannot be found on the system, download the samsung version of rlottie and
# compile it. You can force the use of the vendored code by setting `RLOTTIE_NO_PKG_CONFIG`.
# If both `vendor-samsung` and `vendor-telegram` are enabled, samsung has priority.
vendor-samsung = ["__vendor"]

# If rlottie cannot be found on the system, download the telegram version of rlottie and
# compile it. You can force the use of the vendored code by setting `RLOTTIE_NO_PKG_CONFIG`.
# If both `vendor-samsung` and `vendor-telegram` are enabled, samsung has priority.
vendor-telegram = ["__vendor"]

# Internal
__vendor = []
8 changes: 8 additions & 0 deletions rlottie-sys/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
# rlottie-sys [![rlottie-sys on crates.io](https://img.shields.io/crates/v/rlottie-sys.svg)](https://crates.io/crates/rlottie-sys) [![rlottie-sys docs](https://img.shields.io/badge/docs-release-blue)](https://docs.msrd0.de/#rlottie-sys) [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](https://mit-license.org/)

Rust bindings to rlottie.

## Features

- `vendor-samsung` (enabled by default): If rlottie cannot be found on the system download the samsung version of rlottie and compile it. You can force the use of the vendored code by setting `RLOTTIE_NO_PKG_CONFIG`.

- `vendor-telegram`: If rlottie cannot be found on the system download the telegram version of rlottie and compile it. You can force the use of the vendored code by setting `RLOTTIE_NO_PKG_CONFIG`.

If both `vendor-samsung` and `vendor-telegram` are enabled, samsung has priority.
182 changes: 177 additions & 5 deletions rlottie-sys/build.rs
Original file line number Diff line number Diff line change
@@ -1,24 +1,196 @@
use std::{env, path::PathBuf};

#[cfg(feature = "vendor-samsung")]
mod source {
pub const VENDOR: &str = "samsung";
pub const GIT_REPO: &str = "https://github.com/Samsung/rlottie.git";
pub const GIT_REV: &str = "v0.2";
pub const GIT_PATCHES: &[&str] = &["2d7b1fa2b005bba3d4b45e8ebfa632060e8a157a"];
}

#[cfg(all(not(feature = "vendor-samsung"), feature = "vendor-telegram"))]
mod source {
pub const VENDOR: &str = "telegram";
pub const GIT_REPO: &str = "https://github.com/TelegramMessenger/rlottie";
pub const GIT_REV: &str = "67f103bc8b625f2a4a9e94f1d8c7bd84c5a08d1d";
pub const GIT_PATCHES: &[&str] = &["1dd47cec7eb8e1f657f02dce9c497ae60f7cf8c5"];
}

#[cfg(feature = "__vendor")]
fn compile_rlottie() -> Vec<PathBuf> {
use crate::source::*;
use std::{
env::current_dir,
fmt::{self, Display, Formatter},
fs::create_dir_all,
process::{Command, Stdio}
};

struct DisplayCommand<'a>(&'a Command);

impl Display for DisplayCommand<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{:?}", self.0.get_program())?;
for arg in self.0.get_args() {
write!(f, " {arg:?}")?;
}
Ok(())
}
}

fn print_cmd(cmd: &Command) {
println!(
"{} $ {}",
cmd.get_current_dir()
.map(PathBuf::from)
.unwrap_or_else(|| current_dir().unwrap())
.display(),
DisplayCommand(cmd)
);
}

fn try_run(cmd: &mut Command) -> bool {
// no user interactions, ever
cmd.stdin(Stdio::null());

print_cmd(cmd);
cmd.status().is_ok_and(|status| status.success())
}

fn run(cmd: &mut Command) {
// no user interactions, ever
cmd.stdin(Stdio::null());

print_cmd(cmd);
match cmd.status() {
Ok(status) if status.success() => {},
Ok(status) => panic!(
"Failed to run {}: Exit code {:?}",
DisplayCommand(cmd),
status.code()
),
Err(err) => panic!("Failed to run {}: {err:?}", DisplayCommand(cmd))
}
}

let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
let src_dir = out_dir.join(format!("rlottie-{VENDOR}-{GIT_REV}"));
let build_dir = out_dir.join(format!("build-{VENDOR}-{GIT_REV}"));

if !src_dir.exists() {
run(Command::new("git")
.arg("clone")
.arg(GIT_REPO)
.arg(&src_dir)
.current_dir(&out_dir));
// since telegram doesn't merge fixes we need to download pull requests
if VENDOR == "telegram" {
run(Command::new("git")
.arg("config")
.arg("--add")
.arg("remote.origin.fetch")
.arg("refs/pull/*/head:refs/remotes/origin/pull/*")
.current_dir(&src_dir));
run(Command::new("git").arg("fetch").current_dir(&src_dir));
}
}
run(Command::new("git")
.arg("checkout")
.arg(GIT_REV)
.current_dir(&src_dir));
for rev in GIT_PATCHES {
run(Command::new("git")
.arg("cherry-pick")
.arg("-x")
.arg(rev)
.current_dir(&src_dir)
.env("GIT_COMMITTER_NAME", "nobody")
.env("GIT_COMMITTER_EMAIL", "nobody"));
}

// we could revisit this if the `cmake` crate becomes less opinionated
// - `cmake` forces setting of `CMAKE_BUILD_TYPE` which is fine unless you are part
// of an already build-tool-independent configured environment like a linux
// distribution packaging process where they want the same flags for everything
// - `cmake` forces setting of `CMAKE_INSTALL_PREFIX` for no good reason
// - `cmake` has no way to set the build directory, and it doesn't use out_dir
// as its target directory
// - `cmake` prints some weird `cargo:root` line that I only found mentioned here
// https://web.mit.edu/rust-lang_v1.25/arch/amd64_ubuntu1404/share/doc/rust/html/cargo/reference/build-scripts.html#outputs-of-the-build-script
// where it is called "user-defined metadata" but cmake is only part of the build
// script so who would use that metadata?
// cmake::Config::new(&src_dir)
// .out_dir(&build_dir)
// .build_target("rlottie")
// .build();

// for the time being, I force my own opinions on how to use cmake
create_dir_all(&build_dir).expect("Failed to create directory");
let mut cmake = Command::new("cmake");
cmake.arg("-Wno-dev");
cmake.arg("-DBUILD_SHARED_LIBS=OFF");
if try_run(Command::new("which").arg("ninja")) {
cmake.arg("-G").arg("Ninja");
}
if env::var("CFLAGS").is_err() {
let opt_level = env::var("OPT_LEVEL").unwrap();
let mut build_type = "Release";
// TODO rust supports debug info in release builds, we should detect that
if opt_level == "0" || opt_level == "g" {
build_type = "RelWithDebInfo";
} else if opt_level == "s" || opt_level == "z" {
build_type = "MinSizeRel";
}
cmake.arg(format!("-DCMAKE_BUILD_TYPE={build_type}"));
}
cmake.arg(&src_dir);
cmake.current_dir(&build_dir);
run(&mut cmake);
run(Command::new("cmake")
.arg("--build")
.arg(".")
.arg("--target")
.arg("rlottie")
.current_dir(&build_dir));

println!("cargo:rustc-link-search=native={}", build_dir.display());
println!("cargo:rustc-link-lib=static=rlottie");
// no idea how platform/target specific this is but it doesn't work without it
println!("cargo:rustc-link-lib=stdc++");

vec![src_dir.join("inc")]
}

fn main() {
pkg_config::Config::new()
let include_paths = pkg_config::Config::new()
.probe("rlottie")
.expect("Unable to find rlottie");
.map(|lib| lib.include_paths);

#[cfg(not(feature = "__vendor"))]
let include_paths = include_path.expect("Unable to find rlottie");

#[cfg(feature = "__vendor")]
let include_paths = include_paths.unwrap_or_else(|_| compile_rlottie());

println!("cargo:rerun-if-changed=wrapper.h");
let bindings = bindgen::Builder::default()
.formatter(bindgen::Formatter::Prettyplease)
.clang_args(
include_paths
.into_iter()
.map(|path| format!("-I{}", path.display()))
)
.header("wrapper.h")
.parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
.newtype_enum(".*")
.rust_target(bindgen::RustTarget::stable(64, 0).unwrap_or_else(|_| panic!()))
.rust_target(bindgen::RustTarget::stable(70, 0).unwrap_or_else(|_| panic!()))
.size_t_is_usize(true)
.use_core()
.generate()
.expect("Unable to generate bindings");

let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
bindings
.write_to_file(out_path.join("bindings.rs"))
.write_to_file(out_dir.join("bindings.rs"))
.expect("Couldn't write bindings!");
}
Loading