From 40fda22e63355f68cb044f215575d1dc11e573df Mon Sep 17 00:00:00 2001 From: maciektr Date: Mon, 16 Dec 2024 18:49:56 +0100 Subject: [PATCH] AOT package script (#1836) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fixes #1771 commit-id:40843cae --- **Stack**: - #1846 - #1836 ⬅ ⚠️ *Part of a stack created by [spr](https://github.com/ejoffe/spr). Do not merge manually using the UI - doing so may have unexpected results.* Co-authored-by: Mateusz Kowalski --- scarb/src/ops/package.rs | 22 ++++++ scarb/tests/package.rs | 103 ++++++++++++++++++++++++-- website/docs/reference/scripts.md | 8 ++ website/docs/registries/publishing.md | 3 + 4 files changed, 130 insertions(+), 6 deletions(-) diff --git a/scarb/src/ops/package.rs b/scarb/src/ops/package.rs index 582698003..d604b0d41 100644 --- a/scarb/src/ops/package.rs +++ b/scarb/src/ops/package.rs @@ -37,6 +37,8 @@ const RESERVED_FILES: &[&str] = &[ VCS_INFO_FILE_NAME, ]; +const SCARB_PREPACKAGE_SCRIPT_NAME: &str = "package"; + #[derive(Clone)] pub struct PackageOpts { pub allow_dirty: bool, @@ -166,6 +168,8 @@ fn package_one_impl( check_metadata(pkg, ws.config())?; } + run_prepackage_script(pkg, ws)?; + let recipe = prepare_archive_recipe(pkg, opts, ws)?; let num_files = recipe.len(); @@ -218,6 +222,24 @@ fn package_one_impl( Ok(dst) } +fn run_prepackage_script(package: &Package, ws: &Workspace<'_>) -> Result<()> { + if let Some(script_definition) = package.manifest.scripts.get(SCARB_PREPACKAGE_SCRIPT_NAME) { + // Ensure no two instance of Scarb will run `package` script at the same time. + let _guard = ws.target_dir().child("scarb").advisory_lock( + ".scarb-package.lock", + "`package` script", + ws.config(), + ); + ws.config().ui().print(Status::new( + "Running", + &format!("`package` script for `{}`", package.id.name), + )); + ops::execute_script(script_definition, &[], ws, package.root(), None) + } else { + Ok(()) + } +} + fn list_one_impl( pkg_id: PackageId, opts: &PackageOpts, diff --git a/scarb/tests/package.rs b/scarb/tests/package.rs index eec4f1ee5..02b1c4a8b 100644 --- a/scarb/tests/package.rs +++ b/scarb/tests/package.rs @@ -10,6 +10,7 @@ use assert_fs::prelude::*; use assert_fs::TempDir; use indoc::{formatdoc, indoc}; use itertools::Itertools; +use libloading::library_filename; use scarb::DEFAULT_TARGET_DIR_NAME; use scarb_build_metadata::CAIRO_VERSION; use scarb_test_support::cairo_plugin_project_builder::CairoPluginProjectBuilder; @@ -22,7 +23,7 @@ use scarb_test_support::workspace_builder::WorkspaceBuilder; use test_case::test_case; struct PackageChecker { - actual_files: HashMap, + actual_files: HashMap>, base_name: PathBuf, } @@ -39,7 +40,7 @@ impl PackageChecker { fn assert(path: &Path) -> Self { let mut archive = Self::open(path); - let actual_files: HashMap = archive + let actual_files: HashMap> = archive .entries() .expect("failed to get archive entries") .map(|entry| { @@ -48,10 +49,11 @@ impl PackageChecker { .path() .expect("failed to get archive entry path") .into_owned(); - let mut contents = String::new(); + let mut contents: Vec = Vec::new(); entry - .read_to_string(&mut contents) + .read_to_end(&mut contents) .expect("failed to read archive entry contents"); + (name, contents) }) .collect(); @@ -104,9 +106,11 @@ impl PackageChecker { fn read_file(&self, path: impl AsRef) -> &str { let path = self.base_name.join(path); - self.actual_files + let buf = self + .actual_files .get(&path) - .unwrap_or_else(|| panic!("missing file in package tarball: {}", path.display())) + .unwrap_or_else(|| panic!("missing file in package tarball: {}", path.display())); + std::str::from_utf8(buf).expect("file is not valid UTF-8") } fn file_eq(&self, path: impl AsRef, expected_contents: &str) -> &Self { @@ -1676,3 +1680,90 @@ fn files_that_dont_exist_during_packaging_cannot_be_included() { 2: [..] "#}); } + +#[test] +fn package_script_is_run() { + let t = TempDir::new().unwrap(); + simple_project() + .manifest_package_extra(indoc! {r#" + [scripts] + package = "echo 'Hello!'" + "#}) + .build(&t); + Scarb::quick_snapbox() + .current_dir(&t) + .arg("package") + .arg("--no-metadata") + .assert() + .success() + .stdout_matches(indoc! {r#" + [..]Packaging foo v1.0.0 ([..]Scarb.toml) + [..]Running `package` script for `foo` + Hello! + [..]Verifying foo-1.0.0.tar.zst + [..]Compiling foo v1.0.0 ([..]Scarb.toml) + [..]Finished `dev` profile target(s) in [..] + [..]Packaged [..] files[..] + "#}); +} + +#[test] +fn package_proc_macro_with_package_script() { + let t = TempDir::new().unwrap(); + t.child("target/scarb/cairo-plugin") + .create_dir_all() + .unwrap(); + + let dll_filename = library_filename("foo"); + let dll_filename = dll_filename.to_string_lossy().to_string(); + + let script_code = format!( + "cargo build --release && cp target/release/{dll_filename} target/scarb/cairo-plugin" + ); + + CairoPluginProjectBuilder::start() + .name("foo") + .scarb_project(|b| { + b.name("foo") + .version("1.0.0") + .manifest_package_extra(indoc! {r#" + include = ["target/scarb/cairo-plugin"] + "#}) + .manifest_extra(formatdoc! {r#" + [cairo-plugin] + + [scripts] + package = "{script_code}" + "#}) + }) + .lib_rs(indoc! {r#" + use cairo_lang_macro::{ProcMacroResult, TokenStream, attribute_macro}; + + #[attribute_macro] + pub fn some(_attr: TokenStream, token_stream: TokenStream) -> ProcMacroResult { + ProcMacroResult::new(token_stream) + } + "#}) + .build(&t); + + Scarb::quick_snapbox() + .current_dir(&t) + .arg("package") + .arg("--no-metadata") + .assert() + .success(); + + let expected_shared_lib_file = format!("target/scarb/cairo-plugin/{dll_filename}"); + + PackageChecker::assert(&t.child("target/package/foo-1.0.0.tar.zst")) + .name_and_version("foo", "1.0.0") + .contents(&[ + "VERSION", + "Scarb.orig.toml", + "Scarb.toml", + &expected_shared_lib_file, + "Cargo.toml", + "Cargo.orig.toml", + "src/lib.rs", + ]); +} diff --git a/website/docs/reference/scripts.md b/website/docs/reference/scripts.md index 06cf384d4..3bd16bb97 100644 --- a/website/docs/reference/scripts.md +++ b/website/docs/reference/scripts.md @@ -18,6 +18,14 @@ bar = "echo 'World!'" This section should not contain any values with type different from string, including subtables, arrays, or numbers. In case the section is empty, it will be ignored. +### Special script names + +Some script names are reserved for special purposes and their execution might be associated with additional logic. +The following script names are reserved: + +1. `test` - This script will be executed when you run `scarb test` command. +2. `package` - This script will be executed before the packaging process when you run `scarb package` command. + ## Listing scripts To list all available scripts, you can use `scarb run` command. diff --git a/website/docs/registries/publishing.md b/website/docs/registries/publishing.md index f223e9762..51696978b 100644 --- a/website/docs/registries/publishing.md +++ b/website/docs/registries/publishing.md @@ -48,6 +48,9 @@ Use the `scarb package` command to create an archive of your package. You can read about the package compression algorithm and contents in the [Package tarball](./package-tarball) section. Basically when you run the command, Scarb gathers the source code of your package along with metadata files, such as the manifest file, and places them in an archive in `target/package` directory. +You can define an optional script called `package` (learn more on [scripts here](../reference/scripts.md)) in your +manifest file, which will be executed directly before the packaging process. +This can be useful for instance to create some additional files to be included in the package. If you are in a Git repository, Scarb will first check if the repo state is clean and error out in case of any changes present in the Git working directory.