Skip to content

Commit 4481279

Browse files
authored
Merge pull request #6818 from ZZBaron/nix/devshell-support
Fix Nix Package Detection in nix-shell Environment
2 parents 2d69c10 + 80fcef3 commit 4481279

File tree

1 file changed

+181
-28
lines changed

1 file changed

+181
-28
lines changed

xmake/modules/package/manager/nix/find_package.lua

Lines changed: 181 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,113 @@ import("lib.detect.find_tool")
2424
import("private.core.base.is_cross")
2525
import("package.manager.pkgconfig.find_package", {alias = "find_package_from_pkgconfig"})
2626

27+
-- check if we're in a nix-shell environment
28+
function _in_nix_shell()
29+
local in_nix_shell = os.getenv("IN_NIX_SHELL")
30+
return in_nix_shell == "pure" or in_nix_shell == "impure"
31+
end
32+
33+
-- extract store paths from nix environment variables with better filtering
34+
function _extract_nix_store_paths(env_var_name, env_var_value)
35+
local paths = {}
36+
local seen = {}
37+
38+
if env_var_value == "" then
39+
return paths
40+
end
41+
42+
-- Handle different environment variable formats
43+
local separators = {
44+
PATH = ":",
45+
PKG_CONFIG_PATH = ":",
46+
LIBRARY_PATH = ":",
47+
LD_LIBRARY_PATH = ":",
48+
C_INCLUDE_PATH = ":",
49+
CPLUS_INCLUDE_PATH = ":",
50+
NIX_LDFLAGS = "%s", -- space separated, may contain -L flags
51+
NIX_CFLAGS_COMPILE = "%s" -- space separated, may contain -I flags
52+
}
53+
54+
local separator = separators[env_var_name] or ":"
55+
local pattern = separator == ":" and "[^:]+" or "[^%s]+"
56+
57+
for item in env_var_value:gmatch(pattern) do
58+
local clean_item = item
59+
60+
-- Remove flag prefixes for compiler/linker flags
61+
if env_var_name == "NIX_LDFLAGS" then
62+
clean_item = item:gsub("^%-L", "")
63+
elseif env_var_name == "NIX_CFLAGS_COMPILE" then
64+
clean_item = item:gsub("^%-[iI]system%s*", ""):gsub("^%-I", "")
65+
end
66+
67+
if clean_item:startswith("/nix/store/") then
68+
local store_path = clean_item:match("(/nix/store/[^/]+)")
69+
if store_path and not seen[store_path] then
70+
seen[store_path] = true
71+
table.insert(paths, store_path)
72+
end
73+
end
74+
end
75+
76+
return paths
77+
end
78+
79+
-- get current shell buildInputs from environment with improved detection
80+
function _get_shell_build_inputs()
81+
local all_paths = {}
82+
local seen = {}
83+
84+
if not _in_nix_shell() then
85+
return all_paths
86+
end
87+
88+
-- Environment variables to check for nix store paths
89+
local env_vars = {
90+
"PATH",
91+
"PKG_CONFIG_PATH",
92+
"LIBRARY_PATH",
93+
"LD_LIBRARY_PATH",
94+
"C_INCLUDE_PATH",
95+
"CPLUS_INCLUDE_PATH",
96+
"NIX_LDFLAGS",
97+
"NIX_CFLAGS_COMPILE"
98+
}
99+
100+
for _, env_var in ipairs(env_vars) do
101+
local env_value = os.getenv(env_var) or ""
102+
local paths = _extract_nix_store_paths(env_var, env_value)
103+
104+
for _, path in ipairs(paths) do
105+
if not seen[path] then
106+
seen[path] = true
107+
table.insert(all_paths, path)
108+
end
109+
end
110+
end
111+
112+
return all_paths
113+
end
114+
27115
-- get all nix store paths currently available in environment
28116
function _get_available_nix_paths()
29117
local paths = {}
30118
local seen = {}
31119

32-
-- Get paths from environment PATH
120+
-- First, get paths from current shell if we're in nix-shell
121+
if _in_nix_shell() then
122+
local shell_paths = _get_shell_build_inputs()
123+
for _, path in ipairs(shell_paths) do
124+
if not seen[path] then
125+
seen[path] = true
126+
table.insert(paths, path)
127+
end
128+
end
129+
end
130+
131+
-- Get paths from environment PATH (additional check)
33132
local env_path = os.getenv("PATH") or ""
133+
34134
for dir in env_path:gmatch("[^:]+") do
35135
if dir:startswith("/nix/store/") then
36136
local store_path = dir:match("(/nix/store/[^/]+)")
@@ -46,11 +146,12 @@ function _get_available_nix_paths()
46146
os.getenv("NIX_PROFILES") or "",
47147
(os.getenv("HOME") or "") .. "/.nix-profile",
48148
"/nix/var/nix/profiles/default",
49-
"/run/current-system/sw" -- NixOS system packages
149+
"/run/current-system/sw" -- NixOS
50150
}
51151

