Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions e2e/backend/test_github_zip_executable_permissions
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#!/usr/bin/env bash
set -euo pipefail

if [[ "$(uname -s)" != "Linux" ]]; then
echo "Skipping Linux-specific test on non-Linux OS"
exit 0
fi

cat >mise.toml <<EOF
[tools]
"github:Kampfkarren/selene" = { version = "0.30.0", asset_pattern = "selene-0.30.0-linux.zip", bin = "selene" }
EOF

mise install

install_path="$(mise where github:Kampfkarren/selene)"
assert_succeed "test -x '$install_path/selene'"
assert_contains "mise x -- selene --version" "selene 0.30.0"
39 changes: 38 additions & 1 deletion src/backend/static_helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -511,7 +511,6 @@ pub fn install_artifact(
file::untar(file_path, &install_path, &tar_opts)?;

// Extract just the repo name from tool_name (e.g., "opsgenie/opsgenie-lamp" -> "opsgenie-lamp")
// This is needed for matching binary names in ZIP archives where exec bits are lost
let full_tool_name = tv.ba().tool_name.as_str();
let tool_name = full_tool_name.rsplit('/').next().unwrap_or(full_tool_name);

Expand All @@ -523,6 +522,7 @@ pub fn install_artifact(
// bin= values are relative to install_path, so always use install_path or explicit bin_path
if let Some(bin_name) = lookup_with_fallback(opts, "bin") {
let search_dir = explicit_bin_path.as_deref().unwrap_or(&install_path);
make_configured_bin_executable(search_dir, &bin_name)?;
rename_executable_in_dir(search_dir, &bin_name, Some(tool_name))?;
}

Expand All @@ -546,6 +546,14 @@ pub fn install_artifact(
Ok(())
}

fn make_configured_bin_executable(search_dir: &Path, bin_name: &str) -> Result<()> {
let bin_path = search_dir.join(bin_name);
if bin_path.is_file() {
file::make_executable(bin_path)?;
}
Ok(())
}

pub fn verify_artifact(
_tv: &crate::toolset::ToolVersion,
file_path: &Path,
Expand Down Expand Up @@ -1455,4 +1463,33 @@ bin = "tool.exe"
);
}
}

#[cfg(unix)]
#[test]
fn test_make_configured_bin_executable_marks_only_exact_bin() {
use std::os::unix::fs::PermissionsExt;

let tmp = tempfile::tempdir().unwrap();
let binary = tmp.path().join("selene");
let readme = tmp.path().join("README.md");
let config = tmp.path().join("selene.toml");
let unrelated = tmp.path().join("counselene");
std::fs::write(&binary, b"not-a-binary").unwrap();
std::fs::write(&readme, b"not-a-binary").unwrap();
std::fs::write(&config, b"not-a-binary").unwrap();
std::fs::write(&unrelated, b"not-a-binary").unwrap();

std::fs::set_permissions(&binary, std::fs::Permissions::from_mode(0o644)).unwrap();
std::fs::set_permissions(&readme, std::fs::Permissions::from_mode(0o644)).unwrap();
std::fs::set_permissions(&config, std::fs::Permissions::from_mode(0o644)).unwrap();
std::fs::set_permissions(&unrelated, std::fs::Permissions::from_mode(0o644)).unwrap();

make_configured_bin_executable(tmp.path(), "selene").unwrap();
make_configured_bin_executable(tmp.path(), "missing").unwrap();

assert!(file::is_executable(&binary));
assert!(!file::is_executable(&readme));
assert!(!file::is_executable(&config));
assert!(!file::is_executable(&unrelated));
}
}
Loading