1use std::path::{Path, PathBuf};
2
3use crate::{args::PackageOrRockspec, build, workspace::exists_matching_workspace_member};
4use clap::Args;
5use itertools::Itertools;
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::PackageName,
14 progress::MultiProgress,
15 rockspec::Rockspec as _,
16 tree::{self, InstallTree},
17 workspace::Workspace,
18};
19use miette::{miette, IntoDiagnostic, Result};
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(miette!("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().into_diagnostic()?;
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().into_diagnostic()?;
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
123 .first()
124 .ok_or_else(|| miette!("no packages installed"))?;
125 let rock_path = operations::Pack::new(dest_dir, tree, package.clone())
126 .pack()
127 .await?;
128 Ok(vec![rock_path])
129 }
130 lux_lib::tree::RockMatches::Single(local_package_id) => {
131 let lockfile = user_tree.lockfile()?;
132 let package = lockfile.get(&local_package_id).ok_or_else(|| {
133 miette!("package is installed, but was not found in the lockfile")
134 })?;
135 let rock_path = operations::Pack::new(dest_dir, user_tree, package.clone())
136 .pack()
137 .await?;
138 Ok(vec![rock_path])
139 }
140 lux_lib::tree::RockMatches::Many(vec) => {
141 let local_package_id = vec.first();
142 let lockfile = user_tree.lockfile()?;
143 let package = lockfile.get(local_package_id).ok_or_else(|| {
144 miette!(
145 "multiple package installations found, but not found in the lockfile"
146 )
147 })?;
148 let rock_path = operations::Pack::new(dest_dir, user_tree, package.clone())
149 .pack()
150 .await?;
151 Ok(vec![rock_path])
152 }
153 }
154 }
155 Some(PackageOrRockspec::RockSpec(rockspec_path)) => {
156 let content = tokio::fs::read_to_string(&rockspec_path)
157 .await
158 .into_diagnostic()?;
159 let rockspec = match rockspec_path
160 .extension()
161 .map(|ext| ext.to_string_lossy().to_string())
162 .unwrap_or("".into())
163 .as_str()
164 {
165 "rockspec" => Ok(RemoteLuaRockspec::new(&content)?),
166 _ => Err(miette!(
167 "expected a path to a .rockspec or a package requirement."
168 )),
169 }?;
170 let temp_dir = tempdir().into_diagnostic()?;
171 let bar = progress.map(|p| p.new_bar());
172 let config = config.with_tree(temp_dir.path().to_path_buf());
173 let lua = LuaInstallation::new(
174 &lua_version,
175 &config,
176 &progress.map(|progress| progress.new_bar()),
177 )
178 .await?;
179 let tree = config.user_tree(lua_version)?;
180 let package = Build::new()
181 .rockspec(&rockspec)
182 .lua(&lua)
183 .tree(&tree)
184 .entry_type(tree::EntryType::Entrypoint)
185 .config(&config)
186 .progress(&bar)
187 .build()
188 .await?;
189 let rock_path = operations::Pack::new(dest_dir, tree, package)
190 .pack()
191 .await?;
192 Ok(vec![rock_path])
193 }
194 None => pack_workspace(None, &dest_dir, &config).await,
195 }?;
196
197 if rock_paths.len() > 1 {
198 let rock_paths = rock_paths
199 .iter()
200 .map(|path| path.to_slash_lossy().to_string())
201 .join("\n");
202 print!("packed rocks created at\n{}", rock_paths)
203 } else {
204 rock_paths
205 .first()
206 .iter()
207 .for_each(|path| print!("packed rock created at {}", path.display()));
208 }
209 Ok(())
210}