Skip to content

Commit e6c0922

Browse files
authored
Merge pull request #6791 from ZZBaron/feature/nix-support
Add Nix Package Manager Support
2 parents e641d13 + 7d23644 commit e6c0922

File tree

7 files changed

+553
-0
lines changed

7 files changed

+553
-0
lines changed

xmake/core/base/linuxos.lua

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,8 @@ function linuxos.name()
110110
name = "opensuse"
111111
elseif os_release:find("manjaro", 1, true) then
112112
name = "manjaro"
113+
elseif os_release:find("nixos", 1, true) then
114+
name = "nixos"
113115
end
114116
end
115117
end
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
--!A cross-platform build utility based on Lua
2+
--
3+
-- Licensed under the Apache License, Version 2.0 (the "License");
4+
-- you may not use this file except in compliance with the License.
5+
-- You may obtain a copy of the License at
6+
--
7+
-- http://www.apache.org/licenses/LICENSE-2.0
8+
--
9+
-- Unless required by applicable law or agreed to in writing, software
10+
-- distributed under the License is distributed on an "AS IS" BASIS,
11+
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
-- See the License for the specific language governing permissions and
13+
-- limitations under the License.
14+
--
15+
-- Copyright (C) 2015-present, Xmake Open Source Community.
16+
--
17+
-- @author ruki
18+
-- @file find_nix.lua
19+
--
20+
21+
-- imports
22+
import("lib.detect.find_program")
23+
import("lib.detect.find_programver")
24+
25+
-- find nix
26+
--
27+
-- @param opt the argument options, e.g. {version = true}
28+
--
29+
-- @return program, version
30+
--
31+
-- @code
32+
--
33+
-- local nix = find_nix()
34+
-- local nix, version = find_nix({version = true})
35+
--
36+
-- @endcode
37+
--
38+
function main(opt)
39+
-- init options
40+
opt = opt or {}
41+
42+
-- add common nix installation paths if no specific program is given
43+
if not opt.program then
44+
opt.paths = opt.paths or {}
45+
local nix_paths = {
46+
"/nix/var/nix/profiles/default/bin", -- multi-user installation
47+
"/home/" .. (os.getenv("USER") or "user") .. "/.nix-profile/bin", -- single user installation
48+
"/usr/local/bin", -- default path of nix when compiling nix from source
49+
}
50+
51+
-- NixOS-specific paths
52+
if linuxos.name() == "nixos" then
53+
table.insert(nix_paths, "/run/current-system/sw/bin")
54+
end
55+
56+
opt.paths = table.wrap(opt.paths)
57+
for _, nixpath in ipairs(nix_paths) do
58+
table.insert(opt.paths, nixpath)
59+
end
60+
end
61+
62+
-- find program
63+
local program = find_program(opt.program or "nix", opt)
64+
65+
-- find program version
66+
local version = nil
67+
if program and opt and opt.version then
68+
version = find_programver(program, opt, function (output)
69+
-- parse version from "nix (Nix) 2.18.1" format
70+
return output:match("nix %(Nix%) ([%d%.]+)")
71+
end)
72+
end
73+
74+
return program, version
75+
end

xmake/modules/package/manager/find_package.lua

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ function _find_package_with_builtin_rule(package_name, opt)
5959
local find_from_host = not is_cross(plat, arch)
6060
if find_from_host and not is_host("windows") then
6161
table.insert(managers, "brew")
62+
table.insert(managers, "nix")
6263
end
6364
-- vcpkg/conan support multi-platforms/architectures
6465
table.insert(managers, "vcpkg")

