use std::collections::{
HashMap,
HashSet,
};
use ::uni_components::ui_page::UiPage;
use crate::{
Architecture,
SimpleUiPage,
};
mod manifest_parsing;
mod template_instantiation;
const ADB: &'static str = "$ADB$";
const DIR: &'static str = "$DIR$";
pub struct AndroidBuilder {
pages: Vec<(SimpleUiPage, bool)>,
path_to_icon: String,
app_id: String,
sdk_location: String,
min_sdk: String,
target_sdk: String,
ndk_locaiton: String,
app_name: String,
version: String,
version_code: u32,
manifest_processor: crate::manifest_processor::ManifestProcessor,
architecture: HashSet<Architecture>,
release_mode: bool,
}
impl AndroidBuilder {
pub fn new(
path_to_icon: String,
app_id: String,
sdk_location: String,
min_sdk: String,
target_sdk: String,
ndk_locaiton: String,
app_name: String,
) -> Self {
let manifest_processor = crate::manifest_processor::ManifestProcessor::new();
let version = std::env::var("CARGO_PKG_VERSION")
.expect("Error while retrieving CARGO_PKG_VERSION");
let cargo_major = std::env::var("CARGO_PKG_VERSION_MAJOR")
.expect("Error while retrieving CARGO_PKG_VERSION_MAJOR")
.parse::<u32>()
.expect("Incorrect variable value CARGO_PKG_VERSION_MAJOR");
let cargo_minor = std::env::var("CARGO_PKG_VERSION_MINOR")
.expect("Error while retrieving CARGO_PKG_VERSION_MINOR")
.parse::<u32>()
.expect("Incorrect variable value CARGO_PKG_VERSION_MINOR");
let cargo_patch = std::env::var("CARGO_PKG_VERSION_PATCH")
.expect("Error while retrieving CARGO_PKG_VERSION_PATCH")
.parse::<u32>()
.expect("Incorrect variable value CARGO_PKG_VERSION_PATCH");
let version_code = (cargo_major * 1000 + cargo_minor) * 1000 + cargo_patch;
let release_mode = std::env::var("PROFILE") == Ok("release".to_owned());
return Self {
pages: Vec::new(),
path_to_icon,
app_id,
sdk_location,
min_sdk,
target_sdk,
ndk_locaiton,
app_name,
version,
version_code,
manifest_processor,
architecture: Architecture::all(),
release_mode,
};
}
pub fn set_architecture(
&mut self,
archs: HashSet<Architecture>,
) {
self.architecture = archs;
}
pub fn set_version(
&mut self,
version: String,
) {
self.version = version;
}
pub fn set_version_code(
&mut self,
version_code: u32,
) {
self.version_code = version_code;
}
pub fn set_release_mode(
&mut self,
is_release: bool,
) {
self.release_mode = is_release;
}
pub fn add_page<T>(
&mut self,
page: &'static dyn UiPage<Data = T>,
crate_name: &str,
) {
self.add_path(page.path(), crate_name, false);
}
pub fn add_launcher<T>(
&mut self,
page: &'static dyn UiPage<Data = T>,
crate_name: &str,
) {
self.add_path(page.path(), crate_name, true);
}
pub fn execute(self) {
let out_dir = std::env::var("OUT_DIR").expect("OUT_DIR have to be setted up");
let jni_crate_name = "uni_tmp_jni";
let jni_crate_info;
let map: HashMap<_, _> = {
let mut map = HashMap::new();
let resolved = {
let config = cargo::util::config::Config::default()
.expect("Config creation failed");
let mut pr = cargo::core::registry::PackageRegistry::new(&config)
.expect("Package registry failed");
pr.lock_patches();
let path_to_cargotoml =
format!("{}/Cargo.toml", self.manifest_processor.manifest_path());
let path = std::path::Path::new(&path_to_cargotoml);
let source_id = cargo::core::SourceId::for_path(path.clone())
.expect("Source id creation failed");
let (manifest, _) =
cargo::util::toml::read_manifest(&path, source_id, &config).unwrap();
let m = match manifest {
cargo::core::manifest::EitherManifest::Real(m) => m,
cargo::core::manifest::EitherManifest::Virtual(v) => {
panic!("virtual manifest:{:?}", v);
},
};
let pkg = cargo::core::package::Package::new(m, path);
let summary = pkg.summary().clone();
let resolve_ops = cargo::core::resolver::ResolveOpts::everything();
let try_to_use = std::collections::HashSet::new();
let _lock = config
.acquire_package_cache_lock()
.expect("package cache lock failed");
cargo::core::resolver::resolve(
&[(summary, resolve_ops)],
&[],
&mut pr,
&try_to_use,
None,
false,
)
.expect("Resolve failed")
};
for pkg in resolved.iter() {
let sumary = resolved.summary(pkg);
let name: String = sumary.name().to_string();
let may_be_path = self.manifest_processor.get_manifest_path(&name);
match may_be_path {
None => {
log::error!(
"Crate path not found for `{}`. Sounds unsoundness",
name
);
},
Some(path) => {
manifest_parsing::process_manifest(name, path, &mut map);
},
}
}
let jni_crate_pkgid = resolved.query(jni_crate_name).expect(&format!(
"package {} not found in dependencies list",
jni_crate_name,
));
let sumary = resolved.summary(jni_crate_pkgid);
let source = sumary.source_id();
match source.is_path() {
true => jni_crate_info = format!("{{ path=\"{}\"}}", source.url().path()),
false => jni_crate_info = format!("\"{}\"", sumary.version().to_string()),
}
map.iter().filter_map(|(x, c)| c.clone().map(|c| (x.clone(), c))).collect()
};
let deps = map
.iter()
.map(|(n, c)| format!("{} = {{ path=\"{}\" }}", n, c.manifest_path))
.collect::<Vec<_>>()
.join("\n");
let deps = format!("{}\n{} = {}\n", deps, jni_crate_name, jni_crate_info,);
let lib_content = map
.iter()
.flat_map(|(name, config)| {
config.funcs.iter().flat_map(move |funcs| {
funcs.iter().map(move |f| {
let params = f
.params
.iter()
.enumerate()
.map(|(id, t)| format!("_a{}: {}", id, t))
.collect::<Vec<_>>()
.join(",");
let params_2 = f
.params
.iter()
.enumerate()
.map(|(id, _)| format!("_a{}", id))
.collect::<Vec<_>>()
.join(",");
format!(
"
#[no_mangle]
#[allow(non_snake_case)]
pub extern \"C\" fn {}(
{}
) -> {} {{
return {}::{}({});
}}
",
f.export, params, f.result, name, f.name, params_2
)
})
})
})
.collect::<Vec<_>>()
.join("\n");
let activities = {
let mut activities = Vec::new();
for (page, is_launcher) in self.pages.iter() {
activities.push(template_instantiation::ActivityInfo {
name: activity_name_for(&page),
is_launcher: *is_launcher,
});
}
activities
};
let package = format!("com.uniuirust.buildfor.{}", self.app_id);
let lib_content_2 = self
.pages
.iter()
.map(|(s, _)| {
let m_path = s.module_path().to_owned();
let config = manifest_parsing::get_config(m_path.clone());
let main = config
.expect(&format!("UI crates without metadata.uni_android:{}", m_path))
.main_function
.expect(&format!(
"uni_android.main_function not specified in manifest:{}",
m_path
));
let on_create_name = format!(
"Java_{}_{}_onCreateNative",
package.clone().replace("_", "_1").replace(".", "_"),
activity_name_for(&s).replace("_", "_1"),
);
let on_tick_name = format!(
"Java_{}_{}_onTickNative",
package.clone().replace("_", "_1").replace(".", "_"),
activity_name_for(&s).replace("_", "_1"),
);
let on_cleanup_name = format!(
"Java_{}_{}_onCleanupNative",
package.clone().replace("_", "_1").replace(".", "_"),
activity_name_for(&s).replace("_", "_1"),
);
format!(
"
#[no_mangle]
#[allow(non_snake_case)]
pub extern \"C\" fn {}(
env: jni::JNIEnv,
activity: jni::objects::JObject
) {{
{}::{}::{}(env, activity);
}}
#[no_mangle]
#[allow(non_snake_case)]
pub extern \"C\" fn {}(
env: jni::JNIEnv,
activity: jni::objects::JObject,
addr: jni::sys::jlong,
) -> jni::sys::jlong {{
return {}::{}::{}(env, activity, addr);
}}
#[no_mangle]
#[allow(non_snake_case)]
pub extern \"C\" fn {}(
env: jni::JNIEnv,
activity: jni::objects::JObject,
addr: jni::sys::jlong,
) {{
{}::{}::{}(env, activity, addr);
}}
",
on_create_name,
s.module_name(),
crate::android_module_name(main.clone()),
crate::RUN_FUNCTION_NAME,
on_tick_name,
s.module_name(),
crate::android_module_name(main.clone()),
crate::TICK_FUNCTION_NAME,
on_cleanup_name,
s.module_name(),
crate::android_module_name(main),
crate::CLEANUP_FUNCTION_NAME,
)
})
.collect::<Vec<_>>()
.join("\n");
let lib_content = format!(
"extern crate uni_tmp_jni as jni;\n{}",
vec![lib_content, lib_content_2].join("\n")
);
let dir = format!("{}/android", std::env::var("OUT_DIR").unwrap());
let arch_list = self
.architecture
.into_iter()
.map(|x| x.as_quoted_string())
.collect::<Vec<_>>()
.join(", ");
template_instantiation::instantiate_for(
&dir,
&deps,
&arch_list,
&self.app_id,
&self.min_sdk,
&self.target_sdk,
&self.sdk_location,
&self.ndk_locaiton,
&package,
&self.app_name,
&self.version,
&self.version_code.to_string(),
&lib_content,
activities,
self.release_mode,
)
.expect("Template instantiation failed");
{
for (name, config) in map.iter() {
if let Some(files_list) = config.java_files.as_ref() {
for file in files_list {
let dest_dir = format!("{}/app/src/main/java/{}", dir, name);
{
std::fs::create_dir_all(&dest_dir)
.expect(&format!("Can't create dir:{}", dest_dir,));
let dest_dir = std::path::Path::new(&dest_dir);
let dest_dir =
dest_dir.canonicalize().expect("Canonicalization failed");
match dest_dir.starts_with(&out_dir) {
true => {
},
false => {
panic!(
"Java direcotry for crate:{} is not a part of \
OUT directory subtree. Dir:{}",
name,
dest_dir.display(),
);
},
}
}
std::fs::create_dir_all(&dest_dir)
.expect(&format!("Can't create dir:{}", dest_dir,));
let file_last_name = std::path::Path::new(file)
.file_name()
.expect(&format!("Can't extract filename from:{}", file))
.to_str()
.expect(&format!(
"Can't extract filename as string from:{}",
file
));
let source = format!("{}/{}", config.manifest_path, file);
let destination = format!("{}/{}", dest_dir, file_last_name);
std::fs::copy(&source, &destination).expect(&format!(
"Copy failed source:{} destination:{}",
source, destination,
));
}
}
}
}
let gradle_command = match self.release_mode {
true => "assembleRelease",
false => "assembleDebug",
};
let gradle_status = std::process::Command::new("./gradlew")
.arg("--no-daemon")
.arg(gradle_command)
.current_dir(&dir)
.status()
.expect("Gradle build failed. Please see error above");
if !gradle_status.success() {
panic!("Gradle build failed. Please see error above");
}
let file_name = format!("{}/uni_build_generated.rs", out_dir);
let rust = std::include_str!("./build_android/templates/rust.rs")
.replace(ADB, &format!("{}/platform-tools/adb", self.sdk_location))
.replace(DIR, &dir);
std::fs::write(&file_name, &rust).expect("Rust helpers write failed");
let _ = self.path_to_icon.clone();
}
fn add_path(
&mut self,
path: &str,
crate_name: &str,
is_launcher: bool,
) {
let manifest_path =
self.manifest_processor.get_manifest_path(crate_name).expect(&format!(
"Crate:{} not found in manifest:{}. Please add the crate into \
[build-dependencies] list",
crate_name,
self.manifest_processor.manifest_path(),
));
self.pages.push((
SimpleUiPage::new(path.to_owned(), crate_name.to_owned(), manifest_path),
is_launcher,
));
}
}
fn activity_name_for(s: &SimpleUiPage) -> String {
return s.module_name().to_owned();
}