Skip to content

Commit 4742be6

Browse files
committed
Symbolic link support in Premake
1 parent 5c440ef commit 4742be6

File tree

11 files changed

+261
-13
lines changed

11 files changed

+261
-13
lines changed

src/base/os.lua

Lines changed: 28 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -575,21 +575,24 @@
575575

576576
local builtin_rmdir = os.rmdir
577577
function os.rmdir(p)
578-
-- recursively remove subdirectories
579-
local dirs = os.matchdirs(p .. "/*")
580-
for _, dname in ipairs(dirs) do
581-
local ok, err = os.rmdir(dname)
582-
if not ok then
583-
return ok, err
578+
-- Only delete children if the path is not a symlink
579+
if not os.islink(p) then
580+
-- recursively remove subdirectories
581+
local dirs = os.matchdirs(p .. "/*")
582+
for _, dname in ipairs(dirs) do
583+
local ok, err = os.rmdir(dname)
584+
if not ok then
585+
return ok, err
586+
end
584587
end
585-
end
586588

587-
-- remove any files
588-
local files = os.matchfiles(p .. "/*")
589-
for _, fname in ipairs(files) do
590-
local ok, err = os.remove(fname)
591-
if not ok then
592-
return ok, err
589+
-- remove any files
590+
local files = os.matchfiles(p .. "/*")
591+
for _, fname in ipairs(files) do
592+
local ok, err = os.remove(fname)
593+
if not ok then
594+
return ok, err
595+
end
593596
end
594597
end
595598

@@ -633,6 +636,12 @@
633636
echo = function(v)
634637
return "echo " .. v
635638
end,
639+
linkdir = function(v)
640+
return "ln -s " .. path.normalize(v)
641+
end,
642+
linkfile = function(v)
643+
return "ln -s " .. path.normalize(v)
644+
end,
636645
mkdir = function(v)
637646
return "mkdir -p " .. path.normalize(v)
638647
end,
@@ -678,6 +687,12 @@
678687
echo = function(v)
679688
return "echo " .. v
680689
end,
690+
linkdir = function(v)
691+
return "mklink /d " .. path.translate(path.normalize(v))
692+
end,
693+
linkfile = function(v)
694+
return "mklink " .. path.translate(path.normalize(v))
695+
end,
681696
mkdir = function(v)
682697
v = path.translate(path.normalize(v))
683698
return "IF NOT EXIST " .. v .. " (mkdir " .. v .. ")"

src/host/os_linkdir.c

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/**
2+
* \file os_linkdir.c
3+
* \brief Creates a symbolic link to a directory.
4+
* \author Copyright (c) 2024 Jess Perkins and the Premake project
5+
*/
6+
7+
#include <sys/stat.h>
8+
#include "premake.h"
9+
10+
int do_linkdir(lua_State* L, const char* src, const char* dst)
11+
{
12+
#if PLATFORM_WINDOWS
13+
// Prepend the drive letter if a relative path is given
14+
char dstPath[MAX_PATH];
15+
char srcPath[MAX_PATH];
16+
17+
do_normalize(L, srcPath, src);
18+
do_normalize(L, dstPath, dst);
19+
do_translate(dstPath, '\\');
20+
do_translate(srcPath, '\\');
21+
22+
// Promote to wide path
23+
wchar_t wSrcPath[MAX_PATH];
24+
wchar_t wDstPath[MAX_PATH];
25+
26+
MultiByteToWideChar(CP_UTF8, 0, srcPath, -1, wSrcPath, MAX_PATH);
27+
MultiByteToWideChar(CP_UTF8, 0, dstPath, -1, wDstPath, MAX_PATH);
28+
29+
// If the source path is relative, prepend the current working directory
30+
if (!do_isabsolute(src))
31+
{
32+
// Get the current working directory
33+
wchar_t cwd[MAX_PATH];
34+
GetCurrentDirectoryW(MAX_PATH, cwd);
35+
do_translate_w(cwd, L'\\');
36+
37+
// Convert the source path to a relative path
38+
wchar_t relSrcPath[MAX_PATH];
39+
swprintf(relSrcPath, MAX_PATH, L"%c:%s", cwd[0], wSrcPath);
40+
41+
BOOLEAN res = CreateSymbolicLinkW(wDstPath, relSrcPath, SYMBOLIC_LINK_FLAG_DIRECTORY | SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE);
42+
return res != 0;
43+
}
44+
else
45+
{
46+
BOOLEAN res = CreateSymbolicLinkW(wDstPath, wSrcPath, SYMBOLIC_LINK_FLAG_DIRECTORY | SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE);
47+
return res != 0;
48+
}
49+
#else
50+
int res = symlink(src, dst);
51+
return res == 0;
52+
#endif
53+
}
54+
55+
int os_linkdir(lua_State* L)
56+
{
57+
const char* src = luaL_checkstring(L, 1);
58+
const char* dst = luaL_checkstring(L, 2);
59+
60+
int result = do_linkdir(L, src, dst);
61+
if (!result)
62+
{
63+
lua_pushnil(L);
64+
lua_pushfstring(L, "Unable to create link from '%s' to '%s'", src, dst);
65+
return 2;
66+
}
67+
68+
lua_pushboolean(L, 1);
69+
return 1;
70+
}

