diff --git a/crates/moon/src/cli.rs b/crates/moon/src/cli.rs index 325a703e..36f160e7 100644 --- a/crates/moon/src/cli.rs +++ b/crates/moon/src/cli.rs @@ -51,6 +51,9 @@ pub use fmt::*; pub use generate_test_driver::*; pub use info::*; use moonbuild::upgrade::UpgradeSubcommand; +use mooncake::pkg::{ + add::AddSubcommand, install::InstallSubcommand, remove::RemoveSubcommand, tree::TreeSubcommand, +}; pub use new::*; pub use query::*; pub use run::*; diff --git a/crates/moon/src/cli/build.rs b/crates/moon/src/cli/build.rs index e7161b66..9d52aedf 100644 --- a/crates/moon/src/cli/build.rs +++ b/crates/moon/src/cli/build.rs @@ -22,6 +22,7 @@ use moonbuild::entry; use moonbuild::watch::watching; use mooncake::pkg::sync::auto_sync; use moonutil::common::lower_surface_targets; +use moonutil::common::BuildOpt; use moonutil::common::FileLock; use moonutil::common::MoonbuildOpt; use moonutil::common::RunMode; @@ -31,6 +32,7 @@ use moonutil::mooncakes::sync::AutoSyncFlags; use moonutil::mooncakes::RegistryConfig; use n2::trace; use std::path::Path; +use std::path::PathBuf; use std::sync::Arc; use std::thread; @@ -50,8 +52,19 @@ pub struct BuildSubcommand { #[clap(long, short)] pub watch: bool, + #[clap(long, hide = true)] + pub install_path: Option, + #[clap(long, hide = true)] pub show_artifacts: bool, + + // package name (username/hello/lib) + #[clap(long, hide = true)] + pub package: Option, + + // when package is specified, specify the alias of the binary package artifact to install + #[clap(long, hide = true, requires("package"))] + pub bin_alias: Option, } pub fn run_build(cli: &UniversalFlags, cmd: &BuildSubcommand) -> anyhow::Result { @@ -138,6 +151,10 @@ fn run_build_internal( build_graph: cli.build_graph, test_opt: None, check_opt: None, + build_opt: Some(BuildOpt { + install_path: cmd.install_path.clone(), + filter_package: cmd.package.clone(), + }), fmt_opt: None, args: vec![], output_json: false, @@ -152,6 +169,19 @@ fn run_build_internal( &dir_sync_result, )?; + if let Some(bin_alias) = cmd.bin_alias.clone() { + let pkg = module.get_package_by_name_mut_safe(cmd.package.as_ref().unwrap()); + match pkg { + Some(pkg) => { + pkg.bin_name = Some(bin_alias); + } + _ => anyhow::bail!(format!( + "package `{}` not found", + cmd.package.as_ref().unwrap() + )), + } + } + moonutil::common::set_native_backend_link_flags( run_mode, cmd.build_flags.release, diff --git a/crates/moon/src/cli/bundle.rs b/crates/moon/src/cli/bundle.rs index d37be1d8..df1db7df 100644 --- a/crates/moon/src/cli/bundle.rs +++ b/crates/moon/src/cli/bundle.rs @@ -142,6 +142,7 @@ fn run_bundle_internal( run_mode, test_opt: None, check_opt: None, + build_opt: None, fmt_opt: None, args: vec![], verbose: cli.verbose, diff --git a/crates/moon/src/cli/check.rs b/crates/moon/src/cli/check.rs index 9a04be9c..24d46be6 100644 --- a/crates/moon/src/cli/check.rs +++ b/crates/moon/src/cli/check.rs @@ -169,6 +169,7 @@ fn run_check_internal( no_mi: cmd.no_mi, }), test_opt: None, + build_opt: None, fmt_opt: None, args: vec![], no_parallelize: false, diff --git a/crates/moon/src/cli/deps.rs b/crates/moon/src/cli/deps.rs index 238e3d7b..0cf2f80f 100644 --- a/crates/moon/src/cli/deps.rs +++ b/crates/moon/src/cli/deps.rs @@ -17,6 +17,9 @@ // For inquiries, you can contact us via e-mail at jichuruanjian@idea.edu.cn. use anyhow::bail; +use mooncake::pkg::{ + add::AddSubcommand, install::InstallSubcommand, remove::RemoveSubcommand, tree::TreeSubcommand, +}; use moonutil::{ dirs::PackageDirs, mooncakes::{ModuleName, RegistryConfig}, @@ -24,35 +27,19 @@ use moonutil::{ use super::UniversalFlags; -/// Install dependencies -#[derive(Debug, clap::Parser)] -pub struct InstallSubcommand {} - -/// Remove a dependency -#[derive(Debug, clap::Parser)] -pub struct RemoveSubcommand { - /// The package path to remove - pub package_path: String, -} - -/// Add a dependency -#[derive(Debug, clap::Parser)] -pub struct AddSubcommand { - /// The package path to add - pub package_path: String, -} - -/// Display the dependency tree -#[derive(Debug, clap::Parser)] -pub struct TreeSubcommand {} - pub fn install_cli(cli: UniversalFlags, _cmd: InstallSubcommand) -> anyhow::Result { let PackageDirs { source_dir, target_dir, } = cli.source_tgt_dir.try_into_package_dirs()?; let registry_config = RegistryConfig::load(); - mooncake::pkg::install::install(&source_dir, &target_dir, ®istry_config, false) + mooncake::pkg::install::install( + &source_dir, + &target_dir, + ®istry_config, + cli.quiet, + cli.verbose, + ) } pub fn remove_cli(cli: UniversalFlags, cmd: RemoveSubcommand) -> anyhow::Result { @@ -100,9 +87,16 @@ pub fn add_cli(cli: UniversalFlags, cmd: AddSubcommand) -> anyhow::Result { if parts.len() == 2 { let version: &str = parts[1]; let version = version.parse()?; - mooncake::pkg::add::add(&source_dir, &target_dir, &pkg_name, &version, false) + mooncake::pkg::add::add( + &source_dir, + &target_dir, + &pkg_name, + cmd.bin, + &version, + false, + ) } else { - mooncake::pkg::add::add_latest(&source_dir, &target_dir, &pkg_name, false) + mooncake::pkg::add::add_latest(&source_dir, &target_dir, &pkg_name, cmd.bin, false) } } diff --git a/crates/moon/src/cli/doc.rs b/crates/moon/src/cli/doc.rs index 3ed574ad..6c8244c0 100644 --- a/crates/moon/src/cli/doc.rs +++ b/crates/moon/src/cli/doc.rs @@ -93,6 +93,7 @@ pub fn run_doc(cli: UniversalFlags, cmd: DocSubcommand) -> anyhow::Result { run_mode, test_opt: None, check_opt: None, + build_opt: None, fmt_opt: None, args: vec![], verbose: cli.verbose, diff --git a/crates/moon/src/cli/fmt.rs b/crates/moon/src/cli/fmt.rs index 992afd1f..110fef4b 100644 --- a/crates/moon/src/cli/fmt.rs +++ b/crates/moon/src/cli/fmt.rs @@ -77,6 +77,7 @@ pub fn run_fmt(cli: &UniversalFlags, cmd: FmtSubcommand) -> anyhow::Result build_graph: cli.build_graph, test_opt: None, check_opt: None, + build_opt: None, args: vec![], verbose: cli.verbose, quiet: cli.quiet, diff --git a/crates/moon/src/cli/generate_test_driver.rs b/crates/moon/src/cli/generate_test_driver.rs index 17774b4e..141d37a5 100644 --- a/crates/moon/src/cli/generate_test_driver.rs +++ b/crates/moon/src/cli/generate_test_driver.rs @@ -175,6 +175,7 @@ pub fn generate_test_driver( patch_file: cmd.patch_file.clone(), }), check_opt: None, + build_opt: None, fmt_opt: None, sort_input, run_mode, diff --git a/crates/moon/src/cli/info.rs b/crates/moon/src/cli/info.rs index e4a3ed93..a367ea6f 100644 --- a/crates/moon/src/cli/info.rs +++ b/crates/moon/src/cli/info.rs @@ -93,6 +93,7 @@ pub fn run_info(cli: UniversalFlags, cmd: InfoSubcommand) -> anyhow::Result run_mode: RunMode::Check, test_opt: None, check_opt: None, + build_opt: None, fmt_opt: None, args: vec![], verbose: cli.verbose, diff --git a/crates/moon/src/cli/run.rs b/crates/moon/src/cli/run.rs index 0b1f07b3..fd90bf81 100644 --- a/crates/moon/src/cli/run.rs +++ b/crates/moon/src/cli/run.rs @@ -279,6 +279,7 @@ pub fn run_run_internal(cli: &UniversalFlags, cmd: RunSubcommand) -> anyhow::Res build_graph: cli.build_graph, test_opt: None, check_opt: None, + build_opt: None, fmt_opt: None, output_json: false, no_parallelize: false, diff --git a/crates/moon/src/cli/test.rs b/crates/moon/src/cli/test.rs index f0d45c1e..32f008af 100644 --- a/crates/moon/src/cli/test.rs +++ b/crates/moon/src/cli/test.rs @@ -202,6 +202,7 @@ fn run_test_internal( patch_file, }), check_opt: None, + build_opt: None, sort_input, run_mode, quiet: true, diff --git a/crates/moon/tests/test_cases/mod.rs b/crates/moon/tests/test_cases/mod.rs index be07cc13..cdb00864 100644 --- a/crates/moon/tests/test_cases/mod.rs +++ b/crates/moon/tests/test_cases/mod.rs @@ -8020,3 +8020,50 @@ fn test_moon_query() { get_stdout(&dir, ["query", "moonbitlang/x"]); } + +#[test] +fn test_moon_install_bin() { + let dir = TestDir::new("moon_install_bin.in"); + let dir = dir.join("user.in"); + + let mooncakes_path = dir.join(moonutil::common::DEP_PATH); + if mooncakes_path.exists() { + std::fs::remove_dir_all(&mooncakes_path).unwrap(); + } + + let bin_dir = mooncakes_path.join(moonutil::common::MOON_BIN_DIR); + + // moon check should auto install bin deps + get_stdout(&dir, ["check"]); + assert!(bin_dir.exists()); + assert!(bin_dir.join("author2-native.exe").exists()); + assert!(bin_dir.join("author2-js.js").exists()); + assert!(bin_dir.join("author2-wasm.wasm").exists()); + assert!(bin_dir.join("m-wasm.wasm").exists()); + assert!(bin_dir.join("main-js.js").exists()); + + std::fs::remove_dir_all(&mooncakes_path).unwrap(); + + assert!(!bin_dir.exists()); + + // moon install should install bin deps + get_stdout(&dir, ["install"]); + + assert!(bin_dir.exists()); + assert!(bin_dir.join("author2-native.exe").exists()); + assert!(bin_dir.join("author2-js.js").exists()); + assert!(bin_dir.join("author2-wasm.wasm").exists()); + assert!(bin_dir.join("m-wasm.wasm").exists()); + assert!(bin_dir.join("main-js.js").exists()); + + check( + get_stderr(&dir, ["build"]), + expect![[r#" + main-js + lib Hello, world! + () + Executed 1 pre-build task, now up to date + Finished. moon: ran 17 tasks, now up to date + "#]], + ); +} diff --git a/crates/moon/tests/test_cases/moon_install_bin.in/author1.in/.gitignore b/crates/moon/tests/test_cases/moon_install_bin.in/author1.in/.gitignore new file mode 100644 index 00000000..b1283a74 --- /dev/null +++ b/crates/moon/tests/test_cases/moon_install_bin.in/author1.in/.gitignore @@ -0,0 +1,2 @@ +target/ +.mooncakes/ diff --git a/crates/moon/tests/test_cases/moon_install_bin.in/author1.in/README.md b/crates/moon/tests/test_cases/moon_install_bin.in/author1.in/README.md new file mode 100644 index 00000000..ae00983f --- /dev/null +++ b/crates/moon/tests/test_cases/moon_install_bin.in/author1.in/README.md @@ -0,0 +1 @@ +# username/hello \ No newline at end of file diff --git a/crates/moon/tests/test_cases/moon_install_bin.in/author1.in/lib/hello.mbt b/crates/moon/tests/test_cases/moon_install_bin.in/author1.in/lib/hello.mbt new file mode 100644 index 00000000..4dcc7801 --- /dev/null +++ b/crates/moon/tests/test_cases/moon_install_bin.in/author1.in/lib/hello.mbt @@ -0,0 +1,3 @@ +pub fn hello() -> Unit { + println("lib Hello, world!") +} diff --git a/crates/moon/tests/test_cases/moon_install_bin.in/author1.in/lib/moon.pkg.json b/crates/moon/tests/test_cases/moon_install_bin.in/author1.in/lib/moon.pkg.json new file mode 100644 index 00000000..0e0dcd23 --- /dev/null +++ b/crates/moon/tests/test_cases/moon_install_bin.in/author1.in/lib/moon.pkg.json @@ -0,0 +1,3 @@ +{ + +} \ No newline at end of file diff --git a/crates/moon/tests/test_cases/moon_install_bin.in/author1.in/main-js/main.mbt b/crates/moon/tests/test_cases/moon_install_bin.in/author1.in/main-js/main.mbt new file mode 100644 index 00000000..0c8a970b --- /dev/null +++ b/crates/moon/tests/test_cases/moon_install_bin.in/author1.in/main-js/main.mbt @@ -0,0 +1,4 @@ +fn main { + println("main-js") + println(@lib.hello()) +} diff --git a/crates/moon/tests/test_cases/moon_install_bin.in/author1.in/main-js/moon.pkg.json b/crates/moon/tests/test_cases/moon_install_bin.in/author1.in/main-js/moon.pkg.json new file mode 100644 index 00000000..e7bb8cf1 --- /dev/null +++ b/crates/moon/tests/test_cases/moon_install_bin.in/author1.in/main-js/moon.pkg.json @@ -0,0 +1,7 @@ +{ + "is_main": true, + "import": [ + "username/flash/lib" + ], + "bin-target": "js" +} diff --git a/crates/moon/tests/test_cases/moon_install_bin.in/author1.in/main-native/main.mbt b/crates/moon/tests/test_cases/moon_install_bin.in/author1.in/main-native/main.mbt new file mode 100644 index 00000000..a610dd82 --- /dev/null +++ b/crates/moon/tests/test_cases/moon_install_bin.in/author1.in/main-native/main.mbt @@ -0,0 +1,4 @@ +fn main { + println("main-native") + println(@lib.hello()) +} diff --git a/crates/moon/tests/test_cases/moon_install_bin.in/author1.in/main-native/moon.pkg.json b/crates/moon/tests/test_cases/moon_install_bin.in/author1.in/main-native/moon.pkg.json new file mode 100644 index 00000000..2a93e3fb --- /dev/null +++ b/crates/moon/tests/test_cases/moon_install_bin.in/author1.in/main-native/moon.pkg.json @@ -0,0 +1,8 @@ +{ + "is_main": true, + "import": [ + "username/flash/lib" + ], + "bin-target": "native", + "bin-name": "this-is-native" +} diff --git a/crates/moon/tests/test_cases/moon_install_bin.in/author1.in/main-wasm/main.mbt b/crates/moon/tests/test_cases/moon_install_bin.in/author1.in/main-wasm/main.mbt new file mode 100644 index 00000000..40cab69a --- /dev/null +++ b/crates/moon/tests/test_cases/moon_install_bin.in/author1.in/main-wasm/main.mbt @@ -0,0 +1,4 @@ +fn main { + println("main-wasm") + println(@lib.hello()) +} diff --git a/crates/moon/tests/test_cases/moon_install_bin.in/author1.in/main-wasm/moon.pkg.json b/crates/moon/tests/test_cases/moon_install_bin.in/author1.in/main-wasm/moon.pkg.json new file mode 100644 index 00000000..c643fdea --- /dev/null +++ b/crates/moon/tests/test_cases/moon_install_bin.in/author1.in/main-wasm/moon.pkg.json @@ -0,0 +1,8 @@ +{ + "is_main": true, + "import": [ + "username/flash/lib" + ], + "bin-target": "wasm", + "bin-name": "this-is-wasm" +} diff --git a/crates/moon/tests/test_cases/moon_install_bin.in/author1.in/moon.mod.json b/crates/moon/tests/test_cases/moon_install_bin.in/author1.in/moon.mod.json new file mode 100644 index 00000000..6f5ae316 --- /dev/null +++ b/crates/moon/tests/test_cases/moon_install_bin.in/author1.in/moon.mod.json @@ -0,0 +1,9 @@ +{ + "name": "username/flash", + "version": "0.1.0", + "readme": "README.md", + "repository": "", + "license": "Apache-2.0", + "keywords": [], + "description": "" +} \ No newline at end of file diff --git a/crates/moon/tests/test_cases/moon_install_bin.in/author2.in/.gitignore b/crates/moon/tests/test_cases/moon_install_bin.in/author2.in/.gitignore new file mode 100644 index 00000000..b1283a74 --- /dev/null +++ b/crates/moon/tests/test_cases/moon_install_bin.in/author2.in/.gitignore @@ -0,0 +1,2 @@ +target/ +.mooncakes/ diff --git a/crates/moon/tests/test_cases/moon_install_bin.in/author2.in/README.md b/crates/moon/tests/test_cases/moon_install_bin.in/author2.in/README.md new file mode 100644 index 00000000..ae00983f --- /dev/null +++ b/crates/moon/tests/test_cases/moon_install_bin.in/author2.in/README.md @@ -0,0 +1 @@ +# username/hello \ No newline at end of file diff --git a/crates/moon/tests/test_cases/moon_install_bin.in/author2.in/lib/hello.mbt b/crates/moon/tests/test_cases/moon_install_bin.in/author2.in/lib/hello.mbt new file mode 100644 index 00000000..4dcc7801 --- /dev/null +++ b/crates/moon/tests/test_cases/moon_install_bin.in/author2.in/lib/hello.mbt @@ -0,0 +1,3 @@ +pub fn hello() -> Unit { + println("lib Hello, world!") +} diff --git a/crates/moon/tests/test_cases/moon_install_bin.in/author2.in/lib/moon.pkg.json b/crates/moon/tests/test_cases/moon_install_bin.in/author2.in/lib/moon.pkg.json new file mode 100644 index 00000000..0e0dcd23 --- /dev/null +++ b/crates/moon/tests/test_cases/moon_install_bin.in/author2.in/lib/moon.pkg.json @@ -0,0 +1,3 @@ +{ + +} \ No newline at end of file diff --git a/crates/moon/tests/test_cases/moon_install_bin.in/author2.in/main-js/main.mbt b/crates/moon/tests/test_cases/moon_install_bin.in/author2.in/main-js/main.mbt new file mode 100644 index 00000000..0c8a970b --- /dev/null +++ b/crates/moon/tests/test_cases/moon_install_bin.in/author2.in/main-js/main.mbt @@ -0,0 +1,4 @@ +fn main { + println("main-js") + println(@lib.hello()) +} diff --git a/crates/moon/tests/test_cases/moon_install_bin.in/author2.in/main-js/moon.pkg.json b/crates/moon/tests/test_cases/moon_install_bin.in/author2.in/main-js/moon.pkg.json new file mode 100644 index 00000000..10409479 --- /dev/null +++ b/crates/moon/tests/test_cases/moon_install_bin.in/author2.in/main-js/moon.pkg.json @@ -0,0 +1,8 @@ +{ + "is_main": true, + "import": [ + "author2/flash/lib" + ], + "bin-target": "js", + "bin-name": "author2-js" +} diff --git a/crates/moon/tests/test_cases/moon_install_bin.in/author2.in/main-native/main.mbt b/crates/moon/tests/test_cases/moon_install_bin.in/author2.in/main-native/main.mbt new file mode 100644 index 00000000..a610dd82 --- /dev/null +++ b/crates/moon/tests/test_cases/moon_install_bin.in/author2.in/main-native/main.mbt @@ -0,0 +1,4 @@ +fn main { + println("main-native") + println(@lib.hello()) +} diff --git a/crates/moon/tests/test_cases/moon_install_bin.in/author2.in/main-native/moon.pkg.json b/crates/moon/tests/test_cases/moon_install_bin.in/author2.in/main-native/moon.pkg.json new file mode 100644 index 00000000..c54dea0c --- /dev/null +++ b/crates/moon/tests/test_cases/moon_install_bin.in/author2.in/main-native/moon.pkg.json @@ -0,0 +1,8 @@ +{ + "is_main": true, + "import": [ + "author2/flash/lib" + ], + "bin-target": "native", + "bin-name": "author2-native" +} diff --git a/crates/moon/tests/test_cases/moon_install_bin.in/author2.in/main-wasm/main.mbt b/crates/moon/tests/test_cases/moon_install_bin.in/author2.in/main-wasm/main.mbt new file mode 100644 index 00000000..40cab69a --- /dev/null +++ b/crates/moon/tests/test_cases/moon_install_bin.in/author2.in/main-wasm/main.mbt @@ -0,0 +1,4 @@ +fn main { + println("main-wasm") + println(@lib.hello()) +} diff --git a/crates/moon/tests/test_cases/moon_install_bin.in/author2.in/main-wasm/moon.pkg.json b/crates/moon/tests/test_cases/moon_install_bin.in/author2.in/main-wasm/moon.pkg.json new file mode 100644 index 00000000..1855b74a --- /dev/null +++ b/crates/moon/tests/test_cases/moon_install_bin.in/author2.in/main-wasm/moon.pkg.json @@ -0,0 +1,8 @@ +{ + "is_main": true, + "import": [ + "author2/flash/lib" + ], + "bin-target": "wasm", + "bin-name": "author2-wasm" +} diff --git a/crates/moon/tests/test_cases/moon_install_bin.in/author2.in/moon.mod.json b/crates/moon/tests/test_cases/moon_install_bin.in/author2.in/moon.mod.json new file mode 100644 index 00000000..b5e34eaf --- /dev/null +++ b/crates/moon/tests/test_cases/moon_install_bin.in/author2.in/moon.mod.json @@ -0,0 +1,9 @@ +{ + "name": "author2/flash", + "version": "0.1.0", + "readme": "README.md", + "repository": "", + "license": "Apache-2.0", + "keywords": [], + "description": "" +} \ No newline at end of file diff --git a/crates/moon/tests/test_cases/moon_install_bin.in/user.in/.gitignore b/crates/moon/tests/test_cases/moon_install_bin.in/user.in/.gitignore new file mode 100644 index 00000000..b1283a74 --- /dev/null +++ b/crates/moon/tests/test_cases/moon_install_bin.in/user.in/.gitignore @@ -0,0 +1,2 @@ +target/ +.mooncakes/ diff --git a/crates/moon/tests/test_cases/moon_install_bin.in/user.in/README.md b/crates/moon/tests/test_cases/moon_install_bin.in/user.in/README.md new file mode 100644 index 00000000..ae00983f --- /dev/null +++ b/crates/moon/tests/test_cases/moon_install_bin.in/user.in/README.md @@ -0,0 +1 @@ +# username/hello \ No newline at end of file diff --git a/crates/moon/tests/test_cases/moon_install_bin.in/user.in/lib/hello.mbt b/crates/moon/tests/test_cases/moon_install_bin.in/user.in/lib/hello.mbt new file mode 100644 index 00000000..4dcc7801 --- /dev/null +++ b/crates/moon/tests/test_cases/moon_install_bin.in/user.in/lib/hello.mbt @@ -0,0 +1,3 @@ +pub fn hello() -> Unit { + println("lib Hello, world!") +} diff --git a/crates/moon/tests/test_cases/moon_install_bin.in/user.in/lib/moon.pkg.json b/crates/moon/tests/test_cases/moon_install_bin.in/user.in/lib/moon.pkg.json new file mode 100644 index 00000000..0e0dcd23 --- /dev/null +++ b/crates/moon/tests/test_cases/moon_install_bin.in/user.in/lib/moon.pkg.json @@ -0,0 +1,3 @@ +{ + +} \ No newline at end of file diff --git a/crates/moon/tests/test_cases/moon_install_bin.in/user.in/main/main.mbt b/crates/moon/tests/test_cases/moon_install_bin.in/user.in/main/main.mbt new file mode 100644 index 00000000..2807a44e --- /dev/null +++ b/crates/moon/tests/test_cases/moon_install_bin.in/user.in/main/main.mbt @@ -0,0 +1,4 @@ +fn main { + println("main") + println(@lib.hello()) +} diff --git a/crates/moon/tests/test_cases/moon_install_bin.in/user.in/main/moon.pkg.json b/crates/moon/tests/test_cases/moon_install_bin.in/user.in/main/moon.pkg.json new file mode 100644 index 00000000..661f5b10 --- /dev/null +++ b/crates/moon/tests/test_cases/moon_install_bin.in/user.in/main/moon.pkg.json @@ -0,0 +1,13 @@ +{ + "is_main": true, + "import": [ + "good/idea/lib" + ], + "pre-build": [ + { + "input": [], + "output": ["1.txt"], + "command": "node $mooncake_bin/main-js.js" + } + ] +} diff --git a/crates/moon/tests/test_cases/moon_install_bin.in/user.in/moon.mod.json b/crates/moon/tests/test_cases/moon_install_bin.in/user.in/moon.mod.json new file mode 100644 index 00000000..692cd055 --- /dev/null +++ b/crates/moon/tests/test_cases/moon_install_bin.in/user.in/moon.mod.json @@ -0,0 +1,24 @@ +{ + "name": "good/idea", + "version": "0.1.0", + "readme": "README.md", + "repository": "", + "license": "Apache-2.0", + "keywords": [], + "description": "", + "bin-deps": { + "username/flash": { + "path": "../author1.in", + "bin_pkg": [ + "main-js", + { + "name": "main-wasm", + "alias": "m-wasm" + } + ] + }, + "author2/flash": { + "path": "../author2.in" + } + } +} \ No newline at end of file diff --git a/crates/moonbuild/src/bench.rs b/crates/moonbuild/src/bench.rs index af61a269..b660e445 100644 --- a/crates/moonbuild/src/bench.rs +++ b/crates/moonbuild/src/bench.rs @@ -111,6 +111,8 @@ pub fn f() -> Unit {{ alert_list: None, targets: None, pre_build: None, + bin_name: None, + bin_target: None, }; moonutil::common::write_package_json_to_file(&pkg, &moon_pkg).unwrap(); } @@ -128,6 +130,7 @@ pub fn write(config: &Config, base_dir: &Path) { name: "build_matrix".to_string(), version: None, deps: None, + bin_deps: None, readme: None, repository: None, license: None, @@ -197,6 +200,8 @@ pub fn write(config: &Config, base_dir: &Path) { alert_list: None, targets: None, pre_build: None, + bin_name: None, + bin_target: None, }; moonutil::common::write_package_json_to_file(&pkg, &base_dir.join("main").join(MOON_PKG_JSON)) diff --git a/crates/moonbuild/src/gen/gen_build.rs b/crates/moonbuild/src/gen/gen_build.rs index c7e181ef..d8d77578 100644 --- a/crates/moonbuild/src/gen/gen_build.rs +++ b/crates/moonbuild/src/gen/gen_build.rs @@ -26,7 +26,7 @@ use crate::gen::MiAlias; use std::path::{Path, PathBuf}; use std::rc::Rc; -use moonutil::common::{MoonbuildOpt, MooncOpt, MOONBITLANG_CORE, MOON_PKG_JSON}; +use moonutil::common::{BuildOpt, MoonbuildOpt, MooncOpt, MOONBITLANG_CORE, MOON_PKG_JSON}; use n2::graph::{self as n2graph, Build, BuildIns, BuildOuts, FileLoc}; use n2::load::State; use n2::smallmap::SmallMap; @@ -138,18 +138,31 @@ pub fn gen_build_link_item( package_full_name, package_path: pkg.root_path.clone(), link: pkg.link.clone(), + install_path: pkg.install_path.clone(), + bin_name: pkg.bin_name.clone(), }) } pub fn gen_build( m: &ModuleDB, moonc_opt: &MooncOpt, - _moonbuild_opt: &MoonbuildOpt, + moonbuild_opt: &MoonbuildOpt, ) -> anyhow::Result { let mut build_items = vec![]; let mut link_items = vec![]; - for (i, (_, pkg)) in m.get_all_packages().iter().enumerate() { - let is_main = m.entries.contains(&i); + + let pkgs_to_build = if let Some(BuildOpt { + filter_package: Some(filter_package), + .. + }) = moonbuild_opt.build_opt.as_ref() + { + &m.get_filtered_packages_and_its_deps_by_pkgname(filter_package)? + } else { + m.get_all_packages() + }; + + for (_, pkg) in pkgs_to_build { + let is_main = pkg.is_main; if is_main { // entry also need build @@ -460,6 +473,32 @@ pub fn gen_n2_build_state( let (build, fid) = gen_link_command(&mut graph, item, moonc_opt); default.push(fid); graph.add_build(build)?; + + // if we need to install the artifact to a specific path + if let Some(install_path) = item.install_path.as_ref() { + let artifact_output_path = install_path + .join(if let Some(bin_name) = &item.bin_name { + bin_name.clone() + } else { + PathBuf::from(&item.out) + .file_name() + .unwrap() + .to_str() + .unwrap() + .to_string() + }) + .display() + .to_string(); + + let link_item_to_install = BuildLinkDepItem { + out: artifact_output_path, + ..item.clone() + }; + + let (build, fid) = gen_link_command(&mut graph, &link_item_to_install, moonc_opt); + default.push(fid); + graph.add_build(build)?; + } } let mut hashes = n2graph::Hashes::default(); diff --git a/crates/moonbuild/src/gen/gen_check.rs b/crates/moonbuild/src/gen/gen_check.rs index 3f2e8500..5561eb7a 100644 --- a/crates/moonbuild/src/gen/gen_check.rs +++ b/crates/moonbuild/src/gen/gen_check.rs @@ -314,7 +314,7 @@ pub fn gen_check( .. }) = moonbuild_opt.check_opt.as_ref() { - &m.get_filtered_packages_and_its_deps(&moonbuild_opt.source_dir.join(pkg_path)) + &m.get_filtered_packages_and_its_deps_by_pkgpath(&moonbuild_opt.source_dir.join(pkg_path)) } else { m.get_all_packages() }; diff --git a/crates/moonbuild/src/gen/gen_runtest.rs b/crates/moonbuild/src/gen/gen_runtest.rs index 23a48b68..56fa9eff 100644 --- a/crates/moonbuild/src/gen/gen_runtest.rs +++ b/crates/moonbuild/src/gen/gen_runtest.rs @@ -634,6 +634,8 @@ pub fn gen_link_internal_test( package_sources, package_path: pkg.root_path.clone(), link: pkg.link.clone(), + install_path: None, + bin_name: None, }) } @@ -671,6 +673,8 @@ pub fn gen_link_whitebox_test( package_sources, package_path: pkg.root_path.clone(), link: pkg.link.clone(), + install_path: None, + bin_name: None, }) } @@ -735,6 +739,8 @@ pub fn gen_link_blackbox_test( package_sources, package_path: pkg.root_path.clone(), link: pkg.link.clone(), + install_path: None, + bin_name: None, }) } diff --git a/crates/moonbuild/src/new.rs b/crates/moonbuild/src/new.rs index 1e6d14fa..848f416b 100644 --- a/crates/moonbuild/src/new.rs +++ b/crates/moonbuild/src/new.rs @@ -73,6 +73,8 @@ pub fn moon_new_exec( alert_list: None, targets: None, pre_build: None, + bin_name: None, + bin_target: None, }; moonutil::common::write_package_json_to_file(&j, &main_moon_pkg)?; } @@ -152,6 +154,7 @@ fn common( name: cake_full_name.into(), version: Some("0.1.0".parse().unwrap()), deps: None, + bin_deps: None, readme: Some("README.md".into()), repository: Some("".into()), license: license @@ -220,6 +223,8 @@ fn common( alert_list: None, targets: None, pre_build: None, + bin_name: None, + bin_target: None, }; moonutil::common::write_package_json_to_file(&j, &lib_moon_pkg)?; } diff --git a/crates/moonbuild/src/pre_build.rs b/crates/moonbuild/src/pre_build.rs index 5d3df21d..604ddbdf 100644 --- a/crates/moonbuild/src/pre_build.rs +++ b/crates/moonbuild/src/pre_build.rs @@ -19,7 +19,7 @@ use std::path::PathBuf; use std::rc::Rc; -use moonutil::common::MoonbuildOpt; +use moonutil::common::{MoonbuildOpt, DEP_PATH, MOD_DIR, MOONCAKE_BIN, MOON_BIN_DIR, PKG_DIR}; use moonutil::module::ModuleDB; use moonutil::package::StringOrArray; use n2::graph::{self as n2graph, Build, BuildIns, BuildOuts, FileId, FileLoc}; @@ -99,13 +99,26 @@ pub fn load_moon_pre_build( let mut build = Build::new(loc, ins, outs); let moon_bin = std::env::current_exe()?; + let command = if command.starts_with(":embed") { command .replacen(":embed", &format!("{} tool embed", moon_bin.display()), 1) .to_string() } else { command.to_string() - }; + } + .replace( + MOONCAKE_BIN, + &moonbuild_opt + .source_dir + .join(DEP_PATH) + .join(MOON_BIN_DIR) + .display() + .to_string(), + ) + .replace(MOD_DIR, &moonbuild_opt.source_dir.display().to_string()) + .replace(PKG_DIR, &cwd.display().to_string()); + let command = command .replace("$input", &inputs.join(" ")) .replace("$output", &outputs.join(" ")); diff --git a/crates/moonbuild/template/mod.schema.json b/crates/moonbuild/template/mod.schema.json index d7cc9743..5c146acc 100644 --- a/crates/moonbuild/template/mod.schema.json +++ b/crates/moonbuild/template/mod.schema.json @@ -14,6 +14,16 @@ "null" ] }, + "bin-deps": { + "description": "third-party binary dependencies of the module", + "type": [ + "object", + "null" + ], + "additionalProperties": { + "type": "string" + } + }, "deps": { "description": "third-party dependencies of the module", "type": [ diff --git a/crates/moonbuild/template/pkg.schema.json b/crates/moonbuild/template/pkg.schema.json index 20b293ab..582acd14 100644 --- a/crates/moonbuild/template/pkg.schema.json +++ b/crates/moonbuild/template/pkg.schema.json @@ -11,6 +11,18 @@ "null" ] }, + "bin-name": { + "type": [ + "string", + "null" + ] + }, + "bin-target": { + "type": [ + "string", + "null" + ] + }, "import": { "description": "Imported packages of the package", "anyOf": [ diff --git a/crates/mooncake/src/dep_dir.rs b/crates/mooncake/src/dep_dir.rs index 4fe09537..a132c432 100644 --- a/crates/mooncake/src/dep_dir.rs +++ b/crates/mooncake/src/dep_dir.rs @@ -135,7 +135,10 @@ fn diff_dep_dir_state<'a>( } } for user in current.keys() { - if !target.contains_key(user) { + if !target.contains_key(user) + // this is a temporary workaround + && user != moonutil::common::MOON_BIN_DIR + { remove_user.insert(user.clone()); } } diff --git a/crates/mooncake/src/pkg/add.rs b/crates/mooncake/src/pkg/add.rs index fbce0843..e23e3de8 100644 --- a/crates/mooncake/src/pkg/add.rs +++ b/crates/mooncake/src/pkg/add.rs @@ -18,7 +18,7 @@ use colored::Colorize; use moonutil::common::{read_module_desc_file_in_dir, write_module_json_to_file, MOONBITLANG_CORE}; -use moonutil::dependency::DependencyInfo; +use moonutil::dependency::{BinaryDependencyInfo, SourceDependencyInfo}; use moonutil::module::convert_module_to_mod_json; use moonutil::mooncakes::{ModuleName, ModuleSource}; use semver::Version; @@ -28,10 +28,22 @@ use std::rc::Rc; use crate::registry::{self, Registry, RegistryList}; use crate::resolver::resolve_single_root_with_defaults; +/// Add a dependency +#[derive(Debug, clap::Parser)] +pub struct AddSubcommand { + /// The package path to add + pub package_path: String, + + /// Whether to add the dependency as a binary + #[clap(long)] + pub bin: bool, +} + pub fn add_latest( source_dir: &Path, target_dir: &Path, pkg_name: &ModuleName, + bin: bool, quiet: bool, ) -> anyhow::Result { if pkg_name.to_string() == MOONBITLANG_CORE { @@ -55,7 +67,14 @@ pub fn add_latest( .version .clone() .unwrap(); - add(source_dir, target_dir, pkg_name, &latest_version, quiet) + add( + source_dir, + target_dir, + pkg_name, + bin, + &latest_version, + quiet, + ) } #[test] @@ -68,6 +87,7 @@ pub fn add( source_dir: &Path, _target_dir: &Path, pkg_name: &ModuleName, + bin: bool, version: &Version, quiet: bool, ) -> anyhow::Result { @@ -82,13 +102,24 @@ pub fn add( std::process::exit(0); } - m.deps.insert( - pkg_name.to_string(), - DependencyInfo { - version: moonutil::version::as_caret_version_req(version.clone()), - ..Default::default() - }, - ); + if bin { + let bin_deps = m.bin_deps.get_or_insert_with(indexmap::IndexMap::new); + bin_deps.insert( + pkg_name.to_string(), + BinaryDependencyInfo { + version: moonutil::version::as_caret_version_req(version.clone()), + ..Default::default() + }, + ); + } else { + m.deps.insert( + pkg_name.to_string(), + SourceDependencyInfo { + version: moonutil::version::as_caret_version_req(version.clone()), + ..Default::default() + }, + ); + } let ms = ModuleSource::from_local_module(&m, source_dir).expect("Malformed module manifest"); let registries = RegistryList::with_default_registry(); let m = Rc::new(m); diff --git a/crates/mooncake/src/pkg/install.rs b/crates/mooncake/src/pkg/install.rs index 476f6847..67757561 100644 --- a/crates/mooncake/src/pkg/install.rs +++ b/crates/mooncake/src/pkg/install.rs @@ -22,22 +22,32 @@ use anyhow::Context; use moonutil::{ common::read_module_desc_file_in_dir, mooncakes::{result::ResolvedEnv, ModuleSource, RegistryConfig}, + scan::scan, }; -use std::{path::Path, rc::Rc}; +use std::{ + path::{Path, PathBuf}, + rc::Rc, +}; + +/// Install dependencies +#[derive(Debug, clap::Parser)] +pub struct InstallSubcommand {} pub fn install( source_dir: &Path, _target_dir: &Path, registry_config: &RegistryConfig, quiet: bool, + verbose: bool, ) -> anyhow::Result { - install_impl(source_dir, registry_config, quiet, false).map(|_| 0) + install_impl(source_dir, registry_config, quiet, verbose, false).map(|_| 0) } pub(crate) fn install_impl( source_dir: &Path, _registry_config: &RegistryConfig, quiet: bool, + verbose: bool, dont_sync: bool, ) -> anyhow::Result<(ResolvedEnv, DepDir)> { let m = read_module_desc_file_in_dir(source_dir)?; @@ -50,5 +60,168 @@ pub(crate) fn install_impl( crate::dep_dir::sync_deps(&dep_dir, ®istry, &res, quiet) .context("When installing packages")?; } + + if let Some(ref bin_deps) = m.bin_deps { + let install_path = source_dir + .join(moonutil::common::DEP_PATH) + .join(moonutil::common::MOON_BIN_DIR); + + let moon_path = std::env::current_exe() + .map_or_else(|_| "moon".into(), |x| x.to_string_lossy().into_owned()); + + for (bin_mod_to_install, info) in bin_deps { + let bin_mod_path = match info.path { + Some(ref path) => PathBuf::from(path), + None => dep_dir.path().join(bin_mod_to_install), + }; + + if !bin_mod_path.exists() { + anyhow::bail!( + "binary module `{}` not found in `{}`", + bin_mod_to_install, + dep_dir.path().display() + ); + } + + let module_db = get_module_db(&bin_mod_path, &res, &dep_dir)?; + + if let Some(ref bin_pkg) = info.bin_pkg { + for bin_pkg_to_install in bin_pkg { + let (pkg_name, bin_alias) = match bin_pkg_to_install { + moonutil::dependency::BinPkgItem::Simple(pkg_name) => (pkg_name, None), + moonutil::dependency::BinPkgItem::Detailed { name, alias } => { + (name, alias.as_ref()) + } + }; + + let full_pkg_name = format!("{bin_mod_to_install}/{pkg_name}"); + + let pkg = module_db.get_package_by_name_safe(&full_pkg_name); + match pkg { + Some(pkg) => { + build_and_install_bin_package( + &moon_path, + &bin_mod_path, + &full_pkg_name, + &install_path, + pkg.bin_target.to_backend_ext(), + bin_alias, + verbose, + )?; + } + _ => anyhow::bail!(format!("package `{}` not found", full_pkg_name)), + } + } + } else { + for (full_pkg_name, pkg) in module_db + .get_all_packages() + .iter() + .filter(|(_, p)| p.is_main && !p.is_third_party) + { + build_and_install_bin_package( + &moon_path, + &bin_mod_path, + full_pkg_name, + &install_path, + pkg.bin_target.to_backend_ext(), + None, + verbose, + )?; + } + } + } + + // remove all files except .exe, .wasm, .js + if install_path.exists() { + for entry in std::fs::read_dir(&install_path)? { + let entry = entry?; + let path = entry.path(); + if path.is_file() { + let ext = path.extension().and_then(|s| s.to_str()); + if !matches!(ext, Some("exe" | "wasm" | "js")) { + std::fs::remove_file(path)?; + } + } + } + } + } + Ok((res, dep_dir)) } + +fn build_and_install_bin_package( + moon_path: &str, + bin_mod_path: &Path, + full_pkg_name: &str, + install_path: &Path, + bin_target: impl AsRef, + bin_alias: Option<&String>, + verbose: bool, +) -> anyhow::Result<()> { + let mut build_args = vec![ + "build".to_string(), + "--source-dir".to_string(), + bin_mod_path.display().to_string(), + "--install-path".to_string(), + install_path.display().to_string(), + "--target".to_string(), + bin_target.as_ref().to_string(), + "--package".to_string(), + full_pkg_name.to_string(), + ]; + + if let Some(bin_alias) = bin_alias { + build_args.push("--bin-alias".to_string()); + build_args.push(bin_alias.to_string()); + } + + if !verbose { + build_args.push("--quiet".to_string()); + } + + if verbose { + eprintln!("Installing binary package `{}`", full_pkg_name); + } + + std::process::Command::new(moon_path) + .args(&build_args) + .spawn() + .with_context(|| format!("Failed to spawn build process for {}", full_pkg_name))? + .wait() + .with_context(|| format!("Failed to wait for build process of {}", full_pkg_name))?; + + Ok(()) +} + +fn get_module_db( + source_dir: &Path, + resolved_env: &ResolvedEnv, + dep_dir: &DepDir, +) -> anyhow::Result { + let dir_sync_result = crate::dep_dir::resolve_dep_dirs(dep_dir, resolved_env); + let moonbuild_opt = moonutil::common::MoonbuildOpt { + source_dir: source_dir.to_path_buf(), + raw_target_dir: source_dir.join("target"), + target_dir: source_dir.join("target"), + test_opt: None, + check_opt: None, + build_opt: None, + sort_input: false, + run_mode: moonutil::common::RunMode::Build, + quiet: true, + verbose: false, + no_parallelize: false, + build_graph: false, + fmt_opt: None, + args: vec![], + output_json: false, + }; + let module_db = scan( + false, + resolved_env, + &dir_sync_result, + &moonutil::common::MooncOpt::default(), + &moonbuild_opt, + )?; + Ok(module_db) +} diff --git a/crates/mooncake/src/pkg/remove.rs b/crates/mooncake/src/pkg/remove.rs index 71230913..28775203 100644 --- a/crates/mooncake/src/pkg/remove.rs +++ b/crates/mooncake/src/pkg/remove.rs @@ -27,6 +27,13 @@ use moonutil::{ use crate::resolver::resolve_single_root_with_defaults; +/// Remove a dependency +#[derive(Debug, clap::Parser)] +pub struct RemoveSubcommand { + /// The package path to remove + pub package_path: String, +} + pub fn remove( source_dir: &Path, target_dir: &Path, diff --git a/crates/mooncake/src/pkg/sync.rs b/crates/mooncake/src/pkg/sync.rs index be6c33e9..786d0f59 100644 --- a/crates/mooncake/src/pkg/sync.rs +++ b/crates/mooncake/src/pkg/sync.rs @@ -33,7 +33,7 @@ pub fn auto_sync( quiet: bool, ) -> anyhow::Result<(ResolvedEnv, DirSyncResult)> { let (resolved_env, dep_dir) = - super::install::install_impl(source_dir, registry_config, quiet, cli.dont_sync())?; + super::install::install_impl(source_dir, registry_config, quiet, false, cli.dont_sync())?; let dir_sync_result = resolve_dep_dirs(&dep_dir, &resolved_env); log::debug!("Dir sync result: {:?}", dir_sync_result); Ok((resolved_env, dir_sync_result)) diff --git a/crates/mooncake/src/pkg/tree.rs b/crates/mooncake/src/pkg/tree.rs index 66ea9a92..4df66025 100644 --- a/crates/mooncake/src/pkg/tree.rs +++ b/crates/mooncake/src/pkg/tree.rs @@ -25,6 +25,10 @@ use moonutil::common::{ read_module_desc_file_in_dir, read_module_from_json, DEP_PATH, MOON_MOD_JSON, }; +/// Display the dependency tree +#[derive(Debug, clap::Parser)] +pub struct TreeSubcommand {} + pub fn bold(top: &HashSet, item: &str) -> ColoredString { if top.contains(item) { item.bold() diff --git a/crates/mooncake/src/registry/mock.rs b/crates/mooncake/src/registry/mock.rs index f2a23eb9..08265a89 100644 --- a/crates/mooncake/src/registry/mock.rs +++ b/crates/mooncake/src/registry/mock.rs @@ -23,7 +23,7 @@ use std::{ rc::Rc, }; -use moonutil::{dependency::DependencyInfo, module::MoonMod, mooncakes::ModuleName}; +use moonutil::{dependency::SourceDependencyInfo, module::MoonMod, mooncakes::ModuleName}; use semver::{Version, VersionReq}; use super::Registry; @@ -136,7 +136,7 @@ pub fn create_mock_module<'a>( .map(|(name, version)| { ( name.to_string(), - DependencyInfo { + SourceDependencyInfo { version: VersionReq::parse(version).unwrap(), ..Default::default() }, diff --git a/crates/mooncake/src/resolver/mvs.rs b/crates/mooncake/src/resolver/mvs.rs index 89e7c0bd..21bc63e5 100644 --- a/crates/mooncake/src/resolver/mvs.rs +++ b/crates/mooncake/src/resolver/mvs.rs @@ -24,7 +24,7 @@ use std::{ use anyhow::anyhow; use moonutil::{ - dependency::DependencyInfo, + dependency::SourceDependencyInfo, module::MoonMod, mooncakes::{ModuleName, ModuleSource, ModuleSourceKind}, version::as_caret_comparator, @@ -50,7 +50,7 @@ impl Resolver for MvsSolver { fn select_min_version_satisfying<'a>( name: &ModuleName, - req: &DependencyInfo, + req: &SourceDependencyInfo, versions: impl Iterator + 'a, ) -> Result { // We only support caret version requirements, per mvs algorithm, so @@ -80,7 +80,7 @@ fn select_min_version_satisfying<'a>( fn select_min_version_satisfying_in_env( env: &mut ResolverEnv, name: &ModuleName, - req: &DependencyInfo, + req: &SourceDependencyInfo, ) -> Result<(Version, Rc), ResolverError> { let all_versions = env .all_versions_of(name, None) // todo: registry @@ -203,7 +203,16 @@ fn mvs_resolve( // Do a DFS in the graph while let Some((source, module)) = working_list.pop() { log::debug!("-- Solving for {}", source); - for (name, req) in &module.deps { + let mut all_deps = module.deps.clone(); + all_deps.extend( + module + .bin_deps + .clone() + .unwrap_or_default() + .into_iter() + .map(|(k, v)| (k, v.into())), + ); + for (name, req) in &all_deps { let pkg_name = match name.parse() { Ok(v) => v, Err(_) => { @@ -301,7 +310,16 @@ fn mvs_resolve( let curr_id = *visited.get(&pkg).unwrap(); - for (dep_name, req) in &module.deps { + let mut all_deps = module.deps.clone(); + all_deps.extend( + module + .bin_deps + .clone() + .unwrap_or_default() + .into_iter() + .map(|(k, v)| (k, v.into())), + ); + for (dep_name, req) in &all_deps { let dep_name = dep_name.parse().unwrap(); // If any malformed name, it should be reported in the previous round @@ -335,7 +353,7 @@ fn mvs_resolve( } fn resolve_pkg( - req: &DependencyInfo, + req: &SourceDependencyInfo, dependant: &ModuleSource, env: &mut ResolverEnv, pkg_name: &ModuleName, @@ -466,6 +484,7 @@ mod test { deps: { "dep/two": ^0.1.0, }, + bin_deps: None, readme: None, repository: None, license: None, diff --git a/crates/moonutil/src/common.rs b/crates/moonutil/src/common.rs index 14f36440..adacaf3d 100644 --- a/crates/moonutil/src/common.rs +++ b/crates/moonutil/src/common.rs @@ -58,6 +58,12 @@ pub const BLACKBOX_TEST_PATCH: &str = "_test.json"; pub const MOON_DOC_TEST_POSTFIX: &str = "__moonbit_internal_doc_test"; +pub const MOON_BIN_DIR: &str = "__moonbin__"; + +pub const MOONCAKE_BIN: &str = "$mooncake_bin"; +pub const MOD_DIR: &str = "$mod_dir"; +pub const PKG_DIR: &str = "$pkg_dir"; + #[derive(Debug, thiserror::Error)] pub enum SourceError { #[error("`source` should not contain invalid chars `{0:?}`")] @@ -284,6 +290,19 @@ impl TargetBackend { Self::Native => "native", } } + + pub fn str_to_backend(s: &str) -> anyhow::Result { + match s { + "wasm" => Ok(Self::Wasm), + "wasm-gc" => Ok(Self::WasmGC), + "js" => Ok(Self::Js), + "native" => Ok(Self::Native), + _ => bail!( + "invalid backend: {}, only support wasm, wasm-gc, js, native", + s + ), + } + } } #[derive(Debug, Clone, Default)] @@ -338,6 +357,7 @@ pub struct MoonbuildOpt { pub target_dir: PathBuf, pub test_opt: Option, pub check_opt: Option, + pub build_opt: Option, pub sort_input: bool, pub run_mode: RunMode, pub fmt_opt: Option, @@ -355,6 +375,13 @@ impl MoonbuildOpt { } } +#[derive(Debug, Clone, Default)] +pub struct BuildOpt { + pub install_path: Option, + + pub filter_package: Option, +} + #[derive(Debug, Clone, Default)] pub struct CheckOpt { pub package_path: Option, diff --git a/crates/moonutil/src/dependency.rs b/crates/moonutil/src/dependency.rs index 6a5ff4ad..91859095 100644 --- a/crates/moonutil/src/dependency.rs +++ b/crates/moonutil/src/dependency.rs @@ -25,7 +25,7 @@ use serde::{Deserialize, Serialize, Serializer}; /// Information about a specific dependency #[derive(Clone, Serialize, Deserialize, Default)] -pub struct DependencyInfo { +pub struct SourceDependencyInfo { #[serde(serialize_with = "serialize_version_req")] #[serde(default, skip_serializing_if = "version_is_default")] pub version: VersionReq, @@ -45,29 +45,29 @@ fn version_is_default(version: &VersionReq) -> bool { version.comparators.is_empty() } -impl std::fmt::Debug for DependencyInfo { +impl std::fmt::Debug for SourceDependencyInfo { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { if self.is_simple() { write!(f, "{}", self.version) } else { - f.debug_struct("DependencyInfo") + f.debug_struct("SourceDependencyInfo") .field("version", &format_args!("{}", self.version)) .finish() } } } -/// The JSON representation of a dependency info +/// The JSON representation of a source dependency info #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] -pub enum DependencyInfoJson { +pub enum SourceDependencyInfoJson { /// A simple version requirement Simple(#[serde(serialize_with = "serialize_version_req")] VersionReq), /// A detailed dependency info - Detailed(DependencyInfo), + Detailed(SourceDependencyInfo), } -impl DependencyInfo { +impl SourceDependencyInfo { /// Check if the requirement is simple. That is, it only contains a version requirement fn is_simple(&self) -> bool { self.path.is_none() && self.git.is_none() && self.git_branch.is_none() @@ -82,21 +82,21 @@ impl DependencyInfo { } } -impl From for DependencyInfoJson { - fn from(dep: DependencyInfo) -> Self { +impl From for SourceDependencyInfoJson { + fn from(dep: SourceDependencyInfo) -> Self { if dep.is_simple() { - DependencyInfoJson::Simple(dep.version) + SourceDependencyInfoJson::Simple(dep.version) } else { - DependencyInfoJson::Detailed(dep) + SourceDependencyInfoJson::Detailed(dep) } } } -impl From for DependencyInfo { - fn from(dep: DependencyInfoJson) -> Self { +impl From for SourceDependencyInfo { + fn from(dep: SourceDependencyInfoJson) -> Self { match dep { - DependencyInfoJson::Simple(v) => DependencyInfo::from_simple(v), - DependencyInfoJson::Detailed(d) => d, + SourceDependencyInfoJson::Simple(v) => SourceDependencyInfo::from_simple(v), + SourceDependencyInfoJson::Detailed(d) => d, } } } @@ -128,10 +128,108 @@ impl<'a> std::fmt::Display for ComparatorFormatWrapper<'a> { } } -impl FromStr for DependencyInfo { +impl FromStr for SourceDependencyInfo { type Err = semver::Error; fn from_str(s: &str) -> Result { - Ok(DependencyInfo::from_simple(VersionReq::parse(s)?)) + Ok(SourceDependencyInfo::from_simple(VersionReq::parse(s)?)) + } +} + +/// The JSON representation of a binary dependency info +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum BinaryDependencyInfoJson { + /// A simple version requirement + Simple(#[serde(serialize_with = "serialize_version_req")] VersionReq), + /// A detailed dependency info + Detailed(BinaryDependencyInfo), +} + +/// Information about a specific dependency +#[derive(Clone, Serialize, Deserialize, Default, Debug)] +pub struct BinaryDependencyInfo { + #[serde(serialize_with = "serialize_version_req")] + #[serde(default, skip_serializing_if = "version_is_default")] + pub version: VersionReq, + // Other optional fields... + /// Local path to the dependency. Overrides the version requirement. + #[serde(skip_serializing_if = "Option::is_none")] + pub path: Option, + /// Git repository URL. Overrides the version requirement. + #[serde(skip_serializing_if = "Option::is_none")] + pub git: Option, + /// Git branch to use. + #[serde(skip_serializing_if = "Option::is_none", rename = "branch")] + pub git_branch: Option, + + /// Binary packages to compile. + #[serde(skip_serializing_if = "Option::is_none", alias = "bin-pkg")] + pub bin_pkg: Option>, +} + +#[derive(Clone, Serialize, Deserialize, Debug)] +#[serde(untagged)] +pub enum BinPkgItem { + Simple(String), + Detailed { + name: String, + #[serde(skip_serializing_if = "Option::is_none")] + alias: Option, + }, +} + +impl BinaryDependencyInfo { + /// Check if the requirement is simple. That is, it only contains a version requirement + fn is_simple(&self) -> bool { + self.path.is_none() && self.git.is_none() && self.git_branch.is_none() + } + + #[allow(clippy::needless_update)] // More fields will be added later + fn from_simple(version: VersionReq) -> Self { + Self { + version, + ..Default::default() + } + } +} + +impl From for SourceDependencyInfoJson { + fn from(dep: BinaryDependencyInfo) -> Self { + if dep.is_simple() { + SourceDependencyInfoJson::Simple(dep.version) + } else { + SourceDependencyInfoJson::Detailed(dep.into()) + } + } +} + +impl From for SourceDependencyInfo { + fn from(dep: BinaryDependencyInfo) -> Self { + SourceDependencyInfo { + version: dep.version, + path: dep.path, + git: dep.git, + git_branch: dep.git_branch, + } + } +} + +impl From for BinaryDependencyInfoJson { + fn from(dep: BinaryDependencyInfo) -> Self { + if dep.is_simple() { + BinaryDependencyInfoJson::Simple(dep.version) + } else { + BinaryDependencyInfoJson::Detailed(dep) + } + } +} + +impl From for BinaryDependencyInfo { + fn from(dep: BinaryDependencyInfoJson) -> Self { + match dep { + BinaryDependencyInfoJson::Simple(v) => BinaryDependencyInfo::from_simple(v), + BinaryDependencyInfoJson::Detailed(d) => d, + } } } diff --git a/crates/moonutil/src/module.rs b/crates/moonutil/src/module.rs index f42bea6a..b986c8e1 100644 --- a/crates/moonutil/src/module.rs +++ b/crates/moonutil/src/module.rs @@ -16,11 +16,10 @@ // // For inquiries, you can contact us via e-mail at jichuruanjian@idea.edu.cn. -use crate::common::MoonModJSONFormatErrorKind; -use crate::common::MooncOpt; -use crate::common::NameError; -use crate::common::MOON_PKG_JSON; -use crate::dependency::{DependencyInfo, DependencyInfoJson}; +use crate::common::{MoonModJSONFormatErrorKind, MooncOpt, NameError, MOON_PKG_JSON}; +use crate::dependency::{ + BinaryDependencyInfo, BinaryDependencyInfoJson, SourceDependencyInfo, SourceDependencyInfoJson, +}; use crate::package::{AliasJSON, Package, PackageJSON}; use crate::path::ImportPath; use anyhow::bail; @@ -84,6 +83,14 @@ impl ModuleDB { self.packages.get(name).unwrap() } + pub fn get_package_by_name_safe(&self, name: &str) -> Option<&Package> { + self.packages.get(name) + } + + pub fn get_package_by_name_mut_safe(&mut self, name: &str) -> Option<&mut Package> { + self.packages.get_mut(name) + } + pub fn get_package_by_path(&self, path: &Path) -> Option<&Package> { self.packages.values().find(|it| it.root_path == path) } @@ -156,7 +163,10 @@ impl ModuleDB { }) } - pub fn get_filtered_packages_and_its_deps(&self, pkg_path: &Path) -> IndexMap { + pub fn get_filtered_packages_and_its_deps_by_pkgpath( + &self, + pkg_path: &Path, + ) -> IndexMap { let pkg = self.get_package_by_path(pkg_path); match pkg { Some(pkg) => { @@ -172,6 +182,24 @@ impl ModuleDB { } } + pub fn get_filtered_packages_and_its_deps_by_pkgname( + &self, + pkgname: &str, + ) -> anyhow::Result> { + match self.packages.get(pkgname) { + None => bail!("no such package: {}", pkgname), + Some(pkg) => { + let mut resolved = HashSet::new(); + resolved.insert(pkg.full_name().clone()); + self.resolve_deps_of_pkg(pkg, &mut resolved); + let it = resolved + .iter() + .map(|pkg_name| (pkg_name.clone(), self.get_package_by_name(pkg_name).clone())); + Ok(IndexMap::from_iter(it)) + } + } + } + // resolve deps of the given pkg in dfs way fn resolve_deps_of_pkg(&self, pkg: &Package, res: &mut HashSet) { for dep in pkg @@ -465,7 +493,8 @@ pub fn convert_mdb_to_json(module: &ModuleDB) -> ModuleDBJSON { pub struct MoonMod { pub name: String, pub version: Option, - pub deps: IndexMap, + pub deps: IndexMap, + pub bin_deps: Option>, pub readme: Option, pub repository: Option, pub license: Option, @@ -505,7 +534,12 @@ pub struct MoonModJSON { /// third-party dependencies of the module #[serde(skip_serializing_if = "Option::is_none")] #[schemars(with = "Option>")] - pub deps: Option>, + pub deps: Option>, + + /// third-party binary dependencies of the module + #[serde(skip_serializing_if = "Option::is_none")] + #[schemars(with = "Option>")] + pub bin_deps: Option>, /// path to module's README file #[serde(skip_serializing_if = "Option::is_none")] @@ -587,12 +621,17 @@ impl TryFrom for MoonMod { Some(d) => d.into_iter().map(|(k, v)| (k, v.into())).collect(), }; + let bin_deps = j + .bin_deps + .map(|d| d.into_iter().map(|(k, v)| (k, v.into())).collect()); + let source = j.source.map(|s| if s.is_empty() { ".".into() } else { s }); Ok(MoonMod { name: j.name, version, deps, + bin_deps, readme: j.readme, repository: j.repository, license: j.license, @@ -619,6 +658,9 @@ pub fn convert_module_to_mod_json(m: MoonMod) -> MoonModJSON { name: m.name, version: m.version.map(|v| v.to_string()), deps: Some(m.deps.into_iter().map(|(k, v)| (k, v.into())).collect()), + bin_deps: m + .bin_deps + .map(|d| d.into_iter().map(|(k, v)| (k, v.into())).collect()), readme: m.readme, repository: m.repository, license: m.license, diff --git a/crates/moonutil/src/package.rs b/crates/moonutil/src/package.rs index 0b4c7fbe..f9ff4351 100644 --- a/crates/moonutil/src/package.rs +++ b/crates/moonutil/src/package.rs @@ -73,6 +73,12 @@ pub struct Package { pub no_mi: bool, pub doc_test_patch_file: Option, + + pub install_path: Option, + + pub bin_name: Option, + + pub bin_target: TargetBackend, } impl Package { @@ -234,6 +240,16 @@ pub struct MoonPkgJSON { #[serde(alias = "pre-build")] #[schemars(rename = "pre-build")] pub pre_build: Option>, + + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(alias = "bin-name")] + #[schemars(rename = "bin-name")] + pub bin_name: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(alias = "bin-target")] + #[schemars(rename = "bin-target")] + pub bin_target: Option, } #[derive(Debug, Serialize, Deserialize, Clone, JsonSchema)] @@ -243,7 +259,7 @@ pub struct ImportMemory { pub name: String, } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct LinkDepItem { pub out: String, pub core_deps: Vec, // need add parent's core files recursively @@ -251,6 +267,8 @@ pub struct LinkDepItem { pub package_sources: Vec<(String, String)>, // (pkgname, source_dir) pub package_path: PathBuf, pub link: Option, + pub install_path: Option, + pub bin_name: Option, } #[rustfmt::skip] @@ -468,6 +486,9 @@ pub struct MoonPkg { pub targets: Option, pub pre_build: Option>, + + pub bin_name: Option, + pub bin_target: TargetBackend, } #[derive(Debug, Serialize, Deserialize, Clone)] @@ -618,6 +639,12 @@ pub fn convert_pkg_json_to_package(j: MoonPkgJSON) -> anyhow::Result { } } + let bin_target = if let Some(ref b) = j.bin_target { + TargetBackend::str_to_backend(b)? + } else { + TargetBackend::WasmGC + }; + let result = MoonPkg { name: None, is_main, @@ -634,6 +661,8 @@ pub fn convert_pkg_json_to_package(j: MoonPkgJSON) -> anyhow::Result { alert_list: j.alert_list, targets: j.targets, pre_build: j.pre_build, + bin_name: j.bin_name, + bin_target, }; Ok(result) } diff --git a/crates/moonutil/src/scan.rs b/crates/moonutil/src/scan.rs index 52a5e312..b0cca6c8 100644 --- a/crates/moonutil/src/scan.rs +++ b/crates/moonutil/src/scan.rs @@ -364,6 +364,13 @@ fn scan_one_package( patch_file: None, no_mi: false, doc_test_patch_file: None, + install_path: moonbuild_opt + .build_opt + .as_ref() + .and_then(|it| it.install_path.clone()) + .filter(|_| pkg.is_main && !is_third_party), + bin_name: pkg.bin_name, + bin_target: pkg.bin_target, }; if doc_mode { // -o diff --git a/docs/manual-zh/src/commands.md b/docs/manual-zh/src/commands.md index 459a270a..b9fd2d10 100644 --- a/docs/manual-zh/src/commands.md +++ b/docs/manual-zh/src/commands.md @@ -290,12 +290,16 @@ Generate public interface (`.mbti`) files for all packages in the module Add a dependency -**Usage:** `moon add ` +**Usage:** `moon add [OPTIONS] ` ###### **Arguments:** * `` — The package path to add +###### **Options:** + +* `--bin` — Whether to add the dependency as a binary + ## `moon remove` diff --git a/docs/manual-zh/src/source/mod_json_schema.html b/docs/manual-zh/src/source/mod_json_schema.html index 5e57f804..160fe0c3 100644 --- a/docs/manual-zh/src/source/mod_json_schema.html +++ b/docs/manual-zh/src/source/mod_json_schema.html @@ -52,6 +52,16 @@ "null" ] }, + "bin-deps": { + "description": "third-party binary dependencies of the module", + "type": [ + "object", + "null" + ], + "additionalProperties": { + "type": "string" + } + }, "deps": { "description": "third-party dependencies of the module", "type": [ diff --git a/docs/manual-zh/src/source/pkg_json_schema.html b/docs/manual-zh/src/source/pkg_json_schema.html index f2a4b73e..a3f603a8 100644 --- a/docs/manual-zh/src/source/pkg_json_schema.html +++ b/docs/manual-zh/src/source/pkg_json_schema.html @@ -49,6 +49,18 @@ "null" ] }, + "bin-name": { + "type": [ + "string", + "null" + ] + }, + "bin-target": { + "type": [ + "string", + "null" + ] + }, "import": { "description": "Imported packages of the package", "anyOf": [ diff --git a/docs/manual/src/commands.md b/docs/manual/src/commands.md index 459a270a..b9fd2d10 100644 --- a/docs/manual/src/commands.md +++ b/docs/manual/src/commands.md @@ -290,12 +290,16 @@ Generate public interface (`.mbti`) files for all packages in the module Add a dependency -**Usage:** `moon add ` +**Usage:** `moon add [OPTIONS] ` ###### **Arguments:** * `` — The package path to add +###### **Options:** + +* `--bin` — Whether to add the dependency as a binary + ## `moon remove` diff --git a/docs/manual/src/source/mod_json_schema.html b/docs/manual/src/source/mod_json_schema.html index 5e57f804..160fe0c3 100644 --- a/docs/manual/src/source/mod_json_schema.html +++ b/docs/manual/src/source/mod_json_schema.html @@ -52,6 +52,16 @@ "null" ] }, + "bin-deps": { + "description": "third-party binary dependencies of the module", + "type": [ + "object", + "null" + ], + "additionalProperties": { + "type": "string" + } + }, "deps": { "description": "third-party dependencies of the module", "type": [ diff --git a/docs/manual/src/source/pkg_json_schema.html b/docs/manual/src/source/pkg_json_schema.html index f2a4b73e..a3f603a8 100644 --- a/docs/manual/src/source/pkg_json_schema.html +++ b/docs/manual/src/source/pkg_json_schema.html @@ -49,6 +49,18 @@ "null" ] }, + "bin-name": { + "type": [ + "string", + "null" + ] + }, + "bin-target": { + "type": [ + "string", + "null" + ] + }, "import": { "description": "Imported packages of the package", "anyOf": [