Skip to content

Commit 43a26b7

Browse files
authored
add os.findsubdirheader (#2637)
- `os.findsubdirheader`: better support for header files in subdirectory than `os.findheader` - update comment & document of `os.findheader`
1 parent 2bdd54a commit 43a26b7

6 files changed

Lines changed: 155 additions & 4 deletions

File tree

src/base/os.lua

Lines changed: 69 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -143,10 +143,22 @@
143143
end
144144
end
145145

146+
---
147+
-- Attempt to locate and return the path to a header file.
148+
--
149+
-- Searches the same well-known system locations as os.findlib(), but replaces any /lib or /bin
150+
-- components with /include, and also searches in additional headerdirs provided by the caller.
151+
--
152+
-- @param headerpath
153+
-- A partial header file path.
154+
-- @param headerdirs
155+
-- Optional; a string or table of additional paths.
156+
-- If an input is an absolute path, it will be searched directly.
157+
-- If an input is a relative path, it will be treated as is, relative to the current working directory.
158+
-- @return
159+
-- The full path to the directory containing the headerpath if found; `nil` otherwise.
160+
---
146161
function os.findheader(headerpath, headerdirs)
147-
-- headerpath: a partial header file path
148-
-- headerdirs: additional header search paths
149-
150162
local paths = get_library_search_path()
151163

152164
-- replace all /lib and /bin by /include
@@ -165,6 +177,60 @@
165177
return result
166178
end
167179

180+
---
181+
-- Attempt to locate and return the path to a header file under additional subdirectories.
182+
--
183+
-- Searches the same well-known system locations as os.findheader(), but appends additional
184+
-- subdirectory paths provided by the caller to the system search paths before searching.
185+
--
186+
-- @param headerpath
187+
-- A partial header file path.
188+
-- @param additionalpaths
189+
-- Required; a string or table of additional paths.
190+
-- If an input is an absolute path, it will be searched directly.
191+
-- If an input is a relative path, it will be appended to each system search path separately.
192+
-- If an input is an empty string, the system search paths will be searched (path.join returns the original path).
193+
-- @return
194+
-- The full path to the directory containing the headerpath if found; `nil` otherwise.
195+
---
196+
function os.findsubdirheader(headerpath, additionalpaths)
197+
if additionalpaths == nil then
198+
error("os.findsubdirheader: additionalpaths is required", 2)
199+
end
200+
201+
local paths = get_library_search_path()
202+
203+
-- replace all /lib and /bin by /include
204+
paths = table.translate(paths, function (p) return p:gsub('[/\\]lib[0-9]*', '/include'):gsub('[/\\]bin', '/include') end)
205+
206+
local userpaths = {}
207+
208+
if type(additionalpaths) == "string" then
209+
userpaths = { additionalpaths }
210+
elseif type(additionalpaths) == "table" then
211+
userpaths = additionalpaths
212+
end
213+
214+
if #userpaths > 0 then
215+
local basepaths = paths
216+
local newpaths = {}
217+
for _, userpath in ipairs(userpaths) do
218+
if path.isabsolute(userpath) then
219+
-- absolute path: search directly, same as os.findheader
220+
table.insert(newpaths, userpath)
221+
else
222+
for _, p in ipairs(basepaths) do
223+
table.insert(newpaths, path.join(p, userpath))
224+
end
225+
end
226+
end
227+
paths = newpaths
228+
end
229+
230+
local result = os.pathsearch(headerpath, table.unpack(paths))
231+
return result
232+
end
233+
168234
--
169235
-- Retrieve the current target operating system ID string.
170236
--

tests/base/test_os.lua

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,35 @@
102102
os.findheader("test.h", path.getabsolute("folder/subfolder/include")))
103103
end
104104

