From e3e8c7158143ef5e9131d349c9e834daeece9e06 Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Thu, 24 Oct 2024 16:13:54 -0700 Subject: [PATCH 01/19] Update APIs. --- crates/pdk-api/src/api/mod.rs | 29 +++++++++++++++++++++-------- package/src/api-types.ts | 17 +++++++++-------- registry/data/third-party.json | 6 +++--- registry/schema.json | 4 +++- 4 files changed, 36 insertions(+), 20 deletions(-) diff --git a/crates/pdk-api/src/api/mod.rs b/crates/pdk-api/src/api/mod.rs index babd6ff89..3325996bc 100644 --- a/crates/pdk-api/src/api/mod.rs +++ b/crates/pdk-api/src/api/mod.rs @@ -284,13 +284,6 @@ api_struct!( pub struct ExecutableConfig { /// The file to execute, relative from the tool directory. /// Does *not* support virtual paths. - /// - /// The following scenarios are powered by this field: - /// - Is the primary executable. - /// - For primary and secondary bins, the source file to be symlinked, - /// and the extension to use for the symlink file itself. - /// - For primary shim, this field is ignored. - /// - For secondary shims, the file to execute. #[serde(skip_serializing_if = "Option::is_none")] pub exe_path: Option, @@ -311,6 +304,10 @@ api_struct!( #[serde(skip_serializing_if = "Option::is_none")] pub parent_exe_name: Option, + /// Whether this is the primary executable or not. + #[serde(skip_serializing_if = "is_false")] + pub primary: bool, + /// Custom args to prepend to user-provided args within the generated shim. #[serde(skip_serializing_if = "Option::is_none")] pub shim_before_args: Option, @@ -333,6 +330,14 @@ impl ExecutableConfig { } } + pub fn new_primary>(exe_path: T) -> Self { + Self { + exe_path: Some(PathBuf::from(exe_path.as_ref())), + primary: true, + ..ExecutableConfig::default() + } + } + pub fn with_parent, P: AsRef>(exe_path: T, parent_exe: P) -> Self { Self { exe_path: Some(PathBuf::from(exe_path.as_ref())), @@ -346,8 +351,14 @@ api_struct!( /// Output returned by the `locate_executables` function. #[serde(default)] pub struct LocateExecutablesOutput { + /// Configures executable information to be used as proto bins/shims. + /// The map key will be the name of the executable file. + #[serde(skip_serializing_if = "FxHashMap::is_empty")] + pub exes: FxHashMap, + /// Relative directory path from the tool install directory in which - /// pre-installed executables can be located. + /// pre-installed executables can be located. This directory path + /// will be used during `proto active`, but not for bins/shims. #[serde(skip_serializing_if = "Option::is_none")] pub exes_dir: Option, @@ -363,11 +374,13 @@ api_struct!( /// Configures the primary/default executable to create. /// If not provided, a primary shim and binary will *not* be created. + #[deprecated(note = "Use `exes` instead.")] #[serde(skip_serializing_if = "Option::is_none")] pub primary: Option, /// Configures secondary/additional executables to create. /// The map key is the name of the shim/binary file. + #[deprecated(note = "Use `exes` instead.")] #[serde(skip_serializing_if = "FxHashMap::is_empty")] pub secondary: FxHashMap, } diff --git a/package/src/api-types.ts b/package/src/api-types.ts index 13244afa3..7cd43d680 100644 --- a/package/src/api-types.ts +++ b/package/src/api-types.ts @@ -264,13 +264,6 @@ export interface ExecutableConfig { /** * The file to execute, relative from the tool directory. * Does *not* support virtual paths. - * - * The following scenarios are powered by this field: - * - Is the primary executable. - * - For primary and secondary bins, the source file to be symlinked, - * and the extension to use for the symlink file itself. - * - For primary shim, this field is ignored. - * - For secondary shims, the file to execute. */ exePath?: string | null; /** Do not symlink a binary in `~/.proto/bin`. */ @@ -279,6 +272,8 @@ export interface ExecutableConfig { noShim?: boolean; /** The parent executable name required to execute the local executable path. */ parentExeName?: string | null; + /** Whether this is the primary executable or not. */ + primary?: boolean; /** Custom args to append to user-provided args within the generated shim. */ shimAfterArgs?: StringOrVec | null; /** Custom args to prepend to user-provided args within the generated shim. */ @@ -289,9 +284,15 @@ export interface ExecutableConfig { /** Output returned by the `locate_executables` function. */ export interface LocateExecutablesOutput { + /** + * Configures executable information to be used as proto bins/shims. + * The map key will be the name of the executable file. + */ + exes?: Record; /** * Relative directory path from the tool install directory in which - * pre-installed executables can be located. + * pre-installed executables can be located. This directory path + * will be used during `proto active`, but not for bins/shims. */ exesDir?: string | null; /** diff --git a/registry/data/third-party.json b/registry/data/third-party.json index 914be5446..15b036001 100644 --- a/registry/data/third-party.json +++ b/registry/data/third-party.json @@ -689,7 +689,7 @@ "name": "openfga", "description": "OpenFGA is an open-source authorization solution that allows developers to build granular access control.", "author": "crashdump", - "homepageUrl": "https://openfga.dev", + "homepageUrl": "https://openfga.dev/", "repositoryUrl": "https://github.com/crashdump/proto-tools", "bins": [ "openfga" @@ -826,14 +826,14 @@ "sops" ] }, - { + { "id": "staticcheck", "locator": "https://raw.githubusercontent.com/crashdump/proto-tools/main/toml-plugins/staticcheck/plugin.toml", "format": "toml", "name": "staticcheck", "description": "Staticcheck is a state of the art linter for the Go programming language. Using static analysis, it finds bugs and performance issues, offers simplifications, and enforces style rules.", "author": "crashdump", - "homepageUrl": "https://staticcheck.dev", + "homepageUrl": "https://staticcheck.dev/", "repositoryUrl": "https://github.com/crashdump/proto-tools", "bins": [ "staticcheck" diff --git a/registry/schema.json b/registry/schema.json index ec95aa23c..7b6319f92 100644 --- a/registry/schema.json +++ b/registry/schema.json @@ -188,8 +188,10 @@ "description": "Format of the plugin.", "type": "string", "enum": [ + "json", "toml", - "wasm" + "wasm", + "yaml" ] }, "PluginLocator": { From 7087b206a8ae56adc1faee42c6a035b85c532d38 Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Thu, 24 Oct 2024 16:24:27 -0700 Subject: [PATCH 02/19] Rework exe configs. --- crates/cli/src/commands/bin.rs | 4 +- crates/cli/src/commands/plugin/info.rs | 4 +- crates/core/src/flow/link.rs | 2 +- crates/core/src/flow/locate.rs | 99 +++++++++++++++++++------- 4 files changed, 78 insertions(+), 31 deletions(-) diff --git a/crates/cli/src/commands/bin.rs b/crates/cli/src/commands/bin.rs index 5ba29d642..7c3e53f28 100644 --- a/crates/cli/src/commands/bin.rs +++ b/crates/cli/src/commands/bin.rs @@ -41,7 +41,7 @@ pub async fn bin(session: ProtoSession, args: BinArgs) -> AppResult { tool.symlink_bins(true).await?; for bin in tool.resolve_bin_locations().await? { - if bin.primary { + if bin.config.primary { println!("{}", bin.path.display()); return Ok(None); } @@ -52,7 +52,7 @@ pub async fn bin(session: ProtoSession, args: BinArgs) -> AppResult { tool.generate_shims(true).await?; for shim in tool.resolve_shim_locations().await? { - if shim.primary { + if shim.config.primary { println!("{}", shim.path.display()); return Ok(None); } diff --git a/crates/cli/src/commands/plugin/info.rs b/crates/cli/src/commands/plugin/info.rs index ac37b7e49..6a528ee8e 100644 --- a/crates/cli/src/commands/plugin/info.rs +++ b/crates/cli/src/commands/plugin/info.rs @@ -128,7 +128,7 @@ pub async fn info(session: ProtoSession, args: InfoPluginArgs) -> AppResult { format!( "{} {}", color::path(bin.path), - if bin.primary { + if bin.config.primary { color::muted_light("(primary)") } else { "".into() @@ -144,7 +144,7 @@ pub async fn info(session: ProtoSession, args: InfoPluginArgs) -> AppResult { format!( "{} {}", color::path(shim.path), - if shim.primary { + if shim.config.primary { format_value("(primary)") } else { "".into() diff --git a/crates/core/src/flow/link.rs b/crates/core/src/flow/link.rs index 47f7335e6..54cab13f8 100644 --- a/crates/core/src/flow/link.rs +++ b/crates/core/src/flow/link.rs @@ -61,7 +61,7 @@ impl Tool { shim_entry.env_vars.extend(env_vars); } - if !shim.primary { + if !shim.config.primary { shim_entry.parent = Some(self.id.to_string()); // Only use --alt when the secondary executable exists diff --git a/crates/core/src/flow/locate.rs b/crates/core/src/flow/locate.rs index 62c517bc7..38367eb48 100644 --- a/crates/core/src/flow/locate.rs +++ b/crates/core/src/flow/locate.rs @@ -17,7 +17,6 @@ pub struct ExecutableLocation { pub config: ExecutableConfig, pub name: String, pub path: PathBuf, - pub primary: bool, } impl Tool { @@ -36,13 +35,27 @@ impl Tool { pub async fn resolve_primary_exe_location(&self) -> miette::Result> { let output = self.call_locate_executables().await?; - if let Some(primary) = output.primary { + for (name, config) in output.exes { + if config.primary { + if let Some(exe_path) = &config.exe_path { + return Ok(Some(ExecutableLocation { + path: self.get_product_dir().join(exe_path), + name, + config, + })); + } + } + } + + #[allow(deprecated)] + if let Some(mut primary) = output.primary { if let Some(exe_path) = &primary.exe_path { + primary.primary = true; + return Ok(Some(ExecutableLocation { path: self.get_product_dir().join(exe_path), name: self.id.to_string(), config: primary, - primary: true, })); } } @@ -55,17 +68,33 @@ impl Tool { let output = self.call_locate_executables().await?; let mut locations = vec![]; - for (name, secondary) in output.secondary { - if let Some(exe_path) = &secondary.exe_path { + for (name, config) in output.exes { + if config.primary { + continue; + } + + if let Some(exe_path) = &config.exe_path { locations.push(ExecutableLocation { path: self.get_product_dir().join(exe_path), name, - config: secondary, - primary: false, + config, }); } } + if locations.is_empty() { + #[allow(deprecated)] + for (name, secondary) in output.secondary { + if let Some(exe_path) = &secondary.exe_path { + locations.push(ExecutableLocation { + path: self.get_product_dir().join(exe_path), + name, + config: secondary, + }); + } + } + } + Ok(locations) } @@ -76,7 +105,7 @@ impl Tool { let output = self.call_locate_executables().await?; let mut locations = vec![]; - let mut add = |name: &str, config: ExecutableConfig, primary: bool| { + let mut add = |name: String, config: ExecutableConfig| { if !config.no_bin && config .exe_link_path @@ -85,20 +114,29 @@ impl Tool { .is_some() { locations.push(ExecutableLocation { - path: self.proto.store.bin_dir.join(get_exe_file_name(name)), - name: name.to_owned(), + path: self.proto.store.bin_dir.join(get_exe_file_name(&name)), + name, config, - primary, }); } }; - if let Some(primary) = output.primary { - add(&self.id, primary, true); - } + if output.exes.is_empty() { + #[allow(deprecated)] + if let Some(mut primary) = output.primary { + primary.primary = true; - for (name, secondary) in output.secondary { - add(&name, secondary, false); + add(self.id.to_string(), primary); + } + + #[allow(deprecated)] + for (name, secondary) in output.secondary { + add(name, secondary); + } + } else { + for (name, config) in output.exes { + add(name, config); + } } Ok(locations) @@ -111,23 +149,32 @@ impl Tool { let output = self.call_locate_executables().await?; let mut locations = vec![]; - let mut add = |name: &str, config: ExecutableConfig, primary: bool| { + let mut add = |name: String, config: ExecutableConfig| { if !config.no_shim { locations.push(ExecutableLocation { - path: self.proto.store.shims_dir.join(get_shim_file_name(name)), - name: name.to_owned(), - config: config.clone(), - primary, + path: self.proto.store.shims_dir.join(get_shim_file_name(&name)), + name, + config, }); } }; - if let Some(primary) = output.primary { - add(&self.id, primary, true); - } + if output.exes.is_empty() { + #[allow(deprecated)] + if let Some(mut primary) = output.primary { + primary.primary = true; + + add(self.id.to_string(), primary); + } - for (name, secondary) in output.secondary { - add(&name, secondary, false); + #[allow(deprecated)] + for (name, secondary) in output.secondary { + add(name, secondary); + } + } else { + for (name, config) in output.exes { + add(name, config); + } } Ok(locations) From 7a3a419225d514e66af1defd7678203a97955bfb Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Thu, 24 Oct 2024 17:32:20 -0700 Subject: [PATCH 03/19] Update linking. --- crates/cli/src/commands/clean.rs | 1 + crates/cli/src/commands/install.rs | 2 +- crates/cli/src/commands/pin.rs | 9 +-------- crates/cli/src/commands/regen.rs | 19 +++++++++---------- crates/core/src/flow/link.rs | 2 -- crates/core/src/flow/locate.rs | 11 +++++++++-- crates/core/src/flow/setup.rs | 8 ++------ crates/core/src/layout/store.rs | 1 + crates/warpgate/src/helpers.rs | 10 +++------- 9 files changed, 27 insertions(+), 36 deletions(-) diff --git a/crates/cli/src/commands/clean.rs b/crates/cli/src/commands/clean.rs index 08301d8ec..eacbfa75a 100644 --- a/crates/cli/src/commands/clean.rs +++ b/crates/cli/src/commands/clean.rs @@ -251,6 +251,7 @@ pub async fn purge_tool(session: &ProtoSession, id: &Id, yes: bool) -> miette::R fs::remove_dir_all(inventory_dir)?; // Delete binaries + // TODO for bin in tool.resolve_bin_locations().await? { session.env.store.unlink_bin(&bin.path)?; } diff --git a/crates/cli/src/commands/install.rs b/crates/cli/src/commands/install.rs index 4ed3e211e..c72dd1b21 100644 --- a/crates/cli/src/commands/install.rs +++ b/crates/cli/src/commands/install.rs @@ -105,7 +105,7 @@ async fn pin_version( } if pin { - internal_pin(tool, &spec, pin_type, true).await?; + internal_pin(tool, &spec, pin_type).await?; } Ok(pin) diff --git a/crates/cli/src/commands/pin.rs b/crates/cli/src/commands/pin.rs index 0deddf426..cc9aa0e75 100644 --- a/crates/cli/src/commands/pin.rs +++ b/crates/cli/src/commands/pin.rs @@ -30,13 +30,7 @@ pub async fn internal_pin( tool: &mut Tool, spec: &UnresolvedVersionSpec, pin: PinType, - link: bool, ) -> miette::Result { - // Create symlink to this new version - if pin == PinType::Global && link { - tool.symlink_bins(true).await?; - } - let config_path = ProtoConfig::update(tool.proto.get_config_dir(pin), |config| { config .versions @@ -64,8 +58,7 @@ pub async fn pin(session: ProtoSession, args: PinArgs) -> AppResult { args.spec.clone() }; - let config_path = - internal_pin(&mut tool, &spec, map_pin_type(args.global, args.to), false).await?; + let config_path = internal_pin(&mut tool, &spec, map_pin_type(args.global, args.to)).await?; println!( "Pinned {} to {} in {}", diff --git a/crates/cli/src/commands/regen.rs b/crates/cli/src/commands/regen.rs index 77d7bbd7b..11cd5cfd8 100644 --- a/crates/cli/src/commands/regen.rs +++ b/crates/cli/src/commands/regen.rs @@ -48,27 +48,26 @@ pub async fn regen(session: ProtoSession, args: RegenArgs) -> AppResult { debug!("Loading tools"); let config = session.env.load_config()?; - let global_config = session.env.load_config_manager()?.get_global_config()?; for mut tool in session.load_tools().await? { - // Shims + // Shims - Create once for the configured version. if let Some(version) = config.versions.get(&tool.id) { debug!("Regenerating {} shim", tool.get_name()); tool.resolve_version(version, true).await?; - tool.generate_shims(true).await?; + tool.generate_shims(false).await?; } - // Bins - // Symlinks are only based on the globally pinned versions, - // so we must reference that config instead of the merged one! + // Bins - Create for each installed version. if args.bin { - if let Some(version) = global_config.versions.get(&tool.id) { - debug!("Relinking {} bin", tool.get_name()); + debug!("Relinking {} bin", tool.get_name()); + + for version in tool.inventory.manifest.installed_versions.clone() { + let version = version.to_unresolved_spec(); tool.version = None; - tool.resolve_version(version, true).await?; - tool.symlink_bins(true).await?; + tool.resolve_version(&version, true).await?; + tool.symlink_bins(false).await?; } } } diff --git a/crates/core/src/flow/link.rs b/crates/core/src/flow/link.rs index 54cab13f8..caebdf840 100644 --- a/crates/core/src/flow/link.rs +++ b/crates/core/src/flow/link.rs @@ -35,8 +35,6 @@ impl Tool { } let mut registry: ShimsMap = BTreeMap::default(); - registry.insert(self.id.to_string(), Shim::default()); - let mut to_create = vec![]; for shim in shims { diff --git a/crates/core/src/flow/locate.rs b/crates/core/src/flow/locate.rs index 38367eb48..5d4fa2583 100644 --- a/crates/core/src/flow/locate.rs +++ b/crates/core/src/flow/locate.rs @@ -103,6 +103,7 @@ impl Tool { /// to the binaries final location. pub async fn resolve_bin_locations(&self) -> miette::Result> { let output = self.call_locate_executables().await?; + let version = self.get_resolved_version(); let mut locations = vec![]; let mut add = |name: String, config: ExecutableConfig| { @@ -113,9 +114,15 @@ impl Tool { .or(config.exe_path.as_ref()) .is_some() { + let versioned_name = format!("{name}-{version}"); + locations.push(ExecutableLocation { - path: self.proto.store.bin_dir.join(get_exe_file_name(&name)), - name, + path: self + .proto + .store + .bin_dir + .join(get_exe_file_name(&versioned_name)), + name: versioned_name, config, }); } diff --git a/crates/core/src/flow/setup.rs b/crates/core/src/flow/setup.rs index f2c79ed79..0b54a134f 100644 --- a/crates/core/src/flow/setup.rs +++ b/crates/core/src/flow/setup.rs @@ -103,7 +103,6 @@ impl Tool { } let version = self.get_resolved_version(); - let mut removed_default_version = false; // Remove version from manifest let manifest = &mut self.inventory.manifest; @@ -118,17 +117,14 @@ impl Tool { debug!("Unpinning global version"); versions.remove(&self.id); - removed_default_version = true; } } })?; // If no more default version, delete the symlink, // otherwise the OS will throw errors for missing sources - if removed_default_version || self.inventory.manifest.installed_versions.is_empty() { - for bin in self.resolve_bin_locations().await? { - self.proto.store.unlink_bin(&bin.path)?; - } + for bin in self.resolve_bin_locations().await? { + self.proto.store.unlink_bin(&bin.path)?; } // If no more versions in general, delete all shims diff --git a/crates/core/src/layout/store.rs b/crates/core/src/layout/store.rs index f928a3f0a..58f8b5e0d 100644 --- a/crates/core/src/layout/store.rs +++ b/crates/core/src/layout/store.rs @@ -153,6 +153,7 @@ impl fmt::Debug for Store { f.debug_struct("Store") .field("dir", &self.dir) .field("bin_dir", &self.bin_dir) + .field("cache_dir", &self.cache_dir) .field("inventory_dir", &self.inventory_dir) .field("plugins_dir", &self.plugins_dir) .field("shims_dir", &self.shims_dir) diff --git a/crates/warpgate/src/helpers.rs b/crates/warpgate/src/helpers.rs index ef46d8beb..a8242d36b 100644 --- a/crates/warpgate/src/helpers.rs +++ b/crates/warpgate/src/helpers.rs @@ -20,13 +20,9 @@ pub fn create_cache_key(url: &str, seed: Option<&str>) -> String { } pub fn determine_cache_extension(value: &str) -> Option<&str> { - for ext in [".toml", ".json", ".jsonc", ".yaml", ".yml", ".wasm", ".txt"] { - if value.ends_with(ext) { - return Some(ext); - } - } - - None + [".toml", ".json", ".jsonc", ".yaml", ".yml", ".wasm", ".txt"] + .into_iter() + .find(|ext| value.ends_with(ext)) } pub async fn download_from_url_to_file( From 1db9a4542b00b8c340120b884a4cca965ab091b5 Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Thu, 24 Oct 2024 20:00:26 -0700 Subject: [PATCH 04/19] Include all versions. --- crates/cli/src/commands/bin.rs | 2 +- crates/cli/src/commands/clean.rs | 3 +- crates/cli/src/commands/plugin/info.rs | 30 ++++++++---------- crates/core/src/flow/link.rs | 2 +- crates/core/src/flow/locate.rs | 42 ++++++++++++++++++-------- crates/core/src/flow/setup.rs | 18 ++++++----- 6 files changed, 57 insertions(+), 40 deletions(-) diff --git a/crates/cli/src/commands/bin.rs b/crates/cli/src/commands/bin.rs index 7c3e53f28..57f9c5663 100644 --- a/crates/cli/src/commands/bin.rs +++ b/crates/cli/src/commands/bin.rs @@ -40,7 +40,7 @@ pub async fn bin(session: ProtoSession, args: BinArgs) -> AppResult { if args.bin { tool.symlink_bins(true).await?; - for bin in tool.resolve_bin_locations().await? { + for bin in tool.resolve_bin_locations(false).await? { if bin.config.primary { println!("{}", bin.path.display()); return Ok(None); diff --git a/crates/cli/src/commands/clean.rs b/crates/cli/src/commands/clean.rs index eacbfa75a..83f91815a 100644 --- a/crates/cli/src/commands/clean.rs +++ b/crates/cli/src/commands/clean.rs @@ -251,8 +251,7 @@ pub async fn purge_tool(session: &ProtoSession, id: &Id, yes: bool) -> miette::R fs::remove_dir_all(inventory_dir)?; // Delete binaries - // TODO - for bin in tool.resolve_bin_locations().await? { + for bin in tool.resolve_bin_locations(true).await? { session.env.store.unlink_bin(&bin.path)?; } diff --git a/crates/cli/src/commands/plugin/info.rs b/crates/cli/src/commands/plugin/info.rs index 6a528ee8e..d3deb5fff 100644 --- a/crates/cli/src/commands/plugin/info.rs +++ b/crates/cli/src/commands/plugin/info.rs @@ -49,7 +49,7 @@ pub async fn info(session: ProtoSession, args: InfoPluginArgs) -> AppResult { let mut config = session.env.load_config()?.to_owned(); let tool_config = config.tools.remove(&tool.id).unwrap_or_default(); - let bins = tool.resolve_bin_locations().await?; + let bins = tool.resolve_bin_locations(true).await?; let shims = tool.resolve_shim_locations().await?; if args.json { @@ -106,6 +106,11 @@ pub async fn info(session: ProtoSession, args: InfoPluginArgs) -> AppResult { printer.named_section("Inventory", |p| { p.entry("Store", color::path(tool.get_inventory_dir())); + p.entry( + "Detected version", + color::symbol(tool.get_resolved_version().to_string()), + ); + p.entry("Executable", color::path(exe_file)); if let Some(dir) = exes_dir { @@ -122,22 +127,6 @@ pub async fn info(session: ProtoSession, args: InfoPluginArgs) -> AppResult { Some(color::failure("None")), ); - p.entry_list( - "Binaries", - bins.into_iter().map(|bin| { - format!( - "{} {}", - color::path(bin.path), - if bin.config.primary { - color::muted_light("(primary)") - } else { - "".into() - } - ) - }), - Some(color::failure("None")), - ); - p.entry_list( "Shims", shims.into_iter().map(|shim| { @@ -154,6 +143,13 @@ pub async fn info(session: ProtoSession, args: InfoPluginArgs) -> AppResult { Some(color::failure("None")), ); + p.entry_list( + "Binaries", + bins.into_iter() + .map(|bin| format!("{}", color::path(bin.path))), + Some(color::failure("None")), + ); + let mut versions = tool .inventory .manifest diff --git a/crates/core/src/flow/link.rs b/crates/core/src/flow/link.rs index caebdf840..2847c9de6 100644 --- a/crates/core/src/flow/link.rs +++ b/crates/core/src/flow/link.rs @@ -101,7 +101,7 @@ impl Tool { /// Symlink all primary and secondary binaries for the current tool. #[instrument(skip(self))] pub async fn symlink_bins(&mut self, force: bool) -> miette::Result<()> { - let bins = self.resolve_bin_locations().await?; + let bins = self.resolve_bin_locations(false).await?; if bins.is_empty() { return Ok(()); diff --git a/crates/core/src/flow/locate.rs b/crates/core/src/flow/locate.rs index 5d4fa2583..58e255ffd 100644 --- a/crates/core/src/flow/locate.rs +++ b/crates/core/src/flow/locate.rs @@ -101,9 +101,23 @@ impl Tool { /// Return a list of all binaries that get created in `~/.proto/bin`. /// The list will contain the executable config, and an absolute path /// to the binaries final location. - pub async fn resolve_bin_locations(&self) -> miette::Result> { + pub async fn resolve_bin_locations( + &self, + include_all_versions: bool, + ) -> miette::Result> { let output = self.call_locate_executables().await?; - let version = self.get_resolved_version(); + let versions = if include_all_versions { + self.inventory + .manifest + .installed_versions + .iter() + .collect::>() + } else if let Some(version) = &self.version { + vec![version] + } else { + vec![] + }; + let mut locations = vec![]; let mut add = |name: String, config: ExecutableConfig| { @@ -114,17 +128,19 @@ impl Tool { .or(config.exe_path.as_ref()) .is_some() { - let versioned_name = format!("{name}-{version}"); + for version in &versions { + let versioned_name = format!("{name}-{version}"); - locations.push(ExecutableLocation { - path: self - .proto - .store - .bin_dir - .join(get_exe_file_name(&versioned_name)), - name: versioned_name, - config, - }); + locations.push(ExecutableLocation { + path: self + .proto + .store + .bin_dir + .join(get_exe_file_name(&versioned_name)), + name: versioned_name, + config: config.clone(), + }); + } } }; @@ -146,6 +162,8 @@ impl Tool { } } + locations.sort_by(|a, d| a.path.cmp(&d.path)); + Ok(locations) } diff --git a/crates/core/src/flow/setup.rs b/crates/core/src/flow/setup.rs index 0b54a134f..f8d5a55d8 100644 --- a/crates/core/src/flow/setup.rs +++ b/crates/core/src/flow/setup.rs @@ -121,18 +121,22 @@ impl Tool { } })?; - // If no more default version, delete the symlink, - // otherwise the OS will throw errors for missing sources - for bin in self.resolve_bin_locations().await? { - self.proto.store.unlink_bin(&bin.path)?; - } - - // If no more versions in general, delete all shims + // If no more versions in general, delete all if self.inventory.manifest.installed_versions.is_empty() { + for bin in self.resolve_bin_locations(true).await? { + self.proto.store.unlink_bin(&bin.path)?; + } + for shim in self.resolve_shim_locations().await? { self.proto.store.remove_shim(&shim.path)?; } } + // Otherwise, delete bins for this specific version + else { + for bin in self.resolve_bin_locations(false).await? { + self.proto.store.unlink_bin(&bin.path)?; + } + } Ok(true) } From 2ac7f3eee24c97b17528c174f85272c55cbac49f Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Fri, 25 Oct 2024 15:09:57 -0700 Subject: [PATCH 05/19] Polish. --- crates/cli/src/commands/bin.rs | 2 +- crates/cli/src/commands/plugin/info.rs | 3 +- crates/core/src/flow/link.rs | 12 ++++++- crates/core/src/flow/locate.rs | 36 +++++++++++-------- crates/core/src/tool_manifest.rs | 48 ++++++++++++++++++++++++++ 5 files changed, 83 insertions(+), 18 deletions(-) diff --git a/crates/cli/src/commands/bin.rs b/crates/cli/src/commands/bin.rs index 57f9c5663..8e40d1af1 100644 --- a/crates/cli/src/commands/bin.rs +++ b/crates/cli/src/commands/bin.rs @@ -40,7 +40,7 @@ pub async fn bin(session: ProtoSession, args: BinArgs) -> AppResult { if args.bin { tool.symlink_bins(true).await?; - for bin in tool.resolve_bin_locations(false).await? { + for bin in tool.resolve_bin_locations(false).await?.iter().rev() { if bin.config.primary { println!("{}", bin.path.display()); return Ok(None); diff --git a/crates/cli/src/commands/plugin/info.rs b/crates/cli/src/commands/plugin/info.rs index d3deb5fff..2491f3ae0 100644 --- a/crates/cli/src/commands/plugin/info.rs +++ b/crates/cli/src/commands/plugin/info.rs @@ -145,8 +145,7 @@ pub async fn info(session: ProtoSession, args: InfoPluginArgs) -> AppResult { p.entry_list( "Binaries", - bins.into_iter() - .map(|bin| format!("{}", color::path(bin.path))), + bins.into_iter().map(|bin| color::path(bin.path)), Some(color::failure("None")), ); diff --git a/crates/core/src/flow/link.rs b/crates/core/src/flow/link.rs index 2847c9de6..b716446e4 100644 --- a/crates/core/src/flow/link.rs +++ b/crates/core/src/flow/link.rs @@ -115,10 +115,19 @@ impl Tool { ); } - let tool_dir = self.get_product_dir(); let mut to_create = vec![]; for bin in bins { + let Some(bin_version) = bin.version else { + continue; + }; + + // Create a new product since we need to change the version for each bin + let tool_dir = self + .inventory + .create_product(&VersionSpec::Semantic(SemVer(bin_version))) + .dir; + let input_path = tool_dir.join( bin.config .exe_link_path @@ -126,6 +135,7 @@ impl Tool { .or(bin.config.exe_path.as_ref()) .unwrap(), ); + let output_path = bin.path; if !input_path.exists() { diff --git a/crates/core/src/flow/locate.rs b/crates/core/src/flow/locate.rs index 58e255ffd..dbd73d1fa 100644 --- a/crates/core/src/flow/locate.rs +++ b/crates/core/src/flow/locate.rs @@ -3,6 +3,7 @@ use crate::helpers::ENV_VAR; use crate::tool::Tool; use proto_pdk_api::{ExecutableConfig, LocateExecutablesInput, LocateExecutablesOutput}; use proto_shim::{get_exe_file_name, get_shim_file_name}; +use semver::Version; use serde::Serialize; use starbase_utils::fs; use std::env; @@ -17,6 +18,9 @@ pub struct ExecutableLocation { pub config: ExecutableConfig, pub name: String, pub path: PathBuf, + + #[serde(skip_serializing_if = "Option::is_none")] + pub version: Option, } impl Tool { @@ -42,6 +46,7 @@ impl Tool { path: self.get_product_dir().join(exe_path), name, config, + version: None, })); } } @@ -56,6 +61,7 @@ impl Tool { path: self.get_product_dir().join(exe_path), name: self.id.to_string(), config: primary, + version: None, })); } } @@ -78,6 +84,7 @@ impl Tool { path: self.get_product_dir().join(exe_path), name, config, + version: None, }); } } @@ -90,6 +97,7 @@ impl Tool { path: self.get_product_dir().join(exe_path), name, config: secondary, + version: None, }); } } @@ -106,17 +114,15 @@ impl Tool { include_all_versions: bool, ) -> miette::Result> { let output = self.call_locate_executables().await?; - let versions = if include_all_versions { - self.inventory - .manifest - .installed_versions - .iter() - .collect::>() - } else if let Some(version) = &self.version { - vec![version] - } else { - vec![] - }; + let resolved_version = self.get_resolved_version(); + let versions = self + .inventory + .manifest + .get_bucketed_versions(if include_all_versions { + None + } else { + resolved_version.as_version() + }); let mut locations = vec![]; @@ -128,8 +134,8 @@ impl Tool { .or(config.exe_path.as_ref()) .is_some() { - for version in &versions { - let versioned_name = format!("{name}-{version}"); + for (bucket_version, resolved_version) in &versions { + let versioned_name = format!("{name}-{bucket_version}"); locations.push(ExecutableLocation { path: self @@ -139,6 +145,7 @@ impl Tool { .join(get_exe_file_name(&versioned_name)), name: versioned_name, config: config.clone(), + version: Some(resolved_version.to_owned()), }); } } @@ -162,7 +169,7 @@ impl Tool { } } - locations.sort_by(|a, d| a.path.cmp(&d.path)); + locations.sort_by(|a, d| a.name.cmp(&d.name)); Ok(locations) } @@ -180,6 +187,7 @@ impl Tool { path: self.proto.store.shims_dir.join(get_shim_file_name(&name)), name, config, + version: None, }); } }; diff --git a/crates/core/src/tool_manifest.rs b/crates/core/src/tool_manifest.rs index bc43e0d9c..a5e7f0205 100644 --- a/crates/core/src/tool_manifest.rs +++ b/crates/core/src/tool_manifest.rs @@ -1,5 +1,6 @@ use crate::helpers::{now, read_json_file_with_lock, write_json_file_with_lock}; use rustc_hash::{FxHashMap, FxHashSet}; +use semver::Version; use serde::{Deserialize, Serialize}; use starbase_utils::env::bool_var; use std::{ @@ -68,4 +69,51 @@ impl ToolManifest { Ok(()) } + + pub fn get_bucketed_versions( + &self, + focused_version: Option<&Version>, + ) -> FxHashMap { + let mut versions = FxHashMap::default(); + + let get_keys = |version: &Version| -> Vec { + vec![ + format!("{}", version.major), + format!("{}.{}", version.major, version.minor), + ] + }; + + let mut add = |version: &Version| { + for bucket_key in get_keys(version) { + if let Some(bucket_value) = versions.get_mut(&bucket_key) { + // Always use the highest patch version + if version > bucket_value { + *bucket_value = version.to_owned(); + } + } else { + versions.insert(bucket_key.clone(), version.to_owned()); + } + } + }; + + for spec in &self.installed_versions { + if let Some(version) = spec.as_version() { + add(version); + } + } + + // If we have a focused version, add it to the bucketed map, + // and then filter down the map to the key with the same matching range. + // This may result in a different patch version then the patch in the + // focused version if there is an installed version with a higher patch. + if let Some(version) = focused_version { + add(version); + + let bucket_keys = get_keys(version); + + versions.retain(|key, _| bucket_keys.contains(key)); + } + + versions + } } From 5567cbff57e27b55d0eea89dd2dc000af06507c7 Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Fri, 25 Oct 2024 16:46:43 -0700 Subject: [PATCH 06/19] Add fallback version. --- crates/cli/src/commands/bin.rs | 2 +- crates/core/src/flow/locate.rs | 6 +++++- crates/core/src/tool_manifest.rs | 1 + 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/crates/cli/src/commands/bin.rs b/crates/cli/src/commands/bin.rs index 8e40d1af1..57f9c5663 100644 --- a/crates/cli/src/commands/bin.rs +++ b/crates/cli/src/commands/bin.rs @@ -40,7 +40,7 @@ pub async fn bin(session: ProtoSession, args: BinArgs) -> AppResult { if args.bin { tool.symlink_bins(true).await?; - for bin in tool.resolve_bin_locations(false).await?.iter().rev() { + for bin in tool.resolve_bin_locations(false).await? { if bin.config.primary { println!("{}", bin.path.display()); return Ok(None); diff --git a/crates/core/src/flow/locate.rs b/crates/core/src/flow/locate.rs index dbd73d1fa..e05fb4cfe 100644 --- a/crates/core/src/flow/locate.rs +++ b/crates/core/src/flow/locate.rs @@ -135,7 +135,11 @@ impl Tool { .is_some() { for (bucket_version, resolved_version) in &versions { - let versioned_name = format!("{name}-{bucket_version}"); + let versioned_name = if bucket_version == "*" { + name.clone() + } else { + format!("{name}-{bucket_version}") + }; locations.push(ExecutableLocation { path: self diff --git a/crates/core/src/tool_manifest.rs b/crates/core/src/tool_manifest.rs index a5e7f0205..c5cf1bc4f 100644 --- a/crates/core/src/tool_manifest.rs +++ b/crates/core/src/tool_manifest.rs @@ -78,6 +78,7 @@ impl ToolManifest { let get_keys = |version: &Version| -> Vec { vec![ + "*".to_string(), format!("{}", version.major), format!("{}.{}", version.major, version.minor), ] From 799be7fee1a0755370beeb433f024c53c1fbfd1b Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Fri, 25 Oct 2024 17:08:34 -0700 Subject: [PATCH 07/19] Update tests. --- crates/cli/src/commands/regen.rs | 5 +- crates/cli/tests/clean_test.rs | 12 +++- crates/cli/tests/install_uninstall_test.rs | 72 ++++++---------------- crates/cli/tests/regen_test.rs | 20 ++---- crates/core/src/flow/setup.rs | 2 +- 5 files changed, 35 insertions(+), 76 deletions(-) diff --git a/crates/cli/src/commands/regen.rs b/crates/cli/src/commands/regen.rs index 11cd5cfd8..d6c9630ab 100644 --- a/crates/cli/src/commands/regen.rs +++ b/crates/cli/src/commands/regen.rs @@ -50,11 +50,10 @@ pub async fn regen(session: ProtoSession, args: RegenArgs) -> AppResult { let config = session.env.load_config()?; for mut tool in session.load_tools().await? { - // Shims - Create once for the configured version. - if let Some(version) = config.versions.get(&tool.id) { + // Shims - Create once if has a configured version. + if config.versions.contains_key(&tool.id) { debug!("Regenerating {} shim", tool.get_name()); - tool.resolve_version(version, true).await?; tool.generate_shims(false).await?; } diff --git a/crates/cli/tests/clean_test.rs b/crates/cli/tests/clean_test.rs index 82a590314..fefaf2d19 100644 --- a/crates/cli/tests/clean_test.rs +++ b/crates/cli/tests/clean_test.rs @@ -42,7 +42,11 @@ mod clean { #[test] fn purges_tool_bin() { let sandbox = create_empty_proto_sandbox(); - sandbox.create_file(".proto/tools/node/fake/file", ""); + sandbox.create_file(".proto/tools/node/1.2.3/fake/file", ""); + sandbox.create_file( + ".proto/tools/node/manifest.json", + r#"{ "installed_versions": ["1.2.3"] }"#, + ); sandbox.create_file(".proto/bin/other", ""); let bin = sandbox.path().join(".proto").join(if cfg!(windows) { @@ -52,7 +56,11 @@ mod clean { }); #[allow(deprecated)] - std::fs::soft_link(sandbox.path().join(".proto/tools/node/fake/file"), &bin).unwrap(); + std::fs::soft_link( + sandbox.path().join(".proto/tools/node/1.2.3/fake/file"), + &bin, + ) + .unwrap(); sandbox .run_bin(|cmd| { diff --git a/crates/cli/tests/install_uninstall_test.rs b/crates/cli/tests/install_uninstall_test.rs index 4644dfbdc..daf8ef0e3 100644 --- a/crates/cli/tests/install_uninstall_test.rs +++ b/crates/cli/tests/install_uninstall_test.rs @@ -602,7 +602,7 @@ mod install_uninstall { #[cfg(not(windows))] #[test] - fn symlinks_bin_when_pinning() { + fn symlinks_bins() { let sandbox = create_empty_proto_sandbox(); sandbox @@ -610,48 +610,28 @@ mod install_uninstall { cmd.arg("install") .arg("node") .arg("19.0.0") - .arg("--pin") .arg("--") .arg("--no-bundled-npm"); }) .success(); - let link = sandbox.path().join(".proto/bin").join("node"); + let link1 = sandbox.path().join(".proto/bin/node"); + let link2 = sandbox.path().join(".proto/bin/node-19"); + let link3 = sandbox.path().join(".proto/bin/node-19.0"); + let src = sandbox.path().join(".proto/tools/node/19.0.0/bin/node"); - assert!(link.exists()); + assert!(link1.exists()); + assert!(link2.exists()); + assert!(link3.exists()); - assert_eq!( - std::fs::read_link(link).unwrap(), - sandbox - .path() - .join(".proto/tools/node/19.0.0") - .join("bin/node") - ); - } - - #[cfg(not(windows))] - #[test] - fn symlinks_bin_on_first_install_without_pinning() { - let sandbox = create_empty_proto_sandbox(); - - sandbox - .run_bin(|cmd| { - cmd.arg("install") - .arg("node") - .arg("19.0.0") - .arg("--") - .arg("--no-bundled-npm"); - }) - .success(); - - let link = sandbox.path().join(".proto/bin").join("node"); - - assert!(link.exists()); + assert_eq!(std::fs::read_link(link1).unwrap(), src); + assert_eq!(std::fs::read_link(link2).unwrap(), src); + assert_eq!(std::fs::read_link(link3).unwrap(), src); } #[cfg(windows)] #[test] - fn creates_bin_when_pinning() { + fn creates_bins() { let sandbox = create_empty_proto_sandbox(); sandbox @@ -665,29 +645,13 @@ mod install_uninstall { }) .success(); - let link = sandbox.path().join(".proto/bin").join("node.exe"); - - assert!(link.exists()); - } - - #[cfg(windows)] - #[test] - fn creates_bin_on_first_install_without_pinning() { - let sandbox = create_empty_proto_sandbox(); - - sandbox - .run_bin(|cmd| { - cmd.arg("install") - .arg("node") - .arg("19.0.0") - .arg("--") - .arg("--no-bundled-npm"); - }) - .success(); - - let link = sandbox.path().join(".proto/bin").join("node.exe"); + let link1 = sandbox.path().join(".proto/bin/node.exe"); + let link2 = sandbox.path().join(".proto/bin/node-19.exe"); + let link3 = sandbox.path().join(".proto/bin/node-19.0.exe"); - assert!(link.exists()); + assert!(link1.exists()); + assert!(link2.exists()); + assert!(link3.exists()); } } } diff --git a/crates/cli/tests/regen_test.rs b/crates/cli/tests/regen_test.rs index 3e3de8511..5aa798275 100644 --- a/crates/cli/tests/regen_test.rs +++ b/crates/cli/tests/regen_test.rs @@ -9,6 +9,7 @@ fn install_node(sandbox: &Sandbox) { .run_bin(|cmd| { cmd.arg("install") .arg("node") + .arg("20.0.0") .arg("--pin") .arg("--") .arg("--no-bundled-npm"); @@ -148,22 +149,7 @@ mod regen_bin { } #[test] - fn doesnt_link_nonglobal_tools() { - let sandbox = create_empty_proto_sandbox(); - - sandbox.create_file(".prototools", r#"node = "20.0.0""#); - - sandbox - .run_bin(|cmd| { - cmd.arg("regen").arg("--bin"); - }) - .success(); - - assert!(!get_bin_path(sandbox.path(), "node").exists()); - } - - #[test] - fn links_global_tools() { + fn links_tool_with_bucketed_versions() { let sandbox = create_empty_proto_sandbox(); install_node(&sandbox); @@ -175,5 +161,7 @@ mod regen_bin { .success(); assert!(get_bin_path(sandbox.path(), "node").exists()); + assert!(get_bin_path(sandbox.path(), "node-20").exists()); + assert!(get_bin_path(sandbox.path(), "node-20.0").exists()); } } diff --git a/crates/core/src/flow/setup.rs b/crates/core/src/flow/setup.rs index f8d5a55d8..08bdc3c2c 100644 --- a/crates/core/src/flow/setup.rs +++ b/crates/core/src/flow/setup.rs @@ -59,7 +59,7 @@ impl Tool { } self.generate_shims(false).await?; - self.symlink_bins(false).await?; + self.symlink_bins(true).await?; self.cleanup().await?; let version = self.get_resolved_version(); From 23ff5d43c5c599de91b8d9d1f288bd94f97500f2 Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Fri, 25 Oct 2024 17:39:34 -0700 Subject: [PATCH 08/19] Fix clean test. --- CHANGELOG.md | 13 +++++++++++++ crates/cli/tests/clean_test.rs | 27 +++++++++++++++------------ 2 files changed, 28 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d7bb0e278..d8aa0340d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,10 +12,23 @@ ## Unreleased +#### 💥 Breaking + +- Each tool's primary executable file name is no longer based on the plugin's identifier, and is now based on what's configured in the new `LocateExecutablesOutput.exes` setting. +- We've reworked how the `~/.proto/bin` directory works. Instead of only symlinking globally pinned versions, we now create a symlink for every tool executable, and every major + minor version installed within that tool. For example, when we install `node`, we may have the following: + - `~/.proto/bin/node` - Points to the highest installed version. + - `~/.proto/bin/node-` - Points to the highest version within that major range (`~major`). Is created for each separate major version, for example: `node-20`, `node-22`. + - `~/.proto/bin/node-.` - Points to the highest version within that major + minor range (`~major.minor`). Is created for each separate major + minor version, for example: `node-20.1`, `node-22.4`. +- WASM API + - Deprecated `LocateExecutablesOutput.primary` and `LocateExecutablesOutput.secondary` (use `exes` instead). + #### 🚀 Updates - Added support for JSON and YAML based configurations for non-WASM schema based plugins. This is an alternative to TOML, but supports all the same settings. - We now cache all text-based HTTP requests made from WASM plugins for 12 hours. This should greatly reduce the overhead cost of making requests, and will help for situations where an internet connection is lost. +- WASM API + - Added `ExecutableConfig.primary`. + - Added `LocateExecutablesOutput.exes`. ## 0.41.7 diff --git a/crates/cli/tests/clean_test.rs b/crates/cli/tests/clean_test.rs index fefaf2d19..a1f9bddf0 100644 --- a/crates/cli/tests/clean_test.rs +++ b/crates/cli/tests/clean_test.rs @@ -49,18 +49,17 @@ mod clean { ); sandbox.create_file(".proto/bin/other", ""); - let bin = sandbox.path().join(".proto").join(if cfg!(windows) { - "bin/node.exe" - } else { - "bin/node" - }); + let bin1 = sandbox.path().join(".proto/bin/node"); + let bin2 = sandbox.path().join(".proto/bin/node-1"); + let bin3 = sandbox.path().join(".proto/bin/node-1.2"); + let src = sandbox.path().join(".proto/tools/node/1.2.3/fake/file"); #[allow(deprecated)] - std::fs::soft_link( - sandbox.path().join(".proto/tools/node/1.2.3/fake/file"), - &bin, - ) - .unwrap(); + std::fs::soft_link(&src, &bin1).unwrap(); + #[allow(deprecated)] + std::fs::soft_link(&src, &bin2).unwrap(); + #[allow(deprecated)] + std::fs::soft_link(&src, &bin3).unwrap(); sandbox .run_bin(|cmd| { @@ -68,8 +67,12 @@ mod clean { }) .success(); - assert!(!bin.exists()); - assert!(bin.symlink_metadata().is_err()); + assert!(!bin1.exists()); + assert!(bin1.symlink_metadata().is_err()); + assert!(!bin2.exists()); + assert!(bin2.symlink_metadata().is_err()); + assert!(!bin3.exists()); + assert!(bin3.symlink_metadata().is_err()); } #[test] From 0436d9e528d36a776fc3bf481c689bae17e7a965 Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Fri, 25 Oct 2024 17:48:50 -0700 Subject: [PATCH 09/19] Rework teardown. --- crates/core/src/flow/setup.rs | 44 +++++++++++++++++++++-------------- 1 file changed, 26 insertions(+), 18 deletions(-) diff --git a/crates/core/src/flow/setup.rs b/crates/core/src/flow/setup.rs index 08bdc3c2c..a655a877b 100644 --- a/crates/core/src/flow/setup.rs +++ b/crates/core/src/flow/setup.rs @@ -103,26 +103,15 @@ impl Tool { } let version = self.get_resolved_version(); - - // Remove version from manifest - let manifest = &mut self.inventory.manifest; - manifest.installed_versions.remove(&version); - manifest.versions.remove(&version); - manifest.save()?; - - // Unpin global version if a match - ProtoConfig::update(self.proto.get_config_dir(PinType::Global), |config| { - if let Some(versions) = &mut config.versions { - if versions.get(&self.id).is_some_and(|v| v == &version) { - debug!("Unpinning global version"); - - versions.remove(&self.id); - } - } - })?; + let is_last_version = self.inventory.manifest.installed_versions.len() == 1 + && self + .inventory + .manifest + .installed_versions + .contains(&version); // If no more versions in general, delete all - if self.inventory.manifest.installed_versions.is_empty() { + if is_last_version { for bin in self.resolve_bin_locations(true).await? { self.proto.store.unlink_bin(&bin.path)?; } @@ -138,6 +127,25 @@ impl Tool { } } + // Unpin global version if a match + ProtoConfig::update(self.proto.get_config_dir(PinType::Global), |config| { + if let Some(versions) = &mut config.versions { + if versions.get(&self.id).is_some_and(|v| v == &version) { + debug!("Unpinning global version"); + + versions.remove(&self.id); + } + } + })?; + + // Remove version from manifest + // We must do this last because the location resolves above + // require `installed_versions` to have values! + let manifest = &mut self.inventory.manifest; + manifest.installed_versions.remove(&version); + manifest.versions.remove(&version); + manifest.save()?; + Ok(true) } From b1cfa6ea66468203e64c15cf0e7aa2a6b469029b Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Fri, 25 Oct 2024 20:38:06 -0700 Subject: [PATCH 10/19] Add bin manager. --- crates/core/src/flow/locate.rs | 34 ++++++--- crates/core/src/flow/setup.rs | 19 +++-- crates/core/src/layout/bin_manager.rs | 102 ++++++++++++++++++++++++++ crates/core/src/layout/mod.rs | 2 + crates/pdk-test-utils/src/macros.rs | 2 +- 5 files changed, 142 insertions(+), 17 deletions(-) create mode 100644 crates/core/src/layout/bin_manager.rs diff --git a/crates/core/src/flow/locate.rs b/crates/core/src/flow/locate.rs index e05fb4cfe..31a288b26 100644 --- a/crates/core/src/flow/locate.rs +++ b/crates/core/src/flow/locate.rs @@ -1,5 +1,6 @@ use crate::error::ProtoError; use crate::helpers::ENV_VAR; +use crate::layout::BinManager; use crate::tool::Tool; use proto_pdk_api::{ExecutableConfig, LocateExecutablesInput, LocateExecutablesOutput}; use proto_shim::{get_exe_file_name, get_shim_file_name}; @@ -112,20 +113,31 @@ impl Tool { pub async fn resolve_bin_locations( &self, include_all_versions: bool, + ) -> miette::Result> { + self.resolve_bin_locations_with_manager( + BinManager::from_manifest(&self.inventory.manifest), + include_all_versions, + ) + .await + } + + pub async fn resolve_bin_locations_with_manager( + &self, + bin_manager: BinManager, + include_all_versions: bool, ) -> miette::Result> { let output = self.call_locate_executables().await?; let resolved_version = self.get_resolved_version(); - let versions = self - .inventory - .manifest - .get_bucketed_versions(if include_all_versions { - None - } else { - resolved_version.as_version() - }); - let mut locations = vec![]; + let versions = if include_all_versions { + bin_manager.get_buckets() + } else if let Some(version) = resolved_version.as_version() { + bin_manager.get_buckets_focused_to_version(version) + } else { + return Ok(locations); + }; + let mut add = |name: String, config: ExecutableConfig| { if !config.no_bin && config @@ -135,7 +147,7 @@ impl Tool { .is_some() { for (bucket_version, resolved_version) in &versions { - let versioned_name = if bucket_version == "*" { + let versioned_name = if *bucket_version == "*" { name.clone() } else { format!("{name}-{bucket_version}") @@ -149,7 +161,7 @@ impl Tool { .join(get_exe_file_name(&versioned_name)), name: versioned_name, config: config.clone(), - version: Some(resolved_version.to_owned()), + version: Some((*resolved_version).to_owned()), }); } } diff --git a/crates/core/src/flow/setup.rs b/crates/core/src/flow/setup.rs index a655a877b..5dd70e40d 100644 --- a/crates/core/src/flow/setup.rs +++ b/crates/core/src/flow/setup.rs @@ -1,4 +1,5 @@ use crate::flow::install::InstallOptions; +use crate::layout::BinManager; use crate::proto_config::{PinType, ProtoConfig}; use crate::tool::Tool; use crate::tool_manifest::ToolManifestVersion; @@ -103,7 +104,9 @@ impl Tool { } let version = self.get_resolved_version(); - let is_last_version = self.inventory.manifest.installed_versions.len() == 1 + let mut bin_manager = BinManager::from_manifest(&self.inventory.manifest); + + let is_last_installed_version = self.inventory.manifest.installed_versions.len() == 1 && self .inventory .manifest @@ -111,8 +114,11 @@ impl Tool { .contains(&version); // If no more versions in general, delete all - if is_last_version { - for bin in self.resolve_bin_locations(true).await? { + if is_last_installed_version { + for bin in self + .resolve_bin_locations_with_manager(bin_manager, true) + .await? + { self.proto.store.unlink_bin(&bin.path)?; } @@ -121,8 +127,11 @@ impl Tool { } } // Otherwise, delete bins for this specific version - else { - for bin in self.resolve_bin_locations(false).await? { + else if bin_manager.remove_version_from_spec(&version) { + for bin in self + .resolve_bin_locations_with_manager(bin_manager, false) + .await? + { self.proto.store.unlink_bin(&bin.path)?; } } diff --git a/crates/core/src/layout/bin_manager.rs b/crates/core/src/layout/bin_manager.rs new file mode 100644 index 000000000..7088a4706 --- /dev/null +++ b/crates/core/src/layout/bin_manager.rs @@ -0,0 +1,102 @@ +use crate::tool_manifest::ToolManifest; +use rustc_hash::{FxHashMap, FxHashSet}; +use semver::Version; +use std::mem; +use version_spec::VersionSpec; + +#[derive(Debug, Default)] +pub struct BinManager { + buckets: FxHashMap, + versions: FxHashSet, +} + +impl BinManager { + pub fn from_manifest(manifest: &ToolManifest) -> Self { + let mut manager = Self::default(); + + for spec in &manifest.installed_versions { + if let Some(version) = spec.as_version() { + manager.add_version(version); + } + } + + manager + } + + pub fn get_buckets(&self) -> FxHashMap<&String, &Version> { + self.buckets.iter().collect() + } + + pub fn get_buckets_focused_to_version( + &self, + version: &Version, + ) -> FxHashMap<&String, &Version> { + let bucket_keys = self.get_keys(version); + + self.buckets + .iter() + .filter(|(key, _)| bucket_keys.contains(key)) + .collect() + } + + pub fn add_version(&mut self, version: &Version) { + for bucket_key in self.get_keys(version) { + if let Some(bucket_value) = self.buckets.get_mut(&bucket_key) { + // Always use the highest patch version + if version > bucket_value { + *bucket_value = version.to_owned(); + } + } else { + self.buckets.insert(bucket_key.clone(), version.to_owned()); + } + } + + self.versions.insert(version.to_owned()); + } + + pub fn rebuild_buckets(&mut self) { + self.buckets.clear(); + + for version in mem::take(&mut self.versions) { + self.add_version(&version); + } + } + + pub fn remove_version(&mut self, version: &Version) -> bool { + let mut rebuild = false; + + for bucket_key in self.get_keys(version) { + if self + .buckets + .get(&bucket_key) + .is_some_and(|bucket_value| bucket_value == version) + { + rebuild = true; + } + } + + self.versions.remove(version); + + if rebuild { + self.rebuild_buckets(); + } + + rebuild + } + + pub fn remove_version_from_spec(&mut self, spec: &VersionSpec) -> bool { + if let Some(version) = spec.as_version() { + return self.remove_version(version); + } + + false + } + + fn get_keys(&self, version: &Version) -> Vec { + vec![ + "*".to_string(), + format!("{}", version.major), + format!("{}.{}", version.major, version.minor), + ] + } +} diff --git a/crates/core/src/layout/mod.rs b/crates/core/src/layout/mod.rs index 699bb6811..30c111960 100644 --- a/crates/core/src/layout/mod.rs +++ b/crates/core/src/layout/mod.rs @@ -1,7 +1,9 @@ +mod bin_manager; mod inventory; mod product; mod store; +pub use bin_manager::*; pub use inventory::*; pub use product::*; pub use store::*; diff --git a/crates/pdk-test-utils/src/macros.rs b/crates/pdk-test-utils/src/macros.rs index cf1ca11c6..b759a6fa1 100644 --- a/crates/pdk-test-utils/src/macros.rs +++ b/crates/pdk-test-utils/src/macros.rs @@ -34,7 +34,7 @@ macro_rules! generate_download_install_tests { plugin.tool.locate_exe_file().await.unwrap(); // Check things exist - for bin in plugin.tool.resolve_bin_locations().await.unwrap() { + for bin in plugin.tool.resolve_bin_locations(true).await.unwrap() { assert!(bin.path.exists()); } From 9c9ff0933192ecc9e5cd21cf9b290521f7cfa6bd Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Fri, 25 Oct 2024 20:40:36 -0700 Subject: [PATCH 11/19] Move shims registry. --- crates/core/src/flow/link.rs | 4 ++-- crates/core/src/layout/mod.rs | 2 ++ crates/core/src/{ => layout}/shim_registry.rs | 6 +++--- crates/core/src/lib.rs | 1 - 4 files changed, 7 insertions(+), 6 deletions(-) rename crates/core/src/{ => layout}/shim_registry.rs (88%) diff --git a/crates/core/src/flow/link.rs b/crates/core/src/flow/link.rs index b716446e4..e6fcd8729 100644 --- a/crates/core/src/flow/link.rs +++ b/crates/core/src/flow/link.rs @@ -1,4 +1,4 @@ -use crate::shim_registry::{Shim, ShimRegistry, ShimsMap}; +use crate::layout::{Shim, ShimRegistry, ShimsMap}; use crate::tool::Tool; use miette::IntoDiagnostic; use proto_pdk_api::*; @@ -92,7 +92,7 @@ impl Tool { ); } - ShimRegistry::update(&self.proto, registry)?; + ShimRegistry::update(&self.proto.store.shims_dir, registry)?; } Ok(()) diff --git a/crates/core/src/layout/mod.rs b/crates/core/src/layout/mod.rs index 30c111960..be293dc8e 100644 --- a/crates/core/src/layout/mod.rs +++ b/crates/core/src/layout/mod.rs @@ -1,9 +1,11 @@ mod bin_manager; mod inventory; mod product; +mod shim_registry; mod store; pub use bin_manager::*; pub use inventory::*; pub use product::*; +pub use shim_registry::*; pub use store::*; diff --git a/crates/core/src/shim_registry.rs b/crates/core/src/layout/shim_registry.rs similarity index 88% rename from crates/core/src/shim_registry.rs rename to crates/core/src/layout/shim_registry.rs index d89d6fac9..06a459d05 100644 --- a/crates/core/src/shim_registry.rs +++ b/crates/core/src/layout/shim_registry.rs @@ -1,8 +1,8 @@ use crate::helpers::{read_json_file_with_lock, write_json_file_with_lock}; -use crate::proto::ProtoEnvironment; use rustc_hash::FxHashMap; use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; +use std::path::Path; #[derive(Default, Deserialize, PartialEq, Serialize)] #[serde(default)] @@ -28,12 +28,12 @@ pub type ShimsMap = BTreeMap; pub struct ShimRegistry; impl ShimRegistry { - pub fn update>(proto: P, entries: ShimsMap) -> miette::Result<()> { + pub fn update(shims_dir: &Path, entries: ShimsMap) -> miette::Result<()> { if entries.is_empty() { return Ok(()); } - let file = proto.as_ref().store.shims_dir.join("registry.json"); + let file = shims_dir.join("registry.json"); let mut config: ShimsMap = if file.exists() { read_json_file_with_lock(&file)? diff --git a/crates/core/src/lib.rs b/crates/core/src/lib.rs index 8118e41dd..509941f3b 100644 --- a/crates/core/src/lib.rs +++ b/crates/core/src/lib.rs @@ -6,7 +6,6 @@ pub mod layout; mod proto; mod proto_config; pub mod registry; -mod shim_registry; mod tool; mod tool_loader; mod tool_manifest; From 532d74e6080a14ab2f0c3b2f0a77c0239af212e2 Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Fri, 25 Oct 2024 20:51:36 -0700 Subject: [PATCH 12/19] Add tests. --- crates/core/tests/bin_manager_test.rs | 119 ++++++++++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 crates/core/tests/bin_manager_test.rs diff --git a/crates/core/tests/bin_manager_test.rs b/crates/core/tests/bin_manager_test.rs new file mode 100644 index 000000000..c61e0c049 --- /dev/null +++ b/crates/core/tests/bin_manager_test.rs @@ -0,0 +1,119 @@ +use proto_core::layout::BinManager; +use rustc_hash::FxHashMap; +use semver::Version; + +mod bin_manager { + use super::*; + + #[test] + fn creates_buckets_per_version() { + let v1 = Version::new(1, 2, 3); + let v2 = Version::new(4, 5, 6); + + let mut bins = BinManager::default(); + bins.add_version(&v1); + bins.add_version(&v2); + + assert_eq!( + bins.get_buckets(), + FxHashMap::from_iter([ + (&"*".to_string(), &v2), + (&"1".to_string(), &v1), + (&"1.2".to_string(), &v1), + (&"4".to_string(), &v2), + (&"4.5".to_string(), &v2), + ]) + ); + } + + #[test] + fn highest_replaces() { + let v1 = Version::new(1, 2, 3); + let v2 = Version::new(1, 3, 4); + + let mut bins = BinManager::default(); + bins.add_version(&v1); + + assert_eq!( + bins.get_buckets(), + FxHashMap::from_iter([ + (&"*".to_string(), &v1), + (&"1".to_string(), &v1), + (&"1.2".to_string(), &v1), + ]) + ); + + bins.add_version(&v2); + + assert_eq!( + bins.get_buckets(), + FxHashMap::from_iter([ + (&"*".to_string(), &v2), + (&"1".to_string(), &v2), + (&"1.2".to_string(), &v1), + (&"1.3".to_string(), &v2), + ]) + ); + } + + #[test] + fn lowest_doesnt_replace() { + let v1 = Version::new(1, 2, 3); + let v2 = Version::new(1, 1, 4); + + let mut bins = BinManager::default(); + bins.add_version(&v1); + + assert_eq!( + bins.get_buckets(), + FxHashMap::from_iter([ + (&"*".to_string(), &v1), + (&"1".to_string(), &v1), + (&"1.2".to_string(), &v1), + ]) + ); + + bins.add_version(&v2); + + assert_eq!( + bins.get_buckets(), + FxHashMap::from_iter([ + (&"*".to_string(), &v1), + (&"1".to_string(), &v1), + (&"1.1".to_string(), &v2), + (&"1.2".to_string(), &v1), + ]) + ); + } + + #[test] + fn removing_rebuilds_buckets() { + let v1 = Version::new(1, 2, 3); + let v2 = Version::new(1, 3, 4); + + let mut bins = BinManager::default(); + bins.add_version(&v1); + bins.add_version(&v2); + + assert_eq!( + bins.get_buckets(), + FxHashMap::from_iter([ + (&"*".to_string(), &v2), + (&"1".to_string(), &v2), + (&"1.2".to_string(), &v1), + (&"1.3".to_string(), &v2), + ]) + ); + + bins.remove_version(&v2); + + assert_eq!( + bins.get_buckets(), + FxHashMap::from_iter([ + (&"*".to_string(), &v1), + (&"1".to_string(), &v1), + (&"1.2".to_string(), &v1), + ]) + ); + } +} From 401b24249e40217b4b84d989da737050bc49ced9 Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Fri, 25 Oct 2024 20:54:03 -0700 Subject: [PATCH 13/19] Remove old bucket code. --- crates/core/src/tool_manifest.rs | 49 -------------------------------- 1 file changed, 49 deletions(-) diff --git a/crates/core/src/tool_manifest.rs b/crates/core/src/tool_manifest.rs index c5cf1bc4f..bc43e0d9c 100644 --- a/crates/core/src/tool_manifest.rs +++ b/crates/core/src/tool_manifest.rs @@ -1,6 +1,5 @@ use crate::helpers::{now, read_json_file_with_lock, write_json_file_with_lock}; use rustc_hash::{FxHashMap, FxHashSet}; -use semver::Version; use serde::{Deserialize, Serialize}; use starbase_utils::env::bool_var; use std::{ @@ -69,52 +68,4 @@ impl ToolManifest { Ok(()) } - - pub fn get_bucketed_versions( - &self, - focused_version: Option<&Version>, - ) -> FxHashMap { - let mut versions = FxHashMap::default(); - - let get_keys = |version: &Version| -> Vec { - vec![ - "*".to_string(), - format!("{}", version.major), - format!("{}.{}", version.major, version.minor), - ] - }; - - let mut add = |version: &Version| { - for bucket_key in get_keys(version) { - if let Some(bucket_value) = versions.get_mut(&bucket_key) { - // Always use the highest patch version - if version > bucket_value { - *bucket_value = version.to_owned(); - } - } else { - versions.insert(bucket_key.clone(), version.to_owned()); - } - } - }; - - for spec in &self.installed_versions { - if let Some(version) = spec.as_version() { - add(version); - } - } - - // If we have a focused version, add it to the bucketed map, - // and then filter down the map to the key with the same matching range. - // This may result in a different patch version then the patch in the - // focused version if there is an installed version with a higher patch. - if let Some(version) = focused_version { - add(version); - - let bucket_keys = get_keys(version); - - versions.retain(|key, _| bucket_keys.contains(key)); - } - - versions - } } From fea86ccb5ea0c2d8ba0c3ae87c2b016ec0bca57d Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Fri, 25 Oct 2024 21:46:14 -0700 Subject: [PATCH 14/19] Fix tests. --- crates/cli/src/commands/install.rs | 4 +--- crates/cli/src/commands/pin.rs | 5 +++-- crates/cli/src/commands/status.rs | 4 +--- crates/core/src/flow/setup.rs | 14 ++++++++------ 4 files changed, 13 insertions(+), 14 deletions(-) diff --git a/crates/cli/src/commands/install.rs b/crates/cli/src/commands/install.rs index c72dd1b21..72da7a677 100644 --- a/crates/cli/src/commands/install.rs +++ b/crates/cli/src/commands/install.rs @@ -221,9 +221,7 @@ pub async fn do_install( tool.disable_caching(); // Resolve version first so subsequent steps can reference the resolved version - tool.resolve_version(&version, false).await?; - - let resolved_version = tool.get_resolved_version(); + let resolved_version = tool.resolve_version(&version, false).await?; // Check if already installed, or if forced, overwrite previous install if !args.force && tool.is_setup(&version).await? { diff --git a/crates/cli/src/commands/pin.rs b/crates/cli/src/commands/pin.rs index cc9aa0e75..fa59ca84f 100644 --- a/crates/cli/src/commands/pin.rs +++ b/crates/cli/src/commands/pin.rs @@ -52,8 +52,9 @@ pub async fn pin(session: ProtoSession, args: PinArgs) -> AppResult { let mut tool = session.load_tool(&args.id).await?; let spec = if args.resolve { - tool.resolve_version(&args.spec, false).await?; - tool.get_resolved_version().to_unresolved_spec() + tool.resolve_version(&args.spec, false) + .await? + .to_unresolved_spec() } else { args.spec.clone() }; diff --git a/crates/cli/src/commands/status.rs b/crates/cli/src/commands/status.rs index 8a156a38a..a86b94ad4 100644 --- a/crates/cli/src/commands/status.rs +++ b/crates/cli/src/commands/status.rs @@ -121,9 +121,7 @@ async fn resolve_item_versions( // Resolve a version based on the configured spec, and ignore errors // as they indicate a version could not be resolved! - if tool.resolve_version(&config_version, false).await.is_ok() { - let version = tool.get_resolved_version(); - + if let Ok(version) = tool.resolve_version(&config_version, false).await { // Determine the install status if !version.is_latest() { if tool.is_installed() { diff --git a/crates/core/src/flow/setup.rs b/crates/core/src/flow/setup.rs index 5dd70e40d..f5e060ccb 100644 --- a/crates/core/src/flow/setup.rs +++ b/crates/core/src/flow/setup.rs @@ -53,17 +53,12 @@ impl Tool { initial_version: &UnresolvedVersionSpec, options: InstallOptions, ) -> miette::Result { - self.resolve_version(initial_version, false).await?; + let version = self.resolve_version(initial_version, false).await?; if !self.install(options).await? { return Ok(false); } - self.generate_shims(false).await?; - self.symlink_bins(true).await?; - self.cleanup().await?; - - let version = self.get_resolved_version(); let default_version = self .metadata .default_version @@ -90,6 +85,13 @@ impl Tool { // Allow plugins to override manifest self.sync_manifest().await?; + // Create all the things + self.generate_shims(false).await?; + self.symlink_bins(true).await?; + + // Remove temp files + self.cleanup().await?; + Ok(true) } From 38c15521491b194b647747843765d8f4502f763b Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Sat, 26 Oct 2024 19:46:39 -0700 Subject: [PATCH 15/19] Use spec. --- CHANGELOG.md | 1 + crates/core/src/flow/locate.rs | 6 +-- crates/core/src/flow/setup.rs | 2 +- crates/core/src/layout/bin_manager.rs | 69 ++++++++++++++------------- crates/core/tests/bin_manager_test.rs | 62 ++++++++++++++++++++---- 5 files changed, 94 insertions(+), 46 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d8aa0340d..e74121a33 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ - `~/.proto/bin/node` - Points to the highest installed version. - `~/.proto/bin/node-` - Points to the highest version within that major range (`~major`). Is created for each separate major version, for example: `node-20`, `node-22`. - `~/.proto/bin/node-.` - Points to the highest version within that major + minor range (`~major.minor`). Is created for each separate major + minor version, for example: `node-20.1`, `node-22.4`. + - `~/.proto/bin/node-canary` - Points to a canary install, if it exists. - WASM API - Deprecated `LocateExecutablesOutput.primary` and `LocateExecutablesOutput.secondary` (use `exes` instead). diff --git a/crates/core/src/flow/locate.rs b/crates/core/src/flow/locate.rs index 31a288b26..8698f4736 100644 --- a/crates/core/src/flow/locate.rs +++ b/crates/core/src/flow/locate.rs @@ -132,10 +132,8 @@ impl Tool { let versions = if include_all_versions { bin_manager.get_buckets() - } else if let Some(version) = resolved_version.as_version() { - bin_manager.get_buckets_focused_to_version(version) } else { - return Ok(locations); + bin_manager.get_buckets_focused_to_version(&resolved_version) }; let mut add = |name: String, config: ExecutableConfig| { @@ -161,7 +159,7 @@ impl Tool { .join(get_exe_file_name(&versioned_name)), name: versioned_name, config: config.clone(), - version: Some((*resolved_version).to_owned()), + version: resolved_version.as_version().map(|v| v.to_owned()), }); } } diff --git a/crates/core/src/flow/setup.rs b/crates/core/src/flow/setup.rs index f5e060ccb..84643ad39 100644 --- a/crates/core/src/flow/setup.rs +++ b/crates/core/src/flow/setup.rs @@ -129,7 +129,7 @@ impl Tool { } } // Otherwise, delete bins for this specific version - else if bin_manager.remove_version_from_spec(&version) { + else if bin_manager.remove_version(&version) { for bin in self .resolve_bin_locations_with_manager(bin_manager, false) .await? diff --git a/crates/core/src/layout/bin_manager.rs b/crates/core/src/layout/bin_manager.rs index 7088a4706..f7bfa8aeb 100644 --- a/crates/core/src/layout/bin_manager.rs +++ b/crates/core/src/layout/bin_manager.rs @@ -1,13 +1,12 @@ use crate::tool_manifest::ToolManifest; use rustc_hash::{FxHashMap, FxHashSet}; -use semver::Version; use std::mem; use version_spec::VersionSpec; #[derive(Debug, Default)] pub struct BinManager { - buckets: FxHashMap, - versions: FxHashSet, + buckets: FxHashMap, + versions: FxHashSet, } impl BinManager { @@ -15,23 +14,21 @@ impl BinManager { let mut manager = Self::default(); for spec in &manifest.installed_versions { - if let Some(version) = spec.as_version() { - manager.add_version(version); - } + manager.add_version(spec); } manager } - pub fn get_buckets(&self) -> FxHashMap<&String, &Version> { + pub fn get_buckets(&self) -> FxHashMap<&String, &VersionSpec> { self.buckets.iter().collect() } pub fn get_buckets_focused_to_version( &self, - version: &Version, - ) -> FxHashMap<&String, &Version> { - let bucket_keys = self.get_keys(version); + spec: &VersionSpec, + ) -> FxHashMap<&String, &VersionSpec> { + let bucket_keys = self.get_keys(spec); self.buckets .iter() @@ -39,19 +36,27 @@ impl BinManager { .collect() } - pub fn add_version(&mut self, version: &Version) { - for bucket_key in self.get_keys(version) { + pub fn add_version(&mut self, spec: &VersionSpec) { + if matches!(spec, VersionSpec::Alias(_)) { + return; + } + + for bucket_key in self.get_keys(spec) { + if bucket_key == "canary" && !spec.is_canary() { + continue; + } + if let Some(bucket_value) = self.buckets.get_mut(&bucket_key) { // Always use the highest patch version - if version > bucket_value { - *bucket_value = version.to_owned(); + if spec > bucket_value { + *bucket_value = spec.to_owned(); } } else { - self.buckets.insert(bucket_key.clone(), version.to_owned()); + self.buckets.insert(bucket_key.clone(), spec.to_owned()); } } - self.versions.insert(version.to_owned()); + self.versions.insert(spec.to_owned()); } pub fn rebuild_buckets(&mut self) { @@ -62,20 +67,20 @@ impl BinManager { } } - pub fn remove_version(&mut self, version: &Version) -> bool { + pub fn remove_version(&mut self, spec: &VersionSpec) -> bool { let mut rebuild = false; - for bucket_key in self.get_keys(version) { + for bucket_key in self.get_keys(spec) { if self .buckets .get(&bucket_key) - .is_some_and(|bucket_value| bucket_value == version) + .is_some_and(|bucket_value| bucket_value == spec) { rebuild = true; } } - self.versions.remove(version); + self.versions.remove(spec); if rebuild { self.rebuild_buckets(); @@ -84,19 +89,19 @@ impl BinManager { rebuild } - pub fn remove_version_from_spec(&mut self, spec: &VersionSpec) -> bool { - if let Some(version) = spec.as_version() { - return self.remove_version(version); + fn get_keys(&self, spec: &VersionSpec) -> Vec { + let mut keys = vec![]; + + if spec.is_canary() { + keys.push("canary".to_string()); + } else if let Some(version) = spec.as_version() { + keys.extend([ + "*".to_string(), + format!("{}", version.major), + format!("{}.{}", version.major, version.minor), + ]); } - false - } - - fn get_keys(&self, version: &Version) -> Vec { - vec![ - "*".to_string(), - format!("{}", version.major), - format!("{}.{}", version.major, version.minor), - ] + keys } } diff --git a/crates/core/tests/bin_manager_test.rs b/crates/core/tests/bin_manager_test.rs index c61e0c049..f78c5130c 100644 --- a/crates/core/tests/bin_manager_test.rs +++ b/crates/core/tests/bin_manager_test.rs @@ -1,14 +1,14 @@ use proto_core::layout::BinManager; +use proto_core::VersionSpec; use rustc_hash::FxHashMap; -use semver::Version; mod bin_manager { use super::*; #[test] fn creates_buckets_per_version() { - let v1 = Version::new(1, 2, 3); - let v2 = Version::new(4, 5, 6); + let v1 = VersionSpec::parse("1.2.3").unwrap(); + let v2 = VersionSpec::parse("4.5.6").unwrap(); let mut bins = BinManager::default(); bins.add_version(&v1); @@ -26,10 +26,54 @@ mod bin_manager { ); } + #[test] + fn creates_buckets_per_version_for_calver() { + let v1 = VersionSpec::parse("2000-10-25").unwrap(); + let v2 = VersionSpec::parse("2100-01").unwrap(); + + let mut bins = BinManager::default(); + bins.add_version(&v1); + bins.add_version(&v2); + + assert_eq!( + bins.get_buckets(), + FxHashMap::from_iter([ + (&"*".to_string(), &v2), + (&"2000".to_string(), &v1), + (&"2000.10".to_string(), &v1), + (&"2100".to_string(), &v2), + (&"2100.1".to_string(), &v2), + ]) + ); + } + + #[test] + fn creates_bucket_for_canary() { + let v1 = VersionSpec::Canary; + + let mut bins = BinManager::default(); + bins.add_version(&v1); + + assert_eq!( + bins.get_buckets(), + FxHashMap::from_iter([(&"canary".to_string(), &v1),]) + ); + } + + #[test] + fn doesnt_create_for_aliases() { + let v1 = VersionSpec::Alias("test".into()); + + let mut bins = BinManager::default(); + bins.add_version(&v1); + + assert_eq!(bins.get_buckets(), FxHashMap::default()); + } + #[test] fn highest_replaces() { - let v1 = Version::new(1, 2, 3); - let v2 = Version::new(1, 3, 4); + let v1 = VersionSpec::parse("1.2.3").unwrap(); + let v2 = VersionSpec::parse("1.3.4").unwrap(); let mut bins = BinManager::default(); bins.add_version(&v1); @@ -58,8 +102,8 @@ mod bin_manager { #[test] fn lowest_doesnt_replace() { - let v1 = Version::new(1, 2, 3); - let v2 = Version::new(1, 1, 4); + let v1 = VersionSpec::parse("1.2.3").unwrap(); + let v2 = VersionSpec::parse("1.1.4").unwrap(); let mut bins = BinManager::default(); bins.add_version(&v1); @@ -88,8 +132,8 @@ mod bin_manager { #[test] fn removing_rebuilds_buckets() { - let v1 = Version::new(1, 2, 3); - let v2 = Version::new(1, 3, 4); + let v1 = VersionSpec::parse("1.2.3").unwrap(); + let v2 = VersionSpec::parse("1.3.4").unwrap(); let mut bins = BinManager::default(); bins.add_version(&v1); From 46280d2c77cce05514fd5cbad15c359a63919806 Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Sat, 26 Oct 2024 20:08:42 -0700 Subject: [PATCH 16/19] Clean up regen. --- crates/cli/src/commands/regen.rs | 18 +++++++----------- crates/core/src/flow/link.rs | 2 +- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/crates/cli/src/commands/regen.rs b/crates/cli/src/commands/regen.rs index d6c9630ab..b66fb8cfe 100644 --- a/crates/cli/src/commands/regen.rs +++ b/crates/cli/src/commands/regen.rs @@ -12,6 +12,8 @@ pub struct RegenArgs { #[tracing::instrument(skip_all)] pub async fn regen(session: ProtoSession, args: RegenArgs) -> AppResult { + let store = &session.env.store; + if args.bin { println!("Regenerating bins and shims..."); } else { @@ -21,13 +23,13 @@ pub async fn regen(session: ProtoSession, args: RegenArgs) -> AppResult { // Delete all shims debug!("Removing old shims"); - fs::remove_dir_all(&session.env.store.shims_dir)?; + fs::remove_dir_all(&store.shims_dir)?; // Delete all bins (except for proto) if args.bin { debug!("Removing old bins"); - for file in fs::read_dir_all(&session.env.store.bin_dir)? { + for file in fs::read_dir_all(&store.bin_dir)? { let path = file.path(); let name = fs::file_name(&path); @@ -40,7 +42,7 @@ pub async fn regen(session: ProtoSession, args: RegenArgs) -> AppResult { continue; } - session.env.store.unlink_bin(&path)?; + store.unlink_bin(&path)?; } } @@ -54,20 +56,14 @@ pub async fn regen(session: ProtoSession, args: RegenArgs) -> AppResult { if config.versions.contains_key(&tool.id) { debug!("Regenerating {} shim", tool.get_name()); - tool.generate_shims(false).await?; + tool.generate_shims(true).await?; } // Bins - Create for each installed version. if args.bin { debug!("Relinking {} bin", tool.get_name()); - for version in tool.inventory.manifest.installed_versions.clone() { - let version = version.to_unresolved_spec(); - - tool.version = None; - tool.resolve_version(&version, true).await?; - tool.symlink_bins(false).await?; - } + tool.symlink_bins(true).await?; } } diff --git a/crates/core/src/flow/link.rs b/crates/core/src/flow/link.rs index e6fcd8729..d8c55d998 100644 --- a/crates/core/src/flow/link.rs +++ b/crates/core/src/flow/link.rs @@ -101,7 +101,7 @@ impl Tool { /// Symlink all primary and secondary binaries for the current tool. #[instrument(skip(self))] pub async fn symlink_bins(&mut self, force: bool) -> miette::Result<()> { - let bins = self.resolve_bin_locations(false).await?; + let bins = self.resolve_bin_locations(true).await?; if bins.is_empty() { return Ok(()); From 14994c86386dbb06c792b051093bd7417e945fa3 Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Sat, 26 Oct 2024 20:22:19 -0700 Subject: [PATCH 17/19] Clean cache dir. --- crates/cli/src/commands/clean.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/crates/cli/src/commands/clean.rs b/crates/cli/src/commands/clean.rs index 83f91815a..3c807b93e 100644 --- a/crates/cli/src/commands/clean.rs +++ b/crates/cli/src/commands/clean.rs @@ -333,6 +333,18 @@ pub async fn internal_clean( ); } + debug!("Cleaning cache directory..."); + + let results = + fs::remove_dir_stale_contents(&session.env.store.cache_dir, Duration::from_secs(86400))?; + + if log && results.files_deleted > 0 { + println!( + "Successfully cleaned {} cache files ({} bytes)", + results.files_deleted, results.bytes_saved + ); + } + Ok(None) } From e881a5473097a70f96232ffa37a59dc409e07f67 Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Sat, 26 Oct 2024 20:23:25 -0700 Subject: [PATCH 18/19] chore: Release --- Cargo.lock | 8 ++++---- crates/cli/Cargo.toml | 4 ++-- crates/codegen/Cargo.toml | 4 ++-- crates/core/Cargo.toml | 4 ++-- crates/pdk-api/Cargo.toml | 2 +- crates/pdk-test-utils/Cargo.toml | 6 +++--- crates/pdk/Cargo.toml | 4 ++-- 7 files changed, 16 insertions(+), 16 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index caa14b43f..c29bd4589 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2342,7 +2342,7 @@ dependencies = [ [[package]] name = "proto_core" -version = "0.41.7" +version = "0.42.0" dependencies = [ "clap", "convert_case", @@ -2390,7 +2390,7 @@ dependencies = [ [[package]] name = "proto_pdk" -version = "0.23.0" +version = "0.24.0" dependencies = [ "extism-pdk", "proto_pdk_api", @@ -2401,7 +2401,7 @@ dependencies = [ [[package]] name = "proto_pdk_api" -version = "0.23.1" +version = "0.24.0" dependencies = [ "proto_pdk_api", "rustc-hash 2.0.0", @@ -2417,7 +2417,7 @@ dependencies = [ [[package]] name = "proto_pdk_test_utils" -version = "0.28.0" +version = "0.29.0" dependencies = [ "proto_core", "proto_pdk_api", diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index 498d0a173..aeddd1fe9 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -32,9 +32,9 @@ name = "proto-shim" path = "src/main_shim.rs" [dependencies] -proto_core = { version = "0.41.7", path = "../core", features = ["clap"] } +proto_core = { version = "0.42.0", path = "../core", features = ["clap"] } proto_installer = { version = "0.7.1", path = "../installer" } -proto_pdk_api = { version = "0.23.1", path = "../pdk-api" } +proto_pdk_api = { version = "0.24.0", path = "../pdk-api" } proto_shim = { version = "0.5.0", path = "../shim" } system_env = { version = "0.6.1", path = "../system-env" } anyhow = { workspace = true } diff --git a/crates/codegen/Cargo.toml b/crates/codegen/Cargo.toml index 83a5b8074..8dc653e9e 100644 --- a/crates/codegen/Cargo.toml +++ b/crates/codegen/Cargo.toml @@ -9,8 +9,8 @@ publish = false dist = false [dependencies] -proto_core = { version = "0.41.7", path = "../core" } -proto_pdk_api = { version = "0.23.1", path = "../pdk-api", features = [ +proto_core = { version = "0.42.0", path = "../core" } +proto_pdk_api = { version = "0.24.0", path = "../pdk-api", features = [ "schematic", ] } schematic = { workspace = true, features = [ diff --git a/crates/core/Cargo.toml b/crates/core/Cargo.toml index 0b43f4de2..f68bd40f5 100644 --- a/crates/core/Cargo.toml +++ b/crates/core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "proto_core" -version = "0.41.7" +version = "0.42.0" edition = "2021" license = "MIT" description = "Core proto APIs." @@ -8,7 +8,7 @@ homepage = "https://moonrepo.dev/proto" repository = "https://github.com/moonrepo/proto" [dependencies] -proto_pdk_api = { version = "0.23.1", path = "../pdk-api", features = [ +proto_pdk_api = { version = "0.24.0", path = "../pdk-api", features = [ "schematic", ] } proto_shim = { version = "0.5.0", path = "../shim" } diff --git a/crates/pdk-api/Cargo.toml b/crates/pdk-api/Cargo.toml index e0ab44195..7f7261404 100644 --- a/crates/pdk-api/Cargo.toml +++ b/crates/pdk-api/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "proto_pdk_api" -version = "0.23.1" +version = "0.24.0" edition = "2021" license = "MIT" description = "Core APIs for creating proto WASM plugins." diff --git a/crates/pdk-test-utils/Cargo.toml b/crates/pdk-test-utils/Cargo.toml index 19c003a8e..48a8ce820 100644 --- a/crates/pdk-test-utils/Cargo.toml +++ b/crates/pdk-test-utils/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "proto_pdk_test_utils" -version = "0.28.0" +version = "0.29.0" edition = "2021" license = "MIT" description = "Utilities for testing proto WASM plugins." @@ -8,8 +8,8 @@ homepage = "https://moonrepo.dev/proto" repository = "https://github.com/moonrepo/proto" [dependencies] -proto_core = { version = "0.41.7", path = "../core" } -proto_pdk_api = { version = "0.23.1", path = "../pdk-api" } +proto_core = { version = "0.42.0", path = "../core" } +proto_pdk_api = { version = "0.24.0", path = "../pdk-api" } warpgate = { version = "0.18.2", path = "../warpgate" } serde = { workspace = true } serde_json = { workspace = true } diff --git a/crates/pdk/Cargo.toml b/crates/pdk/Cargo.toml index 2c08a9c52..682dff477 100644 --- a/crates/pdk/Cargo.toml +++ b/crates/pdk/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "proto_pdk" -version = "0.23.0" +version = "0.24.0" edition = "2021" license = "MIT" description = "A plugin development kit for creating proto WASM plugins." @@ -8,7 +8,7 @@ homepage = "https://moonrepo.dev/proto" repository = "https://github.com/moonrepo/proto" [dependencies] -proto_pdk_api = { version = "0.23.1", path = "../pdk-api" } +proto_pdk_api = { version = "0.24.0", path = "../pdk-api" } warpgate_pdk = { version = "0.8.0", path = "../warpgate-pdk" } extism-pdk = { workspace = true } rustc-hash = { workspace = true } From 89878e74df23457fdcdd06258c8974232be793a5 Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Sat, 26 Oct 2024 20:25:30 -0700 Subject: [PATCH 19/19] chore: Release --- Cargo.lock | 8 ++++---- crates/cli/Cargo.toml | 2 +- crates/codegen/Cargo.toml | 2 +- crates/core/Cargo.toml | 4 ++-- crates/pdk-test-utils/Cargo.toml | 6 +++--- crates/pdk/Cargo.toml | 2 +- crates/warpgate/Cargo.toml | 2 +- 7 files changed, 13 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c29bd4589..0e2de72be 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2342,7 +2342,7 @@ dependencies = [ [[package]] name = "proto_core" -version = "0.42.0" +version = "0.43.0" dependencies = [ "clap", "convert_case", @@ -2390,7 +2390,7 @@ dependencies = [ [[package]] name = "proto_pdk" -version = "0.24.0" +version = "0.25.0" dependencies = [ "extism-pdk", "proto_pdk_api", @@ -2417,7 +2417,7 @@ dependencies = [ [[package]] name = "proto_pdk_test_utils" -version = "0.29.0" +version = "0.30.0" dependencies = [ "proto_core", "proto_pdk_api", @@ -3856,7 +3856,7 @@ dependencies = [ [[package]] name = "warpgate" -version = "0.18.2" +version = "0.19.0" dependencies = [ "extism", "miette", diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index aeddd1fe9..10a3aaa83 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -32,7 +32,7 @@ name = "proto-shim" path = "src/main_shim.rs" [dependencies] -proto_core = { version = "0.42.0", path = "../core", features = ["clap"] } +proto_core = { version = "0.43.0", path = "../core", features = ["clap"] } proto_installer = { version = "0.7.1", path = "../installer" } proto_pdk_api = { version = "0.24.0", path = "../pdk-api" } proto_shim = { version = "0.5.0", path = "../shim" } diff --git a/crates/codegen/Cargo.toml b/crates/codegen/Cargo.toml index 8dc653e9e..a725df035 100644 --- a/crates/codegen/Cargo.toml +++ b/crates/codegen/Cargo.toml @@ -9,7 +9,7 @@ publish = false dist = false [dependencies] -proto_core = { version = "0.42.0", path = "../core" } +proto_core = { version = "0.43.0", path = "../core" } proto_pdk_api = { version = "0.24.0", path = "../pdk-api", features = [ "schematic", ] } diff --git a/crates/core/Cargo.toml b/crates/core/Cargo.toml index f68bd40f5..410d90ca5 100644 --- a/crates/core/Cargo.toml +++ b/crates/core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "proto_core" -version = "0.42.0" +version = "0.43.0" edition = "2021" license = "MIT" description = "Core proto APIs." @@ -15,7 +15,7 @@ proto_shim = { version = "0.5.0", path = "../shim" } version_spec = { version = "0.7.0", path = "../version-spec", features = [ "schematic", ] } -warpgate = { version = "0.18.2", path = "../warpgate", features = [ +warpgate = { version = "0.19.0", path = "../warpgate", features = [ "schematic", ] } clap = { workspace = true, optional = true } diff --git a/crates/pdk-test-utils/Cargo.toml b/crates/pdk-test-utils/Cargo.toml index 48a8ce820..a51681722 100644 --- a/crates/pdk-test-utils/Cargo.toml +++ b/crates/pdk-test-utils/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "proto_pdk_test_utils" -version = "0.29.0" +version = "0.30.0" edition = "2021" license = "MIT" description = "Utilities for testing proto WASM plugins." @@ -8,9 +8,9 @@ homepage = "https://moonrepo.dev/proto" repository = "https://github.com/moonrepo/proto" [dependencies] -proto_core = { version = "0.42.0", path = "../core" } +proto_core = { version = "0.43.0", path = "../core" } proto_pdk_api = { version = "0.24.0", path = "../pdk-api" } -warpgate = { version = "0.18.2", path = "../warpgate" } +warpgate = { version = "0.19.0", path = "../warpgate" } serde = { workspace = true } serde_json = { workspace = true } starbase_sandbox = { workspace = true } diff --git a/crates/pdk/Cargo.toml b/crates/pdk/Cargo.toml index 682dff477..094364f55 100644 --- a/crates/pdk/Cargo.toml +++ b/crates/pdk/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "proto_pdk" -version = "0.24.0" +version = "0.25.0" edition = "2021" license = "MIT" description = "A plugin development kit for creating proto WASM plugins." diff --git a/crates/warpgate/Cargo.toml b/crates/warpgate/Cargo.toml index 850fac404..5b46d20f3 100644 --- a/crates/warpgate/Cargo.toml +++ b/crates/warpgate/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "warpgate" -version = "0.18.2" +version = "0.19.0" edition = "2021" license = "MIT" description = "Download, resolve, and manage Extism WASM plugins."