diff options
| author | ruki <[email protected]> | 2025-09-12 22:50:06 +0800 |
|---|---|---|
| committer | GitHub <[email protected]> | 2025-09-12 22:50:06 +0800 |
| commit | e6c092279a2306dc56567d034013c5f0ff4e4b15 (patch) | |
| tree | a02239bf9c098de4cbba6ad5e7edbd3f94685ded | |
| parent | e641d139eef96d83222719a5f4f349edd87f1c9a (diff) | |
| parent | 7d236443e298850f9faf12b5966f3bf570be2215 (diff) | |
Merge pull request #6791 from ZZBaron/feature/nix-support
Add Nix Package Manager Support
| -rw-r--r-- | xmake/core/base/linuxos.lua | 2 | ||||
| -rw-r--r-- | xmake/modules/detect/tools/find_nix.lua | 75 | ||||
| -rw-r--r-- | xmake/modules/package/manager/find_package.lua | 1 | ||||
| -rw-r--r-- | xmake/modules/package/manager/install_package.lua | 2 | ||||
| -rw-r--r-- | xmake/modules/package/manager/nix/find_package.lua | 246 | ||||
| -rw-r--r-- | xmake/modules/package/manager/nix/install_package.lua | 72 | ||||
| -rw-r--r-- | xmake/modules/package/manager/nix/search_package.lua | 155 |
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 |
