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

Break up the ubrn command line build commands #71

Merged
merged 4 commits into from
Aug 18, 2024
Merged
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
170 changes: 131 additions & 39 deletions crates/ubrn_cli/src/android.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/
*/
use serde::Deserialize;
use std::{fmt::Display, fs, process::Command, str::FromStr};
use std::{collections::HashMap, fmt::Display, fs, process::Command, str::FromStr};

use clap::Args;

Expand All @@ -15,6 +15,7 @@ use ubrn_common::{mk_dir, rm_dir, run_cmd, CrateMetadata};
use crate::{
building::{CommonBuildArgs, ExtraArgs},
config::ProjectConfig,
rust::CrateConfig,
workspace,
};

Expand Down Expand Up @@ -115,68 +116,159 @@ pub(crate) struct AndroidArgs {

#[clap(flatten)]
pub(crate) common_args: CommonBuildArgs,

/// Suppress the copying of the Rust library into the JNI library directories.
#[clap(long = "no-jniLibs")]
no_jni_libs: bool,
}

impl AndroidArgs {
pub(crate) fn build(&self) -> Result<Vec<Utf8PathBuf>> {
let config: ProjectConfig = self.project_config()?;
let project_root = config.project_root();

let crate_ = &config.crate_;
let rust_dir = crate_.directory()?;
let manifest_path = crate_.manifest_path()?;

let android = &config.android;

let jni_libs = android.jni_libs(project_root);
rm_dir(&jni_libs)?;

let mut target_files: Vec<_> = Vec::new();
for target in &android.targets {
let target = target.parse::<Target>()?;
let mut cmd = Command::new("cargo");
cmd.arg("ndk")
.arg("--manifest-path")
.arg(&manifest_path)
.arg("--target")
.arg(target.to_string())
.arg("--platform")
.arg(format!("{}", android.api_level));

if !self.common_args.release {
cmd.arg("--no-strip");
let target_files = if self.common_args.no_cargo {
let files = self.find_existing(&crate_.metadata()?, &android.targets);
if !files.is_empty() {
files
} else {
self.cargo_build_all(
crate_,
&android.targets,
&android.cargo_extras,
android.api_level,
)?
}
} else {
self.cargo_build_all(
crate_,
&android.targets,
&android.cargo_extras,
android.api_level,
)?
};

if !self.no_jni_libs {
let project_root = config.project_root();
self.copy_into_jni_libs(
&crate_.metadata()?,
&android.jni_libs(project_root),
&target_files,
)?;
}

cmd.arg("--").arg("build");
if self.common_args.release {
cmd.arg("--release");
}
Ok(target_files.into_values().collect())
}

fn cargo_build_all(
&self,
crate_: &CrateConfig,
targets: &[String],
cargo_extras: &ExtraArgs,
api_level: usize,
) -> Result<HashMap<Target, Utf8PathBuf>> {
let rust_dir = crate_.directory()?;
let manifest_path = crate_.manifest_path()?;
let metadata = crate_.metadata()?;
let mut target_files = HashMap::new();
let profile = self.common_args.profile();
for target in targets {
let target =
self.cargo_build(target, &manifest_path, cargo_extras, api_level, &rust_dir)?;
let library = metadata.library_path(Some(target.triple()), profile);
metadata.library_path_exists(&library)?;
target_files.insert(target, library);
}
Ok(target_files)
}

cmd.args(android.cargo_extras.clone());
fn cargo_build(
&self,
target: &str,
manifest_path: &Utf8PathBuf,
cargo_extras: &ExtraArgs,
api_level: usize,
rust_dir: &Utf8PathBuf,
) -> Result<Target> {
let target = target.parse::<Target>()?;
let mut cmd = Command::new("cargo");
cmd.arg("ndk")
.arg("--manifest-path")
.arg(manifest_path)
.arg("--target")
.arg(target.to_string())
.arg("--platform")
.arg(format!("{}", api_level));
if !self.common_args.release {
cmd.arg("--no-strip");
}
cmd.arg("--").arg("build");
if self.common_args.release {
cmd.arg("--release");
}
cmd.args(cargo_extras.clone());
run_cmd(cmd.current_dir(rust_dir))?;
Ok(target)
}

run_cmd(cmd.current_dir(&rust_dir))?;
let metadata = crate_.metadata()?;
let src_lib = metadata.library_path(
Some(target.triple()),
CrateMetadata::profile(self.common_args.release),
)?;
fn find_existing(
&self,
metadata: &CrateMetadata,
targets: &[String],
) -> HashMap<Target, Utf8PathBuf> {
let profile = self.common_args.profile();
targets
.iter()
.filter_map(|target| {
let target = target.parse::<Target>();
match target {
Ok(target) => Some(target),
Err(_) => None,
}
})
.filter_map(|target| {
let library = metadata.library_path(Some(target.triple()), profile);
if library.exists() {
Some((target, library))
} else {
None
}
})
.collect()
}

fn copy_into_jni_libs(
&self,
metadata: &CrateMetadata,
jni_libs: &Utf8Path,
target_files: &HashMap<Target, Utf8PathBuf>,
) -> Result<()> {
println!("-- Copying into jniLibs directory");
println!("rm -Rf {jni_libs}");
rm_dir(jni_libs)?;
for (target, library) in target_files {
let dst_dir = jni_libs.join(target.to_string());
mk_dir(&dst_dir)?;

let dst_lib = dst_dir.join(metadata.library_file(Some(target.triple())));
fs::copy(&src_lib, &dst_lib)?;

target_files.push(src_lib);
println!("cp {library} {dst_lib}");
fs::copy(library, &dst_lib)?;
}

Ok(target_files)
Ok(())
}

pub(crate) fn project_config(&self) -> Result<ProjectConfig> {
self.config.clone().try_into()
}

pub(crate) fn config(&self) -> Utf8PathBuf {
self.config.clone()
}
}

#[derive(Debug, Deserialize, Default, Clone)]
#[derive(Debug, Deserialize, Default, Clone, Hash, PartialEq, Eq)]
pub enum Target {
#[serde(rename = "armeabi-v7a")]
ArmeabiV7a,
Expand Down
50 changes: 14 additions & 36 deletions crates/ubrn_cli/src/building.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,9 @@ use anyhow::{anyhow, Result};
use camino::Utf8PathBuf;
use clap::{Args, Subcommand};
use serde::Deserialize;
use ubrn_bindgen::{BindingsArgs, OutputArgs, SourceArgs};
use ubrn_common::CrateMetadata;

use crate::{android::AndroidArgs, config::ProjectConfig, ios::IOsArgs};
use crate::{android::AndroidArgs, generate::GenerateAllArgs, ios::IOsArgs};

#[derive(Args, Debug)]
pub(crate) struct BuildArgs {
Expand All @@ -30,44 +29,15 @@ pub(crate) enum BuildCmd {
impl BuildArgs {
pub(crate) fn build(&self) -> Result<()> {
let lib_file = self.cmd.build()?;
if self.and_generate() {
if self.cmd.and_generate() {
self.generate(lib_file)?;
}

Ok(())
}

fn generate(&self, lib_file: Utf8PathBuf) -> Result<()> {
let project = self.cmd.project_config()?;
let root = project.project_root();
let pwd = ubrn_common::pwd()?;
let modules = {
let dir = project.crate_.directory()?;
ubrn_common::cd(&dir)?;
let ts_dir = project.bindings.ts_path(root);
let cpp_dir = project.bindings.cpp_path(root);
let config = project.bindings.uniffi_toml_path(root);
if let Some(ref file) = config {
if !file.exists() {
anyhow::bail!("uniffi.toml file {:?} does not exist. Either delete the uniffiToml property or supply a file", file)
}
}
let bindings = BindingsArgs::new(
SourceArgs::library(&lib_file).with_config(config),
OutputArgs::new(&ts_dir, &cpp_dir, false),
);

bindings.run()?
};
ubrn_common::cd(&pwd)?;

let rust_crate = project.crate_.metadata()?;
crate::codegen::render_files(project, rust_crate, modules)?;
Ok(())
}

fn and_generate(&self) -> bool {
self.cmd.and_generate()
GenerateAllArgs::new(lib_file, self.cmd.config()).run()
}
}

Expand All @@ -84,10 +54,10 @@ impl BuildCmd {
.ok_or_else(|| anyhow!("No targets were specified"))
}

pub(crate) fn project_config(&self) -> Result<ProjectConfig> {
fn config(&self) -> Utf8PathBuf {
match self {
Self::Android(a) => a.project_config(),
Self::Ios(a) => a.project_config(),
Self::Android(a) => a.config(),
Self::Ios(a) => a.config(),
}
}

Expand All @@ -109,6 +79,14 @@ pub(crate) struct CommonBuildArgs {
#[clap(long, short, default_value = "false")]
pub(crate) release: bool,

/// If the Rust library has been built for at least one target, then
/// don't re-run cargo build.
///
/// This may be useful if you are using a pre-built library or are
/// managing the build process yourself.
#[clap(long)]
pub(crate) no_cargo: bool,

/// Optionally generate the bindings and turbo-module code for the crate
#[clap(long = "and-generate", short = 'g')]
pub(crate) and_generate: bool,
Expand Down
63 changes: 61 additions & 2 deletions crates/ubrn_cli/src/generate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/
*/
use anyhow::Result;
use camino::Utf8PathBuf;
use clap::{Args, Subcommand};
use ubrn_bindgen::BindingsArgs;
use ubrn_bindgen::{BindingsArgs, OutputArgs, SourceArgs};

use crate::codegen::TurboModuleArgs;
use crate::{codegen::TurboModuleArgs, config::ProjectConfig};

#[derive(Args, Debug)]
pub(crate) struct GenerateArgs {
Expand All @@ -27,6 +28,11 @@ pub(crate) enum GenerateCmd {
Bindings(BindingsArgs),
/// Generate the TurboModule code to plug the bindings into the app
TurboModule(TurboModuleArgs),
/// Generate the Bindings and TurboModule code from a library
/// file and a YAML config file.
///
/// This is the second step of the `--and-generate` option of the build command.
All(GenerateAllArgs),
}

impl GenerateCmd {
Expand All @@ -40,6 +46,59 @@ impl GenerateCmd {
t.run()?;
Ok(())
}
Self::All(t) => {
t.run()?;
Ok(())
}
}
}
}

#[derive(Args, Debug)]
pub(crate) struct GenerateAllArgs {
/// The configuration file for this project
#[clap(long)]
config: Utf8PathBuf,

/// A path to staticlib file.
lib_file: Utf8PathBuf,
}

impl GenerateAllArgs {
pub(crate) fn new(lib_file: Utf8PathBuf, config: Utf8PathBuf) -> Self {
Self { lib_file, config }
}

pub(crate) fn run(&self) -> Result<()> {
let project = self.project_config()?;
let root = project.project_root();
let pwd = ubrn_common::pwd()?;
let lib_file = pwd.join(&self.lib_file);
let modules = {
let dir = project.crate_.directory()?;
ubrn_common::cd(&dir)?;
let ts_dir = project.bindings.ts_path(root);
let cpp_dir = project.bindings.cpp_path(root);
let config = project.bindings.uniffi_toml_path(root);
if let Some(ref file) = config {
if !file.exists() {
anyhow::bail!("uniffi.toml file {:?} does not exist. Either delete the uniffiToml property or supply a file", file)
}
}
let bindings = BindingsArgs::new(
SourceArgs::library(&lib_file).with_config(config),
OutputArgs::new(&ts_dir, &cpp_dir, false),
);

bindings.run()?
};
ubrn_common::cd(&pwd)?;
let rust_crate = project.crate_.metadata()?;
crate::codegen::render_files(project, rust_crate, modules)?;
Ok(())
}

fn project_config(&self) -> Result<ProjectConfig> {
self.config.clone().try_into()
}
}
Loading