Skip to content

Commit

Permalink
feat!: luarocks build backend
Browse files Browse the repository at this point in the history
  • Loading branch information
mrcjkb committed Dec 14, 2024
1 parent ba36d96 commit 6fc34f3
Show file tree
Hide file tree
Showing 13 changed files with 507 additions and 28 deletions.
58 changes: 58 additions & 0 deletions
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# A [WIP] Library & Client implementation of [`luarocks`](https://github.com/luarocks/luarocks)

> [!WARNING]
>
> **rocks is a work in progress
> and does not have a stable release yet.**

Rocks serves as an application for:
- Installing and managing rocks
- Creating Lua projects with dependencies, build scripts and desired Lua versions
- Creating and publishing your own rocks
- Embedding rock manipulation logic into your own application

> [!NOTE]
> This aims to be a full rewrite of `luarocks`, with many flags altered to be more
> ergonomic. This is not a drop-in replacement for `luarocks` commands you may have in scripts.

## :books: Usage

```sh
rocks <command> <options>
```

To view available options and their descriptions, run `rocks --help`.

## Comparison with [`luarocks v3.11.1`](https://github.com/luarocks/luarocks)

As this project is still a work in progress, some luarocks features
have not been (fully) implemented yet.
On the other hand, rocks has some features that are not present in luarocks.

The following table provides a brief (incomplete) comparison:

| | `rocks` | `luarocks v3.11.1` |
| --- | --- | --- |
| `builtin` build spec | :white_check_mark: | :white_check_mark: |
| `make` build spec | :white_check_mark: | :white_check_mark: |
| `rust-mlua` build spec | :white_check_mark: (builtin) | :white_check_mark: (external build backend) |
| `cmake` build spec | :x: (planned) | :white_check_mark: |
| `command` build spec | :x: (planned) | :white_check_mark: |
| custom build backends | :white_check_mark:[^1] | :white_check_mark: |
| install pre-built binary rocks | :x: (planned) | :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: |
| lockfiles | :white_check_mark: | :white_check_mark: (basic, dependency versions only) |
| formatting with stylua | :white_check_mark: | :x: |

[^1]: Supported via a compatibility layer that uses luarocks as a backend.

## :book: License

`rocks` is licensed under [MIT](./LICENSE).

## :green_heart: Contributing

Contributions are more than welcome!
See [CONTRIBUTING.md](./CONTRIBUTING.md) for a guide.
5 changes: 2 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,18 +36,17 @@ The following table provides a brief (incomplete) comparison:
| `builtin` build spec | :white_check_mark: | :white_check_mark: |
| `make` build spec | :white_check_mark: | :white_check_mark: |
| `rust-mlua` build spec | :white_check_mark: (builtin) | :white_check_mark: (external build backend) |
| `tree-sitter` build spec | :x: (planned) | :white_check_mark: (external build backend) |
| `cmake` build spec | :x: (planned) | :white_check_mark: |
| `command` build spec | :x: (planned) | :white_check_mark: |
| custom build backends | :x: (planned[^1]) | :white_check_mark: |
| custom build backends | :white_check_mark:[^1] | :white_check_mark: |
| install pre-built binary rocks | :x: (planned) | :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: |
| lockfiles | :white_check_mark: | :white_check_mark: (basic, dependency versions only) |
| formatting with stylua | :white_check_mark: | :x: |

[^1]: planned via a luarocks compatibility layer.
[^1]: Supported via a compatibility layer that uses luarocks as a backend.

## :book: License

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
local _MODREV, _SPECREV = 'scm', '-1'
rockspec_format = '3.0'
package = 'sample-project-busted'
package = 'sample-project'
version = _MODREV .. _SPECREV

