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