Skip to content

Commit 9f9d51e

Browse files
committed
Symbolic link support in Premake
1 parent 5c440ef commit 9f9d51e

File tree

10 files changed

+231
-13
lines changed

10 files changed

+231
-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: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
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+
// If the source path is relative, prepend the current working directory
23+
if (!do_isabsolute(src))
24+
{
25+
char cwd[MAX_PATH];
26+
do_getcwd(cwd, MAX_PATH);
27+
do_translate(cwd, '\\');
28+
29+
char relSrcPath[MAX_PATH];
30+
snprintf(relSrcPath, MAX_PATH, "%c:%s", cwd[0], srcPath);
31+
32+
BOOLEAN res = CreateSymbolicLinkA(dstPath, relSrcPath, SYMBOLIC_LINK_FLAG_DIRECTORY | SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE);
33+
return res != 0;
34+
}
35+
else
36+
{
37+
BOOLEAN res = CreateSymbolicLinkA(dstPath, srcPath, SYMBOLIC_LINK_FLAG_DIRECTORY | SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE);
38+
return res != 0;
39+
}
40+
#else
41+
int res = symlink(src, dst);
42+
return res == 0;
43+
#endif
44+
}
45+
46+
int os_linkdir(lua_State* L)
47+
{
48+
const char* src = luaL_checkstring(L, 1);
49+
const char* dst = luaL_checkstring(L, 2);
50+
51+
int result = do_linkdir(L, src, dst);
52+
if (!result)
53+
{
54+
lua_pushnil(L);
55+
lua_pushfstring(L, "Unable to create link from '%s' to '%s'", src, dst);
56+
return 2;
57+
}
58+
59+
lua_pushboolean(L, 1);
60+
return 1;
61+
}

src/host/os_linkfile.c

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
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+
// If the source path is relative, prepend the current working directory
22+
if (!do_isabsolute(src))
23+
{
24+
char cwd[MAX_PATH];
25+
do_getcwd(cwd, MAX_PATH);
26+
do_translate(cwd, '\\');
27+
28+
char relSrcPath[MAX_PATH];
29+
snprintf(relSrcPath, MAX_PATH, "%c:%s", cwd[0], srcPath);
30+
31+
BOOLEAN res = CreateSymbolicLinkA(dstPath, relSrcPath, SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE);
32+
return res != 0;
33+
}
34+
else
35+
{
36+
BOOLEAN res = CreateSymbolicLinkA(dstPath, srcPath, SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE);
37+
return res != 0;
38+
}
39+
#else
40+
int res = symlink(src, dst);
41+
return res == 0;
42+
#endif
43+
}
44+
45+
int os_linkfile(lua_State* L)
46+
{
47+
const char* src = luaL_checkstring(L, 1);
48+
const char* dst = luaL_checkstring(L, 2);
49+
50+
int result = do_linkfile(L, src, dst);
51+
if (!result)
52+
{
53+
lua_pushnil(L);
54+
lua_pushfstring(L, "Unable to create link from '%s' to '%s'", src, dst);
55+
return 2;
56+
}
57+
58+
lua_pushboolean(L, 1);
59+
return 1;
60+
}

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: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,8 @@ int os_is64bit(lua_State* L);
145145
int os_isdir(lua_State* L);
146146
int os_isfile(lua_State* L);
147147
int os_islink(lua_State* L);
148+
int os_linkdir(lua_State* L);
149+
int os_linkfile(lua_State* L);
148150
int os_locate(lua_State* L);
149151
int os_matchdone(lua_State* L);
150152
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.

website/sidebars.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -379,6 +379,8 @@ module.exports = {
379379
'os/os.isfile',
380380
'os/os.islink',
381381
'os/os.istarget',
382+
'os/os.linkdir',
383+
'os/os.linkfile',
382384
'os/os.locate',
383385
'os/os.matchdirs',
384386
'os/os.matchfiles',

0 commit comments

Comments
 (0)