293 lines
9.1 KiB
Rust
293 lines
9.1 KiB
Rust
use std::env;
|
|
use std::ffi::OsString;
|
|
use std::fs;
|
|
use std::path::Path;
|
|
use std::path::PathBuf;
|
|
use std::str::FromStr;
|
|
|
|
use anyhow::{anyhow, Result};
|
|
use convert_case::{Case, Casing};
|
|
use serde::Deserialize;
|
|
use structopt::clap::AppSettings;
|
|
use structopt::StructOpt;
|
|
use toml::Value;
|
|
|
|
#[derive(StructOpt, Debug, PartialEq, Deserialize, Default)]
|
|
#[structopt(setting(AppSettings::DeriveDisplayOrder))]
|
|
pub struct RawOpts {
|
|
/// Path of input Rust code
|
|
#[structopt(short, long)]
|
|
pub rust_input: String,
|
|
/// Path of output generated Dart code
|
|
#[structopt(short, long)]
|
|
pub dart_output: String,
|
|
/// If provided, generated Dart declaration code to this separate file
|
|
#[structopt(long)]
|
|
pub dart_decl_output: Option<String>,
|
|
|
|
/// Path of output generated C header
|
|
#[structopt(short, long)]
|
|
pub c_output: Option<Vec<String>>,
|
|
/// Crate directory for your Rust project
|
|
#[structopt(long)]
|
|
pub rust_crate_dir: Option<String>,
|
|
/// Path of output generated Rust code
|
|
#[structopt(long)]
|
|
pub rust_output: Option<String>,
|
|
/// Generated class name
|
|
#[structopt(long)]
|
|
pub class_name: Option<String>,
|
|
/// Line length for dart formatting
|
|
#[structopt(long)]
|
|
pub dart_format_line_length: Option<i32>,
|
|
/// Skip automatically adding `mod bridge_generated;` to `lib.rs`
|
|
#[structopt(long)]
|
|
pub skip_add_mod_to_lib: bool,
|
|
/// Path to the installed LLVM
|
|
#[structopt(long)]
|
|
pub llvm_path: Option<Vec<String>>,
|
|
/// LLVM compiler opts
|
|
#[structopt(long)]
|
|
pub llvm_compiler_opts: Option<String>,
|
|
/// Path to root of Dart project, otherwise inferred from --dart-output
|
|
#[structopt(long)]
|
|
pub dart_root: Option<String>,
|
|
/// Skip running build_runner even when codegen-capable code is detected
|
|
#[structopt(long)]
|
|
pub no_build_runner: bool,
|
|
/// Show debug messages.
|
|
#[structopt(short, long)]
|
|
pub verbose: bool,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct Opts {
|
|
pub rust_input_path: String,
|
|
pub dart_output_path: String,
|
|
pub dart_decl_output_path: Option<String>,
|
|
pub c_output_path: Vec<String>,
|
|
pub rust_crate_dir: String,
|
|
pub rust_output_path: String,
|
|
pub class_name: String,
|
|
pub dart_format_line_length: i32,
|
|
pub skip_add_mod_to_lib: bool,
|
|
pub llvm_path: Vec<String>,
|
|
pub llvm_compiler_opts: String,
|
|
pub manifest_path: String,
|
|
pub dart_root: Option<String>,
|
|
pub build_runner: bool,
|
|
}
|
|
|
|
pub fn parse(raw: RawOpts) -> Opts {
|
|
let rust_input_path = canon_path(&raw.rust_input);
|
|
|
|
let rust_crate_dir = canon_path(&raw.rust_crate_dir.unwrap_or_else(|| {
|
|
fallback_rust_crate_dir(&rust_input_path)
|
|
.unwrap_or_else(|_| panic!("{}", format_fail_to_guess_error("rust_crate_dir")))
|
|
}));
|
|
let manifest_path = {
|
|
let mut path = std::path::PathBuf::from_str(&rust_crate_dir).unwrap();
|
|
path.push("Cargo.toml");
|
|
path_to_string(path).unwrap()
|
|
};
|
|
let rust_output_path = canon_path(&raw.rust_output.unwrap_or_else(|| {
|
|
fallback_rust_output_path(&rust_input_path)
|
|
.unwrap_or_else(|_| panic!("{}", format_fail_to_guess_error("rust_output")))
|
|
}));
|
|
let class_name = raw.class_name.unwrap_or_else(|| {
|
|
fallback_class_name(&*rust_crate_dir)
|
|
.unwrap_or_else(|_| panic!("{}", format_fail_to_guess_error("class_name")))
|
|
});
|
|
let c_output_path = raw
|
|
.c_output
|
|
.map(|outputs| {
|
|
outputs
|
|
.iter()
|
|
.map(|output| canon_path(output))
|
|
.collect::<Vec<_>>()
|
|
})
|
|
.unwrap_or_else(|| {
|
|
vec![fallback_c_output_path()
|
|
.unwrap_or_else(|_| panic!("{}", format_fail_to_guess_error("c_output")))]
|
|
});
|
|
|
|
let dart_root = {
|
|
let dart_output = &raw.dart_output;
|
|
raw.dart_root
|
|
.as_deref()
|
|
.map(canon_path)
|
|
.or_else(|| fallback_dart_root(dart_output).ok())
|
|
};
|
|
|
|
Opts {
|
|
rust_input_path,
|
|
dart_output_path: canon_path(&raw.dart_output),
|
|
dart_decl_output_path: raw
|
|
.dart_decl_output
|
|
.as_ref()
|
|
.map(|s| canon_path(s.as_str())),
|
|
c_output_path,
|
|
rust_crate_dir,
|
|
rust_output_path,
|
|
class_name,
|
|
dart_format_line_length: raw.dart_format_line_length.unwrap_or(80),
|
|
skip_add_mod_to_lib: raw.skip_add_mod_to_lib,
|
|
llvm_path: raw.llvm_path.unwrap_or_else(|| {
|
|
vec![
|
|
"/opt/homebrew/opt/llvm".to_owned(), // Homebrew root
|
|
"/usr/local/opt/llvm".to_owned(), // Homebrew x86-64 root
|
|
// Possible Linux LLVM roots
|
|
"/usr/lib/llvm-9".to_owned(),
|
|
"/usr/lib/llvm-10".to_owned(),
|
|
"/usr/lib/llvm-11".to_owned(),
|
|
"/usr/lib/llvm-12".to_owned(),
|
|
"/usr/lib/llvm-13".to_owned(),
|
|
"/usr/lib/llvm-14".to_owned(),
|
|
"/usr/lib/".to_owned(),
|
|
"/usr/lib64/".to_owned(),
|
|
"C:/Program Files/llvm".to_owned(), // Default on Windows
|
|
"C:/Program Files/LLVM".to_owned(),
|
|
"C:/msys64/mingw64".to_owned(), // https://packages.msys2.org/package/mingw-w64-x86_64-clang
|
|
]
|
|
}),
|
|
llvm_compiler_opts: raw.llvm_compiler_opts.unwrap_or_else(|| "".to_string()),
|
|
manifest_path,
|
|
dart_root,
|
|
build_runner: !raw.no_build_runner,
|
|
}
|
|
}
|
|
|
|
fn format_fail_to_guess_error(name: &str) -> String {
|
|
format!(
|
|
"fail to guess {}, please specify it manually in command line arguments",
|
|
name
|
|
)
|
|
}
|
|
|
|
fn fallback_rust_crate_dir(rust_input_path: &str) -> Result<String> {
|
|
let mut dir_curr = Path::new(rust_input_path)
|
|
.parent()
|
|
.ok_or_else(|| anyhow!(""))?;
|
|
|
|
loop {
|
|
let path_cargo_toml = dir_curr.join("Cargo.toml");
|
|
|
|
if path_cargo_toml.exists() {
|
|
return Ok(dir_curr
|
|
.as_os_str()
|
|
.to_str()
|
|
.ok_or_else(|| anyhow!(""))?
|
|
.to_string());
|
|
}
|
|
|
|
if let Some(next_parent) = dir_curr.parent() {
|
|
dir_curr = next_parent;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
Err(anyhow!(
|
|
"look at parent directories but none contains Cargo.toml"
|
|
))
|
|
}
|
|
|
|
fn fallback_c_output_path() -> Result<String> {
|
|
let named_temp_file = Box::leak(Box::new(tempfile::Builder::new().suffix(".h").tempfile()?));
|
|
Ok(named_temp_file
|
|
.path()
|
|
.to_str()
|
|
.ok_or_else(|| anyhow!(""))?
|
|
.to_string())
|
|
}
|
|
|
|
fn fallback_rust_output_path(rust_input_path: &str) -> Result<String> {
|
|
Ok(Path::new(rust_input_path)
|
|
.parent()
|
|
.ok_or_else(|| anyhow!(""))?
|
|
.join("bridge_generated.rs")
|
|
.to_str()
|
|
.ok_or_else(|| anyhow!(""))?
|
|
.to_string())
|
|
}
|
|
|
|
fn fallback_dart_root(dart_output_path: &str) -> Result<String> {
|
|
let mut res = canon_pathbuf(dart_output_path);
|
|
while res.pop() {
|
|
if res.join("pubspec.yaml").is_file() {
|
|
return res
|
|
.to_str()
|
|
.map(ToString::to_string)
|
|
.ok_or_else(|| anyhow!("Non-utf8 path"));
|
|
}
|
|
}
|
|
Err(anyhow!(
|
|
"Root of Dart library could not be inferred from Dart output"
|
|
))
|
|
}
|
|
|
|
fn fallback_class_name(rust_crate_dir: &str) -> Result<String> {
|
|
let cargo_toml_path = Path::new(rust_crate_dir).join("Cargo.toml");
|
|
let cargo_toml_content = fs::read_to_string(cargo_toml_path)?;
|
|
|
|
let cargo_toml_value = cargo_toml_content.parse::<Value>()?;
|
|
let package_name = cargo_toml_value
|
|
.get("package")
|
|
.ok_or_else(|| anyhow!("no `package` in Cargo.toml"))?
|
|
.get("name")
|
|
.ok_or_else(|| anyhow!("no `name` in Cargo.toml"))?
|
|
.as_str()
|
|
.ok_or_else(|| anyhow!(""))?;
|
|
|
|
Ok(package_name.to_case(Case::Pascal))
|
|
}
|
|
|
|
fn canon_path(sub_path: &str) -> String {
|
|
let path = canon_pathbuf(sub_path);
|
|
path_to_string(path).unwrap_or_else(|_| panic!("fail to parse path: {}", sub_path))
|
|
}
|
|
|
|
fn canon_pathbuf(sub_path: &str) -> PathBuf {
|
|
let mut path =
|
|
env::current_dir().unwrap_or_else(|_| panic!("fail to parse path: {}", sub_path));
|
|
path.push(sub_path);
|
|
path
|
|
}
|
|
|
|
fn path_to_string(path: PathBuf) -> Result<String, OsString> {
|
|
path.into_os_string().into_string()
|
|
}
|
|
|
|
impl Opts {
|
|
pub fn dart_api_class_name(&self) -> String {
|
|
self.class_name.clone()
|
|
}
|
|
|
|
pub fn dart_api_impl_class_name(&self) -> String {
|
|
format!("{}Impl", self.class_name)
|
|
}
|
|
|
|
pub fn dart_wire_class_name(&self) -> String {
|
|
format!("{}Wire", self.class_name)
|
|
}
|
|
|
|
/// Returns None if the path terminates in "..", or not utf8.
|
|
pub fn dart_output_path_name(&self) -> Option<&str> {
|
|
let name = Path::new(&self.dart_output_path);
|
|
let root = name.file_name()?.to_str()?;
|
|
if let Some((name, _)) = root.rsplit_once('.') {
|
|
Some(name)
|
|
} else {
|
|
Some(root)
|
|
}
|
|
}
|
|
|
|
pub fn dart_output_freezed_path(&self) -> Option<String> {
|
|
Some(
|
|
Path::new(&self.dart_output_path)
|
|
.with_extension("freezed.dart")
|
|
.to_str()?
|
|
.to_owned(),
|
|
)
|
|
}
|
|
}
|