1use std::path::{Path, PathBuf};
2
3use crate::{args::PackageOrRockspec, build, workspace::exists_matching_workspace_member};
4use clap::Args;
5use eyre::{eyre, OptionExt, Result};
6use itertools::Itertools;
7use lux_lib::{
8 build::{Build, BuildBehaviour},
9 config::Config,
10 lua_installation::LuaInstallation,
11 lua_rockspec::RemoteLuaRockspec,
12 lua_version::LuaVersion,
13 operations::{self, Install, PackageInstallSpec},
14 package::PackageName,
15 progress::MultiProgress,
16 rockspec::Rockspec as _,
17 tree::{self, InstallTree},
18 workspace::Workspace,
19};
20use path_slash::PathBufExt;
21use tempfile::tempdir;
22
23#[derive(Args)]
24pub struct Pack {
25 #[clap(value_parser)]
41 package_or_rockspec: Option<PackageOrRockspec>,
42}
43
44async fn pack_workspace(
45 member: Option<&PackageName>,
46 dest_dir: &Path,
47 config: &Config,
48) -> Result<Vec<PathBuf>> {
49 let workspace = Workspace::current_or_err()?;
50
51 let packages = match member {
54 Some(package_name) => {
56 let project = workspace.select_member(package_name)?;
57 project
58 .toml()
59 .into_remote(None)?
60 .to_lua_remote_rockspec_string()?;
61
62 let mut build = build::Build::default();
63 build.package = Some(package_name.clone());
64 build::build(build, config.clone())
65 }
66 None => {
68 for project in workspace.members() {
69 project
70 .toml()
71 .into_remote(None)?
72 .to_lua_remote_rockspec_string()?;
73 }
74 build::build(build::Build::default(), config.clone())
75 }
76 }
77 .await?;
78
79 if packages.is_empty() {
80 return Err(eyre!("build did not produce a package"));
81 }
82
83 let mut rock_paths = Vec::new();
84 for package in packages {
85 let tree = workspace.tree(config)?;
86 let rock_path = operations::Pack::new(dest_dir.to_path_buf(), tree, package)
87 .pack()
88 .await?;
89 rock_paths.push(rock_path);
90 }
91
92 Ok(rock_paths)
93}
94
95pub async fn pack(args: Pack, config: Config) -> Result<()> {
96 let lua_version = LuaVersion::from(&config)?.clone();
97 let dest_dir = std::env::current_dir()?;
98 let progress = MultiProgress::new_arc(&config);
99 let rock_paths: Vec<PathBuf> = match args.package_or_rockspec {
100 Some(PackageOrRockspec::Package(package_req))
101 if exists_matching_workspace_member(&package_req)? =>
102 {
103 pack_workspace(Some(package_req.name()), &dest_dir, &config).await
104 }
105 Some(PackageOrRockspec::Package(package_req)) => {
106 let user_tree = config.user_tree(lua_version.clone())?;
107 match user_tree.match_rocks(&package_req)? {
108 lux_lib::tree::RockMatches::NotFound(_) => {
109 let temp_dir = tempdir()?;
110 let temp_config = config.with_tree(temp_dir.path().to_path_buf());
111 let tree = temp_config.user_tree(lua_version.clone())?;
112 let packages = Install::new(&temp_config)
113 .package(
114 PackageInstallSpec::new(package_req, tree::EntryType::Entrypoint)
115 .build_behaviour(BuildBehaviour::Force)
116 .build(),
117 )
118 .tree(tree.clone())
119 .progress(progress)
120 .install()
121 .await?;
122 let package = packages.first().ok_or_eyre("no packages installed")?;
123 let rock_path = operations::Pack::new(dest_dir, tree, package.clone())
124 .pack()
125 .await?;
126 Ok(vec![rock_path])
127 }
128 lux_lib::tree::RockMatches::Single(local_package_id) => {
129 let lockfile = user_tree.lockfile()?;
130 let package = lockfile
131 .get(&local_package_id)
132 .ok_or_eyre("package is installed, but was not found in the lockfile")?;
133 let rock_path = operations::Pack::new(dest_dir, user_tree, package.clone())
134 .pack()
135 .await?;
136 Ok(vec![rock_path])
137 }
138 lux_lib::tree::RockMatches::Many(vec) => {
139 let local_package_id = vec.first();
140 let lockfile = user_tree.lockfile()?;
141 let package = lockfile.get(local_package_id).ok_or_eyre(
142 "multiple package installations found, but not found in the lockfile",
143 )?;
144 let rock_path = operations::Pack::new(dest_dir, user_tree, package.clone())
145 .pack()
146 .await?;
147 Ok(vec![rock_path])
148 }
149 }
150 }
151 Some(PackageOrRockspec::RockSpec(rockspec_path)) => {
152 let content = tokio::fs::read_to_string(&rockspec_path).await?;
153 let rockspec = match rockspec_path
154 .extension()
155 .map(|ext| ext.to_string_lossy().to_string())
156 .unwrap_or("".into())
157 .as_str()
158 {
159 "rockspec" => Ok(RemoteLuaRockspec::new(&content)?),
160 _ => Err(eyre!(
161 "expected a path to a .rockspec or a package requirement."
162 )),
163 }?;
164 let temp_dir = tempdir()?;
165 let bar = progress.map(|p| p.new_bar());
166 let config = config.with_tree(temp_dir.path().to_path_buf());
167 let lua = LuaInstallation::new(
168 &lua_version,
169 &config,
170 &progress.map(|progress| progress.new_bar()),
171 )
172 .await?;
173 let tree = config.user_tree(lua_version)?;
174 let package = Build::new()
175 .rockspec(&rockspec)
176 .lua(&lua)
177 .tree(&tree)
178 .entry_type(tree::EntryType::Entrypoint)
179 .config(&config)
180 .progress(&bar)
181 .build()
182 .await?;
183 let rock_path = operations::Pack::new(dest_dir, tree, package)
184 .pack()
185 .await?;
186 Ok(vec![rock_path])
187 }
188 None => pack_workspace(None, &dest_dir, &config).await,
189 }?;
190
191 if rock_paths.len() > 1 {
192 let rock_paths = rock_paths
193 .iter()
194 .map(|path| path.to_slash_lossy().to_string())
195 .join("\n");
196 print!("packed rocks created at\n{}", rock_paths)
197 } else {
198 rock_paths
199 .first()
200 .iter()
201 .for_each(|path| print!("packed rock created at {}", path.display()));
202 }
203 Ok(())
204}