summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorruki <[email protected]>2025-09-12 22:50:06 +0800
committerGitHub <[email protected]>2025-09-12 22:50:06 +0800
commite6c092279a2306dc56567d034013c5f0ff4e4b15 (patch)
treea02239bf9c098de4cbba6ad5e7edbd3f94685ded
parente641d139eef96d83222719a5f4f349edd87f1c9a (diff)
parent7d236443e298850f9faf12b5966f3bf570be2215 (diff)
Merge pull request #6791 from ZZBaron/feature/nix-support
Add Nix Package Manager Support
-rw-r--r--xmake/core/base/linuxos.lua2
-rw-r--r--xmake/modules/detect/tools/find_nix.lua75
-rw-r--r--xmake/modules/package/manager/find_package.lua1
-rw-r--r--xmake/modules/package/manager/install_package.lua2
-rw-r--r--xmake/modules/package/manager/nix/find_package.lua246
-rw-r--r--xmake/modules/package/manager/nix/install_package.lua72
-rw-r--r--xmake/modules/package/manager/nix/search_package.lua155
7 files changed, 553 insertions, 0 deletions
diff --git a/xmake/core/base/linuxos.lua b/xmake/core/base/linuxos.lua
index 649c11a39..238502856 100644
--- a/xmake/core/base/linuxos.lua
+++ b/xmake/core/base/linuxos.lua
@@ -110,6 +110,8 @@ function linuxos.name()
name = "opensuse"
elseif os_release:find("manjaro", 1, true) then
name = "manjaro"
+ elseif os_release:find("nixos", 1, true) then
+ name = "nixos"
end
end
end
diff --git a/xmake/modules/detect/tools/find_nix.lua b/xmake/modules/detect/tools/find_nix.lua
new file mode 100644
index 000000000..52531e029
--- /dev/null
+++ b/xmake/modules/detect/tools/find_nix.lua
@@ -0,0 +1,75 @@
+--!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 ruki
+-- @file find_nix.lua
+--
+
+-- imports
+import("lib.detect.find_program")
+import("lib.detect.find_programver")
+
+-- find nix
+--
+-- @param opt the argument options, e.g. {version = true}
+--
+-- @return program, version
+--
+-- @code
+--
+-- local nix = find_nix()
+-- local nix, version = find_nix({version = true})
+--
+-- @endcode
+--
+function main(opt)
+ -- init options
+ opt = opt or {}
+
+ -- add common nix installation paths if no specific program is given
+ if not opt.program then
+ opt.paths = opt.paths or {}
+ local nix_paths = {
+ "/nix/var/nix/profiles/default/bin", -- multi-user installation
+ "/home/" .. (os.getenv("USER") or "user") .. "/.nix-profile/bin", -- single user installation
+ "/usr/local/bin", -- default path of nix when compiling nix from source
+ }
+
+ -- NixOS-specific paths
+ if linuxos.name() == "nixos" then
+ table.insert(nix_paths, "/run/current-system/sw/bin")
+ end
+
+ opt.paths = table.wrap(opt.paths)
+ for _, nixpath in ipairs(nix_paths) do
+ table.insert(opt.paths, nixpath)
+ end
+ end
+
+ -- find program
+ local program = find_program(opt.program or "nix", opt)
+
+ -- find program version
+ local version = nil
+ if program and opt and opt.version then
+ version = find_programver(program, opt, function (output)
+ -- parse version from "nix (Nix) 2.18.1" format
+ return output:match("nix %(Nix%) ([%d%.]+)")
+ end)
+ end
+
+ return program, version
+end \ No newline at end of file
diff --git a/xmake/modules/package/manager/find_package.lua b/xmake/modules/package/manager/find_package.lua
index f6209c453..8bce4264a 100644
--- a/xmake/modules/package/manager/find_package.lua
+++ b/xmake/modules/package/manager/find_package.lua
@@ -59,6 +59,7 @@ function _find_package_with_builtin_rule(package_name, opt)
local find_from_host = not is_cross(plat, arch)
if find_from_host and not is_host("windows") then
table.insert(managers, "brew")
+ table.insert(managers, "nix")
end
-- vcpkg/conan support multi-platforms/architectures
table.insert(managers, "vcpkg")
diff --git a/xmake/modules/package/manager/install_package.lua b/xmake/modules/package/manager/install_package.lua
index 66772aee1..a2c605ba7 100644
--- a/xmake/modules/package/manager/install_package.lua
+++ b/xmake/modules/package/manager/install_package.lua
@@ -41,9 +41,11 @@ function _install_package(manager_name, package_name, opt)
table.insert(managers, "portage")
table.insert(managers, "brew")
table.insert(managers, "zypper")
+ table.insert(managers, "nix")
elseif is_host("macosx") then
table.insert(managers, "vcpkg")
table.insert(managers, "brew")
+ table.insert(managers, "nix")
end
assert(#managers > 0, "no suitable package manager!")
diff --git a/xmake/modules/package/manager/nix/find_package.lua b/xmake/modules/package/manager/nix/find_package.lua
new file mode 100644
index 000000000..579156153
--- /dev/null
+++ b/xmake/modules/package/manager/nix/find_package.lua
@@ -0,0 +1,246 @@
+--!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 ruki
+-- @file find_package.lua
+--
+
+-- imports
+import("core.base.option")
+import("lib.detect.find_tool")
+import("private.core.base.is_cross")
+import("package.manager.pkgconfig.find_package", {alias = "find_package_from_pkgconfig"})
+
+-- get all nix store paths currently available in environment
+function _get_available_nix_paths()
+ local paths = {}
+ local seen = {}
+
+ -- Get paths from environment PATH
+ local env_path = os.getenv("PATH") or ""
+ for dir in env_path:gmatch("[^:]+") do
+ if dir:startswith("/nix/store/") then
+ local store_path = dir:match("(/nix/store/[^/]+)")
+ if store_path and not seen[store_path] then
+ seen[store_path] = true
+ table.insert(paths, store_path)
+ end
+ end
+ end
+
+ -- Get paths from common Nix environment locations
+ local env_locations = {
+ os.getenv("NIX_PROFILES") or "",
+ (os.getenv("HOME") or "") .. "/.nix-profile",
+ "/nix/var/nix/profiles/default",
+ "/run/current-system/sw" -- NixOS system packages
+ }
+
+ for _, location in ipairs(env_locations) do
+ if location ~= "" and os.isdir(location) then
+ -- Check if it's a symlink to store path
+ local target = try {function()
+ return os.iorunv("readlink", {"-f", location}):trim()
+ end}
+
+ if target and target:startswith("/nix/store/") then
+ local store_path = target:match("(/nix/store/[^/]+)")
+ if store_path and not seen[store_path] then
+ seen[store_path] = true
+ table.insert(paths, store_path)
+ end
+ end
+
+ -- Also check for manifest (generation info)
+ local manifest = path.join(location, "manifest.nix")
+ if os.isfile(manifest) then
+ local manifest_content = io.readfile(manifest)
+
+ if manifest_content then
+ -- Extract store paths from manifest
+ for store_path in manifest_content:gmatch('(/nix/store/[^"\'%s]+)') do
+ if not seen[store_path] then
+ seen[store_path] = true
+ table.insert(paths, store_path)
+ end
+ end
+ end
+ end
+ end
+ end
+
+ return paths
+end
+
+-- find package in a specific nix store path
+function _find_in_store_path(store_path, name)
+ if not os.isdir(store_path) then
+ return nil
+ end
+
+ local result = {}
+
+ -- Find include directories
+ local includedir = path.join(store_path, "include")
+ if os.isdir(includedir) then
+ result.includedirs = {includedir}
+ end
+
+ -- Find libraries
+ local libdir = path.join(store_path, "lib")
+ if os.isdir(libdir) then
+ result.linkdirs = {libdir}
+ result.links = {}
+ result.libfiles = {}
+
+ -- Scan for library files
+ local libfiles = os.files(path.join(libdir, "*.so*"),
+ path.join(libdir, "*.a"),
+ path.join(libdir, "*.dylib*"))
+
+ for _, libfile in ipairs(libfiles) do
+ local filename = path.filename(libfile)
+ 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)
+
+ if filename:endswith(".a") then
+ result.static = true
+ else
+ result.shared = true
+ end
+ end
+ end
+ end
+
+ -- Find pkg-config files
+ local pkgconfigdirs = {
+ path.join(store_path, "lib", "pkgconfig"),
+ path.join(store_path, "share", "pkgconfig")
+ }
+
+ for _, pcdir in ipairs(pkgconfigdirs) do
+ if os.isdir(pcdir) then
+ local pcfiles = os.files(path.join(pcdir, name .. ".pc"))
+ if #pcfiles > 0 then
+ -- Use pkg-config with configdirs
+ local pcresult = find_package_from_pkgconfig(name, {configdirs = pcdir})
+
+ if pcresult then
+ return pcresult
+ end
+ end
+ end
+ end
+
+ -- Return result if we found anything useful
+ if result.includedirs or result.linkdirs then
+ return result
+ end
+
+ return nil
+end
+
+-- try to build package with modern nix (flakes)
+function _try_modern_nix_build(name)
+ local nix = find_tool("nix")
+ if not nix then
+ return nil
+ end
+
+ -- Try with flakes syntax
+ local storepath = try {function()
+ return os.iorunv(nix.program, {"build", "nixpkgs#" .. name, "--print-out-paths", "--no-link"}):trim()
+ end}
+
+ return storepath
+end
+
+-- try to build package with legacy nix
+function _try_legacy_nix_build(name)
+ local nix_build = find_tool("nix-build")
+ if not nix_build then
+ return nil
+ end
+
+ -- Try legacy nix-build
+ local storepath = try {function()
+ return os.iorunv(nix_build.program, {"<nixpkgs>", "-A", name, "--no-out-link"}):trim()
+ end}
+
+ return storepath
+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(6) -- Remove "nix::" prefix
+ force_nix = true
+ end
+
+ -- Get all available Nix store paths
+ local nix_paths = _get_available_nix_paths()
+
+ -- Search through available paths first (unless we're forced to build)
+ if #nix_paths > 0 and not force_nix then
+ for _, store_path in ipairs(nix_paths) do
+ local result = _find_in_store_path(store_path, actual_name)
+ if result then
+ if opt.verbose or option.get("verbose") then
+ print("Found " .. actual_name .. " in: " .. store_path)
+ end
+ return result
+ end
+ end
+ end
+
+ -- If not found in available paths or forced to build, try building
+ local storepath = nil
+
+ -- Try modern nix first
+ storepath = _try_modern_nix_build(actual_name)
+
+ -- Fallback to legacy nix-build
+ if not storepath then
+ storepath = _try_legacy_nix_build(actual_name)
+ end
+
+ if storepath and os.isdir(storepath) then
+ local result = _find_in_store_path(storepath, actual_name)
+ if result then
+ if opt.verbose or option.get("verbose") then
+ print("Built and found " .. actual_name .. " in: " .. storepath)
+ end
+ return result
+ end
+ end
+
+ return nil
+end \ No newline at end of file
diff --git a/xmake/modules/package/manager/nix/install_package.lua b/xmake/modules/package/manager/nix/install_package.lua
new file mode 100644
index 000000000..507cf6d13
--- /dev/null
+++ b/xmake/modules/package/manager/nix/install_package.lua
@@ -0,0 +1,72 @@
+--!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 ruki
+-- @file install_package.lua
+--
+
+-- imports
+import("core.base.option")
+import("lib.detect.find_tool")
+import("private.core.base.is_cross")
+
+-- install package
+--
+-- @param name the package name, e.g. zlib
+-- @param opt the options, e.g. {verbose = true}
+--
+-- @return true or false
+--
+function main(name, opt)
+
+ -- find nix tools (try modern first, then legacy)
+ local nix = find_tool("nix")
+ local nix_env = find_tool("nix-env")
+
+ if not nix and not nix_env then
+ raise("nix not found!")
+ end
+
+ -- check architecture
+ if is_cross(opt.plat, opt.arch) then
+ raise("cannot install package(%s) for cross compilation!", name)
+ end
+
+ local success = false
+
+ -- try modern nix first
+ if nix then
+ local argv = {"profile", "install", "nixpkgs#" .. name}
+ if opt.verbose or option.get("verbose") then
+ table.insert(argv, "--verbose")
+ end
+
+ success = try {function()
+ os.vrunv(nix.program, argv)
+ return true
+ end}
+ end
+
+ -- fallback to nix-env
+ if not success and nix_env then
+ local argv = {"-iA", "nixpkgs." .. name}
+ if opt.verbose or option.get("verbose") then
+ table.insert(argv, "--verbose")
+ end
+
+ os.vrunv(nix_env.program, argv)
+ end
+end \ No newline at end of file
diff --git a/xmake/modules/package/manager/nix/search_package.lua b/xmake/modules/package/manager/nix/search_package.lua
new file mode 100644
index 000000000..b95978736
--- /dev/null
+++ b/xmake/modules/package/manager/nix/search_package.lua
@@ -0,0 +1,155 @@
+--!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 ruki
+-- @file search_package.lua
+--
+
+-- imports
+import("core.base.option")
+import("lib.detect.find_tool")
+
+-- search packages using modern nix search
+function _search_with_flakes(nix, name)
+ local results = {}
+
+ -- use nix search to find packages
+ local searchdata = try {function ()
+ return os.iorunv(nix.program, {"search", "nixpkgs", name, "--json"})
+ end}
+
+ if searchdata then
+ -- parse JSON output
+ local ok, data = try {function ()
+ return json.decode(searchdata)
+ end}
+
+ if ok and data then
+ for pkgname, pkginfo in pairs(data) do
+ -- extract package name from the full path (e.g., "legacyPackages.x86_64-linux.cmake" -> "cmake")
+ local simplename = pkgname:match("([^%.]+)$")
+ if simplename and simplename:find(name, 1, true) then
+ table.insert(results, {
+ name = "nix::" .. simplename,
+ version = pkginfo.version or "unknown",
+ description = pkginfo.description or ""
+ })
+ end
+ end
+ end
+ end
+
+ return results
+end
+
+-- search packages using legacy nix-env
+function _search_with_env(name)
+ local results = {}
+ local nixenv = find_tool("nix-env")
+ if not nixenv then
+ return results
+ end
+
+ -- use nix-env to search for packages
+ local searchdata = try {function ()
+ return os.iorunv(nixenv.program, {"-qaP", "*" .. name .. "*"})
+ end}
+
+ if searchdata then
+ -- parse nix-env output format:
+ -- nixpkgs.cmake cmake-3.27.7
+ -- nixpkgs.cmake-cursor cmake-cursor-0.2.1
+ -- nixpkgs.cmakeWithGui cmake-3.27.7
+
+ for _, line in ipairs(searchdata:split("\n", {plain = true})) do
+ line = line:trim()
+ if line ~= "" then
+ local parts = line:split("%s+", {limit = 2})
+ if #parts >= 2 then
+ local fullname = parts[1]
+ local version_desc = parts[2]
+
+ -- extract simple package name
+ local pkgname = fullname:match("nixpkgs%.(.+)")
+ if pkgname and pkgname:find(name, 1, true) then
+ -- try to separate version from description
+ local version = version_desc:match("^([%d%.%-]+)")
+ local description = version_desc:gsub("^[%d%.%-]+%s*", "")
+
+ table.insert(results, {
+ name = "nix::" .. pkgname,
+ version = version or "unknown",
+ description = description or ""
+ })
+ end
+ end
+ end
+ end
+ end
+
+ return results
+end
+
+-- search package using the nix package manager
+--
+-- @param name the package name with pattern
+--
+function main(name)
+ -- find nix
+ local nix = find_tool("nix")
+ if not nix then
+ raise("nix not found!")
+ end
+
+ -- check if we have flakes enabled (modern nix)
+ local hasflakes = try {function ()
+ return os.iorunv(nix.program, {"search", "--help"}, {stdout = os.nuldev()})
+ end}
+
+ local results = {}
+
+ -- try modern search first
+ if hasflakes then
+ results = try {function ()
+ return _search_with_flakes(nix, name)
+ end} or {}
+ end
+
+ -- fallback to legacy search if modern search failed or returned no results
+ if #results == 0 then
+ results = try {function ()
+ return _search_with_env(name)
+ end} or {}
+ end
+
+ -- remove duplicates and sort
+ local seen = {}
+ local unique_results = {}
+ for _, result in ipairs(results) do
+ local key = result.name
+ if not seen[key] then
+ seen[key] = true
+ table.insert(unique_results, result)
+ end
+ end
+
+ -- sort by package name
+ table.sort(unique_results, function(a, b)
+ return a.name < b.name
+ end)
+
+ return unique_results
+end \ No newline at end of file