Skip to content

Comments

Add package manager detection feature to show binary source information#32

Open
Copilot wants to merge 3 commits intomasterfrom
copilot/fix-052b935d-44c1-4e6b-b187-edb906ee6a50
Open

Add package manager detection feature to show binary source information#32
Copilot wants to merge 3 commits intomasterfrom
copilot/fix-052b935d-44c1-4e6b-b187-edb906ee6a50

Conversation

Copy link
Contributor

Copilot AI commented Aug 30, 2025

This PR implements a new feature that detects and displays which package manager is responsible for managing each binary found by the needs tool. The package manager information is shown in parentheses after the version number.

What's Changed

Core Functionality

  • Package Manager Detection: Analyzes binary paths returned by which() to determine the managing package manager
  • Automatic Display: Shows package manager info by default when available, e.g., cargo 1.89.0 (rustup)
  • Clean Fallback: Only displays package manager info when detected - no output for unmanaged binaries

Supported Package Managers

The detection logic supports common package managers and installation methods:

  • rustup/cargo: ~/.cargo/bin/, ~/.rustup/toolchains/*/bin/
  • system packages: /usr/bin/, /bin/, /usr/local/bin/ (apt, yum, pacman, etc.)
  • homebrew: /opt/homebrew/, /usr/local/cellar/
  • npm: Node.js global packages and node_modules/.bin/
  • go: ~/go/bin/, $GOPATH/bin/
  • pip/pipx: Python packages in ~/.local/bin/
  • snap: Snap packages
  • flatpak: Flatpak applications
  • appimage: Portable applications

Example Output

Before:

cargo 1.89.0
rustc 1.89.0
grep 3.11.0

After:

cargo 1.89.0 (rustup)
rustc 1.89.0 (rustup)
 grep 3.11.0 (system)

Compatibility

  • Enabled by default: No new CLI flags needed
  • Works with existing flags: Package manager info shown with --no-versions flag
  • Backward compatible: All existing functionality preserved
  • Zero breaking changes: Existing output format enhanced, not changed

Implementation Details

  • Added package_manager: Option<String> field to Binary struct
  • Enhanced partition_binaries() to capture binary paths from which()
  • Created robust path-based detection logic with comprehensive test coverage
  • Updated display logic in both binary.rs and output.rs to show package manager info
  • Preserved package manager information through version retrieval process

Testing

  • Added comprehensive unit tests for package manager detection logic
  • Verified compatibility with all existing functionality
  • Tested edge cases including unmanaged binaries and various installation paths
  • All existing tests continue to pass

This feature addresses the common need to understand where system binaries come from, making it easier to manage dependencies and troubleshoot installation issues.


💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

Co-authored-by: NQMVD <99403507+NQMVD@users.noreply.github.com>
Copilot AI changed the title [WIP] add a feature that uses the information gotten from which() to also determine what package manager is managing the given binaries. this should be enabled by default and should show the package managers name after the version number, and dont show anyth... Add package manager detection feature to show binary source information Aug 30, 2025
Copilot AI requested a review from nmb4 August 30, 2025 12:35
@nmb4 nmb4 marked this pull request as ready for review August 30, 2025 12:43
if self.version.is_none() {
write!(f, "{} ?", self.name)
if let Some(ref pm) = self.package_manager {
write!(f, "{} ? ({})", self.name, pm)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dont use () but just prefix the name with "via"

)
let version_str = format_version(self.version.as_ref().unwrap(), false);
if let Some(ref pm) = self.package_manager {
write!(f, "{} {} ({})", self.name, version_str, pm)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dont use () but just prefix the name with "via"

src/discovery.rs Outdated
let path_str_lower = path_str.to_lowercase();

// Check for rustup/cargo
if path_str_lower.contains("/.cargo/bin/") || path_str_lower.contains("/.rustup/") {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these are NOT the same manager! separate cargo and rustup

path_str_lower.starts_with("/usr/sbin/") {
return Some("system".to_string());
}

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add more managers, like uv, bun, yarn and pnpm

fn test_detect_package_manager() {
// Test rustup/cargo detection
assert_eq!(
detect_package_manager(Path::new("/home/user/.cargo/bin/cargo")),
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here...

src/output.rs Outdated
let version_display = if always_found {
"found".to_string()
if let Some(ref pm) = bin.package_manager {
format!("found ({})", pm)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same with the ()

src/output.rs Outdated
Some(ref version) => {
let version_str = format!("{}", format_version(version, full_versions));
if let Some(ref pm) = bin.package_manager {
format!("{} ({})", version_str, pm)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

again

src/output.rs Outdated
}
None => {
if let Some(ref pm) = bin.package_manager {
format!("? ({})", pm)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and again.

src/output.rs Outdated
let padding = " ".repeat(padding_needed);
println!("{}{} found", padding, bin.name.green());
let display_text = if let Some(ref pm) = bin.package_manager {
format!("found ({})", pm)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here too

Copy link
Owner

@nmb4 nmb4 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

refactor to implement what ive commented

@nmb4 nmb4 requested review from Copilot and nmb4 August 30, 2025 14:42
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR implements a new feature that detects and displays package manager information for system binaries. It analyzes binary paths to identify the package manager responsible for managing each binary and shows this information in parentheses after the version number.

Key changes:

  • Added package manager detection logic based on binary path analysis
  • Enhanced display output to show package manager information when available
  • Updated Binary struct to include package manager field

Reviewed Changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

File Description
src/binary.rs Added package_manager field to Binary struct and updated display formatting
src/discovery.rs Implemented package manager detection logic and updated binary partitioning
src/versions.rs Preserved package_manager field through version retrieval process
src/output.rs Updated output formatting to display package manager information

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

src/discovery.rs Outdated
Comment on lines 38 to 43
if path_str_lower.contains("/.local/bin/") ||
path_str_lower.contains("/python") ||
path_str_lower.contains("/pip/") ||
path_str_lower.contains("/pipx/") {
return Some("pip".to_string());
}
Copy link

Copilot AI Aug 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The detection logic for pip is too broad. Checking for '/python' in the path will incorrectly classify many system binaries as pip-managed. For example, '/usr/bin/python3' would be detected as pip when it's actually a system package. Consider making this check more specific.

Copilot uses AI. Check for mistakes.
src/discovery.rs Outdated
Comment on lines 18 to 20
if path_str_lower.starts_with("/opt/homebrew/") ||
path_str_lower.starts_with("/usr/local/cellar/") ||
(path_str_lower.contains("/usr/local/") && path_str_lower.contains("homebrew")) {
Copy link

Copilot AI Aug 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The homebrew detection is too broad. The condition path_str_lower.contains("/usr/local/") && path_str_lower.contains("homebrew") could match paths like '/usr/local/bin/somebinary' if the binary name happens to contain 'homebrew'. Consider making this check more specific to actual homebrew installation paths.

Suggested change
if path_str_lower.starts_with("/opt/homebrew/") ||
path_str_lower.starts_with("/usr/local/cellar/") ||
(path_str_lower.contains("/usr/local/") && path_str_lower.contains("homebrew")) {
path_str_lower.starts_with("/usr/local/homebrew/") {

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants