Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

new: Implement new bins strategy. #643

Merged
merged 19 commits into from
Oct 29, 2024
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,24 @@

## 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-<major>` - 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-<major>.<minor>` - 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).

#### 🚀 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

Expand Down
10 changes: 5 additions & 5 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions crates/cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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.43.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 }
Expand Down
6 changes: 3 additions & 3 deletions crates/cli/src/commands/bin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ 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? {
if bin.primary {
for bin in tool.resolve_bin_locations(false).await? {
if bin.config.primary {
println!("{}", bin.path.display());
return Ok(None);
}
Expand All @@ -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);
}
Expand Down
14 changes: 13 additions & 1 deletion crates/cli/src/commands/clean.rs
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,7 @@ pub async fn purge_tool(session: &ProtoSession, id: &Id, yes: bool) -> miette::R
fs::remove_dir_all(inventory_dir)?;

// Delete binaries
for bin in tool.resolve_bin_locations().await? {
for bin in tool.resolve_bin_locations(true).await? {
session.env.store.unlink_bin(&bin.path)?;
}

Expand Down Expand Up @@ -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)
}

Expand Down
6 changes: 2 additions & 4 deletions crates/cli/src/commands/install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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? {
Expand Down
14 changes: 4 additions & 10 deletions crates/cli/src/commands/pin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,7 @@ pub async fn internal_pin(
tool: &mut Tool,
spec: &UnresolvedVersionSpec,
pin: PinType,
link: bool,
) -> miette::Result<PathBuf> {
// 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
Expand All @@ -58,14 +52,14 @@ 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()
};

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 {}",
Expand Down
31 changes: 13 additions & 18 deletions crates/cli/src/commands/plugin/info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand All @@ -122,29 +127,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),
if bin.primary {
color::muted_light("(primary)")
} else {
"".into()
}
)
}),
Some(color::failure("None")),
);

p.entry_list(
"Shims",
shims.into_iter().map(|shim| {
format!(
"{} {}",
color::path(shim.path),
if shim.primary {
if shim.config.primary {
format_value("(primary)")
} else {
"".into()
Expand All @@ -154,6 +143,12 @@ pub async fn info(session: ProtoSession, args: InfoPluginArgs) -> AppResult {
Some(color::failure("None")),
);

p.entry_list(
"Binaries",
bins.into_iter().map(|bin| color::path(bin.path)),
Some(color::failure("None")),
);

let mut versions = tool
.inventory
.manifest
Expand Down
26 changes: 10 additions & 16 deletions crates/cli/src/commands/regen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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);

Expand All @@ -40,36 +42,28 @@ pub async fn regen(session: ProtoSession, args: RegenArgs) -> AppResult {
continue;
}

session.env.store.unlink_bin(&path)?;
store.unlink_bin(&path)?;
}
}

// Regenerate everything!
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
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(true).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());

tool.version = None;
tool.resolve_version(version, true).await?;
tool.symlink_bins(true).await?;
}
tool.symlink_bins(true).await?;
}
}

Expand Down
4 changes: 1 addition & 3 deletions crates/cli/src/commands/status.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
29 changes: 20 additions & 9 deletions crates/cli/tests/clean_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,26 +42,37 @@ 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) {
"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/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| {
cmd.arg("clean").arg("--yes").arg("--purge").arg("node");
})
.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]
Expand Down
Loading