test_dependencies = {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
rockspec_format = "3.0"
package = "foo"
version = "1.0.0-1"
source = {
url = 'https://github.com/nvim-neorocks/luarocks-stub',
}
86 changes: 86 additions & 0 deletions rocks-lib/src/build/luarocks.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
use std::{io, path::Path};

use crate::{
config::Config,
lua_installation::LuaInstallation,
luarocks_installation::{ExecLuaRocksError, LuaRocksError, LuaRocksInstallation},
progress::{Progress, ProgressBar},
rockspec::Rockspec,
tree::RockLayout,
};

use tempdir::TempDir;
use thiserror::Error;

use super::utils::recursive_copy_dir;

#[derive(Error, Debug)]
pub enum LuarocksBuildError {
#[error(transparent)]
Io(#[from] io::Error),
#[error("error instantiating luarocks compatibility layer: {0}")]
LuaRocksError(#[from] LuaRocksError),
#[error("error running 'luarocks make': {0}")]
ExecLuaRocksError(#[from] ExecLuaRocksError),
}

pub(crate) async fn build(
rockspec: &Rockspec,
output_paths: &RockLayout,
lua: &LuaInstallation,
config: &Config,
build_dir: &Path,
progress: &Progress<ProgressBar>,
) -> Result<(), LuarocksBuildError> {
progress.map(|p| {
p.set_message(format!(
"Building {} {} with luarocks...",
rockspec.package, rockspec.version
))
});
let rockspec_temp_dir = TempDir::new("temp-rockspec")?.into_path();
let rockspec_file = rockspec_temp_dir.join(format!(
"{}-{}.rockspec",
rockspec.package, rockspec.version
));
std::fs::write(&rockspec_file, &rockspec.raw_content)?;
let luarocks = LuaRocksInstallation::new(config)?;
let luarocks_tree = TempDir::new("luarocks-compat-tree")?;
luarocks.make(&rockspec_file, build_dir, luarocks_tree.path(), lua)?;
install(rockspec, &luarocks_tree.into_path(), output_paths, config)
}

fn install(
rockspec: &Rockspec,
luarocks_tree: &Path,
output_paths: &RockLayout,
config: &Config,
) -> Result<(), LuarocksBuildError> {
let lua_version = rockspec
.lua_version_from_config(config)
.expect("could not get lua version!");
std::fs::create_dir_all(&output_paths.bin)?;
let package_dir = luarocks_tree
.join("lib")
.join("lib")
.join("luarocks")
.join(format!(
"rocks-{}",
&lua_version.version_compatibility_str()
))
.join(format!("{}", rockspec.package))
.join(format!("{}", rockspec.version));
recursive_copy_dir(&package_dir.join("doc"), &output_paths.doc)?;
recursive_copy_dir(&luarocks_tree.join("bin"), &output_paths.bin)?;
let src_dir = luarocks_tree
.join("share")
.join("lua")
.join(lua_version.version_compatibility_str());
recursive_copy_dir(&src_dir, &output_paths.src)?;
let lib_dir = luarocks_tree
.join("lib")
.join("lua")
.join(lua_version.version_compatibility_str());
recursive_copy_dir(&lib_dir, &output_paths.lib)?;
Ok(())
}
21 changes: 9 additions & 12 deletions rocks-lib/src/build/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,13 @@ use crate::{
};
pub(crate) mod utils; // Make build utilities available as a submodule
use indicatif::style::TemplateError;
use luarocks::LuarocksBuildError;
use make::MakeError;
use rust_mlua::RustError;
use thiserror::Error;
use utils::recursive_copy_dir;
mod builtin;
mod luarocks;
mod make;
mod rust_mlua;
pub mod variables;
Expand All @@ -43,6 +46,8 @@ pub enum BuildError {
stdout: String,
stderr: String,
},
#[error(transparent)]
LuarocksBuildError(#[from] LuarocksBuildError),
}

#[derive(Copy, Clone, Debug, PartialEq, Eq)]
Expand Down Expand Up @@ -87,6 +92,9 @@ async fn run_build(
.run(output_paths, false, lua, config, build_dir, progress)
.await?
}
Some(BuildBackendSpec::LuaRock(_)) => {
luarocks::build(rockspec, output_paths, lua, config, build_dir, progress).await?;
}
None => (),
_ => unimplemented!(),
}
Expand Down Expand Up @@ -214,19 +222,8 @@ pub async fn build(

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

// Copy over all `copy_directories` to their respective paths
for directory in &rockspec.build.current_platform().copy_directories {
for file in walkdir::WalkDir::new(build_dir.join(directory))
.into_iter()
.flatten()
{
if file.file_type().is_file() {
let filepath = file.path();
let target = output_paths.etc.join(filepath);
std::fs::create_dir_all(target.parent().unwrap())?;
std::fs::copy(filepath, target)?;
}
}
recursive_copy_dir(&build_dir.join(directory), &output_paths.etc)?;
}

Ok(package)
Expand Down
18 changes: 18 additions & 0 deletions rocks-lib/src/build/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,24 @@ pub(crate) fn copy_lua_to_module_path(
Ok(())
}

pub(crate) fn recursive_copy_dir(src: &PathBuf, dest: &Path) -> Result<(), io::Error> {
if src.exists() {
for file in walkdir::WalkDir::new(src)
.into_iter()
.flatten()
.filter(|file| file.file_type().is_file())
{
let relative_src_path: PathBuf =
pathdiff::diff_paths(src.join(file.clone().into_path()), src)
.expect("failed to copy directories!");
let filepath = file.path();
let target = dest.join(relative_src_path);
std::fs::create_dir_all(target.parent().unwrap())?;
std::fs::copy(filepath, target)?;
}
}
Ok(())
}
fn validate_output(output: Output) -> Result<(), BuildError> {
if !output.status.success() {
return Err(BuildError::CommandFailure {
Expand Down
40 changes: 32 additions & 8 deletions rocks-lib/src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,19 @@ impl LuaVersion {
LuaVersion::LuaJIT52 => "5.2.0".parse().unwrap(),
}
}
pub fn as_version_req(&self) -> PackageVersionReq {
pub fn version_compatibility_str(&self) -> String {
match self {
LuaVersion::Lua51 | LuaVersion::LuaJIT => "~> 5.1".parse().unwrap(),
LuaVersion::Lua52 | LuaVersion::LuaJIT52 => "~> 5.2".parse().unwrap(),
LuaVersion::Lua53 => "~> 5.3".parse().unwrap(),
LuaVersion::Lua54 => "~> 5.4".parse().unwrap(),
LuaVersion::Lua51 | LuaVersion::LuaJIT => "5.1".into(),
LuaVersion::Lua52 | LuaVersion::LuaJIT52 => "5.2".into(),
LuaVersion::Lua53 => "5.3".into(),
LuaVersion::Lua54 => "5.4".into(),
}
}
pub fn as_version_req(&self) -> PackageVersionReq {
format!("~> {}", self.version_compatibility_str())
.parse()
.unwrap()
}

/// Get the LuaVersion from a version that has been parsed from the `lua -v` output
pub fn from_version(version: PackageVersion) -> Result<LuaVersion, LuaVersionError> {
Expand Down Expand Up @@ -139,6 +144,7 @@ pub struct Config {
lua_dir: PathBuf,
lua_version: Option<LuaVersion>,
tree: PathBuf,
luarocks_tree: PathBuf,
no_project: bool,
verbose: bool,
timeout: Duration,
Expand Down Expand Up @@ -170,6 +176,10 @@ impl Config {
..self
}
}

pub fn with_tree(self, tree: PathBuf) -> Self {
Self { tree, ..self }
}
}

impl Config {
Expand Down Expand Up @@ -206,6 +216,11 @@ impl Config {
&self.tree
}

/// The tree in which to install luarocks for use as a compatibility layer
pub fn luarocks_tree(&self) -> &PathBuf {
&self.luarocks_tree
}

pub fn no_project(&self) -> bool {
self.no_project
}
Expand Down Expand Up @@ -259,6 +274,7 @@ pub struct ConfigBuilder {
lua_dir: Option<PathBuf>,
lua_version: Option<LuaVersion>,
tree: Option<PathBuf>,
luarocks_tree: Option<PathBuf>,
no_project: Option<bool>,
verbose: Option<bool>,
timeout: Option<Duration>,
Expand Down Expand Up @@ -319,6 +335,13 @@ impl ConfigBuilder {
Self { tree, ..self }
}

pub fn luarocks_tree(self, luarocks_tree: Option<PathBuf>) -> Self {
Self {
luarocks_tree,
..self
}
}

pub fn no_project(self, no_project: Option<bool>) -> Self {
Self { no_project, ..self }
}
Expand Down Expand Up @@ -349,6 +372,7 @@ impl ConfigBuilder {

pub fn build(self) -> Result<Config, ConfigError> {
let data_dir = self.data_dir.unwrap_or(Config::get_default_data_path()?);
let cache_dir = self.cache_dir.unwrap_or(Config::get_default_cache_path()?);
let current_project = Project::current()?;
let lua_version = self
.lua_version
Expand All @@ -358,7 +382,6 @@ impl ConfigBuilder {
.or(crate::lua_installation::get_installed_lua_version("lua")
.ok()
.and_then(|version| LuaVersion::from_version(version).ok()));

Ok(Config {
enable_development_rockspecs: self.enable_development_rockspecs.unwrap_or(false),
server: self
Expand All @@ -380,13 +403,14 @@ impl ConfigBuilder {
.map(|project| project.root().join(".rocks"))
}
})
.unwrap_or_else(|| data_dir.clone()),
.unwrap_or_else(|| data_dir.join("tree")),
luarocks_tree: self.luarocks_tree.unwrap_or(data_dir.join(".luarocks")),
no_project: self.no_project.unwrap_or(false),
verbose: self.verbose.unwrap_or(false),
timeout: self.timeout.unwrap_or_else(|| Duration::from_secs(30)),
make: self.make.unwrap_or("make".into()),
variables: self.variables.unwrap_or_default(),
cache_dir: self.cache_dir.unwrap_or(Config::get_default_cache_path()?),
cache_dir,
data_dir,
})
}
Expand Down
1 change: 1 addition & 0 deletions rocks-lib/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ pub mod config;
pub mod hash;
pub mod lockfile;
pub mod lua_installation;
pub mod luarocks_installation;
pub mod manifest;
pub mod operations;
pub mod package;
Expand Down
Loading

0 comments on commit 6fc34f3

Please sign in to comment.