diff options
| author | zzbaron <[email protected]> | 2026-03-29 01:31:10 -0400 |
|---|---|---|
| committer | zzbaron <[email protected]> | 2026-03-29 01:31:10 -0400 |
| commit | 10689dada1a50935295741dfbfc895e4f7020bd1 (patch) | |
| tree | b90667808c21aa2f86f4fbc056e9cb301e29b2ad | |
| parent | 76ecc812e959a271e2ebc476073948e354e643ea (diff) | |
nix: improve config and source selection
| -rw-r--r-- | xmake/modules/package/manager/nix/configurations.lua | 39 | ||||
| -rw-r--r-- | xmake/modules/package/manager/nix/find_package.lua | 278 |
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 |