src/host/os_linkfile.c

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/**
2+
* \file os_linkfile.c
3+
* \brief Creates a symbolic link to a file.
4+
* \author Copyright (c) 2024 Jess Perkins and the Premake project
5+
*/
6+
7+
#include <sys/stat.h>
8+
#include "premake.h"
9+
10+
int do_linkfile(lua_State* L, const char* src, const char* dst)
11+
{
12+
#if PLATFORM_WINDOWS
13+
char dstPath[MAX_PATH];
14+
char srcPath[MAX_PATH];
15+
16+
do_normalize(L, srcPath, src);
17+
do_normalize(L, dstPath, dst);
18+
do_translate(dstPath, '\\');
19+
do_translate(srcPath, '\\');
20+
21+
// Promote to wide path
22+
wchar_t wSrcPath[MAX_PATH];
23+
wchar_t wDstPath[MAX_PATH];
24+
25+
MultiByteToWideChar(CP_UTF8, 0, srcPath, -1, wSrcPath, MAX_PATH);
26+
MultiByteToWideChar(CP_UTF8, 0, dstPath, -1, wDstPath, MAX_PATH);
27+
28+
// If the source path is relative, prepend the current working directory
29+
if (!do_isabsolute(src))
30+
{
31+
// Get the current working directory
32+
wchar_t cwd[MAX_PATH];
33+
GetCurrentDirectoryW(MAX_PATH, cwd);
34+
do_translate_w(cwd, L'\\');
35+
36+
// Convert the source path to a relative path
37+
wchar_t relSrcPath[MAX_PATH];
38+
swprintf(relSrcPath, MAX_PATH, L"%c:%s", cwd[0], wSrcPath);
39+
40+
BOOLEAN res = CreateSymbolicLinkW(wDstPath, relSrcPath, SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE);
41+
return res != 0;
42+
}
43+
else
44+
{
45+
BOOLEAN res = CreateSymbolicLinkW(wDstPath, wSrcPath, SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE);
46+
return res != 0;
47+
}
48+
#else
49+
int res = symlink(src, dst);
50+
return res == 0;
51+
#endif
52+
}
53+
54+
int os_linkfile(lua_State* L)
55+
{
56+
const char* src = luaL_checkstring(L, 1);
57+
const char* dst = luaL_checkstring(L, 2);
58+
59+
int result = do_linkfile(L, src, dst);
60+
if (!result)
61+
{
62+
lua_pushnil(L);
63+
lua_pushfstring(L, "Unable to create link from '%s' to '%s'", src, dst);
64+
return 2;
65+
}
66+
67+
lua_pushboolean(L, 1);
68+
return 1;
69+
}

src/host/path_translate.c

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,17 @@ void do_translate(char* value, const char sep)
1919
}
2020

2121

