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