-
Notifications
You must be signed in to change notification settings - Fork 71
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Scaffold
scarb package
command, implementing --list
arg
commit-id:cd0036df
- Loading branch information
Showing
6 changed files
with
330 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
use std::collections::BTreeMap; | ||
|
||
use anyhow::Result; | ||
use camino::Utf8PathBuf; | ||
use serde::Serializer; | ||
|
||
use scarb::core::{Config, PackageName}; | ||
use scarb::ops; | ||
use scarb::ops::PackageOpts; | ||
use scarb_ui::Message; | ||
|
||
use crate::args::PackageArgs; | ||
|
||
#[tracing::instrument(skip_all, level = "info")] | ||
pub fn run(args: PackageArgs, config: &Config) -> Result<()> { | ||
let ws = ops::read_workspace(config.manifest_path(), config)?; | ||
let packages = args | ||
.packages_filter | ||
.match_many(&ws)? | ||
.into_iter() | ||
.map(|p| p.id) | ||
.collect::<Vec<_>>(); | ||
|
||
let opts = PackageOpts; | ||
|
||
if args.list { | ||
let result = ops::package_list(&packages, &opts, &ws)?; | ||
ws.config().ui().print(ListMessage(result)); | ||
} else { | ||
ops::package(&packages, &opts, &ws)?; | ||
} | ||
|
||
Ok(()) | ||
} | ||
|
||
struct ListMessage(BTreeMap<PackageName, Vec<Utf8PathBuf>>); | ||
|
||
impl Message for ListMessage { | ||
fn print_text(self) | ||
where | ||
Self: Sized, | ||
{ | ||
let mut first = true; | ||
let single = self.0.len() == 1; | ||
for (package, files) in self.0 { | ||
if !single { | ||
if !first { | ||
println!(); | ||
} | ||
println!("{package}:",); | ||
} | ||
|
||
for file in files { | ||
println!("{file}"); | ||
} | ||
|
||
first = false; | ||
} | ||
} | ||
|
||
fn structured<S: Serializer>(self, _ser: S) -> Result<S::Ok, S::Error> | ||
where | ||
Self: Sized, | ||
{ | ||
todo!("JSON output is not implemented yet.") | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,179 @@ | ||
use std::collections::BTreeMap; | ||
|
||
use anyhow::{ensure, Result}; | ||
use camino::Utf8PathBuf; | ||
|
||
use scarb_ui::components::Status; | ||
|
||
use crate::core::{Package, PackageId, PackageName, Workspace}; | ||
use crate::flock::FileLockGuard; | ||
use crate::{ops, DEFAULT_SOURCE_PATH, MANIFEST_FILE_NAME}; | ||
|
||
const VERSION: u8 = 1; | ||
const VERSION_FILE_NAME: &str = "VERSION"; | ||
const ORIGINAL_MANIFEST_FILE_NAME: &str = "Scarb.orig.toml"; | ||
|
||
const RESERVED_FILES: &[&str] = &[VERSION_FILE_NAME, ORIGINAL_MANIFEST_FILE_NAME]; | ||
|
||
pub struct PackageOpts; | ||
|
||
/// A listing of files to include in the archive, without actually building it yet. | ||
/// | ||
/// This struct is used to facilitate both building the package, and listing its contents without | ||
/// actually making it. | ||
type ArchiveRecipe = Vec<ArchiveFile>; | ||
|
||
struct ArchiveFile { | ||
/// The relative path in the archive (not including top-level package name directory). | ||
path: Utf8PathBuf, | ||
#[allow(dead_code)] | ||
/// The contents of the file. | ||
contents: ArchiveFileContents, | ||
} | ||
|
||
enum ArchiveFileContents { | ||
/// Absolute path to the file on disk to add to the archive. | ||
OnDisk(Utf8PathBuf), | ||
/// Generate file contents automatically. | ||
/// | ||
/// This variant stores a closure, so that file generation can be deferred to the very moment | ||
/// it is needed. | ||
/// For example, when listing package contents, we do not have files contents. | ||
Generated(Box<dyn FnOnce() -> Result<Vec<u8>>>), | ||
} | ||
|
||
pub fn package( | ||
packages: &[PackageId], | ||
opts: &PackageOpts, | ||
ws: &Workspace<'_>, | ||
) -> Result<Vec<FileLockGuard>> { | ||
before_package(ws)?; | ||
|
||
packages | ||
.iter() | ||
.map(|pkg| { | ||
let pkg_name = pkg.to_string(); | ||
let message = Status::new("Packaging", &pkg_name); | ||
if packages.len() <= 1 { | ||
ws.config().ui().verbose(message); | ||
} else { | ||
ws.config().ui().print(message); | ||
} | ||
|
||
package_one_impl(*pkg, opts, ws) | ||
}) | ||
.collect() | ||
} | ||
|
||
pub fn package_one( | ||
package: PackageId, | ||
opts: &PackageOpts, | ||
ws: &Workspace<'_>, | ||
) -> Result<FileLockGuard> { | ||
before_package(ws)?; | ||
package_one_impl(package, opts, ws) | ||
} | ||
|
||
pub fn package_list( | ||
packages: &[PackageId], | ||
opts: &PackageOpts, | ||
ws: &Workspace<'_>, | ||
) -> Result<BTreeMap<PackageName, Vec<Utf8PathBuf>>> { | ||
packages | ||
.iter() | ||
.map(|pkg| Ok((pkg.name.clone(), list_one_impl(*pkg, opts, ws)?))) | ||
.collect() | ||
} | ||
|
||
fn before_package(ws: &Workspace<'_>) -> Result<()> { | ||
ops::resolve_workspace(ws)?; | ||
Ok(()) | ||
} | ||
|
||
fn package_one_impl( | ||
pkg_id: PackageId, | ||
_opts: &PackageOpts, | ||
ws: &Workspace<'_>, | ||
) -> Result<FileLockGuard> { | ||
let pkg = ws.fetch_package(&pkg_id)?; | ||
|
||
// TODO(mkaput): Check metadata | ||
|
||
// TODO(#643): Check dirty in VCS (but do not do it when listing!). | ||
|
||
let _recipe = prepare_archive_recipe(pkg)?; | ||
|
||
todo!("Actual packaging is not implemented yet.") | ||
} | ||
|
||
fn list_one_impl( | ||
pkg_id: PackageId, | ||
_opts: &PackageOpts, | ||
ws: &Workspace<'_>, | ||
) -> Result<Vec<Utf8PathBuf>> { | ||
let pkg = ws.fetch_package(&pkg_id)?; | ||
let recipe = prepare_archive_recipe(pkg)?; | ||
Ok(recipe.into_iter().map(|f| f.path).collect()) | ||
} | ||
|
||
fn prepare_archive_recipe(pkg: &Package) -> Result<ArchiveRecipe> { | ||
let mut recipe = source_files(pkg)?; | ||
|
||
check_no_reserved_files(&recipe)?; | ||
|
||
// Add normalized manifest file. | ||
recipe.push(ArchiveFile { | ||
path: MANIFEST_FILE_NAME.into(), | ||
contents: ArchiveFileContents::Generated(Box::new({ | ||
let pkg = pkg.clone(); | ||
|| normalize_manifest(pkg) | ||
})), | ||
}); | ||
|
||
// Add original manifest file. | ||
recipe.push(ArchiveFile { | ||
path: ORIGINAL_MANIFEST_FILE_NAME.into(), | ||
contents: ArchiveFileContents::OnDisk(pkg.manifest_path().to_owned()), | ||
}); | ||
|
||
// Add archive version file. | ||
recipe.push(ArchiveFile { | ||
path: VERSION_FILE_NAME.into(), | ||
contents: ArchiveFileContents::Generated(Box::new(|| Ok(VERSION.to_string().into_bytes()))), | ||
}); | ||
|
||
// Sort archive files alphabetically, putting the version file first. | ||
recipe.sort_unstable_by_key(|f| { | ||
let priority = if f.path == VERSION_FILE_NAME { 0 } else { 1 }; | ||
(priority, f.path.clone()) | ||
}); | ||
|
||
Ok(recipe) | ||
} | ||
|
||
fn source_files(pkg: &Package) -> Result<ArchiveRecipe> { | ||
// TODO(mkaput): Implement this properly. | ||
Ok(vec![ArchiveFile { | ||
path: DEFAULT_SOURCE_PATH.into(), | ||
contents: ArchiveFileContents::OnDisk(pkg.root().join(DEFAULT_SOURCE_PATH)), | ||
}]) | ||
} | ||
|
||
fn check_no_reserved_files(recipe: &ArchiveRecipe) -> Result<()> { | ||
let mut found = Vec::new(); | ||
for file in recipe { | ||
if RESERVED_FILES.contains(&file.path.as_str()) { | ||
found.push(file.path.as_str()); | ||
} | ||
} | ||
ensure!( | ||
found.is_empty(), | ||
"invalid inclusion of reserved files in package: {}", | ||
found.join(", ") | ||
); | ||
Ok(()) | ||
} | ||
|
||
fn normalize_manifest(_pkg: Package) -> Result<Vec<u8>> { | ||
todo!("Manifest normalization is not implemented yet.") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
use assert_fs::fixture::PathChild; | ||
use assert_fs::TempDir; | ||
use indoc::indoc; | ||
|
||
use scarb_test_support::command::Scarb; | ||
use scarb_test_support::project_builder::ProjectBuilder; | ||
use scarb_test_support::workspace_builder::WorkspaceBuilder; | ||
|
||
#[test] | ||
fn list_simple() { | ||
let t = TempDir::new().unwrap(); | ||
ProjectBuilder::start() | ||
.name("foo") | ||
.version("1.0.0") | ||
.build(&t); | ||
|
||
Scarb::quick_snapbox() | ||
.arg("package") | ||
.arg("--list") | ||
.current_dir(&t) | ||
.assert() | ||
.success() | ||
.stdout_eq(indoc! {r#" | ||
VERSION | ||
Scarb.orig.toml | ||
Scarb.toml | ||
src/lib.cairo | ||
"#}); | ||
} | ||
|
||
#[test] | ||
fn list_workspace() { | ||
let t = TempDir::new().unwrap(); | ||
ProjectBuilder::start().name("first").build(&t.child("first")); | ||
ProjectBuilder::start() | ||
.name("second") | ||
.build(&t.child("second")); | ||
WorkspaceBuilder::start() | ||
// Trick to test if packages are sorted alphabetically by name in the output. | ||
.add_member("second") | ||
.add_member("first") | ||
.build(&t); | ||
|
||
Scarb::quick_snapbox() | ||
.arg("package") | ||
.arg("--list") | ||
.current_dir(&t) | ||
.assert() | ||
.success() | ||
.stdout_eq(indoc! {r#" | ||
first: | ||
VERSION | ||
Scarb.orig.toml | ||
Scarb.toml | ||
src/lib.cairo | ||
second: | ||
VERSION | ||
Scarb.orig.toml | ||
Scarb.toml | ||
src/lib.cairo | ||
"#}); | ||
} |