22+
void do_translate_w(wchar_t* value, const wchar_t sep)
23+
{
24+
wchar_t* ch;
25+
for (ch = value; *ch != L'\0'; ++ch) {
26+
if (*ch == L'/' || *ch == L'\\') {
27+
*ch = sep;
28+
}
29+
}
30+
}
31+
32+
2233
static void translate(char* result, const char* value, const char sep)
2334
{
2435
strcpy(result, value);

src/host/premake.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,8 @@ static const luaL_Reg os_functions[] = {
7676
{ "hostarch", os_hostarch },
7777
{ "isfile", os_isfile },
7878
{ "islink", os_islink },
79+
{ "linkdir", os_linkdir },
80+
{ "linkfile", os_linkfile },
7981
{ "locate", os_locate },
8082
{ "matchdone", os_matchdone },
8183
{ "matchisfile", os_matchisfile },

src/host/premake.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ int do_locate(lua_State* L, const char* filename, const char* path);
110110
void do_normalize(lua_State* L, char* buffer, const char* path);
111111
int do_pathsearch(lua_State* L, const char* filename, const char* path);
112112
void do_translate(char* value, const char sep);
113+
void do_translate_w(wchar_t* value, const wchar_t sep);
113114

114115
int term_doGetTextColor();
115116
void term_doSetTextColor(int color);
@@ -145,6 +146,8 @@ int os_is64bit(lua_State* L);
145146
int os_isdir(lua_State* L);
146147
int os_isfile(lua_State* L);
147148
int os_islink(lua_State* L);
149+
int os_linkdir(lua_State* L);
150+
int os_linkfile(lua_State* L);
148151
int os_locate(lua_State* L);
149152
int os_matchdone(lua_State* L);
150153
int os_matchisfile(lua_State* L);

tests/base/test_os.lua

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,22 @@
6262
test.isfalse(os.isfile("no_such_file.lua"))
6363
end
6464

65+
--
66+
-- os.linkdir() and os.linkfile() tests
67+
--
68+
69+
function suite.linkdir()
70+
test.istrue(os.linkdir("folder/subfolder", "folder/subfolder2"))
71+
test.istrue(os.islink("folder/subfolder2"))
72+
os.rmdir("folder/subfolder2")
73+
end
74+
75+
function suite.linkfile()
76+
test.istrue(os.linkfile("folder/ok.lua", "folder/ok2.lua"))
77+
test.istrue(os.islink("folder/ok2.lua"))
78+
os.remove("folder/ok2.lua")
79+
end
80+
6581

6682

6783
--
@@ -274,6 +290,24 @@
274290
end
275291

276292
--
293+
-- os.translateCommand() LINKDIR/LINKFILE tests
294+
--
295+
function suite.translateCommand_windowsLinkDir()
296+
test.isequal('mklink /d a b', os.translateCommands('{LINKDIR} a b', "windows"))
297+
end
298+
299+
function suite.translateCommand_windowsLinkFile()
300+
test.isequal('mklink a b', os.translateCommands('{LINKFILE} a b', "windows"))
301+
end
302+
303+
function suite.translateCommand_posixLinkDir()
304+
test.isequal('ln -s a b', os.translateCommands('{LINKDIR} a b', "posix"))
305+
end
306+
307+
function suite.translateCommand_posixLinkFile()
308+
test.isequal('ln -s a b', os.translateCommands('{LINKFILE} a b', "posix"))
309+
end
310+
--
277311
-- os.getWindowsRegistry windows tests
278312
--
279313
function suite.getreg_nonExistentValue()

website/docs/Tokens.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,8 @@ The available tokens, and their replacements:
106106
| {COPYDIR} | xcopy /Q /E /Y /I {args} | cp -rf {args} |
107107
| {DELETE} | del {args} | rm -rf {args} |
108108
| {ECHO} | echo {args} | echo {args} |
109+
| {LINKDIR} | mklink /d {args} | ln -s {args} |
110+
| {LINKFILE} | mklink {args} | ln -s {args} |
109111
| {MKDIR} | IF NOT EXIST {args} (mkdir {args}) | mkdir -p {args} |
110112
| {MOVE} | move /Y {args} | mv -f {args} |
111113
| {RMDIR} | rmdir /S /Q {args} | rm -rf {args} |
@@ -133,6 +135,10 @@ buildcommands {
133135
}
134136
```
135137

138+
### Symbolic Links and Windows
139+
140+
For Windows, it is required to create symbolic links from an elevated context or to have Developer Mode enabled. The minimum required Windows version to execute symbolic links is Windows 10.
141+
136142
## Tokens and Filters
137143

138144
Tokens are not expanded in filters. See [issue 1306](https://github.com/premake/premake-core/issues/1036#issuecomment-379685035) for some illustrative examples.

website/docs/os/os.linkdir.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
Creates a new symbolic link to a directory.
2+
3+
```lua
4+
os.linkdir("src", "dst")
5+
```
6+
7+
### Parameters ###
8+
9+
`src` is the path of the directory to create a symbolic link to.
10+
`dst` is the path to the created symbolic link.
11+
12+
### Return Value ###
13+
14+
True if successful, otherwise nil and an error message.
15+
16+
### Availability ###
17+
18+
Premake 5.0-beta4 or later.

website/docs/os/os.linkfile.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
Creates a new symbolic link to a file.
2+
3+
```lua
4+
os.linkfile("src", "dst")
5+
```
6+
7+
### Parameters ###
8+
9+
`src` is the path of the file to create a symbolic link to.
10+
`dst` is the path to the created symbolic link.
11+
12+
### Return Value ###
13+
14+
True if successful, otherwise nil and an error message.
15+
16+
### Availability ###
17+
18+
Premake 5.0-beta4 or later.

0 commit comments

Comments
 (0)