105+
function suite.findsubdirheader_provided_relative()
106+
local os_getenv = os.getenv
107+
os.getenv = create_mock_os_getenv({ [get_LD_PATH_variable_name()] = get_surrounded_env_path("folder/subfolder/lib") })
108+
109+
test.isequal(path.getabsolute("folder/subfolder/include/testlib"), os.findsubdirheader("testlib2.h", "testlib"))
110+
111+
os.getenv = os_getenv
112+
end
113+
114+
function suite.findsubdirheader_failure()
115+
test.isfalse(os.findsubdirheader("Knights/who/say/Ni.hpp", "nonexistent"))
116+
end
117+
118+
function suite.findsubdirheader_provided_absolute()
119+
test.isequal(path.getabsolute("folder/subfolder/include"),
120+
os.findsubdirheader("test.h", path.getabsolute("folder/subfolder/include")))
121+
end
122+
123+
function suite.findsubdirheader_mixed_with_empty()
124+
local os_getenv = os.getenv
125+
os.getenv = create_mock_os_getenv({ [get_LD_PATH_variable_name()] = get_surrounded_env_path("folder/subfolder/lib") })
126+
127+
-- "testlib" finds testlib2.h; "" allows test.h to be found in the base include dir
128+
test.isequal(path.getabsolute("folder/subfolder/include/testlib"), os.findsubdirheader("testlib2.h", {"testlib", ""}))
129+
test.isequal(path.getabsolute("folder/subfolder/include"), os.findsubdirheader("test.h", {"testlib", ""}))
130+
131+
os.getenv = os_getenv
132+
end
133+
105134
function suite.findheader_frompath_lib()
106135
local os_getenv = os.getenv
107136
os.getenv = create_mock_os_getenv({ [get_LD_PATH_variable_name()] = get_surrounded_env_path("folder/subfolder/lib") })
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
// Only used for presence in tests

website/docs/os/os.findheader.md

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,28 @@ p = os.findheader("headerfile" [, additionalpaths])
88

99
`headerfile` is a file name or a the end of a file path to locate.
1010

11-
`additionalpaths` is a string or a table of one or more additional search path.
11+
`additionalpaths` is a string or a table of one or more additional search path. Absolute paths are searched directly; relative paths are treated as is, relative to the current working directory.
1212

1313
### Return Value ###
1414

1515
The path containing the header file, if found. Otherwise, nil.
1616

17+
### Example ###
18+
19+
``` lua
20+
os.findheader("stdlib.h") -- e.g. /usr/include
21+
os.findheader("freetype2/ft2build.h") -- e.g. /usr/include
22+
os.findheader("ft2build.h", {"/usr/local/include/freetype2", "/usr/include/freetype2"}) -- e.g. /usr/include/freetype2
23+
```
24+
1725
### Remarks ###
1826
`os.findheader` mostly use the same paths as [[os.findlib]] but replace `/lib` by `/include`.
1927

2028
### Availability ###
2129

2230
Premake 5.0 or later.
31+
32+
### See Also ###
33+
34+
* [os.findsubdirheader](os.findsubdirheader.md)
35+
* [os.findlib](os.findlib.md)
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
Scan the well-known system locations looking for a header file, with support for subdirectory paths.
2+
3+
```lua
4+
p = os.findsubdirheader("headerfile", additionalpaths)
5+
```
6+
7+
### Parameters ###
8+
9+
`headerfile` is a file name or the end of a file path to locate.
10+
11+
`additionalpaths` is a required string or table of one or more subdirectory paths:
12+
13+
- **Relative paths** are joined with every default include search path (cross-join); only the resulting subdirectories are searched.
14+
- **Absolute paths** are searched directly, acting the same behaviour of [[os.findheader]].
15+
- **Empty string `""`** is treated as a path segment that resolves to the base include path itself, so it can be mixed with other entries (e.g. `{"freetype2", ""}` searches both `<base>/freetype2` and `<base>`).
16+
17+
### Return Value ###
18+
19+
The path containing the header file, if found. Otherwise, nil.
20+
21+
### Example ###
22+
23+
``` lua
24+
os.findsubdirheader("ft2build.h", "freetype2") -- e.g. /usr/include/freetype2
25+
os.findsubdirheader("gl.h", {"OpenGL", "GL"}) -- e.g. /usr/include/GL
26+
os.findsubdirheader("ft2build.h", "/your/path/to/freetype2") -- e.g. /your/path/to/freetype2
27+
```
28+
29+
### Remarks ###
30+
Unlike [[os.findheader]], relative paths in `additionalpaths` are resolved against each default search path, allowing discovery of headers in named subdirectories without requiring an absolute path. **Only** the specified subdirectories are searched, unless `additionalpaths` contains an empty string.
31+
32+
`os.findsubdirheader` uses the same base paths as [[os.findheader]], which mostly uses the same paths as [[os.findlib]] but replace `/lib` by `/include`.
33+
34+
### Availability ###
35+
36+
Premake 5.0 or later.
37+
38+
### See Also ###
39+
40+
* [os.findheader](os.findheader.md)
41+
* [os.findlib](os.findlib.md)

website/sidebars.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -383,6 +383,7 @@ module.exports = {
383383
'os/os.execute',
384384
'os/os.executef',
385385
'os/os.findheader',
386+
'os/os.findsubdirheader',
386387
'os/os.findlib',
387388
'os/os.get',
388389
'os/os.getcwd',

0 commit comments

Comments
 (0)