Skip to content

Rewrite of Nix Package Manager Support#6861

Merged
waruqi merged 19 commits intoxmake-io:devfrom
ZZBaron:nix/rework-package-detection
Sep 29, 2025
Merged

Rewrite of Nix Package Manager Support#6861
waruqi merged 19 commits intoxmake-io:devfrom
ZZBaron:nix/rework-package-detection

Conversation

@ZZBaron
Copy link
Contributor

@ZZBaron ZZBaron commented Sep 27, 2025

This PR completely rewrites the Nix package manager integration for xmake as the original version was too naive and unreliable. The rewrite introduces caching, derivation parsing, and multi-environment support.

Improvements:

  1. Environment Detection

Before: Only checked basic environment variables and nix-shell state
After: Discovers packages from all the standard Nix environments:

Nix shells (nix-shell, nix develop)
User profiles (nix profile, nix-env)
Home Manager (both tool and NixOS module)
NixOS system packages
NixOS user packages
Current system closure (as fallback for NixOS when nixos-option is not configured)

  1. Multi-Level Caching

Session cache: Fast in-memory cache for repeated operations within the same build
Persistent cache: Disk-based cache that survives across xmake invocations
Environment-aware: Cache keys include environment state to detect changes
Selective invalidation: Only re-scans when the Nix environment actually changes

  1. Derivation Analysis

Before: Simple string matching against store path names
After: Uses nix derivation show to extract accurate package metadata:

Real package names (pname) instead of guessing from paths (unreliable)
Version information from derivation metadata
Multiple output support (dev, lib, bin, etc.)
Structured attribute handling

  1. Propagated Dependencies Support

Automatically follows nix-support/propagated-build-inputs
Ensures transitive dependencies are available for linking
Cached traversal prevents redundant filesystem operations

  1. Enhanced Package Information Extraction

pkg-config integration: Leverages existing .pc files when available
Multiple output awareness: Handles packages with separate dev/lib/bin outputs

Apologies for any deviations from xmake's coding conventions or API patterns. I'm still new to xmake source code and lua. I realize this packs a lot of functionality into one file, maybe I could move parts of the implementation to other dirs?

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @ZZBaron, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces a complete overhaul of xmake's Nix package manager support, transforming it from a basic and often unreliable integration into a sophisticated and robust system. The rewrite focuses on comprehensive environment detection, multi-level caching, accurate derivation analysis, and proper handling of propagated and transitive dependencies. This significantly enhances xmake's ability to reliably find and utilize Nix-managed packages, improving build consistency and developer experience for Nix users.

Highlights

  • Comprehensive Environment Detection: The Nix package manager integration now discovers packages from a wide array of Nix environments, including Nix shells, user profiles, Home Manager (tool and NixOS module), NixOS system/user packages, and the current system closure.
  • Multi-Level Caching System: A robust caching mechanism has been implemented, featuring both in-memory session caching and disk-based persistent caching. Cache keys are environment-aware, ensuring selective invalidation and efficient re-scanning only when the Nix environment truly changes.
  • Advanced Derivation Analysis: The system now utilizes 'nix derivation show' to accurately extract package metadata, providing real package names (pname), version information, and support for multiple outputs (dev, lib, bin). This replaces the previous naive string matching.
  • Propagated Dependencies Support: The integration automatically follows 'nix-support/propagated-build-inputs' to ensure that transitive dependencies are correctly identified and available for linking, with cached traversal to prevent redundant operations.
  • Enhanced Package Information Extraction: Package information extraction is significantly improved through better pkg-config integration and explicit awareness of multiple package outputs, allowing for more precise dependency resolution.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This is an impressive and comprehensive rewrite of the Nix package manager integration. The new implementation is far more robust and feature-rich than the original, with excellent additions like multi-level caching, proper derivation parsing, and extensive environment detection. The code is well-structured and demonstrates a deep understanding of the Nix ecosystem. I've identified a couple of correctness bugs, primarily related to caching and utility function usage, and have also suggested some refactoring for improved robustness and maintainability. Overall, this is a fantastic contribution that will significantly improve xmake's Nix support.

local pattern = separator == ":" and "[^:]+" or "[^%s]+"
-- Create a hash-like key from the environment
local key_parts = {}
for k, v in pairs(env_data) do
Copy link
Member

Choose a reason for hiding this comment

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

use table.orderpairs() instead of table.sort

local missing = {}
if not nix_store then table.insert(missing, "nix-store") end
if not nix then table.insert(missing, "nix") end
print("Nix: Required tools not found: " .. table.concat(missing, ", "))
Copy link
Member

Choose a reason for hiding this comment

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

use wprint


-- Get the derivation path
local drv_output = try {function()
return os.iorunv(nix_store.program, {"--query", "--valid-derivers", store_path}):trim() -- not "--deriver" because:
Copy link
Member

Choose a reason for hiding this comment

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

local outdata = os.iorunv
if outdata then
    return outdata:trim()
end
return outdata

"derivation", "show",
drv_path,
"--extra-experimental-features", "nix-command flakes"
}):trim()
Copy link
Member

Choose a reason for hiding this comment

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

and here

table.insert(paths, path)
end
-- Parse the JSON output
local derivation_data, parse_error = json.decode(derivation_json)
Copy link
Member

Choose a reason for hiding this comment

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

decode failed, it will raise errors.

so parse_error is nil

  local derivation_data = json.decode(derivation_json)

end
-- Parse the JSON output
local derivation_data, parse_error = json.decode(derivation_json)
if not derivation_data or parse_error then
Copy link
Member

Choose a reason for hiding this comment

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

remove parse_error

end

-- remove duplicates from array
local function remove_duplicates(arr)
Copy link
Member

Choose a reason for hiding this comment

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

use table.unique

arr = table.unique(arr)

end

-- PackageInfo data
local PackageInfo = {}
Copy link
Member

Choose a reason for hiding this comment

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

use core.base.object to define object

local jobgraph = jobgraph or object {_init = {"_name", "_jobs", "_size", "_dag", "_groups"}}

Copy link
Member

Choose a reason for hiding this comment

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

and please use lower case + _

like: package_info

local visited = {}

-- Add initial paths
for _, store_path in ipairs(store_paths) do
Copy link
Member

Choose a reason for hiding this comment

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

local all_paths = table.unique(store_paths)

-- get store paths from nix command output
local function get_store_paths_from_command(command, args, opt)
local output = try {function()
return os.iorunv(command, args):trim()
Copy link
Member

Choose a reason for hiding this comment

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

here

result.libfiles = {}
local user = os.getenv("USER") or "unknown"
local output = try {function()
return os.iorunv(nixos_option.program, {"users.users." .. user .. ".packages"}):trim()
Copy link
Member

Choose a reason for hiding this comment

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

here

end

local output = try {function()
return os.iorunv(nixos_option.program, {"environment.systemPackages"}):trim()
Copy link
Member

Choose a reason for hiding this comment

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

here

for _, dep_path in ipairs(all_deps) do
if not seen[dep_path] then
seen[dep_path] = true
table.insert(filtered_paths, dep_path)
Copy link
Member

Choose a reason for hiding this comment

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

use table.unique

Copy link
Member

Choose a reason for hiding this comment

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

and here

Copy link
Contributor Author

Choose a reason for hiding this comment

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

apologies

@waruqi waruqi merged commit fd822e0 into xmake-io:dev Sep 29, 2025
22 checks passed
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