From bcb208725ed21ab4215c343b7d9ab623fbd716de Mon Sep 17 00:00:00 2001 From: Taylor Thomas Date: Fri, 9 Aug 2024 15:17:23 -0600 Subject: [PATCH] feat(*)!: Adds back support for the offline flag (#328) This adds back in support for offline deps. It required a change to the dependency resolver to error early if offline was requested without a lock file present Signed-off-by: Taylor Thomas --- Cargo.lock | 4 +- Cargo.toml | 2 +- crates/core/src/registry.rs | 113 +++++++++++++++++++-------------- crates/wit/src/commands/add.rs | 7 +- crates/wit/src/lib.rs | 6 +- src/bin/cargo-component.rs | 2 +- src/commands/add.rs | 4 +- src/commands/new.rs | 4 +- src/commands/publish.rs | 2 +- src/commands/update.rs | 2 +- src/config.rs | 3 +- src/registry.rs | 4 +- 12 files changed, 88 insertions(+), 65 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9a1e84d1..7fff07ea 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4527,7 +4527,7 @@ dependencies = [ [[package]] name = "wasm-pkg-client" version = "0.4.1" -source = "git+https://github.com/bytecodealliance/wasm-pkg-tools.git?rev=47ad11a549c23ac48ecee9226d395fc7c6063250#47ad11a549c23ac48ecee9226d395fc7c6063250" +source = "git+https://github.com/bytecodealliance/wasm-pkg-tools.git?rev=c48006aa1bcff1e69f4f8fc6689249b314985ab1#c48006aa1bcff1e69f4f8fc6689249b314985ab1" dependencies = [ "anyhow", "async-trait", @@ -4557,7 +4557,7 @@ dependencies = [ [[package]] name = "wasm-pkg-common" version = "0.4.1" -source = "git+https://github.com/bytecodealliance/wasm-pkg-tools.git?rev=47ad11a549c23ac48ecee9226d395fc7c6063250#47ad11a549c23ac48ecee9226d395fc7c6063250" +source = "git+https://github.com/bytecodealliance/wasm-pkg-tools.git?rev=c48006aa1bcff1e69f4f8fc6689249b314985ab1#c48006aa1bcff1e69f4f8fc6689249b314985ab1" dependencies = [ "anyhow", "bytes", diff --git a/Cargo.toml b/Cargo.toml index 460323d4..47457592 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -107,7 +107,7 @@ warg-protocol = "0.7.0" warg-server = "0.7.0" wasi-preview1-component-adapter-provider = "23.0.1" wasm-metadata = "0.208.1" -wasm-pkg-client = { git = "https://github.com/bytecodealliance/wasm-pkg-tools.git", rev = "47ad11a549c23ac48ecee9226d395fc7c6063250" } +wasm-pkg-client = { git = "https://github.com/bytecodealliance/wasm-pkg-tools.git", rev = "c48006aa1bcff1e69f4f8fc6689249b314985ab1" } wasmparser = "0.208.1" wasmprinter = "0.208.1" wat = "1.208.1" diff --git a/crates/core/src/registry.rs b/crates/core/src/registry.rs index c5684816..70b6c3d7 100644 --- a/crates/core/src/registry.rs +++ b/crates/core/src/registry.rs @@ -451,29 +451,42 @@ pub struct DependencyResolver<'a> { } impl<'a> DependencyResolver<'a> { - /// Creates a new dependency resolver. - pub fn new(config: Config, lock_file: Option>, cache: FileCache) -> Self { - let client = CachingClient::new(Client::new(config), cache); - DependencyResolver { + /// Creates a new dependency resolver. If `config` is `None`, then the resolver will be set to + /// offline mode and a lock file must be given as well. Anything that will require network + /// access will fail in offline mode. + pub fn new( + config: Option, + lock_file: Option>, + cache: FileCache, + ) -> anyhow::Result { + if config.is_none() && lock_file.is_none() { + anyhow::bail!("lock file must be provided when offline mode is enabled"); + } + let client = CachingClient::new(config.map(Client::new), cache); + Ok(DependencyResolver { client: Arc::new(client), lock_file, registries: Default::default(), resolutions: Default::default(), - } + }) } /// Creates a new dependency resolver with the given client. This is useful when you already - /// have a client available + /// have a client available. If the client is set to offline mode, then a lock file must be + /// given or this will error pub fn new_with_client( client: Arc>, lock_file: Option>, - ) -> Self { - DependencyResolver { + ) -> anyhow::Result { + if client.is_readonly() && lock_file.is_none() { + anyhow::bail!("lock file must be provided when offline mode is enabled"); + } + Ok(DependencyResolver { client, lock_file, registries: Default::default(), resolutions: Default::default(), - } + }) } /// Add a dependency to the resolver. @@ -590,45 +603,53 @@ impl<'a> Registry<'a> { // We need to clone a handle to the client because we mutably borrow self below. Might // be worth replacing the mutable borrow with a RwLock down the line. let client = self.client.clone(); - let versions = load_package( - &mut self.packages, - &self.client.client, - dependency.package.clone(), - ) - .await? - .with_context(|| { - format!( - "package `{name}` was not found in component registry `{registry}`", - name = dependency.package - ) - })?; - - let (selected_version, digest) = match &dependency.locked { - Some((version, digest)) => { - // The dependency had a lock file entry, so attempt to do an exact match first - let exact_req = VersionReq { - comparators: vec![Comparator { - op: Op::Exact, - major: version.major, - minor: Some(version.minor), - patch: Some(version.patch), - pre: version.pre.clone(), - }], - }; - - // If an exact match can't be found, fallback to the latest release to satisfy - // the version requirement; this can happen when packages are yanked. If we did - // find an exact match, return the digest for comparison after fetching the - // release - find_latest_release(versions, &exact_req).map(|v| (v, Some(digest))).or_else(|| find_latest_release(versions, dependency.version).map(|v| (v, None))) - } - None => find_latest_release(versions, dependency.version).map(|v| (v, None)), - }.with_context(|| format!("component registry package `{name}` has no release matching version requirement `{version}`", name = dependency.package, version = dependency.version))?; + + let (selected_version, digest) = if client.is_readonly() { + dependency + .locked + .as_ref() + .map(|(ver, digest)| (ver, Some(digest))) + .ok_or_else(|| { + anyhow::anyhow!("Couldn't find locked dependency while in offline mode") + })? + } else { + let versions = + load_package(&mut self.packages, &self.client, dependency.package.clone()) + .await? + .with_context(|| { + format!( + "package `{name}` was not found in component registry `{registry}`", + name = dependency.package + ) + })?; + + match &dependency.locked { + Some((version, digest)) => { + // The dependency had a lock file entry, so attempt to do an exact match first + let exact_req = VersionReq { + comparators: vec![Comparator { + op: Op::Exact, + major: version.major, + minor: Some(version.minor), + patch: Some(version.patch), + pre: version.pre.clone(), + }], + }; + + // If an exact match can't be found, fallback to the latest release to satisfy + // the version requirement; this can happen when packages are yanked. If we did + // find an exact match, return the digest for comparison after fetching the + // release + find_latest_release(versions, &exact_req).map(|v| (&v.version, Some(digest))).or_else(|| find_latest_release(versions, dependency.version).map(|v| (&v.version, None))) + } + None => find_latest_release(versions, dependency.version).map(|v| (&v.version, None)), + }.with_context(|| format!("component registry package `{name}` has no release matching version requirement `{version}`", name = dependency.package, version = dependency.version))? + }; + // We need to clone a handle to the client because we mutably borrow self above. Might // be worth replacing the mutable borrow with a RwLock down the line. let release = client - .client - .get_release(&dependency.package, &selected_version.version) + .get_release(&dependency.package, selected_version) .await?; if let Some(digest) = digest { if &release.content_digest != digest { @@ -662,7 +683,7 @@ impl<'a> Registry<'a> { async fn load_package<'b>( packages: &'b mut HashMap>, - client: &Client, + client: &CachingClient, package: PackageRef, ) -> Result>> { match packages.entry(package) { diff --git a/crates/wit/src/commands/add.rs b/crates/wit/src/commands/add.rs index 86e0144d..3fd1f21d 100644 --- a/crates/wit/src/commands/add.rs +++ b/crates/wit/src/commands/add.rs @@ -14,12 +14,12 @@ use wasm_pkg_client::{caching::FileCache, PackageRef}; use crate::config::{Config, CONFIG_FILE_NAME}; async fn resolve_version( - pkg_config: wasm_pkg_client::Config, + pkg_config: Option, package: &VersionedPackageName, registry: &Option, file_cache: FileCache, ) -> Result { - let mut resolver = DependencyResolver::new(pkg_config, None, file_cache); + let mut resolver = DependencyResolver::new(pkg_config, None, file_cache)?; let dependency = Dependency::Package(RegistryPackage { name: Some(package.name.clone()), version: package @@ -113,7 +113,8 @@ impl AddCommand { } None => { let version = - resolve_version(pkg_config, &self.package, &self.registry, file_cache).await?; + resolve_version(Some(pkg_config), &self.package, &self.registry, file_cache) + .await?; let package = RegistryPackage { name: self.name.is_some().then(|| self.package.name.clone()), diff --git a/crates/wit/src/lib.rs b/crates/wit/src/lib.rs index 76d571c5..e3f1eb9b 100644 --- a/crates/wit/src/lib.rs +++ b/crates/wit/src/lib.rs @@ -47,10 +47,10 @@ async fn resolve_dependencies( .transpose()?; let mut resolver = DependencyResolver::new( - pkg_config, + Some(pkg_config), lock_file.as_ref().map(LockFileResolver::new), file_cache, - ); + )?; for (name, dep) in &config.dependencies { resolver.add_dependency(name, dep).await?; @@ -389,7 +389,7 @@ pub async fn update_lockfile( file_cache: FileCache, ) -> Result<()> { // Resolve all dependencies as if the lock file does not exist - let mut resolver = DependencyResolver::new(pkg_config, None, file_cache); + let mut resolver = DependencyResolver::new(Some(pkg_config), None, file_cache)?; for (name, dep) in &config.dependencies { resolver.add_dependency(name, dep).await?; } diff --git a/src/bin/cargo-component.rs b/src/bin/cargo-component.rs index 7a5e74cf..84a56e66 100644 --- a/src/bin/cargo-component.rs +++ b/src/bin/cargo-component.rs @@ -181,7 +181,7 @@ async fn main() -> Result<()> { } let spawn_args: Vec<_> = std::env::args().skip(1).collect(); - let client = config.client(cache_dir).await?; + let client = config.client(cache_dir, cargo_args.offline).await?; if let Err(e) = run_cargo_command( client, &config, diff --git a/src/commands/add.rs b/src/commands/add.rs index d6b86760..0bcec262 100644 --- a/src/commands/add.rs +++ b/src/commands/add.rs @@ -73,7 +73,7 @@ impl AddCommand { let config = Config::new(self.common.new_terminal(), self.common.config.clone())?; let metadata = load_metadata(self.manifest_path.as_deref())?; - let client = config.client(self.common.cache_dir.clone()).await?; + let client = config.client(self.common.cache_dir.clone(), false).await?; let spec = match &self.spec { Some(spec) => Some(spec.clone()), @@ -130,7 +130,7 @@ impl AddCommand { client: Arc>, name: &PackageRef, ) -> Result { - let mut resolver = DependencyResolver::new_with_client(client, None); + let mut resolver = DependencyResolver::new_with_client(client, None)?; let dependency = Dependency::Package(RegistryPackage { name: Some(self.package.name.clone()), version: self diff --git a/src/commands/new.rs b/src/commands/new.rs index 9f3c784a..114a990e 100644 --- a/src/commands/new.rs +++ b/src/commands/new.rs @@ -158,7 +158,7 @@ impl NewCommand { Some(s) => Some(format!("{s}@{version}", version = VersionReq::STAR).parse()?), None => None, }; - let client = config.client(self.common.cache_dir.clone()).await?; + let client = config.client(self.common.cache_dir.clone(), false).await?; let target = self.resolve_target(client, target).await?; let source = self.generate_source(&target).await?; @@ -531,7 +531,7 @@ world example {{ package, world, }) => { - let mut resolver = DependencyResolver::new_with_client(client, None); + let mut resolver = DependencyResolver::new_with_client(client, None)?; let dependency = Dependency::Package(package); resolver.add_dependency(&name, &dependency).await?; diff --git a/src/commands/publish.rs b/src/commands/publish.rs index 4c8b8841..6f97cd58 100644 --- a/src/commands/publish.rs +++ b/src/commands/publish.rs @@ -83,7 +83,7 @@ impl PublishCommand { log::debug!("executing publish command"); let config = Config::new(self.common.new_terminal(), self.common.config.clone())?; - let client = config.client(self.common.cache_dir.clone()).await?; + let client = config.client(self.common.cache_dir.clone(), false).await?; if let Some(target) = &self.target { if !is_wasm_target(target) { diff --git a/src/commands/update.rs b/src/commands/update.rs index a4fdf834..a03ffe2d 100644 --- a/src/commands/update.rs +++ b/src/commands/update.rs @@ -43,7 +43,7 @@ impl UpdateCommand { let packages = load_component_metadata(&metadata, [].iter(), true)?; let lock_update_allowed = !self.frozen && !self.locked; - let client = config.client(self.common.cache_dir).await?; + let client = config.client(self.common.cache_dir, false).await?; crate::update_lockfile( client, &config, diff --git a/src/config.rs b/src/config.rs index 6743b5f1..7da45950 100644 --- a/src/config.rs +++ b/src/config.rs @@ -532,9 +532,10 @@ impl Config { pub async fn client( &self, cache: Option, + offline: bool, ) -> anyhow::Result>> { Ok(Arc::new(CachingClient::new( - Client::new(self.pkg_config.clone()), + (!offline).then(|| Client::new(self.pkg_config.clone())), FileCache::new(cache_dir(cache)?).await?, ))) } diff --git a/src/registry.rs b/src/registry.rs index fe69a894..e65c21d4 100644 --- a/src/registry.rs +++ b/src/registry.rs @@ -60,7 +60,7 @@ impl<'a> PackageDependencyResolution<'a> { return Ok(Default::default()); } - let mut resolver = DependencyResolver::new_with_client(client, lock_file); + let mut resolver = DependencyResolver::new_with_client(client, lock_file)?; for (name, dependency) in target_deps.iter() { resolver.add_dependency(name, dependency).await?; @@ -78,7 +78,7 @@ impl<'a> PackageDependencyResolution<'a> { return Ok(Default::default()); } - let mut resolver = DependencyResolver::new_with_client(client, lock_file); + let mut resolver = DependencyResolver::new_with_client(client, lock_file)?; for (name, dependency) in &metadata.section.dependencies { resolver.add_dependency(name, dependency).await?;