xmake/modules/package/manager/install_package.lua

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,11 @@ function _install_package(manager_name, package_name, opt)
4141
table.insert(managers, "portage")
4242
table.insert(managers, "brew")
4343
table.insert(managers, "zypper")
44+
table.insert(managers, "nix")
4445
elseif is_host("macosx") then
4546
table.insert(managers, "vcpkg")
4647
table.insert(managers, "brew")
48+
table.insert(managers, "nix")
4749
end
4850
assert(#managers > 0, "no suitable package manager!")
4951

Lines changed: 246 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,246 @@
1+
--!A cross-platform build utility based on Lua
2+
--
3+
-- Licensed under the Apache License, Version 2.0 (the "License");
4+
-- you may not use this file except in compliance with the License.
5+
-- You may obtain a copy of the License at
6+
--
7+
-- http://www.apache.org/licenses/LICENSE-2.0
8+
--
9+
-- Unless required by applicable law or agreed to in writing, software
10+
-- distributed under the License is distributed on an "AS IS" BASIS,
11+
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
-- See the License for the specific language governing permissions and
13+
-- limitations under the License.
14+
--
15+
-- Copyright (C) 2015-present, Xmake Open Source Community.
16+
--
17+
-- @author ruki
18+
-- @file find_package.lua
19+
--
20+
21+
-- imports
22+
import("core.base.option")
23+
import("lib.detect.find_tool")
24+
import("private.core.base.is_cross")
25+
import("package.manager.pkgconfig.find_package", {alias = "find_package_from_pkgconfig"})
26+
27+
-- get all nix store paths currently available in environment
28+
function _get_available_nix_paths()
29+
local paths = {}
30+
local seen = {}
31+
32+
-- Get paths from environment PATH
33+
local env_path = os.getenv("PATH") or ""
34+
for dir in env_path:gmatch("[^:]+") do
35+
if dir:startswith("/nix/store/") then
36+
local store_path = dir:match("(/nix/store/[^/]+)")
37+
if store_path and not seen[store_path] then
38+
seen[store_path] = true
39+
table.insert(paths, store_path)
40+
end
41+
end
42+
end
43+
44+
-- Get paths from common Nix environment locations
45+
local env_locations = {
46+
os.getenv("NIX_PROFILES") or "",
47+
(os.getenv("HOME") or "") .. "/.nix-profile",
48+
"/nix/var/nix/profiles/default",
49+
"/run/current-system/sw" -- NixOS system packages
50+
}
51+
52+
for _, location in ipairs(env_locations) do
53+
if location ~= "" and os.isdir(location) then
54+
-- Check if it's a symlink to store path
55+
local target = try {function()
56+
return os.iorunv("readlink", {"-f", location}):trim()
57+
end}
58+
59+
if target and target:startswith("/nix/store/") then
60+
local store_path = target:match("(/nix/store/[^/]+)")
61+
if store_path and not seen[store_path] then
62+
seen[store_path] = true
63+
table.insert(paths, store_path)
64+
end
65+
end
66+
67+
-- Also check for manifest (generation info)
68+
local manifest = path.join(location, "manifest.nix")
69+
if os.isfile(manifest) then
70+
local manifest_content = io.readfile(manifest)
71+
72+
if manifest_content then
73+
-- Extract store paths from manifest
74+
for store_path in manifest_content:gmatch('(/nix/store/[^"\'%s]+)') do
75+
if not seen[store_path] then
76+
seen[store_path] = true
77+
table.insert(paths, store_path)
78+
end
79+
end
80+
end
81+
end
82+
end
83+
end
84+
85+
return paths
86+
end
87+
88+
-- find package in a specific nix store path
89+
function _find_in_store_path(store_path, name)
90+
if not os.isdir(store_path) then
91+
return nil
92+
end
93+
94+
local result = {}
95+
96+
-- Find include directories
97+
local includedir = path.join(store_path, "include")
98+
if os.isdir(includedir) then
99+
result.includedirs = {includedir}
100+
end
101+
102+
-- Find libraries
103+
local libdir = path.join(store_path, "lib")
104+
if os.isdir(libdir) then
105+
result.linkdirs = {libdir}
106+
result.links = {}
107+
result.libfiles = {}
108+
109+
-- Scan for library files
110+
local libfiles = os.files(path.join(libdir, "*.so*"),
111+
path.join(libdir, "*.a"),
112+
path.join(libdir, "*.dylib*"))
113+
114+
for _, libfile in ipairs(libfiles) do
115+
local filename = path.filename(libfile)
116+
local linkname = filename:match("^lib(.+)%.so") or
117+
filename:match("^lib(.+)%.a") or
118+
filename:match("^lib(.+)%.dylib")
119+
120+
if linkname then
121+
table.insert(result.links, linkname)
122+
table.insert(result.libfiles, libfile)
123+
124+
if filename:endswith(".a") then
125+
result.static = true
126+
else
127+
result.shared = true
128+
end
129+
end
130+
end
131+
end
132+
133+
-- Find pkg-config files
134+
local pkgconfigdirs = {
135+
path.join(store_path, "lib", "pkgconfig"),
136+
path.join(store_path, "share", "pkgconfig")
137+
}
138+
139+
for _, pcdir in ipairs(pkgconfigdirs) do
140+
if os.isdir(pcdir) then
141+
local pcfiles = os.files(path.join(pcdir, name .. ".pc"))
142+
if #pcfiles > 0 then
143+
-- Use pkg-config with configdirs
144+
local pcresult = find_package_from_pkgconfig(name, {configdirs = pcdir})
145+
146+
if pcresult then
147+
return pcresult
148+
end
149+
end
150+
end
151+
end
152+
153+
-- Return result if we found anything useful
154+
if result.includedirs or result.linkdirs then
155+
return result
156+
end
157+
158+
return nil
159+
end
160+
161+
-- try to build package with modern nix (flakes)
162+
function _try_modern_nix_build(name)
163+
local nix = find_tool("nix")
164+
if not nix then
165+
return nil
166+
end
167+
168+
-- Try with flakes syntax
169+
local storepath = try {function()
170+
return os.iorunv(nix.program, {"build", "nixpkgs#" .. name, "--print-out-paths", "--no-link"}):trim()
171+
end}
172+
173+
return storepath
174+
end
175+
176+
-- try to build package with legacy nix
177+
function _try_legacy_nix_build(name)
178+
local nix_build = find_tool("nix-build")
179+
if not nix_build then
180+
return nil
181+
end
182+
183+
-- Try legacy nix-build
184+
local storepath = try {function()
185+
return os.iorunv(nix_build.program, {"<nixpkgs>", "-A", name, "--no-out-link"}):trim()
186+
end}
187+
188+
return storepath
189+
end
190+
191+
-- main find function
192+
function main(name, opt)
193+
opt = opt or {}
194+
195+
-- Check for cross compilation
196+
if is_cross(opt.plat, opt.arch) then
197+
return
198+
end
199+
200+
-- Handle nix:: prefix
201+
local actual_name = name
202+
local force_nix = false
203+
if name:startswith("nix::") then
204+
actual_name = name:sub(6) -- Remove "nix::" prefix
205+
force_nix = true
206+
end
207+
208+
-- Get all available Nix store paths
209+
local nix_paths = _get_available_nix_paths()
210+
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
214+
local result = _find_in_store_path(store_path, actual_name)
215+
if result then
216+
if opt.verbose or option.get("verbose") then
217+
print("Found " .. actual_name .. " in: " .. store_path)
218+
end
219+
return result
220+
end
221+
end
222+
end
223+
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)
240+
end
241+
return result
242+
end
243+
end
244+
245+
return nil
246+
end

0 commit comments

Comments
 (0)