Skip to main content

lux_cli/
pack.rs

1use std::{path::PathBuf, str::FromStr};
2
3use crate::build;
4use clap::Args;
5use eyre::{eyre, OptionExt, Result};
6use lux_lib::{
7    build::{Build, BuildBehaviour},
8    config::{Config, LuaVersion},
9    lua_installation::LuaInstallation,
10    lua_rockspec::RemoteLuaRockspec,
11    operations::{self, Install, PackageInstallSpec},
12    package::PackageReq,
13    progress::MultiProgress,
14    project::Project,
15    rockspec::Rockspec as _,
16    tree,
17};
18use tempfile::tempdir;
19
20#[derive(Debug, Clone)]
21pub enum PackageOrRockspec {
22    Package(PackageReq),
23    RockSpec(PathBuf),
24}
25
26impl FromStr for PackageOrRockspec {
27    type Err = eyre::Error;
28
29    fn from_str(s: &str) -> Result<Self, Self::Err> {
30        let path = PathBuf::from(s);
31        if path.is_file() {
32            Ok(Self::RockSpec(path))
33        } else {
34            let pkg = PackageReq::from_str(s).map_err(|err| {
35                eyre!(
36                    "No file {0} found and cannot parse package query: {1}",
37                    s,
38                    err
39                )
40            })?;
41            Ok(Self::Package(pkg))
42        }
43    }
44}
45
46#[derive(Args)]
47pub struct Pack {
48    /// Path to a RockSpec or a package query for a package to pack.{n}
49    /// Prioritises installed rocks and will install a rock to a temporary{n}
50    /// directory if none is found.{n}
51    /// In case of multiple matches, the latest version will be packed.{n}
52    ///{n}
53    /// Examples:{n}
54    ///     - "pkg"{n}
55    ///     - "pkg@1.0.0"{n}
56    ///     - "pkg>=1.0.0"{n}
57    ///     - "/path/to/foo-1.0.0-1.rockspec"{n}
58    ///{n}
59    /// If not set, lux will build the current project and attempt to pack it.{n}
60    /// To be able to pack a project, lux must be able to generate a release or dev{n}
61    /// Lua rockspec.{n}
62    #[clap(value_parser)]
63    package_or_rockspec: Option<PackageOrRockspec>,
64}
65
66pub async fn pack(args: Pack, config: Config) -> Result<()> {
67    let lua_version = LuaVersion::from(&config)?.clone();
68    let dest_dir = std::env::current_dir()?;
69    let progress = MultiProgress::new_arc(&config);
70    let result: Result<PathBuf> = match args.package_or_rockspec {
71        Some(PackageOrRockspec::Package(package_req)) => {
72            let user_tree = config.user_tree(lua_version.clone())?;
73            match user_tree.match_rocks(&package_req)? {
74                lux_lib::tree::RockMatches::NotFound(_) => {
75                    let temp_dir = tempdir()?;
76                    let temp_config = config.with_tree(temp_dir.path().to_path_buf());
77                    let tree = temp_config.user_tree(lua_version.clone())?;
78                    let packages = Install::new(&temp_config)
79                        .package(
80                            PackageInstallSpec::new(package_req, tree::EntryType::Entrypoint)
81                                .build_behaviour(BuildBehaviour::Force)
82                                .build(),
83                        )
84                        .tree(tree.clone())
85                        .progress(progress)
86                        .install()
87                        .await?;
88                    let package = packages.first().ok_or_eyre("no packages installed")?;
89                    let rock_path = operations::Pack::new(dest_dir, tree, package.clone())
90                        .pack()
91                        .await?;
92                    Ok(rock_path)
93                }
94                lux_lib::tree::RockMatches::Single(local_package_id) => {
95                    let lockfile = user_tree.lockfile()?;
96                    let package = lockfile
97                        .get(&local_package_id)
98                        .ok_or_eyre("package is installed, but was not found in the lockfile")?;
99                    let rock_path = operations::Pack::new(dest_dir, user_tree, package.clone())
100                        .pack()
101                        .await?;
102                    Ok(rock_path)
103                }
104                lux_lib::tree::RockMatches::Many(vec) => {
105                    let local_package_id = vec.first();
106                    let lockfile = user_tree.lockfile()?;
107                    let package = lockfile.get(local_package_id).ok_or_eyre(
108                        "multiple package installations found, but not found in the lockfile",
109                    )?;
110                    let rock_path = operations::Pack::new(dest_dir, user_tree, package.clone())
111                        .pack()
112                        .await?;
113                    Ok(rock_path)
114                }
115            }
116        }
117        Some(PackageOrRockspec::RockSpec(rockspec_path)) => {
118            let content = tokio::fs::read_to_string(&rockspec_path).await?;
119            let rockspec = match rockspec_path
120                .extension()
121                .map(|ext| ext.to_string_lossy().to_string())
122                .unwrap_or("".into())
123                .as_str()
124            {
125                "rockspec" => Ok(RemoteLuaRockspec::new(&content)?),
126                _ => Err(eyre!(
127                    "expected a path to a .rockspec or a package requirement."
128                )),
129            }?;
130            let temp_dir = tempdir()?;
131            let bar = progress.map(|p| p.new_bar());
132            let config = config.with_tree(temp_dir.path().to_path_buf());
133            let lua = LuaInstallation::new(
134                &lua_version,
135                &config,
136                &progress.map(|progress| progress.new_bar()),
137            )
138            .await?;
139            let tree = config.user_tree(lua_version)?;
140            let package = Build::new()
141                .rockspec(&rockspec)
142                .lua(&lua)
143                .tree(&tree)
144                .entry_type(tree::EntryType::Entrypoint)
145                .config(&config)
146                .progress(&bar)
147                .build()
148                .await?;
149            let rock_path = operations::Pack::new(dest_dir, tree, package)
150                .pack()
151                .await?;
152            Ok(rock_path)
153        }
154        None => {
155            let project = Project::current_or_err()?;
156            // luarocks expects a `<package>-<version>.rockspec` in the package root,
157            // so we add a guard that it can be created here.
158            project
159                .toml()
160                .into_remote(None)?
161                .to_lua_remote_rockspec_string()?;
162            let package = build::build(build::Build::default(), config.clone())
163                .await?
164                .ok_or_eyre("build did not produce a package")?;
165            let tree = project.tree(&config)?;
166            let rock_path = operations::Pack::new(dest_dir, tree, package)
167                .pack()
168                .await?;
169            Ok(rock_path)
170        }
171    };
172    print!("packed rock created at {}", result?.display());
173    Ok(())
174}