Skip to content

Commit

Permalink
feat: rocks pack
Browse files Browse the repository at this point in the history
  • Loading branch information
mrcjkb committed Jan 29, 2025
1 parent dc4f85e commit b9f6658
Show file tree
Hide file tree
Showing 12 changed files with 595 additions and 51 deletions.
8 changes: 8 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions rocks-bin/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ spinners = "4.1.1"
stylua = { version = "2.0.0", features = ["fromstr", "lua52"] }
strum = "0.26.3"
strum_macros = "0.26.4"
tempdir = "0.3.7"
termcolor = "1.4.1"
text_trees = "0.1.2"
tokio = { version = "1.43.0", features = ["full"] }
Expand Down
6 changes: 4 additions & 2 deletions rocks-bin/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use install::Install;
use install_rockspec::InstallRockspec;
use list::ListCmd;
use outdated::Outdated;
use pack::Pack;
use path::Path;
use pin::ChangePin;
use remove::Remove;
Expand Down Expand Up @@ -40,6 +41,7 @@ pub mod install_lua;
pub mod install_rockspec;
pub mod list;
pub mod outdated;
pub mod pack;
pub mod path;
pub mod pin;
pub mod project;
Expand Down Expand Up @@ -157,8 +159,8 @@ pub enum Commands {
New(NewProject),
/// List outdated rocks.
Outdated(Outdated),
/// [UNIMPLEMENTED] Create a rock, packing sources or binaries.
Pack,
/// Create a packed rock for distribution, packing sources or binaries.
Pack(Pack),
/// Return the currently configured package path.
Path(Path),
/// Pin an existing rock, preventing any updates to the package.
Expand Down
4 changes: 2 additions & 2 deletions rocks-bin/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use rocks::{
build, check, config,
debug::Debug,
doc, download, fetch, format, info, install, install_lua, install_rockspec, list, outdated,
path, pin, project, purge, remove, run, run_lua, search, test, uninstall, unpack, update,
pack, path, pin, project, purge, remove, run, run_lua, search, test, uninstall, unpack, update,
upload::{self},
which, Cli, Commands,
};
Expand Down Expand Up @@ -80,7 +80,7 @@ async fn main() {
Commands::Config(config_cmd) => config::config(config_cmd, config).unwrap(),
Commands::Doc(doc_args) => doc::doc(doc_args, config).await.unwrap(),
Commands::Lint => unimplemented!(),
Commands::Pack => unimplemented!(),
Commands::Pack(pack_args) => pack::pack(pack_args, config).await.unwrap(),
Commands::Uninstall(uninstall_data) => {
uninstall::uninstall(uninstall_data, config).await.unwrap()
}
Expand Down
119 changes: 119 additions & 0 deletions rocks-bin/src/pack.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
use std::{path::PathBuf, str::FromStr};

use clap::Args;
use eyre::{eyre, Result};
use rocks_lib::{
build::{Build, BuildBehaviour},
config::{Config, LuaVersion},
lua_rockspec::LuaRockspec,
operations::{self, Install},
package::PackageReq,
progress::MultiProgress,
project::rocks_toml::RocksToml,
tree::Tree,
};
use tempdir::TempDir;

#[derive(Debug, Clone)]
pub enum PackageOrRockspec {
Package(PackageReq),
RockSpec(PathBuf),
}

impl FromStr for PackageOrRockspec {
type Err = eyre::Error;

fn from_str(s: &str) -> Result<Self, Self::Err> {
let path = PathBuf::from(s);
if path.is_file() {
Ok(Self::RockSpec(path))
} else {
let pkg = PackageReq::from_str(s).map_err(|err| {
eyre!(
"No file {0} found and cannot parse package query: {1}",
s,
err
)
})?;
Ok(Self::Package(pkg))
}
}
}

#[derive(Args)]
pub struct Pack {
/// Path to a RockSpec or a package query for a package to pack.
/// Prioritises installed rocks and will install a rock to a temporary
/// directory if none is found.
/// In case of multiple matches, the latest version will be packed.
/// Examples: "pkg", "[email protected]", "pkg>=1.0.0"
#[clap(value_parser)]
package_or_rockspec: PackageOrRockspec,
}

pub async fn pack(args: Pack, config: Config) -> Result<()> {
let lua_version = LuaVersion::from(&config)?;
let dest_dir = std::env::current_dir()?;
let progress = MultiProgress::new_arc();
let result: Result<PathBuf> = match args.package_or_rockspec {
PackageOrRockspec::Package(package_req) => {
let default_tree = Tree::new(config.tree().clone(), lua_version.clone())?;
match default_tree.match_rocks(&package_req)? {
rocks_lib::tree::RockMatches::NotFound(_) => {
let temp_dir = TempDir::new("rocks-pack")?.into_path();
let tree = Tree::new(temp_dir.clone(), lua_version.clone())?;
let packages = Install::new(&config)
.package(BuildBehaviour::Force, package_req)
.progress(progress)
.install()
.await?;
let package = packages.first().unwrap();
let rock_path =
operations::Pack::new(dest_dir, tree, package.clone()).pack()?;
Ok(rock_path)
}
rocks_lib::tree::RockMatches::Single(local_package_id) => {
let lockfile = default_tree.lockfile()?;
let package = lockfile.get(&local_package_id).unwrap();
let rock_path =
operations::Pack::new(dest_dir, default_tree, package.clone()).pack()?;
Ok(rock_path)
}
rocks_lib::tree::RockMatches::Many(vec) => {
let local_package_id = vec.first().unwrap();
let lockfile = default_tree.lockfile()?;
let package = lockfile.get(local_package_id).unwrap();
let rock_path =
operations::Pack::new(dest_dir, default_tree, package.clone()).pack()?;
Ok(rock_path)
}
}
}
PackageOrRockspec::RockSpec(rockspec_path) => {
let content = std::fs::read_to_string(&rockspec_path)?;
let rockspec = match rockspec_path
.extension()
.map(|ext| ext.to_string_lossy().to_string())
.unwrap_or("".into())
.as_str()
{
".rockspec" => Ok(LuaRockspec::new(&content)?),
".toml" => Ok(RocksToml::new(&content)?
.into_validated_rocks_toml()?
.to_rockspec()?),
_ => Err(eyre!(
"expected a path to a .rockspec or rocks.toml or a package requirement."
)),
}?;
let temp_dir = TempDir::new("rocks-pack")?.into_path();
let bar = progress.map(|p| p.new_bar());
let tree = Tree::new(temp_dir.clone(), lua_version.clone())?;
let config = config.with_tree(temp_dir);
let package = Build::new(&rockspec, &config, &bar).build().await?;
let rock_path = operations::Pack::new(dest_dir, tree, package).pack()?;
Ok(rock_path)
}
};
print!("packed rock created at {}", result?.display());
Ok(())
}
1 change: 1 addition & 0 deletions rocks-lib/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ bon = { version = "3.3.2", features = ["implied-bounds"] }
clean-path = "0.2.1"
diffy = "0.4.0"
toml = "0.8.19"
md5 = "0.7.0"

[dev-dependencies]
httptest = { version = "0.16.1" }
Expand Down
122 changes: 110 additions & 12 deletions rocks-lib/src/luarocks/install_binary_rock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -131,26 +131,40 @@ impl<'a> BinaryRockInstall<'a> {
let output_paths = tree.rock(&package)?;
let rock_manifest = RockManifest::new(&rock_manifest_content)?;
install_manifest_entry(
&rock_manifest.lib,
&rock_manifest.lib.entries,
&unpack_dir.join("lib"),
&output_paths.lib,
)?;
install_manifest_entry(
&rock_manifest.lua,
&rock_manifest.lua.entries,
&unpack_dir.join("lua"),
&output_paths.src,
)?;
install_manifest_entry(
&rock_manifest.bin,
&rock_manifest.bin.entries,
&unpack_dir.join("bin"),
&output_paths.bin,
)?;
install_manifest_entry(
&rock_manifest.doc,
&rock_manifest.doc.entries,
&unpack_dir.join("doc"),
&output_paths.doc,
)?;
install_manifest_entry(&rock_manifest.root, &unpack_dir, &output_paths.etc)?;
install_manifest_entry(
&rock_manifest.root.entries,
&unpack_dir,
&output_paths.rock_path,
)?;
// rename <name>-<version>.rockspec
let rockspec_path = output_paths.rock_path.join(format!(
"{}-{}.rockspec",
package.name(),
package.version()
));
if rockspec_path.is_file() {
std::fs::copy(&rockspec_path, output_paths.rockspec_path())?;
std::fs::remove_file(&rockspec_path)?;
}
Ok(package)
}
}
Expand All @@ -173,37 +187,115 @@ fn install_manifest_entry(
#[cfg(test)]
mod test {

use io::Read;

use crate::{
config::ConfigBuilder,
operations::{unpack_rockspec, DownloadedPackedRockBytes},
config::{ConfigBuilder, LuaVersion},
operations::{unpack_rockspec, DownloadedPackedRockBytes, Pack, Remove},
progress::MultiProgress,
};

use super::*;

/// This relatively large integration test case tests the following:
///
/// - Install a packed rock that was packed using luarocks 3.11 from the test resources.
/// - Pack the rock using our own `Pack` implementation.
/// - Verify that the `rock_manifest` entry of the original packed rock and our own packed rock
/// are equal (this means luarocks should be able to install our packed rock).
/// - Uninstall the local package.
/// - Install the package from our packed rock.
/// - Verify that the contents of the install directories when installing from both packed rocks
/// are the same.
#[tokio::test]
async fn install_binary_rock() {
async fn install_binary_rock_roundtrip() {
if std::env::var("ROCKS_SKIP_IMPURE_TESTS").unwrap_or("0".into()) == "1" {
println!("Skipping impure test");
return;
}
let content = std::fs::read("resources/test/toml-edit-0.6.0-1.linux-x86_64.rock").unwrap();
let rock_bytes = Bytes::copy_from_slice(&content);
let packed_rock_file_name = "toml-edit-0.6.0-1.linux-x86_64.rock".to_string();
let cursor = Cursor::new(rock_bytes.clone());
let mut zip = zip::ZipArchive::new(cursor).unwrap();
let manifest_index = zip.index_for_path("rock_manifest").unwrap();
let mut manifest_file = zip.by_index(manifest_index).unwrap();
let mut content = String::new();
manifest_file.read_to_string(&mut content).unwrap();
let orig_manifest = RockManifest::new(&content).unwrap();
let rock = DownloadedPackedRockBytes {
name: "toml-edit".into(),
version: "0.6.0-1".parse().unwrap(),
bytes: rock_bytes,
file_name: "toml-edit-0.6.0-1.linux-x86_64.rock".into(),
file_name: packed_rock_file_name.clone(),
};
let rockspec = unpack_rockspec(&rock).await.unwrap();
let dir = assert_fs::TempDir::new().unwrap();
let install_root = assert_fs::TempDir::new().unwrap();
let config = ConfigBuilder::new()
.unwrap()
.tree(Some(dir.to_path_buf()))
.tree(Some(install_root.to_path_buf()))
.build()
.unwrap();
let progress = MultiProgress::new();
let bar = progress.new_bar();
BinaryRockInstall::new(
let local_package = BinaryRockInstall::new(
&rockspec,
RemotePackageSource::Test,
rock.bytes,
&config,
&Progress::Progress(bar),
)
.install()
.await
.unwrap();
let tree = Tree::new(
install_root.to_path_buf(),
LuaVersion::from(&config).unwrap(),
)
.unwrap();
let installed_rock_layout = tree.rock_layout(&local_package);
let orig_install_tree_integrity = installed_rock_layout.rock_path.hash().unwrap();
let pack_dest_dir = assert_fs::TempDir::new().unwrap();
let packed_rock = Pack::new(
pack_dest_dir.to_path_buf(),
tree.clone(),
local_package.clone(),
)
.pack()
.unwrap();
assert_eq!(
packed_rock
.file_name()
.unwrap()
.to_string_lossy()
.to_string(),
packed_rock_file_name.clone()
);
// let's make sure our own pack/unpack implementation roundtrips correctly
Remove::new(&config)
.package(local_package.id())
.remove()
.await
.unwrap();
let content = std::fs::read(&packed_rock).unwrap();
let rock_bytes = Bytes::copy_from_slice(&content);
let cursor = Cursor::new(rock_bytes.clone());
let mut zip = zip::ZipArchive::new(cursor).unwrap();
let manifest_index = zip.index_for_path("rock_manifest").unwrap();
let mut manifest_file = zip.by_index(manifest_index).unwrap();
let mut content = String::new();
manifest_file.read_to_string(&mut content).unwrap();
let packed_manifest = RockManifest::new(&content).unwrap();
assert_eq!(packed_manifest, orig_manifest);
let rock = DownloadedPackedRockBytes {
name: "toml-edit".into(),
version: "0.6.0-1".parse().unwrap(),
bytes: rock_bytes,
file_name: packed_rock_file_name.clone(),
};
let rockspec = unpack_rockspec(&rock).await.unwrap();
let bar = progress.new_bar();
let local_package = BinaryRockInstall::new(
&rockspec,
RemotePackageSource::Test,
rock.bytes,
Expand All @@ -213,5 +305,11 @@ mod test {
.install()
.await
.unwrap();
let installed_rock_layout = tree.rock_layout(&local_package);
assert!(installed_rock_layout.rockspec_path().is_file());
let new_install_tree_integrity = installed_rock_layout.rock_path.hash().unwrap();
assert!(orig_install_tree_integrity
.matches(&new_install_tree_integrity)
.is_some());
}
}
Loading

0 comments on commit b9f6658

Please sign in to comment.