lux_cli/
pack.rs

1use std::{path::PathBuf, str::FromStr};
2
3use crate::build;
4use clap::Args;
5use eyre::{eyre, 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 tempdir::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();
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::new("lux-pack")?.into_path();
76                    let temp_config = config.with_tree(temp_dir);
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().unwrap();
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.get(&local_package_id).unwrap();
97                    let rock_path = operations::Pack::new(dest_dir, user_tree, package.clone())
98                        .pack()
99                        .await?;
100                    Ok(rock_path)
101                }
102                lux_lib::tree::RockMatches::Many(vec) => {
103                    let local_package_id = vec.first().unwrap();
104                    let lockfile = user_tree.lockfile()?;
105                    let package = lockfile.get(local_package_id).unwrap();
106                    let rock_path = operations::Pack::new(dest_dir, user_tree, package.clone())
107                        .pack()
108                        .await?;
109                    Ok(rock_path)
110                }
111            }
112        }
113        Some(PackageOrRockspec::RockSpec(rockspec_path)) => {
114            let content = std::fs::read_to_string(&rockspec_path)?;
115            let rockspec = match rockspec_path
116                .extension()
117                .map(|ext| ext.to_string_lossy().to_string())
118                .unwrap_or("".into())
119                .as_str()
120            {
121                ".rockspec" => Ok(RemoteLuaRockspec::new(&content)?),
122                _ => Err(eyre!(
123                    "expected a path to a .rockspec or a package requirement."
124                )),
125            }?;
126            let temp_dir = TempDir::new("lux-pack")?.into_path();
127            let bar = progress.map(|p| p.new_bar());
128            let config = config.with_tree(temp_dir);
129            let lua = LuaInstallation::new(
130                &lua_version,
131                &config,
132                &progress.map(|progress| progress.new_bar()),
133            )
134            .await?;
135            let tree = config.user_tree(lua_version)?;
136            let package = Build::new()
137                .rockspec(&rockspec)
138                .lua(&lua)
139                .tree(&tree)
140                .entry_type(tree::EntryType::Entrypoint)
141                .config(&config)
142                .progress(&bar)
143                .build()
144                .await?;
145            let rock_path = operations::Pack::new(dest_dir, tree, package)
146                .pack()
147                .await?;
148            Ok(rock_path)
149        }
150        None => {
151            let project = Project::current_or_err()?;
152            // luarocks expects a `<package>-<version>.rockspec` in the package root,
153            // so we add a guard that it can be created here.
154            project
155                .toml()
156                .into_remote()?
157                .to_lua_remote_rockspec_string()?;
158            let package = build::build(build::Build::default(), config.clone())
159                .await?
160                .expect("exptected a `LocalPackage`");
161            let tree = project.tree(&config)?;
162            let rock_path = operations::Pack::new(dest_dir, tree, package)
163                .pack()
164                .await?;
165            Ok(rock_path)
166        }
167    };
168    print!("packed rock created at {}", result?.display());
169    Ok(())
170}