1use std::{path::PathBuf, str::FromStr};
2
3use crate::build;
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::PackageReq,
15 progress::MultiProgress,
16 rockspec::Rockspec as _,
17 tree,
18 workspace::Workspace,
19};
20use path_slash::PathBufExt;
21use tempfile::tempdir;
22
23#[derive(Debug, Clone)]
24pub enum PackageOrRockspec {
25 Package(PackageReq),
26 RockSpec(PathBuf),
27}
28
29impl FromStr for PackageOrRockspec {
30 type Err = eyre::Error;
31
32 fn from_str(s: &str) -> Result<Self, Self::Err> {
33 let path = PathBuf::from(s);
34 if path.is_file() {
35 Ok(Self::RockSpec(path))
36 } else {
37 let pkg = PackageReq::from_str(s).map_err(|err| {
38 eyre!(
39 "No file {0} found and cannot parse package query: {1}",
40 s,
41 err
42 )
43 })?;
44 Ok(Self::Package(pkg))
45 }
46 }
47}
48
49#[derive(Args)]
50pub struct Pack {
51 #[clap(value_parser)]
66 package_or_rockspec: Option<PackageOrRockspec>,
67}
68
69pub async fn pack(args: Pack, config: Config) -> Result<()> {
70 let lua_version = LuaVersion::from(&config)?.clone();
71 let dest_dir = std::env::current_dir()?;
72 let progress = MultiProgress::new_arc(&config);
73 let rock_paths: Result<Vec<PathBuf>> = match args.package_or_rockspec {
74 Some(PackageOrRockspec::Package(package_req)) => {
75 let user_tree = config.user_tree(lua_version.clone())?;
76 match user_tree.match_rocks(&package_req)? {
77 lux_lib::tree::RockMatches::NotFound(_) => {
78 let temp_dir = tempdir()?;
79 let temp_config = config.with_tree(temp_dir.path().to_path_buf());
80 let tree = temp_config.user_tree(lua_version.clone())?;
81 let packages = Install::new(&temp_config)
82 .package(
83 PackageInstallSpec::new(package_req, tree::EntryType::Entrypoint)
84 .build_behaviour(BuildBehaviour::Force)
85 .build(),
86 )
87 .tree(tree.clone())
88 .progress(progress)
89 .install()
90 .await?;
91 let package = packages.first().ok_or_eyre("no packages installed")?;
92 let rock_path = operations::Pack::new(dest_dir, tree, package.clone())
93 .pack()
94 .await?;
95 Ok(vec![rock_path])
96 }
97 lux_lib::tree::RockMatches::Single(local_package_id) => {
98 let lockfile = user_tree.lockfile()?;
99 let package = lockfile
100 .get(&local_package_id)
101 .ok_or_eyre("package is installed, but was not found in the lockfile")?;
102 let rock_path = operations::Pack::new(dest_dir, user_tree, package.clone())
103 .pack()
104 .await?;
105 Ok(vec![rock_path])
106 }
107 lux_lib::tree::RockMatches::Many(vec) => {
108 let local_package_id = vec.first();
109 let lockfile = user_tree.lockfile()?;
110 let package = lockfile.get(local_package_id).ok_or_eyre(
111 "multiple package installations found, but not found in the lockfile",
112 )?;
113 let rock_path = operations::Pack::new(dest_dir, user_tree, package.clone())
114 .pack()
115 .await?;
116 Ok(vec![rock_path])
117 }
118 }
119 }
120 Some(PackageOrRockspec::RockSpec(rockspec_path)) => {
121 let content = tokio::fs::read_to_string(&rockspec_path).await?;
122 let rockspec = match rockspec_path
123 .extension()
124 .map(|ext| ext.to_string_lossy().to_string())
125 .unwrap_or("".into())
126 .as_str()
127 {
128 "rockspec" => Ok(RemoteLuaRockspec::new(&content)?),
129 _ => Err(eyre!(
130 "expected a path to a .rockspec or a package requirement."
131 )),
132 }?;
133 let temp_dir = tempdir()?;
134 let bar = progress.map(|p| p.new_bar());
135 let config = config.with_tree(temp_dir.path().to_path_buf());
136 let lua = LuaInstallation::new(
137 &lua_version,
138 &config,
139 &progress.map(|progress| progress.new_bar()),
140 )
141 .await?;
142 let tree = config.user_tree(lua_version)?;
143 let package = Build::new()
144 .rockspec(&rockspec)
145 .lua(&lua)
146 .tree(&tree)
147 .entry_type(tree::EntryType::Entrypoint)
148 .config(&config)
149 .progress(&bar)
150 .build()
151 .await?;
152 let rock_path = operations::Pack::new(dest_dir, tree, package)
153 .pack()
154 .await?;
155 Ok(vec![rock_path])
156 }
157 None => {
158 let workspace = Workspace::current_or_err()?;
159 for project in workspace.members() {
162 project
163 .toml()
164 .into_remote(None)?
165 .to_lua_remote_rockspec_string()?;
166 }
167 let mut rock_paths = Vec::new();
168 let packages = build::build(build::Build::default(), config.clone()).await?;
169 if packages.is_empty() {
170 return Err(eyre!("build did not produce a package"));
171 }
172 for package in packages {
173 let tree = workspace.tree(&config)?;
174 let rock_path = operations::Pack::new(dest_dir.clone(), tree, package)
175 .pack()
176 .await?;
177 rock_paths.push(rock_path);
178 }
179 Ok(rock_paths)
180 }
181 };
182 let rock_paths = rock_paths?;
183 if rock_paths.len() > 1 {
184 let rock_paths = rock_paths
185 .iter()
186 .map(|path| path.to_slash_lossy().to_string())
187 .join("\n");
188 print!("packed rocks created at\n{}", rock_paths)
189 } else {
190 rock_paths
191 .first()
192 .iter()
193 .for_each(|path| print!("packed rock created at {}", path.display()));
194 }
195 Ok(())
196}