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(all(test, feature = "impure_tests"))]
78mod tests {
79    use super::*;
80
81    use crate::{
82        config::ConfigBuilder, fs, lua_installation::detect_installed_lua_version,
83        lua_version::LuaVersion, tree::InstallTree,
84    };
85    use assert_fs::prelude::PathCopy;
86    use std::path::PathBuf;
87
88    #[tokio::test]
89    /// Non-regression for #980
90    async fn builtin_build_autodetect_bin_scripts() {
91        let project_root =
92            PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("resources/test/sample-projects/init/");
93        let data_dir: PathBuf = assert_fs::TempDir::new().unwrap().path().into();
94        let temp_dir = assert_fs::TempDir::new().unwrap();
95        temp_dir.copy_from(&project_root, &["**"]).unwrap();
96        let project_root = temp_dir.path();
97        let foo_bin_dir = project_root.join("src").join("bin");
98        fs::tokio::create_dir_all(&foo_bin_dir).await.unwrap();
99        let foo_bin_file = foo_bin_dir.join("foo");
100        fs::tokio::write(&foo_bin_file, "print('hello')")
101            .await
102            .unwrap();
103        let bar_bin_dir = project_root.join("bin");
104        fs::tokio::create_dir_all(&bar_bin_dir).await.unwrap();
105        let bar_bin_file = bar_bin_dir.join("bar");
106        fs::tokio::write(&bar_bin_file, "print('hello')")
107            .await
108            .unwrap();
109        let lua_version = detect_installed_lua_version().or(Some(LuaVersion::Lua51));
110        let config = ConfigBuilder::new()
111            .unwrap()
112            .data_dir(Some(data_dir))
113            .lua_version(lua_version)
114            .build()
115            .unwrap();
116        let workspace = Workspace::from_exact(project_root).unwrap().unwrap();
117        let tree = workspace.tree(&config).unwrap();
118        BuildWorkspace::new(&workspace, &config)
119            .no_lock(false)
120            .only_deps(false)
121            .build()
122            .await
123            .unwrap();
124        let bin_dir = tree.bin();
125        assert!(bin_dir.join("foo").is_file());
126        assert!(bin_dir.join("bar").is_file());
127    }
128
129    #[tokio::test]
130    /// Non-regression for #1563
131    async fn builtin_build_support_src_init_lua() {
132        let data_dir: PathBuf = assert_fs::TempDir::new().unwrap().path().into();
133        let project_root =
134            PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("resources/test/sample-projects/init/");
135        let temp_dir = assert_fs::TempDir::new().unwrap();
136        temp_dir.copy_from(&project_root, &["**"]).unwrap();
137        let project_root = temp_dir.path();
138        let src_dir = project_root.join("src");
139        fs::tokio::create_dir_all(&src_dir).await.unwrap();
140        let init_lua_file = src_dir.join("init.lua");
141        fs::tokio::write(&init_lua_file, "print('hello')")
142            .await
143            .unwrap();
144        let lua_version = detect_installed_lua_version().or(Some(LuaVersion::Lua51));
145        let config = ConfigBuilder::new()
146            .unwrap()
147            .data_dir(Some(data_dir))
148            .lua_version(lua_version)
149            .build()
150            .unwrap();
151        let workspace = Workspace::from_exact(project_root).unwrap().unwrap();
152        let package = BuildWorkspace::new(&workspace, &config)
153            .no_lock(false)
154            .only_deps(false)
155            .build()
156            .await
157            .unwrap();
158        let package = package.first().unwrap();
159        let tree = workspace.tree(&config).unwrap();
160        let src_dir = tree.layout_for(&package.spec).src;
161        assert!(src_dir.join("init.lua").is_file());
162    }
163}