diff --git a/Cargo.lock b/Cargo.lock index 06aaf26fc..b12a8253c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -87,109 +87,6 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9" -[[package]] -name = "async-channel" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf46fee83e5ccffc220104713af3292ff9bc7c64c7de289f66dae8e38d826833" -dependencies = [ - "concurrent-queue", - "event-listener", - "futures-core", -] - -[[package]] -name = "async-executor" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17adb73da160dfb475c183343c8cccd80721ea5a605d3eb57125f0a7b7a92d0b" -dependencies = [ - "async-lock", - "async-task", - "concurrent-queue", - "fastrand", - "futures-lite", - "slab", -] - -[[package]] -name = "async-fs" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "279cf904654eeebfa37ac9bb1598880884924aab82e290aa65c9e77a0e142e06" -dependencies = [ - "async-lock", - "autocfg", - "blocking", - "futures-lite", -] - -[[package]] -name = "async-io" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c374dda1ed3e7d8f0d9ba58715f924862c63eae6849c92d3a18e7fbde9e2794" -dependencies = [ - "async-lock", - "autocfg", - "concurrent-queue", - "futures-lite", - "libc", - "log", - "parking", - "polling", - "slab", - "socket2", - "waker-fn", - "windows-sys 0.42.0", -] - -[[package]] -name = "async-lock" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8101efe8695a6c17e02911402145357e718ac92d3ff88ae8419e84b1707b685" -dependencies = [ - "event-listener", - "futures-lite", -] - -[[package]] -name = "async-net" -version = "1.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4051e67316bc7eff608fe723df5d32ed639946adcd69e07df41fd42a7b411f1f" -dependencies = [ - "async-io", - "autocfg", - "blocking", - "futures-lite", -] - -[[package]] -name = "async-process" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6381ead98388605d0d9ff86371043b5aa922a3905824244de40dc263a14fcba4" -dependencies = [ - "async-io", - "async-lock", - "autocfg", - "blocking", - "cfg-if", - "event-listener", - "futures-lite", - "libc", - "signal-hook", - "windows-sys 0.42.0", -] - -[[package]] -name = "async-task" -version = "4.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a40729d2133846d9ed0ea60a8b9541bccddab49cd30f0715a1da672fe9a2524" - [[package]] name = "async-trait" version = "0.1.66" @@ -210,12 +107,6 @@ dependencies = [ "num-traits 0.2.15", ] -[[package]] -name = "atomic-waker" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "debc29dde2e69f9e47506b525f639ed42300fc014a3e007832592448fa8e4599" - [[package]] name = "atty" version = "0.2.14" @@ -263,20 +154,6 @@ dependencies = [ "generic-array", ] -[[package]] -name = "blocking" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c67b173a56acffd6d2326fb7ab938ba0b00a71480e14902b2591c87bc5741e8" -dependencies = [ - "async-channel", - "async-lock", - "async-task", - "atomic-waker", - "fastrand", - "futures-lite", -] - [[package]] name = "bstr" version = "0.2.17" @@ -321,6 +198,12 @@ version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" +[[package]] +name = "bytes" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" + [[package]] name = "bytesize" version = "1.1.0" @@ -881,15 +764,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "82a90734b3d5dcf656e7624cca6bce9c3a90ee11f900e80141a7427ccfb3d317" -[[package]] -name = "concurrent-queue" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c278839b831783b70278b14df4d45e1beb1aad306c07bb796637de9a0e323e8e" -dependencies = [ - "crossbeam-utils", -] - [[package]] name = "console" version = "0.15.5" @@ -1286,12 +1160,6 @@ dependencies = [ "libc", ] -[[package]] -name = "event-listener" -version = "2.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" - [[package]] name = "fastrand" version = "1.9.0" @@ -1400,21 +1268,6 @@ version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfb8371b6fb2aeb2d280374607aeabfc99d95c72edfe51692e42d3d7f0d08531" -[[package]] -name = "futures-lite" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7694489acd39452c77daa48516b894c153f192c3578d5a839b62c58099fcbf48" -dependencies = [ - "fastrand", - "futures-core", - "futures-io", - "memchr", - "parking", - "pin-project-lite", - "waker-fn", -] - [[package]] name = "futures-macro" version = "0.3.26" @@ -2487,6 +2340,18 @@ dependencies = [ "adler", ] +[[package]] +name = "mio" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9" +dependencies = [ + "libc", + "log", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys 0.45.0", +] + [[package]] name = "ndarray" version = "0.13.1" @@ -2704,12 +2569,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" -[[package]] -name = "parking" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72" - [[package]] name = "parking_lot" version = "0.11.2" @@ -2836,20 +2695,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" -[[package]] -name = "polling" -version = "2.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22122d5ec4f9fe1b3916419b76be1e80bcb93f618d071d2edf841b137b2a2bd6" -dependencies = [ - "autocfg", - "cfg-if", - "libc", - "log", - "wepoll-ffi", - "windows-sys 0.42.0", -] - [[package]] name = "portable-atomic" version = "0.3.19" @@ -3182,12 +3027,11 @@ dependencies = [ "serde_json", "serde_test", "similar-asserts", - "smol", - "smol-potat", "smol_str", "snapbox", "test-case", "thiserror", + "tokio", "toml 0.7.2", "toml_edit 0.19.6", "tracing", @@ -3356,44 +3200,6 @@ version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" -[[package]] -name = "smol" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13f2b548cd8447f8de0fdf1c592929f70f4fc7039a05e47404b0d096ec6987a1" -dependencies = [ - "async-channel", - "async-executor", - "async-fs", - "async-io", - "async-lock", - "async-net", - "async-process", - "blocking", - "futures-lite", -] - -[[package]] -name = "smol-potat" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "894ffa61af5c0fab697c8c29b1ab10cb6ec4978a1ccac4a81b5b312df1ffd88e" -dependencies = [ - "async-io", - "smol-potat-macro", -] - -[[package]] -name = "smol-potat-macro" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b7cd8129a18069385b4eadaa81182b1451fab312ad6f58d1d99253082bf3932" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "smol_str" version = "0.1.24" @@ -3431,16 +3237,6 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "485e65c1203eb37244465e857d15a26d3a85a5410648ccb53b18bd44cb3a7336" -[[package]] -name = "socket2" -version = "0.4.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd" -dependencies = [ - "libc", - "winapi", -] - [[package]] name = "sprs" version = "0.7.1" @@ -3656,6 +3452,35 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" +[[package]] +name = "tokio" +version = "1.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03201d01c3c27a29c8a5cee5b55a93ddae1ccf6f08f65365c2c918f8c1b76f64" +dependencies = [ + "autocfg", + "bytes", + "libc", + "memchr", + "mio", + "num_cpus", + "pin-project-lite", + "signal-hook-registry", + "tokio-macros", + "windows-sys 0.45.0", +] + +[[package]] +name = "tokio-macros" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "toml" version = "0.4.10" @@ -3890,12 +3715,6 @@ dependencies = [ "libc", ] -[[package]] -name = "waker-fn" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" - [[package]] name = "walkdir" version = "2.3.2" @@ -3973,15 +3792,6 @@ version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d" -[[package]] -name = "wepoll-ffi" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d743fdedc5c64377b5fc2bc036b01c7fd642205a0d96356034ae3404d49eb7fb" -dependencies = [ - "cc", -] - [[package]] name = "which" version = "4.4.0" diff --git a/Cargo.toml b/Cargo.toml index e76c20bbc..398ffc8bc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -53,13 +53,12 @@ serde = { version = "1.0.156", features = ["serde_derive"] } serde_json = "1.0.94" serde_test = "1.0.147" similar-asserts = { version = "1.4.2", features = ["serde"] } -smol = "1.3.0" -smol-potat = "1.1.2" smol_str = { version = "0.1.23", features = ["serde"] } snapbox = { version = "0.4.8", features = ["cmd", "path"] } tempfile = "3.4.0" test-case = "3.0.0" thiserror = "1.0.39" +tokio = { version = "1.26.0", features = ["macros", "process", "io-util", "rt", "rt-multi-thread", "sync"] } toml = "0.7.1" toml_edit = { version = "0.19.6", features = ["serde"] } tracing = "0.1.37" diff --git a/scarb/Cargo.toml b/scarb/Cargo.toml index f96706e83..85f398aaa 100644 --- a/scarb/Cargo.toml +++ b/scarb/Cargo.toml @@ -47,7 +47,7 @@ petgraph.workspace = true semver.workspace = true serde.workspace = true serde_json.workspace = true -smol.workspace = true +tokio.workspace = true smol_str.workspace = true thiserror.workspace = true toml.workspace = true @@ -69,7 +69,6 @@ ntest.workspace = true predicates.workspace = true serde_test.workspace = true similar-asserts.workspace = true -smol-potat.workspace = true snapbox.workspace = true test-case.workspace = true diff --git a/scarb/src/core/config.rs b/scarb/src/core/config.rs index 9d37487cb..e52985bdf 100644 --- a/scarb/src/core/config.rs +++ b/scarb/src/core/config.rs @@ -7,6 +7,7 @@ use std::{env, mem}; use anyhow::{anyhow, Context, Result}; use camino::{Utf8Path, Utf8PathBuf}; use once_cell::sync::OnceCell; +use tokio::runtime::{Builder, Runtime}; use tracing::trace; use which::which_in; @@ -32,6 +33,7 @@ pub struct Config { log_filter_directive: OsString, offline: bool, compilers: CompilerRepository, + tokio_runtime: OnceCell, } impl Config { @@ -77,6 +79,7 @@ impl Config { log_filter_directive: b.log_filter_directive.unwrap_or_default(), offline: b.offline, compilers, + tokio_runtime: OnceCell::new(), }) } @@ -171,6 +174,11 @@ impl Config { not_static_al } + pub fn tokio_runtime(&self) -> &Runtime { + self.tokio_runtime + .get_or_init(|| Builder::new_multi_thread().enable_all().build().unwrap()) + } + /// States whether the _Offline Mode_ is turned on. /// /// For checking whether Scarb can communicate with the network, prefer to use diff --git a/scarb/src/core/registry/source_map.rs b/scarb/src/core/registry/source_map.rs index 541111ef5..cad81ea15 100644 --- a/scarb/src/core/registry/source_map.rs +++ b/scarb/src/core/registry/source_map.rs @@ -4,7 +4,7 @@ use std::sync::Arc; use anyhow::Result; use async_trait::async_trait; use itertools::Itertools; -use smol::lock::RwLock; +use tokio::sync::RwLock; use tracing::trace; use crate::core::registry::Registry; diff --git a/scarb/src/flock.rs b/scarb/src/flock.rs index 3f4d85001..a12bb17f3 100644 --- a/scarb/src/flock.rs +++ b/scarb/src/flock.rs @@ -6,7 +6,7 @@ use std::{fmt, io}; use anyhow::{Context, Result}; use camino::{Utf8Path, Utf8PathBuf}; use fs4::{lock_contended_error, FileExt}; -use smol::lock::Mutex; +use tokio::sync::Mutex; use crate::core::Config; use crate::internal::asyncx::AwaitSync; @@ -84,8 +84,8 @@ impl<'f> AdvisoryLock<'f> { /// This lock is global per-process and can be acquired recursively. /// An RAII structure is returned to release the lock, and if this process abnormally /// terminates the lock is also released. - pub fn acquire(&self) -> Result { - self.acquire_async().await_sync() + pub fn acquire(&self, config: &Config) -> Result { + self.acquire_async().await_sync(config.tokio_runtime()) } /// Async version of [`Self::acquire`]. diff --git a/scarb/src/internal/async_cache.rs b/scarb/src/internal/async_cache.rs index 6dd299150..28597c719 100644 --- a/scarb/src/internal/async_cache.rs +++ b/scarb/src/internal/async_cache.rs @@ -4,7 +4,7 @@ use std::hash::Hash; use anyhow::Result; use futures::future::{LocalBoxFuture, Shared}; use futures::prelude::*; -use smol::lock::RwLock; +use tokio::sync::RwLock; use crate::internal::cloneable_error::CloneableResult; @@ -58,7 +58,7 @@ mod tests { use super::AsyncCache; - #[smol_potat::test] + #[tokio::test] async fn load() { let cache = AsyncCache::new((), |key: usize, _ctx: ()| { static COUNTER: AtomicU8 = AtomicU8::new(0); @@ -71,7 +71,7 @@ mod tests { assert_eq!(cache.load(2).await.unwrap(), (2, 1)); } - #[smol_potat::test] + #[tokio::test] async fn load_err() { let cache = AsyncCache::new((), |key: usize, _ctx: ()| { static COUNTER: AtomicU8 = AtomicU8::new(0); diff --git a/scarb/src/internal/asyncx.rs b/scarb/src/internal/asyncx.rs index 7ce94f470..765df45ea 100644 --- a/scarb/src/internal/asyncx.rs +++ b/scarb/src/internal/asyncx.rs @@ -1,17 +1,18 @@ use std::future::IntoFuture; +use tokio::runtime::Runtime; pub trait AwaitSync { /// The output that the future will produce on completion. type Output; /// Synchronously await a future by starting a small one-off runtime internally. - fn await_sync(self) -> Self::Output; + fn await_sync(self, runtime: &Runtime) -> Self::Output; } impl AwaitSync for F { type Output = F::Output; - fn await_sync(self) -> Self::Output { - smol::block_on(self.into_future()) + fn await_sync(self, runtime: &Runtime) -> Self::Output { + runtime.block_on(self.into_future()) } } diff --git a/scarb/src/ops/resolve.rs b/scarb/src/ops/resolve.rs index fc1ac6ccc..534bad323 100644 --- a/scarb/src/ops/resolve.rs +++ b/scarb/src/ops/resolve.rs @@ -51,7 +51,7 @@ pub fn resolve_workspace(ws: &Workspace<'_>) -> Result { Ok(WorkspaceResolve { resolve, packages }) } - .await_sync() + .await_sync(ws.config().tokio_runtime()) } #[tracing::instrument(skip_all, level = "debug")] diff --git a/scarb/src/process.rs b/scarb/src/process.rs index 611f83047..35461acfd 100644 --- a/scarb/src/process.rs +++ b/scarb/src/process.rs @@ -1,12 +1,13 @@ use std::ffi::OsStr; -use std::io::{BufRead, BufReader, Read}; +use std::fmt; use std::path::Path; use std::process::{Command, Stdio}; use std::sync::Arc; -use std::{fmt, thread}; use anyhow::{anyhow, bail, Context, Result}; +use tokio::io::{AsyncBufReadExt, AsyncRead, BufReader}; use tracing::{debug, debug_span, warn, Span}; +use tracing_futures::Instrument; use crate::core::Config; use crate::ui::{Spinner, Status}; @@ -49,62 +50,58 @@ pub fn exec_replace(cmd: &mut Command) -> Result<()> { /// Runs the process, waiting for completion, and mapping non-success exit codes to an error. #[tracing::instrument(level = "trace", skip_all)] -pub fn exec(cmd: &mut Command, config: &Config) -> Result<()> { - let cmd_str = shlex_join(cmd); +pub async fn async_exec(cmd: &mut tokio::process::Command, config: &Config) -> Result<()> { + let cmd_str = shlex_join(cmd.as_std()); config.ui().verbose(Status::new("Running", &cmd_str)); let _spinner = config.ui().widget(Spinner::new(cmd_str.clone())); - return thread::scope(move |s| { - let mut proc = cmd - .stdin(Stdio::null()) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .spawn() - .with_context(|| anyhow!("could not execute process: {cmd_str}"))?; - - let span = Arc::new(debug_span!("exec", pid = proc.id())); - let _enter = span.enter(); - debug!("{cmd_str}"); - - let stdout = proc.stdout.take().expect("we asked Rust to pipe stdout"); - s.spawn({ - let span = debug_span!("out"); - move || { - let mut stdout = stdout; - pipe_to_logs(&span, &mut stdout); - } - }); - - let stderr = proc.stderr.take().expect("we asked Rust to pipe stderr"); - s.spawn({ - let span = debug_span!("err"); - move || { - let mut stderr = stderr; - pipe_to_logs(&span, &mut stderr); - } - }); - - let exit_status = proc - .wait() - .with_context(|| anyhow!("could not wait for proces termination: {cmd_str}"))?; - if exit_status.success() { - Ok(()) - } else { - bail!("process did not exit successfully: {exit_status}"); - } - }); - - fn pipe_to_logs(span: &Span, stream: &mut dyn Read) { - let _enter = span.enter(); - let stream = BufReader::with_capacity(128, stream); - for line in stream.lines() { + async fn pipe_to_logs(stream: T) { + let mut reader = BufReader::new(stream).lines(); + loop { + let line = reader.next_line().await; match line { - Ok(line) => debug!("{line}"), + Ok(Some(line)) => debug!("{line}"), + Ok(None) => break, Err(err) => warn!("{err:?}"), } } } + let runtime = config.tokio_runtime(); + let mut proc = cmd + .stdin(Stdio::null()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .with_context(|| anyhow!("could not execute process: {cmd_str}"))?; + + let span = Arc::new(debug_span!("exec", pid = proc.id())); + let _enter = span.enter(); + debug!("{cmd_str}"); + + let stdout = proc.stdout.take().expect("we asked Rust to pipe stdout"); + let span = debug_span!("out"); + runtime.spawn(async move { + let mut stdout = stdout; + pipe_to_logs(&mut stdout).instrument(span).await; + }); + + let stderr = proc.stderr.take().expect("we asked Rust to pipe stderr"); + runtime.spawn(async move { + let span = debug_span!("err"); + let mut stderr = stderr; + pipe_to_logs(&mut stderr).instrument(span).await + }); + + let exit_status = proc + .wait() + .await + .with_context(|| anyhow!("could not wait for proces termination: {cmd_str}"))?; + if exit_status.success() { + Ok(()) + } else { + bail!("process did not exit successfully: {exit_status}"); + } } #[cfg(unix)] diff --git a/scarb/src/resolver/mod.rs b/scarb/src/resolver/mod.rs index 1d21440a2..631b95ebb 100644 --- a/scarb/src/resolver/mod.rs +++ b/scarb/src/resolver/mod.rs @@ -122,6 +122,7 @@ mod tests { use itertools::Itertools; use semver::Version; use similar_asserts::assert_serde_eq; + use tokio::runtime::Builder; use crate::core::package::PackageName; use crate::core::registry::mock::{deps, pkgs, registry, MockRegistry}; @@ -133,6 +134,7 @@ mod tests { roots: &[&[ManifestDependency]], expected: Result<&[PackageId], &str>, ) { + let runtime = Builder::new_multi_thread().build().unwrap(); let root_names = (1..).map(|n| PackageName::new(format!("ROOT_{n}"))); let summaries = roots @@ -150,7 +152,7 @@ mod tests { }) .collect_vec(); - let resolve = super::resolve(&summaries, &mut registry).await_sync(); + let resolve = super::resolve(&summaries, &mut registry).await_sync(&runtime); let resolve = resolve .map(|r| { diff --git a/scarb/src/sources/git/client.rs b/scarb/src/sources/git/client.rs index 43189c804..52e19baae 100644 --- a/scarb/src/sources/git/client.rs +++ b/scarb/src/sources/git/client.rs @@ -8,14 +8,15 @@ //! repositories as source of super important information. use std::fmt; -use std::process::Command; use anyhow::{anyhow, bail, Context, Result}; use camino::Utf8PathBuf; +use tokio::process::Command; +use tokio::sync::{Mutex, MutexGuard}; use crate::core::{Config, GitReference}; use crate::flock::Filesystem; -use crate::process::exec; +use crate::process::async_exec; use crate::ui::Verbosity; use super::canonical_url::CanonicalUrl; @@ -44,7 +45,7 @@ impl fmt::Debug for GitRemote { pub struct GitDatabase { remote: GitRemote, path: Utf8PathBuf, - repo: gix::Repository, + repo: Mutex, } impl fmt::Debug for GitDatabase { @@ -97,7 +98,7 @@ impl GitRemote { } #[tracing::instrument(level = "trace", skip(config))] - pub fn checkout( + pub async fn checkout( &self, fs: &Filesystem<'_>, db: Option, @@ -110,15 +111,16 @@ impl GitRemote { // version of `reference`, so return that database and the rev we resolve to. if let Some(db) = db { db.fetch(self.url.as_str(), reference, config) + .await .with_context(|| format!("failed to fetch into: {fs}"))?; match locked_rev { Some(rev) => { - if db.contains(rev) { + if db.contains(rev).await { return Ok((db, rev)); } } None => { - if let Ok(rev) = db.resolve(reference) { + if let Ok(rev) = db.resolve(reference).await { return Ok((db, rev)); } } @@ -133,10 +135,11 @@ impl GitRemote { } let db = GitDatabase::init_bare(self, fs)?; db.fetch(self.url.as_str(), reference, config) + .await .with_context(|| format!("failed to clone into: {fs}"))?; let rev = match locked_rev { Some(rev) => rev, - None => db.resolve(reference)?, + None => db.resolve(reference).await?, }; Ok((db, rev)) } @@ -151,7 +154,7 @@ impl GitDatabase { Ok(Self { remote: remote.clone(), path: path.to_path_buf(), - repo, + repo: repo.into(), }) } @@ -162,12 +165,16 @@ impl GitDatabase { Ok(Self { remote: remote.clone(), path: path.to_path_buf(), - repo, + repo: repo.into(), }) } + async fn repo(&self) -> MutexGuard<'_, gix::Repository> { + self.repo.lock().await + } + #[tracing::instrument(level = "trace", skip(config))] - fn fetch(&self, url: &str, reference: &GitReference, config: &Config) -> Result<()> { + async fn fetch(&self, url: &str, reference: &GitReference, config: &Config) -> Result<()> { if !config.network_allowed() { bail!("cannot fetch from `{}` in offline mode", self.remote); } @@ -186,29 +193,32 @@ impl GitDatabase { cmd.arg("--update-head-ok"); cmd.arg(url); cmd.args(refspecs); - cmd.current_dir(self.repo.path()); - exec(&mut cmd, config) + cmd.current_dir(self.repo().await.path()); + async_exec(&mut cmd, config).await } - pub fn copy_to( + pub async fn copy_to( &self, fs: &Filesystem<'_>, rev: Rev, config: &Config, ) -> Result> { - let checkout = GitCheckout::clone(self, fs, rev, config)?; - checkout.reset(config)?; + let checkout = GitCheckout::clone(self, fs, rev, config).await?; + checkout.reset(config).await?; Ok(checkout) } - pub fn contains(&self, rev: Rev) -> bool { - self.repo.rev_parse_single(rev.oid.as_bytes()).is_ok() + pub async fn contains(&self, rev: Rev) -> bool { + self.repo() + .await + .rev_parse_single(rev.oid.as_bytes()) + .is_ok() } #[tracing::instrument(level = "trace")] - pub fn resolve(&self, reference: &GitReference) -> Result { + pub async fn resolve(&self, reference: &GitReference) -> Result { use GitReference::*; - let repo = &self.repo; + let repo = self.repo().await; match reference { Tag(t) => Ok(repo .try_find_reference(&format!("refs/remotes/origin/tags/{t}")) @@ -243,15 +253,21 @@ impl GitDatabase { } } - pub fn short_id_of(&self, rev: Rev) -> Result { - let obj = self.repo.find_object(rev.oid)?; + pub async fn short_id_of(&self, rev: Rev) -> Result { + let repo = self.repo().await; + let obj = repo.find_object(rev.oid)?; Ok(obj.id().shorten_or_id().to_string()) } } impl<'d> GitCheckout<'d> { #[tracing::instrument(level = "trace", skip(config))] - fn clone(db: &'d GitDatabase, fs: &Filesystem<'_>, rev: Rev, config: &Config) -> Result { + async fn clone( + db: &'d GitDatabase, + fs: &Filesystem<'_>, + rev: Rev, + config: &Config, + ) -> Result> { unsafe { fs.recreate()?; } @@ -263,20 +279,21 @@ impl<'d> GitCheckout<'d> { with_verbosity_flags(&mut cmd, config); cmd.args(["--config", "core.autocrlf=false"]); cmd.arg("--recurse-submodules"); - cmd.arg(db.repo.path()); + let repo = db.repo().await; + cmd.arg(repo.path()); cmd.arg(&location); - exec(&mut cmd, config)?; + async_exec(&mut cmd, config).await?; Ok(Self { db, location, rev }) } #[tracing::instrument(level = "trace", skip(config))] - fn reset(&self, config: &Config) -> Result<()> { + async fn reset(&self, config: &Config) -> Result<()> { let mut cmd = git_command(); cmd.args(["reset", "--hard"]); cmd.arg(self.rev.to_string()); cmd.current_dir(&self.location); - exec(&mut cmd, config) + async_exec(&mut cmd, config).await } } diff --git a/scarb/src/sources/git/mod.rs b/scarb/src/sources/git/mod.rs index a8872b0f9..0a8aeaa30 100644 --- a/scarb/src/sources/git/mod.rs +++ b/scarb/src/sources/git/mod.rs @@ -1,9 +1,8 @@ -use std::{fmt, mem}; +use std::fmt; use anyhow::{Context, Result}; use async_trait::async_trait; -use smol::lock::OnceCell; -use smol::unblock; +use tokio::sync::OnceCell; use url::Url; use canonical_url::CanonicalUrl; @@ -76,70 +75,56 @@ impl<'c> GitSource<'c> { let remote = self.remote.clone(); let requested_reference = self.requested_reference.clone(); let locked_rev = self.locked_rev; - - // HACK: We know that we will not use &Config outside scope of this function, - // but `smol::unblock` lifetime bounds force us to think so. - let config: &'static Config = unsafe { mem::transmute(self.config) }; - - return unblock(move || inner(source_id, remote, requested_reference, locked_rev, config)) - .await; - - fn inner( - source_id: SourceId, - remote: GitRemote, - requested_reference: GitReference, - locked_rev: Option, - config: &Config, - ) -> Result> { - let remote_ident = remote.ident(); - - let registry_fs = config.dirs().registry_dir(); - let git_fs = registry_fs.child("git"); - let all_db_fs = git_fs.child("db"); - - let db_fs = all_db_fs.child(&format!("{remote_ident}.git")); - let db = GitDatabase::open(&remote, &db_fs).ok(); - let (db, actual_rev) = match (db, locked_rev) { - // If we have a locked revision, and we have a preexisting database - // which has that revision, then no update needs to happen. - (Some(db), Some(rev)) if db.contains(rev) => (db, rev), - - // If Scarb is in offline mode, source is not locked to particular revision, - // and there is a functional database, then try to resolve our reference - // with the preexisting repository. - (Some(db), None) if !config.network_allowed() => { - let rev = db.resolve(&requested_reference).context( - "failed to lookup reference in preexisting repository, and \ + let remote_ident = remote.ident(); + + let registry_fs = self.config.dirs().registry_dir(); + let git_fs = registry_fs.child("git"); + let all_db_fs = git_fs.child("db"); + + let db_fs = all_db_fs.child(&format!("{remote_ident}.git")); + let db = GitDatabase::open(&remote, &db_fs).ok(); + let (db, actual_rev) = match (db, locked_rev) { + // If we have a locked revision, and we have a preexisting database + // which has that revision, then no update needs to happen. + (Some(db), Some(rev)) if db.contains(rev).await => (db, rev), + + // If Scarb is in offline mode, source is not locked to particular revision, + // and there is a functional database, then try to resolve our reference + // with the preexisting repository. + (Some(db), None) if !self.config.network_allowed() => { + let rev = db.resolve(&requested_reference).await.context( + "failed to lookup reference in preexisting repository, and \ cannot check for updates in offline mode (--offline)", - )?; - (db, rev) + )?; + (db, rev) + } + + // Now we can freely update the database. + (db, locked_rev) => { + // The actual error will be produced by `checkout`. + if self.config.network_allowed() { + self.config + .ui() + .print(Status::new("Updating", &format!("git repository {remote}"))); } - // Now we can freely update the database. - (db, locked_rev) => { - // The actual error will be produced by `checkout`. - if config.network_allowed() { - config - .ui() - .print(Status::new("Updating", &format!("git repository {remote}"))); - } - - remote.checkout(&db_fs, db, &requested_reference, locked_rev, config)? - } - }; + remote + .checkout(&db_fs, db, &requested_reference, locked_rev, self.config) + .await? + } + }; - let all_checkouts_fs = git_fs.child("checkouts"); - let db_checkouts_fs = all_checkouts_fs.child(&remote_ident); - let checkout_fs = db_checkouts_fs.child(db.short_id_of(actual_rev)?); - let checkout = db.copy_to(&checkout_fs, actual_rev, config)?; + let all_checkouts_fs = git_fs.child("checkouts"); + let db_checkouts_fs = all_checkouts_fs.child(&remote_ident); + let checkout_fs = db_checkouts_fs.child(db.short_id_of(actual_rev).await?); + let checkout = db.copy_to(&checkout_fs, actual_rev, self.config).await?; - let path_source = PathSource::recursive_at(&checkout.location, source_id, config); + let path_source = PathSource::recursive_at(&checkout.location, source_id, self.config); - Ok(InnerState { - path_source, - actual_rev, - }) - } + Ok(InnerState { + path_source, + actual_rev, + }) } } diff --git a/scarb/src/sources/path.rs b/scarb/src/sources/path.rs index e7b0505dd..a0934f3ba 100644 --- a/scarb/src/sources/path.rs +++ b/scarb/src/sources/path.rs @@ -4,7 +4,7 @@ use std::ops::Deref; use anyhow::{anyhow, Result}; use async_trait::async_trait; use camino::Utf8Path; -use smol::lock::OnceCell; +use tokio::sync::OnceCell; use crate::core::config::Config; use crate::core::manifest::{ManifestDependency, Summary}; diff --git a/scarb/src/sources/standard_lib.rs b/scarb/src/sources/standard_lib.rs index ad57d5b74..2eca18138 100644 --- a/scarb/src/sources/standard_lib.rs +++ b/scarb/src/sources/standard_lib.rs @@ -3,7 +3,7 @@ use std::fmt; use anyhow::{Context, Result}; use async_trait::async_trait; use include_dir::{include_dir, Dir}; -use smol::lock::OnceCell; +use tokio::sync::OnceCell; use tracing::trace; use crate::core::config::Config; diff --git a/scarb/tests/e2e/flock.rs b/scarb/tests/e2e/flock.rs index 1c4cb1684..6a4053f23 100644 --- a/scarb/tests/e2e/flock.rs +++ b/scarb/tests/e2e/flock.rs @@ -87,7 +87,7 @@ fn locking_package_cache() { let config = Scarb::test_config(manifest); thread::scope(|s| { - let lock = config.package_cache_lock().acquire(); + let lock = config.package_cache_lock().acquire(&config); let barrier = Arc::new(Barrier::new(2)); s.spawn({