Skip to main content

lux_lib/operations/
build_workspace.rs

1use crate::{
2    build::BuildBehaviour,
3    config::Config,
4    drivers::sync::{Sync, SyncError, SyncMode, TargetSet},
5    lockfile::LockedPackage,
6    package::PackageName,
7    workspace::Workspace,
8};
9use bon::Builder;
10use thiserror::Error;
11use tracing::{info_span, Instrument};
12
13#[derive(Debug, Error, miette::Diagnostic)]
14#[non_exhaustive]
15pub enum BuildWorkspaceError {
16    #[error(transparent)]
17    #[diagnostic(transparent)]
18    Sync(Box<SyncError>),
19}
20
21impl From<SyncError> for BuildWorkspaceError {
22    fn from(source: SyncError) -> Self {
23        Self::Sync(Box::new(source))
24    }
25}
26
27#[derive(Builder)]
28#[builder(start_fn = new, finish_fn(name = _build, vis = ""))]
29pub struct BuildWorkspace<'a> {
30    #[builder(start_fn)]
31    workspace: &'a Workspace,
32
33    #[builder(start_fn)]
34    config: &'a Config,
35
36    /// Package to build
37    package: Option<PackageName>,
38
39    /// Ignore the project's lockfile and don't create one
40    no_lock: bool,
41
42    /// Build only the dependencies
43    only_deps: bool,
44
45    #[builder(default = BuildBehaviour::Force)]
46    behaviour: BuildBehaviour,
47}
48
49impl<State: build_workspace_builder::State + build_workspace_builder::IsComplete>
50    BuildWorkspaceBuilder<'_, State>
51{
52    pub async fn build(self) -> Result<Vec<LockedPackage>, BuildWorkspaceError> {
53        let build = self._build();
54        let span = match &build.package {
55            Some(package) => info_span!("Building workspace", package = package.to_string()),
56            None => info_span!("Building workspace"),
57        };
58        async move {
59            let report = Sync::new(build.workspace, build.config)
60                .mode(SyncMode::Open)
61                .targets(match build.package {
62                    Some(package) => TargetSet::member(package, false),
63                    None => TargetSet::all(build.workspace, false),
64                })
65                .behaviour(build.behaviour)
66                .only_deps(build.only_deps)
67                .no_lock(build.no_lock)
68                .sync()
69                .await?;
70            Ok(report.added().to_vec())
71        }
72        .instrument(span)
73        .await
74    }
75}
76
77#[cfg(test)]
78mod tests {
79    use super::*;
80
81    use crate::{
82        config::ConfigBuilder, lua_installation::detect_installed_lua_version,
83        lua_version::LuaVersion,
84    };
85    #[cfg(feature = "impure_tests")]
86    use crate::{fs, tree::InstallTree};
87    use assert_fs::prelude::PathCopy;
88    use std::path::PathBuf;
89
90    #[cfg(feature = "impure_tests")]
91    #[tokio::test]
92    /// Non-regression for #980
93    async fn builtin_build_autodetect_bin_scripts() {
94        let project_root =
95            PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("resources/test/sample-projects/init/");
96        let data_dir: PathBuf = assert_fs::TempDir::new().unwrap().path().into();
97        let temp_dir = assert_fs::TempDir::new().unwrap();
98        temp_dir.copy_from(&project_root, &["**"]).unwrap();
99        let project_root = temp_dir.path();
100        let foo_bin_dir = project_root.join("src").join("bin");
101        fs::tokio::create_dir_all(&foo_bin_dir).await.unwrap();
102        let foo_bin_file = foo_bin_dir.join("foo");
103        fs::tokio::write(&foo_bin_file, "print('hello')")
104            .await
105            .unwrap();
106        let bar_bin_dir = project_root.join("bin");
107        fs::tokio::create_dir_all(&bar_bin_dir).await.unwrap();
108        let bar_bin_file = bar_bin_dir.join("bar");
109        fs::tokio::write(&bar_bin_file, "print('hello')")
110            .await
111            .unwrap();
112        let lua_version = detect_installed_lua_version().or(Some(LuaVersion::Lua51));
113        let config = ConfigBuilder::new()
114            .unwrap()
115            .data_dir(Some(data_dir))
116            .lua_version(lua_version)
117            .build()
118            .unwrap();
119        let workspace = Workspace::from_exact(project_root).unwrap().unwrap();
120        let tree = workspace.tree(&config).unwrap();
121        BuildWorkspace::new(&workspace, &config)
122            .no_lock(false)
123            .only_deps(false)
124            .build()
125            .await
126            .unwrap();
127        let bin_dir = tree.bin();
128        assert!(bin_dir.join("foo").is_file());
129        assert!(bin_dir.join("bar").is_file());
130    }
131
132    #[cfg(feature = "impure_tests")]
133    #[tokio::test]
134    /// Non-regression for #1563
135    async fn builtin_build_support_src_init_lua() {
136        let data_dir: PathBuf = assert_fs::TempDir::new().unwrap().path().into();
137        let project_root =
138            PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("resources/test/sample-projects/init/");
139        let temp_dir = assert_fs::TempDir::new().unwrap();
140        temp_dir.copy_from(&project_root, &["**"]).unwrap();
141        let project_root = temp_dir.path();
142        let src_dir = project_root.join("src");
143        fs::tokio::create_dir_all(&src_dir).await.unwrap();
144        let init_lua_file = src_dir.join("init.lua");
145        fs::tokio::write(&init_lua_file, "print('hello')")
146            .await
147            .unwrap();
148        let lua_version = detect_installed_lua_version().or(Some(LuaVersion::Lua51));
149        let config = ConfigBuilder::new()
150            .unwrap()
151            .data_dir(Some(data_dir))
152            .lua_version(lua_version)
153            .build()
154            .unwrap();
155        let workspace = Workspace::from_exact(project_root).unwrap().unwrap();
156        let package = BuildWorkspace::new(&workspace, &config)
157            .no_lock(false)
158            .only_deps(false)
159            .build()
160            .await
161            .unwrap();
162        let package = package.first().unwrap();
163        let tree = workspace.tree(&config).unwrap();
164        let src_dir = tree.layout_for(&package.spec).src;
165        assert!(src_dir.join("init.lua").is_file());
166    }
167
168    #[tokio::test]
169    /// Non-regression for #2101
170    async fn build_install_post_build_artifact() {
171        let data_dir: PathBuf = assert_fs::TempDir::new().unwrap().path().into();
172        let project_root =
173            PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("resources/test/sample-projects/c-src/");
174        let temp_dir = assert_fs::TempDir::new().unwrap();
175        temp_dir.copy_from(&project_root, &["**"]).unwrap();
176        let project_root = temp_dir.path();
177        let lua_version = detect_installed_lua_version().or(Some(LuaVersion::Lua51));
178        let config = ConfigBuilder::new()
179            .unwrap()
180            .data_dir(Some(data_dir))
181            .lua_version(lua_version)
182            .build()
183            .unwrap();
184        let workspace = Workspace::from_exact(project_root).unwrap().unwrap();
185        // TODO: Investigate if build::utils::compile is outputting the .so to a different directory
186        BuildWorkspace::new(&workspace, &config)
187            .no_lock(false)
188            .only_deps(false)
189            .build()
190            .await
191            .unwrap();
192    }
193}