summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorzzbaron <[email protected]>2026-03-29 01:31:10 -0400
committerzzbaron <[email protected]>2026-03-29 01:31:10 -0400
commit10689dada1a50935295741dfbfc895e4f7020bd1 (patch)
treeb90667808c21aa2f86f4fbc056e9cb301e29b2ad
parent76ecc812e959a271e2ebc476073948e354e643ea (diff)
nix: improve config and source selection
-rw-r--r--xmake/modules/package/manager/nix/configurations.lua39
-rw-r--r--xmake/modules/package/manager/nix/find_package.lua278
2 files changed, 149 insertions, 168 deletions
diff --git a/xmake/modules/package/manager/nix/configurations.lua b/xmake/modules/package/manager/nix/configurations.lua
new file mode 100644
index 000000000..1727f477c
--- /dev/null
+++ b/xmake/modules/package/manager/nix/configurations.lua
@@ -0,0 +1,39 @@
+--!A cross-platform build utility based on Lua
+--
+-- Licensed under the Apache License, Version 2.0 (the "License");
+-- you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+-- http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+--
+-- Copyright (C) 2015-present, Xmake Open Source Community.
+--
+-- @author ZZBaron
+-- @file configurations.lua
+--
+
+-- get configurations
+--
+-- Source priority order (highest to lowest):
+-- 1. nix-shell / nix develop / stdenv.mkDerivation
+-- 2. home-manager profile
+-- 3. nix profile / nix-env (~/.nix-profile)
+-- 4. NixOS system profile (/run/current-system/sw)
+--
+-- Usage:
+-- add_requires("nix::zlib", {configs = {use_nix_profile = true}})
+--
+function main()
+ return {
+ use_nix_shell = {description = "Search packages from the active nix-shell / nix develop / stdenv.mkDerivation environment. Default: true.", default = true},
+ use_homemanager = {description = "Search packages installed via home-manager (/etc/profiles/per-user/$USER). Default: false.", default = false},
+ use_nix_profile = {description = "Search packages installed via nix profile or nix-env (~/.nix-profile). Default: false.", default = false},
+ use_nixos_system = {description = "Search packages from the NixOS system profile (/run/current-system/sw). Default: false.", default = false},
+ }
+end \ No newline at end of file
diff --git a/xmake/modules/package/manager/nix/find_package.lua b/xmake/modules/package/manager/nix/find_package.lua
index 79fb236d4..93e284e6e 100644
--- a/xmake/modules/package/manager/nix/find_package.lua
+++ b/xmake/modules/package/manager/nix/find_package.lua
@@ -28,6 +28,8 @@ import("core.cache.memcache")
import("core.base.json")
import("core.base.object")
import("core.base.semver")
+import("core.project.config")
+import("package.manager.nix.configurations")
-- cache keys
local STORE_PATHS_CACHE = "nix_store_paths"
@@ -52,6 +54,26 @@ local function is_in_nix_shell()
return in_nix_shell == "pure" or in_nix_shell == "impure"
end
+local function is_in_nix_build()
+ local v = os.getenv("NIX_BUILD_TOP")
+ return v ~= nil and v ~= ""
+end
+
+-- check if we're in a nix environment (shell or build)
+local function is_in_nix_environment()
+ return is_in_nix_shell() or is_in_nix_build()
+end
+
+-- resolve the effective boolean value for a source option.
+local function resolve_source_opt(key, default, opt)
+ local configs = opt.configs or {}
+ local config_key = "use_" .. key
+ if configs[config_key] ~= nil then
+ return configs[config_key]
+ end
+ return default
+end
+
-- generate cache key for environment state
local function generate_env_cache_key()
local env_vars = {
@@ -385,31 +407,6 @@ local function follow_propagated_inputs(store_paths, opt)
return table.unique(all_paths)
end
--- get store paths from nix command output
-local function get_store_paths_from_command(command, args, opt)
- local output = try {function()
- local outdata = os.iorunv(command, args)
- if outdata then
- return outdata:trim()
- end
- return outdata
- end}
-
- if not output then
- return {}
- end
-
- local store_paths = {}
- for line in output:gmatch("[^\n]+") do
- local store_path = line:match("(/nix/store/[^%s]+)")
- if store_path then
- table.insert(store_paths, store_path)
- end
- end
-
- return follow_propagated_inputs(store_paths, opt)
-end
-
-- parse store paths from environment variables with caching
local function parse_store_paths_from_env(env_vars, opt)
local cache_key = generate_env_cache_key()
@@ -457,9 +454,6 @@ local function parse_store_paths_from_env(env_vars, opt)
end
end
- -- Follow propagated inputs
- paths = follow_propagated_inputs(paths, opt)
-
-- Cache the result
cache:set2(STORE_PATHS_CACHE, cache_key, paths)
memory_cache:set2(STORE_PATHS_CACHE, cache_key, paths)
@@ -474,10 +468,11 @@ local function parse_store_paths_from_env(env_vars, opt)
end
-- STORE PATH EXTRACTION FUNCTIONS
+-- Each returns only the direct root path(s) for that source.
--- extract store paths from nix shell
-local function get_store_paths_nix_shell(opt)
- if not is_in_nix_shell() then
+-- Source: nix-shell / nix develop / stdenv.mkDerivation
+local function get_store_paths_build_inputs(opt)
+ if not is_in_nix_environment() then
return {}
end
@@ -491,138 +486,77 @@ local function get_store_paths_nix_shell(opt)
return parse_store_paths_from_env(build_env_vars, opt)
end
--- Find package from current user's nix profile, includes nix-env installed packages
--- Note: nix-env only lists one output in the profile list
--- $ nix-env -iA nixpkgs.<package> # installs multiple outputs, but only one is listed in the profile
--- this can cause issues if the main output does not contain the necessary files
--- Example: zlib.dev contains the headers, but zlib only contains the library
--- there does not seem to be an straight-forward way to find all outputs...
--- It is better to use nix profile like:
--- $ nix profile install 'nixpkgs#zlib^*'' # installs all outputs
--- $ nix profile install 'nixpkgs#zlib^dev' # installs only the dev output
-local function get_store_paths_nix_profile(opt)
- local nix = find_tool("nix")
- if not nix then
- return {}
- end
-
- return get_store_paths_from_command(
- nix.program,
- {"profile", "list", "--extra-experimental-features", "nix-command flakes"},
- opt
- )
-end
-
--- Popular nix-community tool to declaratively manage user environments (NixOS and non-NixOS)
-local function get_store_paths_home_manager_tool(opt)
- local home_manager = find_tool("home-manager")
- if not home_manager then
- return {}
- end
-
- return get_store_paths_from_command(
- home_manager.program,
- {"packages"},
- opt
- )
-end
-
--- Home manager can be installed as a module in nixos, in which case the home-manager tool is missing.
+-- Source: home-manager profile (/etc/profiles/per-user/$USER)
local function get_store_paths_home_manager_profile(opt)
- local nix_store = find_tool("nix-store")
- if not nix_store then
- return {}
- end
-
local user = os.getenv("USER") or "unknown"
local user_profile = "/etc/profiles/per-user/" .. user
+
if not os.isdir(user_profile) then
+ if opt and (opt.verbose or option.get("verbose")) then
+ print("Nix: Home-manager profile not found at " .. user_profile)
+ end
return {}
end
- return get_store_paths_from_command(
- nix_store.program,
- {"--query", "--requisites", user_profile},
- opt
- )
+ if opt and (opt.verbose or option.get("verbose")) then
+ print("Nix: Using home-manager profile at " .. user_profile)
+ end
+ return {user_profile}
end
--- nixos-option is not always configured properly, but if it is, we can find user/system packages
-local function get_store_paths_nixos_user_packages(opt)
- local nixos_option = find_tool("nixos-option")
- if not nixos_option then
- return {}
- end
-
- local user = os.getenv("USER") or "unknown"
- local output = try {function()
- local outdata = os.iorunv(nixos_option.program, {"users.users." .. user .. ".packages"})
- if outdata then
- return outdata:trim()
- end
- return outdata
- end}
+-- Source: nix profile / nix-env (~/.nix-profile)
+-- Both `nix profile install` and `nix-env -i` install into ~/.nix-profile
+-- Note: nix-env only lists one output in the profile list
+-- $ nix-env -iA nixpkgs.<package> # installs multiple outputs, but only one is listed in the profile
+-- this can cause issues if the main output does not contain the necessary files
+-- Example: zlib.dev contains the headers, but zlib only contains the library
+-- there does not seem to be an straight-forward way to find all outputs...
+-- It is better to use nix profile like:
+-- $ nix profile install 'nixpkgs#zlib^*'' # installs all outputs
+-- $ nix profile install 'nixpkgs#zlib^dev' # installs only the dev output
+local function get_store_paths_from_user_profile(opt)
+ local user_profile = path.join(os.home(), ".nix-profile")
- if output then
- local store_paths = {}
- for store_path in output:gmatch('(/nix/store/[^"\'%s]+)') do
- table.insert(store_paths, store_path)
+ if not os.isdir(user_profile) then
+ if opt and (opt.verbose or option.get("verbose")) then
+ print("Nix: User profile not found at " .. user_profile)
end
-
- return follow_propagated_inputs(store_paths, opt)
- end
- return {}
-end
-
--- extract store paths from nixos system packages
-local function get_store_paths_nixos_system_packages(opt)
- local nixos_option = find_tool("nixos-option")
- if not nixos_option then
return {}
end
- local output = try {function()
- local outdata = os.iorunv(nixos_option.program, {"environment.systemPackages"})
- if outdata then
- return outdata:trim()
- end
- return outdata
- end}
-
- if output then
- local store_paths = {}
- for store_path in output:gmatch('(/nix/store/[^"\'%s]+)') do
- table.insert(store_paths, store_path)
- end
-
- return follow_propagated_inputs(store_paths, opt)
+ if opt and (opt.verbose or option.get("verbose")) then
+ print("Nix: Using user profile at " .. user_profile)
end
- return {}
+ return {user_profile}
end
--- Includes all system/user/home-manager packages
-local function get_store_paths_nixos_current_system(opt)
- local nix_store = find_tool("nix-store")
- if not nix_store then
+-- Source: NixOS system profile
+-- Use /run/current-system/sw
+local function get_store_paths_nixos_system_profile(opt)
+ local sw = "/run/current-system/sw"
+ if not os.isdir(sw) then
+ if opt and (opt.verbose or option.get("verbose")) then
+ print("Nix: NixOS system profile not found at " .. sw)
+ end
return {}
end
- if not os.isdir("/run/current-system") then
- return {}
+ if opt and (opt.verbose or option.get("verbose")) then
+ print("Nix: Using NixOS system profile at " .. sw)
end
-
- return get_store_paths_from_command(
- nix_store.program,
- {"--query", "--requisites", "/run/current-system"},
- opt
- )
+ return {sw}
end
--- get all store paths from all nix environments with caching
local function get_all_store_paths(opt)
local cache = get_nix_cache()
local memory_cache = get_memory_cache()
- local cache_key = "all_environments:" .. generate_env_cache_key()
+
+ -- Cache key includes enabled sources so changing them busts the cache
+ local sources_key = (resolve_source_opt("nix_shell", true, opt) and "1" or "0")
+ .. (resolve_source_opt("homemanager", false, opt) and "1" or "0")
+ .. (resolve_source_opt("nix_profile", false, opt) and "1" or "0")
+ .. (resolve_source_opt("nixos_system", false, opt) and "1" or "0")
+ local cache_key = "all_environments:" .. sources_key .. ":" .. generate_env_cache_key()
-- Check memory cache first
local cached_paths = memory_cache:get2(STORE_PATHS_CACHE, cache_key)
@@ -637,26 +571,27 @@ local function get_all_store_paths(opt)
return cached_paths
end
- -- Extract from all sources
+ -- Collect root paths from each enabled source in priority order
local all_paths = {}
local seen = {}
-
- local get_store_path_functions = {
- get_store_paths_nix_shell,
- get_store_paths_nix_profile,
- get_store_paths_home_manager_tool,
- get_store_paths_home_manager_profile,
- get_store_paths_nixos_user_packages,
- get_store_paths_nixos_system_packages,
- get_store_paths_nixos_current_system
+
+ local sources = {
+ {key = "nix_shell", default = true, fn = get_store_paths_build_inputs},
+ {key = "homemanager", default = false, fn = get_store_paths_home_manager_profile},
+ {key = "nix_profile", default = false, fn = get_store_paths_from_user_profile},
+ {key = "nixos_system", default = false, fn = get_store_paths_nixos_system_profile},
}
-
- for _, func in ipairs(get_store_path_functions) do
- local paths = func(opt)
- for _, store_path in ipairs(paths) do
- if not seen[store_path] then
- seen[store_path] = true
- table.insert(all_paths, store_path)
+
+ for _, source in ipairs(sources) do
+ if resolve_source_opt(source.key, source.default, opt) then
+ if opt and (opt.verbose or option.get("verbose")) then
+ print("Nix: Searching source: " .. source.key)
+ end
+ for _, store_path in ipairs(source.fn(opt)) do
+ if not seen[store_path] then
+ seen[store_path] = true
+ table.insert(all_paths, store_path)
+ end
end
end
end
@@ -667,7 +602,7 @@ local function get_all_store_paths(opt)
cache:save()
if opt and (opt.verbose or option.get("verbose")) then
- print("Nix: Found " .. #all_paths .. " total store paths across all environments")
+ print("Nix: Found " .. #all_paths .. " root paths across enabled sources")
end
return all_paths
@@ -767,7 +702,7 @@ local function extract_package_info(store_paths, package_name, opt)
end
end
- -- Then find their dependencies
+ -- Then find their dependencies (only for the matched package, not the whole source)
local all_deps = follow_propagated_inputs(filtered_paths, opt)
filtered_paths = table.unique(all_deps)
@@ -804,16 +739,10 @@ local function extract_package_info(store_paths, package_name, opt)
end
end
- -- include directories (and their subdirs)
+ -- include directories
local includedir = path.join(store_path, "include")
if os.isdir(includedir) then
pkg:add_includedir(includedir)
- local subdirs = try { function() return os.dirs(path.join(includedir, "*")) end } or {}
- for _, subdir in ipairs(subdirs) do
- if os.isdir(subdir) then
- pkg:add_includedir(subdir)
- end
- end
end
-- bin
@@ -923,7 +852,7 @@ local function find_all_with_pkgconfig(package_name, store_paths, opt)
end
end
- -- Add dependencies of matching packages
+ -- Add dependencies of matching packages (only after filtering to matched package)
filtered_paths = follow_propagated_inputs(filtered_paths, opt)
local all_results = {}
@@ -1067,11 +996,24 @@ end
-- find package using the nix package manager
--
-- @param name the package name
--- @param opt the options, e.g. {verbose = true, version = "1.12.x")
-
+-- @param opt the options, e.g. {verbose = true, version = "1.12.x",
+-- configs = {use_nix_profile = true}}
+--
+-- Each source can be toggled:
+-- add_requires("nix::zlib", {configs = {use_nix_profile = true}})
function main(name, opt)
opt = opt or {}
+ -- only the default source (nix_shell) is enabled but we are
+ -- not inside a nix-shell or stdenv.mkDerivation build
+ local only_nix_shell = resolve_source_opt("nix_shell", true, opt)
+ and not resolve_source_opt("homemanager", false, opt)
+ and not resolve_source_opt("nix_profile", false, opt)
+ and not resolve_source_opt("nixos_system", false, opt)
+ if only_nix_shell and not is_in_nix_environment() then
+ return
+ end
+
-- ensure a stable env cache key is available for the whole run
local memory_cache = get_memory_cache()
memory_cache:set("last_env_key", generate_env_cache_key())
@@ -1083,7 +1025,7 @@ function main(name, opt)
return
end
- -- Get all store paths from all nix environments (cached)
+ -- Get root paths from all enabled sources
local store_paths = get_all_store_paths(opt)
if #store_paths == 0 then
if opt and (opt.verbose or option.get("verbose")) then
@@ -1092,7 +1034,7 @@ function main(name, opt)
return nil
end
- -- Extract all package info (cached)
+ -- Extract all package info
local packages = extract_package_info(store_paths, name, opt)
local keys = table.keys(packages)
if not packages or #keys == 0 then