// SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: 2022 The Gleam contributors use camino::Utf8PathBuf; use gleam_core::{ Error, Result, build::{Mode, Target, Telemetry}, error::{FileIoAction, FileKind}, paths::ProjectPaths, }; use strum::IntoEnumIterator; #[derive(Debug)] pub(crate) struct BuildLock { directory: Utf8PathBuf, filename: String, } impl BuildLock { /// Lock the build directory for the specified mode and target. pub fn new_target(paths: &ProjectPaths, mode: Mode, target: Target) -> Result { let directory = paths.build_directory(); crate::fs::mkdir(&directory)?; let target = match target { Target::Erlang => "erlang", Target::JavaScript => "javascript", Target::Wasm => "wasm", }; Ok(Self { directory, filename: format!("gleam-{mode}-{target}.lock"), }) } /// Lock the packages directory. pub fn new_packages(paths: &ProjectPaths) -> Result { let directory = paths.build_packages_directory(); crate::fs::mkdir(&directory)?; Ok(Self { directory, filename: "gleam.lock".to_string(), }) } /// Construct the lock file path pub fn lock_path(&self) -> Utf8PathBuf { self.directory.join(&self.filename) } /// Lock the directory specified by the lock pub fn lock(&self, telemetry: &Telem) -> Result { let lock_path = self.lock_path(); tracing::debug!(path=?lock_path, "locking_directory"); crate::fs::mkdir(&self.directory)?; let mut file = fslock::LockFile::open(lock_path.as_str()).map_err(|e| Error::FileIo { kind: FileKind::File, path: lock_path.clone(), action: FileIoAction::Create, err: Some(e.to_string()), })?; let lock_error = |error: fslock::Error| Error::FileIo { kind: FileKind::File, action: FileIoAction::Lock, path: lock_path.clone(), err: Some(error.to_string()), }; if !file.try_lock_with_pid().map_err(lock_error)? { telemetry.waiting_for_build_directory_lock(); file.lock_with_pid().map_err(lock_error)?; } Ok(Guard(file)) } /// Lock all build directories. Does not lock the packages directory. pub fn lock_all_build( paths: &ProjectPaths, telemetry: &Telem, ) -> Result> { let mut locks = vec![]; for mode in Mode::iter() { for target in Target::iter() { locks.push(Self::new_target(paths, mode, target)?.lock(telemetry)?); } } Ok(locks) } } #[derive(Debug)] pub(crate) struct Guard( // False positive. This is used in `drop`. Presumably the lint error is a // bug in clippy. #[allow(dead_code)] fslock::LockFile, ); #[test] fn locking_global() { let paths = crate::project_paths_at_current_directory_without_toml(); let lock = BuildLock::new_packages(&paths).expect("make lock"); let _guard1: Guard = lock.lock(&gleam_core::build::NullTelemetry).unwrap(); println!("Locked!") } #[test] fn locking_dev_erlang() { let paths = crate::project_paths_at_current_directory_without_toml(); let lock = BuildLock::new_target(&paths, Mode::Dev, Target::Erlang).expect("make lock"); let _guard1: Guard = lock.lock(&gleam_core::build::NullTelemetry).unwrap(); println!("Locked!") } #[test] fn locking_prod_erlang() { let paths = crate::project_paths_at_current_directory_without_toml(); let lock = BuildLock::new_target(&paths, Mode::Prod, Target::Erlang).expect("make lock"); let _guard1: Guard = lock.lock(&gleam_core::build::NullTelemetry).unwrap(); println!("Locked!") } #[test] fn locking_lsp_erlang() { let paths = crate::project_paths_at_current_directory_without_toml(); let lock = BuildLock::new_target(&paths, Mode::Lsp, Target::Erlang).expect("make lock"); let _guard1: Guard = lock.lock(&gleam_core::build::NullTelemetry).unwrap(); println!("Locked!") } #[test] fn locking_dev_javascript() { let paths = crate::project_paths_at_current_directory_without_toml(); let lock = BuildLock::new_target(&paths, Mode::Dev, Target::JavaScript).expect("make lock"); let _guard1: Guard = lock.lock(&gleam_core::build::NullTelemetry).unwrap(); println!("Locked!") } #[test] fn locking_prod_javascript() { let paths = crate::project_paths_at_current_directory_without_toml(); let lock = BuildLock::new_target(&paths, Mode::Prod, Target::JavaScript).expect("make lock"); let _guard1: Guard = lock.lock(&gleam_core::build::NullTelemetry).unwrap(); println!("Locked!") } #[test] fn locking_lsp_javascript() { let paths = crate::project_paths_at_current_directory_without_toml(); let lock = BuildLock::new_target(&paths, Mode::Lsp, Target::JavaScript).expect("make lock"); let _guard1: Guard = lock.lock(&gleam_core::build::NullTelemetry).unwrap(); println!("Locked!") }