Skip to content

Commit e90319e

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

File tree

10 files changed

+235
-13
lines changed

10 files changed

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

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() windows LINKDIR 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: 7 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,11 @@ 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
141+
windows version to execute symbolic links is Windows 10.
142+
136143
## Tokens and Filters
137144

138145
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)