diff options
| author | zzbaron <[email protected]> | 2025-09-22 12:50:03 -0400 |
|---|---|---|
| committer | zzbaron <[email protected]> | 2025-09-22 12:50:47 -0400 |
| commit | 998b9990ee6141106cebca48f919ed82ba591550 (patch) | |
| tree | 3379651400859dfd4c1fc99c2d8ffcbc69ce9bd0 | |
| parent | f1a64ad50e22c55a08a2553669aba152f70d9eb6 (diff) | |
nix: simplifying package detection
| -rw-r--r-- | xmake/modules/package/manager/nix/find_package.lua | 244 |
1 files changed, 35 insertions, 209 deletions
diff --git a/xmake/modules/package/manager/nix/find_package.lua b/xmake/modules/package/manager/nix/find_package.lua index fb14a6b90..2fec59de9 100644 --- a/xmake/modules/package/manager/nix/find_package.lua +++ b/xmake/modules/package/manager/nix/find_package.lua @@ -29,32 +29,25 @@ function _follow_propagated_inputs(store_paths, opt, visited) visited = visited or {} local all_paths = {} local seen = {} - - -- Add initial paths + for _, store_path in ipairs(store_paths) do if not seen[store_path] then seen[store_path] = true table.insert(all_paths, store_path) end end - - -- Process each path + local i = 1 while i <= #all_paths do local store_path = all_paths[i] - if not visited[store_path] then visited[store_path] = true - - -- Check for propagated-build-inputs file local prop_file = path.join(store_path, "nix-support", "propagated-build-inputs") if os.isfile(prop_file) then - local content = try {function() + local content = try {function() return io.readfile(prop_file):trim() end} - if content and content ~= "" then - -- Parse propagated paths for prop_path in content:gmatch("%S+") do if prop_path:startswith("/nix/store/") and not seen[prop_path] then seen[prop_path] = true @@ -67,10 +60,8 @@ function _follow_propagated_inputs(store_paths, opt, visited) end end end - i = i + 1 end - return all_paths end @@ -78,16 +69,14 @@ end function _parse_store_paths_from_env(env_vars, opt) local paths = {} local seen = {} - + if opt and (opt.verbose or option.get("verbose")) then print("Nix: Parsing store paths from environment variables") end - + for _, var_name in ipairs(env_vars) do local env_value = os.getenv(var_name) or "" - if env_value ~= "" then - -- Split by spaces and colons, extract store paths for item in env_value:gmatch("[^%s:]+") do if item:startswith("/nix/store/") then local store_path = item:match("(/nix/store/[^/]+)") @@ -102,92 +91,49 @@ function _parse_store_paths_from_env(env_vars, opt) end end end - - -- Follow propagated build inputs + paths = _follow_propagated_inputs(paths, opt) - return paths end --- check if we're in a nix-shell environment function _in_nix_shell() local in_nix_shell = os.getenv("IN_NIX_SHELL") return in_nix_shell == "pure" or in_nix_shell == "impure" end --- check if a store path likely contains the requested package function _path_matches_package(store_path, package_name, opt) local path_name = path.basename(store_path) local package_name_lower = package_name:lower() - - -- Extract package name from store path - -- Format: hash-packagename-version[-output] local package_base = path_name:match("^[^%-]+-([^%-]+)") if package_base then local package_base_lower = package_base:lower() - - -- Exact match if package_base_lower == package_name_lower then return true end - - -- Partial match (package name contains or is contained in the base name) if package_base_lower:find(package_name_lower, 1, true) or package_name_lower:find(package_base_lower, 1, true) then return true end end - return false end --- group store paths by package base name -function _group_store_paths_by_package(store_paths, opt) - local packages = {} - - for _, store_path in ipairs(store_paths) do - if os.isdir(store_path) then - local path_name = path.basename(store_path) - - -- Extract package name (everything before version or output suffix) - -- Format: hash-packagename-version[-output] - local package_base = path_name:match("^[^%-]+-([^%-]+)") - if package_base then - if not packages[package_base] then - packages[package_base] = {} - end - table.insert(packages[package_base], store_path) - end - end - end - - return packages -end - --- pkg-config search that handles all outputs function _find_with_pkgconfig(package_name, store_paths, opt) - - -- Only use store paths that might contain the requested package for pkg-config local relevant_paths = {} for _, store_path in ipairs(store_paths) do if _path_matches_package(store_path, package_name, opt) then table.insert(relevant_paths, store_path) end end - if #relevant_paths == 0 then return nil end - - -- Collect all pkg-config directories from relevant store paths local all_pkgconfig_dirs = {} local pkgconfig_env_additions = {} - for _, store_path in ipairs(relevant_paths) do local pkgconfig_dirs = { path.join(store_path, "lib", "pkgconfig"), path.join(store_path, "share", "pkgconfig") } - for _, pkgconfig_dir in ipairs(pkgconfig_dirs) do if os.isdir(pkgconfig_dir) then table.insert(all_pkgconfig_dirs, pkgconfig_dir) @@ -195,63 +141,42 @@ function _find_with_pkgconfig(package_name, store_paths, opt) end end end - if #all_pkgconfig_dirs == 0 then return nil end - - -- Set up PKG_CONFIG_PATH environment for search local original_pkg_config_path = os.getenv("PKG_CONFIG_PATH") or "" local new_pkg_config_path = table.concat(pkgconfig_env_additions, ":") if original_pkg_config_path ~= "" then new_pkg_config_path = new_pkg_config_path .. ":" .. original_pkg_config_path end - - -- set PKG_CONFIG_PATH os.setenv("PKG_CONFIG_PATH", new_pkg_config_path) - - -- Try pkg-config with the enhanced path - local result = nil - - -- First try direct package name - result = find_package_from_pkgconfig(package_name) - + local result = find_package_from_pkgconfig(package_name) if result then return result end - - if not result then - -- Try alternative names - check what .pc files actually exist - for _, pkgconfig_dir in ipairs(all_pkgconfig_dirs) do - local pc_files = try {function() - return os.files(path.join(pkgconfig_dir, "*.pc")) - end} or {} - - for _, pc_file in ipairs(pc_files) do - local pc_name = path.basename(pc_file):match("^(.+)%.pc$") - if pc_name then - local name_lower = package_name:lower() - local pc_lower = pc_name:lower() - - -- Check for partial matches - if pc_lower:find(name_lower, 1, true) or name_lower:find(pc_lower, 1, true) then - result = find_package_from_pkgconfig(pc_name) - if result then - break - end + for _, pkgconfig_dir in ipairs(all_pkgconfig_dirs) do + local pc_files = try {function() + return os.files(path.join(pkgconfig_dir, "*.pc")) + end} or {} + for _, pc_file in ipairs(pc_files) do + local pc_name = path.basename(pc_file):match("^(.+)%.pc$") + if pc_name then + local name_lower = package_name:lower() + local pc_lower = pc_name:lower() + if pc_lower:find(name_lower, 1, true) or name_lower:find(pc_lower, 1, true) then + result = find_package_from_pkgconfig(pc_name) + if result then + break end end end - if result then break end end + if result then break end end - return result end --- extract package info from relevant store paths only function _extract_package_info(store_paths, package_name, opt) - local result = { includedirs = {}, bindirs = {}, @@ -259,18 +184,13 @@ function _extract_package_info(store_paths, package_name, opt) links = {}, libfiles = {} } - - -- First, try pkg-config search with only relevant paths local pkgconfig_result = _find_with_pkgconfig(package_name, store_paths, opt) if pkgconfig_result then return pkgconfig_result end - - -- Find paths that match the requested package local main_package_paths = {} local dependency_paths = {} local found_main_package = false - for _, store_path in ipairs(store_paths) do if _path_matches_package(store_path, package_name, opt) then table.insert(main_package_paths, store_path) @@ -279,28 +199,19 @@ function _extract_package_info(store_paths, package_name, opt) table.insert(dependency_paths, store_path) end end - if not found_main_package then return nil end - - -- Process main package paths first (for bins and primary libs) for _, store_path in ipairs(main_package_paths) do - - -- Add include directories local includedir = path.join(store_path, "include") if os.isdir(includedir) then table.insert(result.includedirs, includedir) if opt and (opt.verbose or option.get("verbose")) then print("Nix: Found main package include dir: " .. includedir) end - - -- Recursively add one level of subdirectories to handle cases where - -- headers are organized in subdirectories (openexr, etc.) - local subdirs = try {function() - return os.dirs(path.join(includedir, "*")) + local subdirs = try {function() + return os.dirs(path.join(includedir, "*")) end} or {} - for _, subdir in ipairs(subdirs) do if os.isdir(subdir) then table.insert(result.includedirs, subdir) @@ -310,8 +221,6 @@ function _extract_package_info(store_paths, package_name, opt) end end end - - -- Add bin directories local bindir = path.join(store_path, "bin") if os.isdir(bindir) then table.insert(result.bindirs, bindir) @@ -319,34 +228,28 @@ function _extract_package_info(store_paths, package_name, opt) print("Nix: Found main package bin dir: " .. bindir) end end - - -- Add lib directories and scan for libraries local libdir = path.join(store_path, "lib") if os.isdir(libdir) then - -- Check if this lib dir actually contains libraries (not just cmake/pkgconfig) local libfiles = try {function() local files = {} local so_files = os.files(path.join(libdir, "*.so*")) or {} local a_files = os.files(path.join(libdir, "*.a")) or {} local dylib_files = os.files(path.join(libdir, "*.dylib*")) or {} for _, f in ipairs(so_files) do table.insert(files, f) end - for _, f in ipairs(a_files) do table.insert(files, f) end + for _, f in ipairs(a_files) do table.insert(files, f) end for _, f in ipairs(dylib_files) do table.insert(files, f) end return files end} or {} - if #libfiles > 0 then table.insert(result.linkdirs, libdir) if opt and (opt.verbose or option.get("verbose")) then print("Nix: Found main package lib dir: " .. libdir .. " (" .. #libfiles .. " libraries)") end - for _, libfile in ipairs(libfiles) do local filename = path.filename(libfile) - local linkname = filename:match("^lib(.+)%.so") or - filename:match("^lib(.+)%.a") or + local linkname = filename:match("^lib(.+)%.so") or + filename:match("^lib(.+)%.a") or filename:match("^lib(.+)%.dylib") - if linkname then table.insert(result.links, linkname) table.insert(result.libfiles, libfile) @@ -356,10 +259,8 @@ function _extract_package_info(store_paths, package_name, opt) end end else - -- If no actual libraries but has cmake/pkgconfig, still add for potential cmake usage local has_cmake = os.isdir(path.join(libdir, "cmake")) local has_pkgconfig = os.isdir(path.join(libdir, "pkgconfig")) - if has_cmake or has_pkgconfig then table.insert(result.linkdirs, libdir) if opt and (opt.verbose or option.get("verbose")) then @@ -369,24 +270,16 @@ function _extract_package_info(store_paths, package_name, opt) end end end - - -- Process dependency paths (include dirs and libs only, no bins) for _, store_path in ipairs(dependency_paths) do - - -- Add include directories from dependencies local includedir = path.join(store_path, "include") if os.isdir(includedir) then table.insert(result.includedirs, includedir) if opt and (opt.verbose or option.get("verbose")) then print("Nix: Found dependency include dir: " .. includedir) end - - -- Recursively add one level of subdirectories to handle cases where - -- headers are organized in subdirectories - local subdirs = try {function() - return os.dirs(path.join(includedir, "*")) + local subdirs = try {function() + return os.dirs(path.join(includedir, "*")) end} or {} - for _, subdir in ipairs(subdirs) do if os.isdir(subdir) then table.insert(result.includedirs, subdir) @@ -396,34 +289,28 @@ function _extract_package_info(store_paths, package_name, opt) end end end - - -- Add lib directories and scan for libraries from dependencies local libdir = path.join(store_path, "lib") if os.isdir(libdir) then - -- Check if this lib dir actually contains libraries (not just cmake/pkgconfig) local libfiles = try {function() local files = {} local so_files = os.files(path.join(libdir, "*.so*")) or {} local a_files = os.files(path.join(libdir, "*.a")) or {} local dylib_files = os.files(path.join(libdir, "*.dylib*")) or {} for _, f in ipairs(so_files) do table.insert(files, f) end - for _, f in ipairs(a_files) do table.insert(files, f) end + for _, f in ipairs(a_files) do table.insert(files, f) end for _, f in ipairs(dylib_files) do table.insert(files, f) end return files end} or {} - if #libfiles > 0 then table.insert(result.linkdirs, libdir) if opt and (opt.verbose or option.get("verbose")) then print("Nix: Found dependency lib dir: " .. libdir .. " (" .. #libfiles .. " libraries)") end - for _, libfile in ipairs(libfiles) do local filename = path.filename(libfile) - local linkname = filename:match("^lib(.+)%.so") or - filename:match("^lib(.+)%.a") or + local linkname = filename:match("^lib(.+)%.so") or + filename:match("^lib(.+)%.a") or filename:match("^lib(.+)%.dylib") - if linkname then table.insert(result.links, linkname) table.insert(result.libfiles, libfile) @@ -433,10 +320,8 @@ function _extract_package_info(store_paths, package_name, opt) end end else - -- If no actual libraries but has cmake/pkgconfig, still add for potential cmake usage local has_cmake = os.isdir(path.join(libdir, "cmake")) local has_pkgconfig = os.isdir(path.join(libdir, "pkgconfig")) - if has_cmake or has_pkgconfig then table.insert(result.linkdirs, libdir) if opt and (opt.verbose or option.get("verbose")) then @@ -446,8 +331,6 @@ function _extract_package_info(store_paths, package_name, opt) end end end - - -- Remove duplicates local function remove_duplicates(arr) local seen = {} local clean = {} @@ -459,19 +342,16 @@ function _extract_package_info(store_paths, package_name, opt) end return clean end - result.includedirs = remove_duplicates(result.includedirs) result.bindirs = remove_duplicates(result.bindirs) result.linkdirs = remove_duplicates(result.linkdirs) result.links = remove_duplicates(result.links) result.libfiles = remove_duplicates(result.libfiles) - - -- Return result only if we found the main package AND have useful information if (#result.includedirs > 0) or (#result.bindirs > 0) or (#result.links > 0) or (#result.linkdirs > 0) then if opt and (opt.verbose or option.get("verbose")) then print("Nix: DEBUG: Package info extraction succeeded for '" .. package_name .. "'") - print("Nix: DEBUG: Found " .. #result.includedirs .. " include dirs, " .. - #result.bindirs .. " bin dirs, " .. #result.linkdirs .. " link dirs, " .. + print("Nix: DEBUG: Found " .. #result.includedirs .. " include dirs, " .. + #result.bindirs .. " bin dirs, " .. #result.linkdirs .. " link dirs, " .. #result.links .. " links") print("Nix: DEBUG: Main package paths: " .. #main_package_paths .. ", Dependency paths: " .. #dependency_paths) end @@ -484,26 +364,21 @@ function _extract_package_info(store_paths, package_name, opt) end end --- priority 1: nix shell (flake or legacy) function _find_in_nix_shell(package_name, opt) if not _in_nix_shell() then return nil end - - -- Parse buildInputs environment variables local build_env_vars = { "buildInputs", - "nativeBuildInputs", + "nativeBuildInputs", "propagatedBuildInputs", "propagatedNativeBuildInputs" } - local store_paths = _parse_store_paths_from_env(build_env_vars, opt) if #store_paths > 0 then if opt and (opt.verbose or option.get("verbose")) then print("Nix: Found " .. #store_paths .. " total store paths in nix-shell") end - local result = _extract_package_info(store_paths, package_name, opt) if result then if opt and (opt.verbose or option.get("verbose")) then @@ -512,33 +387,26 @@ function _find_in_nix_shell(package_name, opt) return result end end - return nil end --- priority 2: profile installs function _find_in_nix_profile(package_name, opt) local nix = find_tool("nix") if not nix then return nil end - local profile_list = try {function() return os.iorunv(nix.program, {"profile", "list", "--extra-experimental-features 'nix-command flakes'"}):trim() end} - if profile_list then local store_paths = {} for line in profile_list:gmatch("[^\n]+") do - -- Parse nix profile list output format local store_path = line:match("(/nix/store/[^%s]+)") if store_path then table.insert(store_paths, store_path) end end - if #store_paths > 0 then - -- Follow propagated inputs for profile packages too store_paths = _follow_propagated_inputs(store_paths, opt) local result = _extract_package_info(store_paths, package_name, opt) if result then @@ -549,21 +417,17 @@ function _find_in_nix_profile(package_name, opt) end end end - return nil end --- priority 3: home-manager (with tool) function _find_in_home_manager_tool(package_name, opt) local home_manager = find_tool("home-manager") if not home_manager then return nil end - local hm_packages = try {function() return os.iorunv(home_manager.program, {"packages"}):trim() end} - if hm_packages then local store_paths = {} for line in hm_packages:gmatch("[^\n]+") do @@ -572,7 +436,6 @@ function _find_in_home_manager_tool(package_name, opt) table.insert(store_paths, store_path) end end - if #store_paths > 0 then store_paths = _follow_propagated_inputs(store_paths, opt) local result = _extract_package_info(store_paths, package_name, opt) @@ -584,28 +447,22 @@ function _find_in_home_manager_tool(package_name, opt) end end end - return nil end --- priority 4: home-manager (without tool) function _find_in_home_manager_profile(package_name, opt) local nix_store = find_tool("nix-store") if not nix_store then return nil end - local user = os.getenv("USER") or "unknown" local user_profile = "/etc/profiles/per-user/" .. user - if not os.isdir(user_profile) then return nil end - local requisites = try {function() return os.iorunv(nix_store.program, {"--query", "--requisites", user_profile}):trim() end} - if requisites then local store_paths = {} for line in requisites:gmatch("[^\n]+") do @@ -613,9 +470,7 @@ function _find_in_home_manager_profile(package_name, opt) table.insert(store_paths, line) end end - if #store_paths > 0 then - -- Note: requisites already includes everything, no need to follow propagated inputs again local result = _extract_package_info(store_paths, package_name, opt) if result then if opt and (opt.verbose or option.get("verbose")) then @@ -625,28 +480,23 @@ function _find_in_home_manager_profile(package_name, opt) end end end - return nil end --- priority 5: nixos user packages function _find_in_nixos_user_packages(package_name, opt) local nixos_option = find_tool("nixos-option") if not nixos_option then return nil end - local user = os.getenv("USER") or "unknown" local user_packages = try {function() return os.iorunv(nixos_option.program, {"users.users." .. user .. ".packages"}):trim() end} - if user_packages then local store_paths = {} for store_path in user_packages:gmatch('(/nix/store/[^"\'%s]+)') do table.insert(store_paths, store_path) end - if #store_paths > 0 then store_paths = _follow_propagated_inputs(store_paths, opt) local result = _extract_package_info(store_paths, package_name, opt) @@ -658,27 +508,22 @@ function _find_in_nixos_user_packages(package_name, opt) end end end - return nil end --- priority 6: nixos system packages function _find_in_nixos_system_packages(package_name, opt) local nixos_option = find_tool("nixos-option") if not nixos_option then return nil end - local system_packages = try {function() return os.iorunv(nixos_option.program, {"environment.systemPackages"}):trim() end} - if system_packages then local store_paths = {} for store_path in system_packages:gmatch('(/nix/store/[^"\'%s]+)') do table.insert(store_paths, store_path) end - if #store_paths > 0 then store_paths = _follow_propagated_inputs(store_paths, opt) local result = _extract_package_info(store_paths, package_name, opt) @@ -690,25 +535,20 @@ function _find_in_nixos_system_packages(package_name, opt) end end end - return nil end --- priority 7: nixos current system function _find_in_nixos_current_system(package_name, opt) local nix_store = find_tool("nix-store") if not nix_store then return nil end - if not os.isdir("/run/current-system") then return nil end - local requisites = try {function() return os.iorunv(nix_store.program, {"--query", "--requisites", "/run/current-system"}):trim() end} - if requisites then local store_paths = {} for line in requisites:gmatch("[^\n]+") do @@ -716,9 +556,7 @@ function _find_in_nixos_current_system(package_name, opt) table.insert(store_paths, line) end end - if #store_paths > 0 then - -- Note: requisites already includes everything, no need to follow propagated inputs again local result = _extract_package_info(store_paths, package_name, opt) if result then if opt and (opt.verbose or option.get("verbose")) then @@ -728,28 +566,20 @@ function _find_in_nixos_current_system(package_name, opt) end end end - return nil end --- main find function function main(name, opt) opt = opt or {} - - -- Check for cross compilation if is_cross(opt.plat, opt.arch) then return end - - -- Handle nix:: prefix local actual_name = name local force_nix = false if name:startswith("nix::") then - actual_name = name:sub(5) -- Remove "nix::" prefix + actual_name = name:sub(5) force_nix = true end - - -- Search priority chain local search_functions = { _find_in_nix_shell, _find_in_nix_profile, @@ -759,18 +589,14 @@ function main(name, opt) _find_in_nixos_system_packages, _find_in_nixos_current_system } - for _, search_func in ipairs(search_functions) do local result = search_func(actual_name, opt) if result then return result end end - - -- No results found if force_nix and opt and (opt.verbose or option.get("verbose")) then print("Nix: Package " .. actual_name .. " not found in any nix environment") end - return nil end
\ No newline at end of file |
