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