Skip to content

Commit

Permalink
feat!: support installing binary .rock packages (#291)
Browse files Browse the repository at this point in the history
  • Loading branch information
mrcjkb committed Jan 4, 2025
1 parent e789652 commit e2ba492
Show file tree
Hide file tree
Showing 29 changed files with 954 additions and 180 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ The following table provides a brief (incomplete) comparison:
| `command` build spec | :white_check_mark: | :white_check_mark: |
| custom build backends | :white_check_mark:[^1] | :white_check_mark: |
| `rust-mlua` build spec | :white_check_mark: (builtin) | :white_check_mark: (external build backend) |
| install pre-built binary rocks | :x: (planned) | :white_check_mark: |
| install pre-built binary rocks | :white_check_mark: | :white_check_mark: |
| parallel builds/installs | :white_check_mark: | :x: |
| install multiple packages with a single command | :white_check_mark: | :x: |
| install packages using version constraints | :white_check_mark: | :x: |
Expand Down
1 change: 1 addition & 0 deletions nix/overlay.nix
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
# disable vendored packages
LIBGIT2_NO_VENDOR = 1;
LIBSSH2_SYS_USE_PKG_CONFIG = 1;
ROCKS_SKIP_IMPURE_TESTS = 1;
};

inherit buildType;
Expand Down
2 changes: 1 addition & 1 deletion rocks-bin/src/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ pub async fn build(data: Build, config: Config) -> Result<()> {
.install()
.await?;

build::Build::new(rockspec, &config, &progress.map(|p| p.new_bar()))
build::Build::new(&rockspec, &config, &progress.map(|p| p.new_bar()))
.pin(pin)
.behaviour(build_behaviour)
.build()
Expand Down
6 changes: 3 additions & 3 deletions rocks-lib/resources/test/sample-tree/5.1/lock.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"48ec344951668eca0e0a4ff284d804a11e4e709194df3191a72ed8fac89cf2e0"
],
"constraint": null,
"source": "luarocks+https://luarocks.org/",
"source": "luarocks_rockspec+https://luarocks.org/",
"hashes": {
"rockspec": "sha256-uU0nuZNNPgilLlLX2n2r+sSE7+N6U4DukIj3rOLvzek=",
"source": "sha256-uU0nuZNNPgilLlLX2n2r+sSE7+N6U4DukIj3rOLvzek="
Expand All @@ -21,7 +21,7 @@
"pinned": false,
"dependencies": [],
"constraint": null,
"source": "luarocks+https://luarocks.org/",
"source": "luarocks_rockspec+https://luarocks.org/",
"hashes": {
"rockspec": "sha256-uU0nuZNNPgilLlLX2n2r+sSE7+N6U4DukIj3rOLvzek=",
"source": "sha256-uU0nuZNNPgilLlLX2n2r+sSE7+N6U4DukIj3rOLvzek="
Expand All @@ -35,7 +35,7 @@
"48ec344951668eca0e0a4ff284d804a11e4e709194df3191a72ed8fac89cf2e0"
],
"constraint": null,
"source": "luarocks+https://luarocks.org/",
"source": "luarocks_rockspec+https://luarocks.org/",
"hashes": {
"rockspec": "sha256-uU0nuZNNPgilLlLX2n2r+sSE7+N6U4DukIj3rOLvzek=",
"source": "sha256-uU0nuZNNPgilLlLX2n2r+sSE7+N6U4DukIj3rOLvzek="
Expand Down
Binary file not shown.
2 changes: 1 addition & 1 deletion rocks-lib/src/build/luarocks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::{io, path::Path};
use crate::{
config::Config,
lua_installation::LuaInstallation,
luarocks_installation::{ExecLuaRocksError, LuaRocksError, LuaRocksInstallation},
luarocks::luarocks_installation::{ExecLuaRocksError, LuaRocksError, LuaRocksInstallation},
progress::{Progress, ProgressBar},
rockspec::Rockspec,
tree::RockLayout,
Expand Down
10 changes: 5 additions & 5 deletions rocks-lib/src/build/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ pub mod variables;
/// A rocks package builder, providing fine-grained control
/// over how a package should be built.
pub struct Build<'a> {
rockspec: Rockspec,
rockspec: &'a Rockspec,
config: &'a Config,
progress: &'a Progress<ProgressBar>,
pin: PinnedState,
Expand All @@ -50,7 +50,7 @@ pub struct Build<'a> {
impl<'a> Build<'a> {
/// Construct a new builder.
pub fn new(
rockspec: Rockspec,
rockspec: &'a Rockspec,
config: &'a Config,
progress: &'a Progress<ProgressBar>,
) -> Self {
Expand Down Expand Up @@ -263,7 +263,7 @@ async fn install(
}

async fn build(
rockspec: Rockspec,
rockspec: &Rockspec,
pinned: PinnedState,
constraint: LockConstraint,
behaviour: BuildBehaviour,
Expand Down Expand Up @@ -341,9 +341,9 @@ async fn build(
None => temp_dir.path().into(),
};

run_build(&rockspec, &output_paths, &lua, config, &build_dir, progress).await?;
run_build(rockspec, &output_paths, &lua, config, &build_dir, progress).await?;

install(&rockspec, &tree, &output_paths, &lua, &build_dir, progress).await?;
install(rockspec, &tree, &output_paths, &lua, &build_dir, progress).await?;

for directory in &rockspec.build.current_platform().copy_directories {
recursive_copy_dir(&build_dir.join(directory), &output_paths.etc)?;
Expand Down
9 changes: 9 additions & 0 deletions rocks-lib/src/hash.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use bytes::Bytes;
use ssri::{Algorithm, Integrity, IntegrityOpts};
use std::fs::File;
use std::io::{self, Read};
Expand Down Expand Up @@ -39,6 +40,14 @@ impl HasIntegrity for TempDir {
}
}

impl HasIntegrity for Bytes {
fn hash(&self) -> io::Result<Integrity> {
let mut integrity_opts = IntegrityOpts::new().algorithm(Algorithm::Sha256);
integrity_opts.input(self);
Ok(integrity_opts.result())
}
}

fn hash_file(path: &Path, integrity_opts: &mut IntegrityOpts) -> io::Result<()> {
let mut file = File::open(path)?;
let mut buffer = Vec::new();
Expand Down
2 changes: 1 addition & 1 deletion rocks-lib/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ pub mod config;
pub mod hash;
pub mod lockfile;
pub mod lua_installation;
pub mod luarocks_installation;
pub mod luarocks;
pub mod manifest;
pub mod operations;
pub mod package;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ expression: lockfile
"pinned": false,
"dependencies": [],
"constraint": null,
"source": "luarocks+https://luarocks.org/",
"source": "luarocks_rockspec+https://luarocks.org/",
"hashes": {
"rockspec": "sha256-uU0nuZNNPgilLlLX2n2r+sSE7+N6U4DukIj3rOLvzek=",
"source": "sha256-uU0nuZNNPgilLlLX2n2r+sSE7+N6U4DukIj3rOLvzek="
Expand Down Expand Up @@ -52,7 +52,7 @@ expression: lockfile
"48ec344951668eca0e0a4ff284d804a11e4e709194df3191a72ed8fac89cf2e0"
],
"constraint": null,
"source": "luarocks+https://luarocks.org/",
"source": "luarocks_rockspec+https://luarocks.org/",
"hashes": {
"rockspec": "sha256-uU0nuZNNPgilLlLX2n2r+sSE7+N6U4DukIj3rOLvzek=",
"source": "sha256-uU0nuZNNPgilLlLX2n2r+sSE7+N6U4DukIj3rOLvzek="
Expand All @@ -66,7 +66,7 @@ expression: lockfile
"48ec344951668eca0e0a4ff284d804a11e4e709194df3191a72ed8fac89cf2e0"
],
"constraint": null,
"source": "luarocks+https://luarocks.org/",
"source": "luarocks_rockspec+https://luarocks.org/",
"hashes": {
"rockspec": "sha256-uU0nuZNNPgilLlLX2n2r+sSE7+N6U4DukIj3rOLvzek=",
"source": "sha256-uU0nuZNNPgilLlLX2n2r+sSE7+N6U4DukIj3rOLvzek="
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ snapshot_kind: text
"pinned": false,
"dependencies": [],
"constraint": null,
"source": "luarocks+https://luarocks.org/",
"source": "luarocks_rockspec+https://luarocks.org/",
"hashes": {
"rockspec": "sha256-uU0nuZNNPgilLlLX2n2r+sSE7+N6U4DukIj3rOLvzek=",
"source": "sha256-uU0nuZNNPgilLlLX2n2r+sSE7+N6U4DukIj3rOLvzek="
Expand All @@ -27,7 +27,7 @@ snapshot_kind: text
"48ec344951668eca0e0a4ff284d804a11e4e709194df3191a72ed8fac89cf2e0"
],
"constraint": null,
"source": "luarocks+https://luarocks.org/",
"source": "luarocks_rockspec+https://luarocks.org/",
"hashes": {
"rockspec": "sha256-uU0nuZNNPgilLlLX2n2r+sSE7+N6U4DukIj3rOLvzek=",
"source": "sha256-uU0nuZNNPgilLlLX2n2r+sSE7+N6U4DukIj3rOLvzek="
Expand All @@ -41,7 +41,7 @@ snapshot_kind: text
"48ec344951668eca0e0a4ff284d804a11e4e709194df3191a72ed8fac89cf2e0"
],
"constraint": null,
"source": "luarocks+https://luarocks.org/",
"source": "luarocks_rockspec+https://luarocks.org/",
"hashes": {
"rockspec": "sha256-uU0nuZNNPgilLlLX2n2r+sSE7+N6U4DukIj3rOLvzek=",
"source": "sha256-uU0nuZNNPgilLlLX2n2r+sSE7+N6U4DukIj3rOLvzek="
Expand Down
212 changes: 212 additions & 0 deletions rocks-lib/src/luarocks/install_binary_rock.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
use std::{
collections::HashMap,
io::{self, Cursor},
path::{Path, PathBuf},
};

use bytes::Bytes;
use tempdir::TempDir;
use thiserror::Error;

use crate::{
build::{
external_dependency::{ExternalDependencyError, ExternalDependencyInfo},
BuildBehaviour,
},
config::Config,
hash::HasIntegrity as _,
lockfile::{LocalPackage, LocalPackageHashes, LockConstraint, PinnedState},
luarocks::rock_manifest::RockManifest,
package::PackageSpec,
progress::{Progress, ProgressBar},
remote_package_source::RemotePackageSource,
rockspec::{LuaVersionError, Rockspec},
tree::Tree,
};

use super::rock_manifest::RockManifestError;

#[derive(Error, Debug)]
pub enum InstallBinaryRockError {
#[error("IO operation failed: {0}")]
Io(#[from] io::Error),
#[error(transparent)]
ExternalDependencyError(#[from] ExternalDependencyError),
#[error(transparent)]
LuaVersionError(#[from] LuaVersionError),
#[error("failed to unpack packed rock: {0}")]
Zip(#[from] zip::result::ZipError),
#[error("rock_manifest not found. Cannot install rock files that were packed using LuaRocks version 1")]
RockManifestNotFound,
#[error(transparent)]
RockManifestError(#[from] RockManifestError),
}

pub(crate) struct BinaryRockInstall<'a> {
rockspec: &'a Rockspec,
rock_bytes: Bytes,
source: RemotePackageSource,
pin: PinnedState,
constraint: LockConstraint,
behaviour: BuildBehaviour,
config: &'a Config,
progress: &'a Progress<ProgressBar>,
}

impl<'a> BinaryRockInstall<'a> {
pub(crate) fn new(
rockspec: &'a Rockspec,
source: RemotePackageSource,
rock_bytes: Bytes,
config: &'a Config,
progress: &'a Progress<ProgressBar>,
) -> Self {
Self {
rockspec,
rock_bytes,
source,
config,
progress,
constraint: LockConstraint::default(),
behaviour: BuildBehaviour::default(),
pin: PinnedState::default(),
}
}

pub(crate) fn pin(self, pin: PinnedState) -> Self {
Self { pin, ..self }
}

pub(crate) fn constraint(self, constraint: LockConstraint) -> Self {
Self { constraint, ..self }
}

pub(crate) fn behaviour(self, behaviour: BuildBehaviour) -> Self {
Self { behaviour, ..self }
}

pub(crate) async fn install(self) -> Result<LocalPackage, InstallBinaryRockError> {
let rockspec = self.rockspec;
self.progress.map(|p| {
p.set_message(format!(
"Unpacking and installing {}@{}...",
rockspec.package, rockspec.version
))
});
for (name, dep) in rockspec.external_dependencies.current_platform() {
let _ = ExternalDependencyInfo::detect(name, dep, self.config.external_deps())?;
}

let lua_version = rockspec.lua_version_from_config(self.config)?;

let tree = Tree::new(self.config.tree().clone(), lua_version.clone())?;

let hashes = LocalPackageHashes {
rockspec: rockspec.hash()?,
source: self.rock_bytes.hash()?,
};
let mut package = LocalPackage::from(
&PackageSpec::new(rockspec.package.clone(), rockspec.version.clone()),
self.constraint,
self.source,
hashes,
);
package.spec.pinned = self.pin;
match tree.lockfile()?.get(&package.id()) {
Some(package) if self.behaviour == BuildBehaviour::NoForce => Ok(package.clone()),
_ => {
let unpack_dir = TempDir::new("rocks-bin-rock").unwrap().into_path();
let cursor = Cursor::new(self.rock_bytes);
let mut zip = zip::ZipArchive::new(cursor)?;
zip.extract(&unpack_dir)?;
let rock_manifest_file = unpack_dir.join("rock_manifest");
if !rock_manifest_file.is_file() {
return Err(InstallBinaryRockError::RockManifestNotFound);
}
let rock_manifest_content = std::fs::read_to_string(rock_manifest_file)?;
let output_paths = tree.rock(&package)?;
let rock_manifest = RockManifest::new(&rock_manifest_content)?;
install_manifest_entry(
&rock_manifest.lib,
&unpack_dir.join("lib"),
&output_paths.lib,
)?;
install_manifest_entry(
&rock_manifest.lua,
&unpack_dir.join("lua"),
&output_paths.src,
)?;
install_manifest_entry(
&rock_manifest.bin,
&unpack_dir.join("bin"),
&output_paths.bin,
)?;
install_manifest_entry(
&rock_manifest.doc,
&unpack_dir.join("doc"),
&output_paths.doc,
)?;
install_manifest_entry(&rock_manifest.root, &unpack_dir, &output_paths.etc)?;
Ok(package)
}
}
}
}

fn install_manifest_entry(
entry: &HashMap<PathBuf, String>,
src: &Path,
dest: &Path,
) -> Result<(), InstallBinaryRockError> {
for relative_src_path in entry.keys() {
let target = dest.join(relative_src_path);
std::fs::create_dir_all(target.parent().unwrap())?;
std::fs::copy(src.join(relative_src_path), target)?;
}
Ok(())
}

#[cfg(test)]
mod test {

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

use super::*;
#[tokio::test]
async fn install_binary_rock() {
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 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(),
};
let rockspec = unpack_rockspec(&rock).await.unwrap();
let dir = assert_fs::TempDir::new().unwrap();
let config = ConfigBuilder::new()
.tree(Some(dir.to_path_buf()))
.build()
.unwrap();
let progress = MultiProgress::new();
let bar = progress.new_bar();
BinaryRockInstall::new(
&rockspec,
RemotePackageSource::Test,
rock.bytes,
&config,
&Progress::Progress(bar),
)
.install()
.await
.unwrap();
}
}
Loading

0 comments on commit e2ba492

Please sign in to comment.