52152
for _, location in ipairs(env_locations) do
53153
if location ~= "" and os.isdir(location) then
154+
54155
-- Check if it's a symlink to store path
55156
local target = try {function()
56157
return os.iorunv("readlink", {"-f", location}):trim()
@@ -81,16 +182,64 @@ function _get_available_nix_paths()
81182
end
82183
end
83184
end
84-
185+
85186
return paths
86187
end
87188

88-
-- find package in a specific nix store path
189+
-- check if a store path actually contains the requested package
190+
function _validate_package_in_store_path(store_path, name)
191+
192+
-- Check if the store path name contains the package name
193+
local store_name = path.basename(store_path):lower()
194+
local search_name = name:lower()
195+
196+
-- Look for exact match, or package name in the store path
197+
local name_match = store_name:find(search_name, 1, true) or
198+
store_name:find((search_name:gsub("%-", "%%-"))) -- handle hyphens
199+
200+
if name_match then
201+
return true
202+
end
203+
204+
-- Check for libraries with the package name
205+
local libdir = path.join(store_path, "lib")
206+
if os.isdir(libdir) then
207+
local libfiles = os.files(path.join(libdir, "lib" .. name .. ".*"))
208+
if #libfiles > 0 then
209+
return true
210+
end
211+
end
212+
213+
-- Check for pkg-config files
214+
local pkgconfigdirs = {
215+
path.join(store_path, "lib", "pkgconfig"),
216+
path.join(store_path, "share", "pkgconfig")
217+
}
218+
219+
for _, pcdir in ipairs(pkgconfigdirs) do
220+
if os.isdir(pcdir) then
221+
local pcfiles = os.files(path.join(pcdir, name .. ".pc"))
222+
if #pcfiles > 0 then
223+
return true
224+
end
225+
end
226+
end
227+
228+
return false
229+
end
230+
231+
-- find package in a specific nix store path with validation
89232
function _find_in_store_path(store_path, name)
233+
90234
if not os.isdir(store_path) then
91235
return nil
92236
end
93237

238+
-- First validate that this store path actually contains our package
239+
if not _validate_package_in_store_path(store_path, name) then
240+
return nil
241+
end
242+
94243
local result = {}
95244

96245
-- Find include directories
@@ -106,7 +255,7 @@ function _find_in_store_path(store_path, name)
106255
result.links = {}
107256
result.libfiles = {}
108257

109-
-- Scan for library files
258+
-- Scan for library files related to our package
110259
local libfiles = os.files(path.join(libdir, "*.so*"),
111260
path.join(libdir, "*.a"),
112261
path.join(libdir, "*.dylib*"))
@@ -118,8 +267,10 @@ function _find_in_store_path(store_path, name)
118267
filename:match("^lib(.+)%.dylib")
119268

120269
if linkname then
121-
table.insert(result.links, linkname)
122-
table.insert(result.libfiles, libfile)
270+
if linkname == name or linkname:find(name, 1, true) then
271+
table.insert(result.links, linkname)
272+
table.insert(result.libfiles, libfile)
273+
end
123274

124275
if filename:endswith(".a") then
125276
result.static = true
@@ -151,7 +302,7 @@ function _find_in_store_path(store_path, name)
151302
end
152303

153304
-- Return result if we found anything useful
154-
if result.includedirs or result.linkdirs then
305+
if result.includedirs or (result.links and #result.links > 0) then
155306
return result
156307
end
157308

@@ -208,9 +359,9 @@ function main(name, opt)
208359
-- Get all available Nix store paths
209360
local nix_paths = _get_available_nix_paths()
210361

211-
-- Search through available paths first (unless we're forced to build)
212-
if #nix_paths > 0 and not force_nix then
213-
for _, store_path in ipairs(nix_paths) do
362+
-- Search through available paths first (prioritize shell environment)
363+
if #nix_paths > 0 then
364+
for i, store_path in ipairs(nix_paths) do
214365
local result = _find_in_store_path(store_path, actual_name)
215366
if result then
216367
if opt.verbose or option.get("verbose") then
@@ -221,24 +372,26 @@ function main(name, opt)
221372
end
222373
end
223374

224-
-- If not found in available paths or forced to build, try building
225-
local storepath = nil
226-
227-
-- Try modern nix first
228-
storepath = _try_modern_nix_build(actual_name)
229-
230-
-- Fallback to legacy nix-build
231-
if not storepath then
232-
storepath = _try_legacy_nix_build(actual_name)
233-
end
234-
235-
if storepath and os.isdir(storepath) then
236-
local result = _find_in_store_path(storepath, actual_name)
237-
if result then
238-
if opt.verbose or option.get("verbose") then
239-
print("Built and found " .. actual_name .. " in: " .. storepath)
375+
-- If not found in available paths and not in nix-shell, try building
376+
if not _in_nix_shell() or force_nix then
377+
local storepath = nil
378+
379+
-- Try modern nix first
380+
storepath = _try_modern_nix_build(actual_name)
381+
382+
-- Fallback to legacy nix-build
383+
if not storepath then
384+
storepath = _try_legacy_nix_build(actual_name)
385+
end
386+
387+
if storepath and os.isdir(storepath) then
388+
local result = _find_in_store_path(storepath, actual_name)
389+
if result then
390+
if opt.verbose or option.get("verbose") then
391+
print("Built and found " .. actual_name .. " in: " .. storepath)
392+
end
393+
return result
240394
end
241-
return result
242395
end
243396
end
244397

0 commit comments

Comments
 (0)