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