Skip to content

Commit

Permalink
Symbolic link support in Premake (#2352)
Browse files Browse the repository at this point in the history
  • Loading branch information
nickclark2016 authored Dec 2, 2024
1 parent 3f4090a commit 7f2a0c8
Show file tree
Hide file tree
Showing 11 changed files with 247 additions and 14 deletions.
41 changes: 28 additions & 13 deletions src/base/os.lua
Original file line number Diff line number Diff line change
Expand Up @@ -575,21 +575,24 @@

local builtin_rmdir = os.rmdir
function os.rmdir(p)
-- recursively remove subdirectories
local dirs = os.matchdirs(p .. "/*")
for _, dname in ipairs(dirs) do
local ok, err = os.rmdir(dname)
if not ok then
return ok, err
-- Only delete children if the path is not a symlink
if not os.islink(p) then
-- recursively remove subdirectories
local dirs = os.matchdirs(p .. "/*")
for _, dname in ipairs(dirs) do
local ok, err = os.rmdir(dname)
if not ok then
return ok, err
end
end
end

-- remove any files
local files = os.matchfiles(p .. "/*")
for _, fname in ipairs(files) do
local ok, err = os.remove(fname)
if not ok then
return ok, err
-- remove any files
local files = os.matchfiles(p .. "/*")
for _, fname in ipairs(files) do
local ok, err = os.remove(fname)
if not ok then
return ok, err
end
end
end

Expand Down Expand Up @@ -633,6 +636,12 @@
echo = function(v)
return "echo " .. v
end,
linkdir = function(v)
return "ln -s " .. path.normalize(v)
end,
linkfile = function(v)
return "ln -s " .. path.normalize(v)
end,
mkdir = function(v)
return "mkdir -p " .. path.normalize(v)
end,
Expand Down Expand Up @@ -678,6 +687,12 @@
echo = function(v)
return "echo " .. v
end,
linkdir = function(v)
return "mklink /d " .. path.translate(path.normalize(v))
end,
linkfile = function(v)
return "mklink " .. path.translate(path.normalize(v))
end,
mkdir = function(v)
v = path.translate(path.normalize(v))
return "IF NOT EXIST " .. v .. " (mkdir " .. v .. ")"
Expand Down
69 changes: 69 additions & 0 deletions src/host/os_linkdir.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/**
* \file os_linkdir.c
* \brief Creates a symbolic link to a directory.
* \author Copyright (c) 2024 Jess Perkins and the Premake project
*/

#include <sys/stat.h>
#include "premake.h"

int do_linkdir(lua_State* L, const char* src, const char* dst)
{
#if PLATFORM_WINDOWS
// Prepend the drive letter if a relative path is given
char dstPath[MAX_PATH];
char srcPath[MAX_PATH];

do_normalize(L, srcPath, src);
do_normalize(L, dstPath, dst);
do_translate(dstPath, '\\');
do_translate(srcPath, '\\');

// Promote to wide path
wchar_t wSrcPath[MAX_PATH];
wchar_t wDstPath[MAX_PATH];

MultiByteToWideChar(CP_UTF8, 0, srcPath, -1, wSrcPath, MAX_PATH);
MultiByteToWideChar(CP_UTF8, 0, dstPath, -1, wDstPath, MAX_PATH);

// If the source path is relative, prepend the current working directory
if (!do_isabsolute(src))
{
// Get the current working directory
wchar_t cwd[MAX_PATH];
GetCurrentDirectoryW(MAX_PATH, cwd);

// Convert the source path to a relative path
wchar_t relSrcPath[MAX_PATH];
swprintf(relSrcPath, MAX_PATH, L"%c:%s", cwd[0], wSrcPath);

BOOLEAN res = CreateSymbolicLinkW(wDstPath, relSrcPath, SYMBOLIC_LINK_FLAG_DIRECTORY | SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE);
return res != 0;
}
else
{
BOOLEAN res = CreateSymbolicLinkW(wDstPath, wSrcPath, SYMBOLIC_LINK_FLAG_DIRECTORY | SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE);
return res != 0;
}
#else
int res = symlink(src, dst);
return res == 0;
#endif
}

int os_linkdir(lua_State* L)
{
const char* src = luaL_checkstring(L, 1);
const char* dst = luaL_checkstring(L, 2);

int result = do_linkdir(L, src, dst);
if (!result)
{
lua_pushnil(L);
lua_pushfstring(L, "Unable to create link from '%s' to '%s'", src, dst);
return 2;
}

lua_pushboolean(L, 1);
return 1;
}
68 changes: 68 additions & 0 deletions src/host/os_linkfile.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/**
* \file os_linkfile.c
* \brief Creates a symbolic link to a file.
* \author Copyright (c) 2024 Jess Perkins and the Premake project
*/

#include <sys/stat.h>
#include "premake.h"

int do_linkfile(lua_State* L, const char* src, const char* dst)
{
#if PLATFORM_WINDOWS
char dstPath[MAX_PATH];
char srcPath[MAX_PATH];

do_normalize(L, srcPath, src);
do_normalize(L, dstPath, dst);
do_translate(dstPath, '\\');
do_translate(srcPath, '\\');

// Promote to wide path
wchar_t wSrcPath[MAX_PATH];
wchar_t wDstPath[MAX_PATH];

MultiByteToWideChar(CP_UTF8, 0, srcPath, -1, wSrcPath, MAX_PATH);
MultiByteToWideChar(CP_UTF8, 0, dstPath, -1, wDstPath, MAX_PATH);

// If the source path is relative, prepend the current working directory
if (!do_isabsolute(src))
{
// Get the current working directory
wchar_t cwd[MAX_PATH];
GetCurrentDirectoryW(MAX_PATH, cwd);

// Convert the source path to a relative path
wchar_t relSrcPath[MAX_PATH];
swprintf(relSrcPath, MAX_PATH, L"%c:%s", cwd[0], wSrcPath);

BOOLEAN res = CreateSymbolicLinkW(wDstPath, relSrcPath, SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE);
return res != 0;
}
else
{
BOOLEAN res = CreateSymbolicLinkW(wDstPath, wSrcPath, SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE);
return res != 0;
}
#else
int res = symlink(src, dst);
return res == 0;
#endif
}

int os_linkfile(lua_State* L)
{
const char* src = luaL_checkstring(L, 1);
const char* dst = luaL_checkstring(L, 2);

int result = do_linkfile(L, src, dst);
if (!result)
{
lua_pushnil(L);
lua_pushfstring(L, "Unable to create link from '%s' to '%s'", src, dst);
return 2;
}

lua_pushboolean(L, 1);
return 1;
}
1 change: 0 additions & 1 deletion src/host/path_translate.c
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ void do_translate(char* value, const char sep)
}
}


static void translate(char* result, const char* value, const char sep)
{
strcpy(result, value);
Expand Down
2 changes: 2 additions & 0 deletions src/host/premake.c
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ static const luaL_Reg os_functions[] = {
{ "hostarch", os_hostarch },
{ "isfile", os_isfile },
{ "islink", os_islink },
{ "linkdir", os_linkdir },
{ "linkfile", os_linkfile },
{ "locate", os_locate },
{ "matchdone", os_matchdone },
{ "matchisfile", os_matchisfile },
Expand Down
2 changes: 2 additions & 0 deletions src/host/premake.h
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,8 @@ int os_is64bit(lua_State* L);
int os_isdir(lua_State* L);
int os_isfile(lua_State* L);
int os_islink(lua_State* L);
int os_linkdir(lua_State* L);
int os_linkfile(lua_State* L);
int os_locate(lua_State* L);
int os_matchdone(lua_State* L);
int os_matchisfile(lua_State* L);
Expand Down
34 changes: 34 additions & 0 deletions tests/base/test_os.lua
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,22 @@
test.isfalse(os.isfile("no_such_file.lua"))
end

--
-- os.linkdir() and os.linkfile() tests
--

function suite.linkdir()
test.istrue(os.linkdir("folder/subfolder", "folder/subfolder2"))
test.istrue(os.islink("folder/subfolder2"))
os.rmdir("folder/subfolder2")
end

function suite.linkfile()
test.istrue(os.linkfile("folder/ok.lua", "folder/ok2.lua"))
test.istrue(os.islink("folder/ok2.lua"))
os.remove("folder/ok2.lua")
end



--
Expand Down Expand Up @@ -274,6 +290,24 @@
end

--
-- os.translateCommand() LINKDIR/LINKFILE tests
--
function suite.translateCommand_windowsLinkDir()
test.isequal('mklink /d a b', os.translateCommands('{LINKDIR} a b', "windows"))
end

function suite.translateCommand_windowsLinkFile()
test.isequal('mklink a b', os.translateCommands('{LINKFILE} a b', "windows"))
end

function suite.translateCommand_posixLinkDir()
test.isequal('ln -s a b', os.translateCommands('{LINKDIR} a b', "posix"))
end

function suite.translateCommand_posixLinkFile()
test.isequal('ln -s a b', os.translateCommands('{LINKFILE} a b', "posix"))
end
--
-- os.getWindowsRegistry windows tests
--
function suite.getreg_nonExistentValue()
Expand Down
6 changes: 6 additions & 0 deletions website/docs/Tokens.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,8 @@ The available tokens, and their replacements:
| {COPYDIR} | xcopy /Q /E /Y /I {args} | cp -rf {args} |
| {DELETE} | del {args} | rm -rf {args} |
| {ECHO} | echo {args} | echo {args} |
| {LINKDIR} | mklink /d {args} | ln -s {args} |
| {LINKFILE} | mklink {args} | ln -s {args} |
| {MKDIR} | IF NOT EXIST {args} (mkdir {args}) | mkdir -p {args} |
| {MOVE} | move /Y {args} | mv -f {args} |
| {RMDIR} | rmdir /S /Q {args} | rm -rf {args} |
Expand Down Expand Up @@ -133,6 +135,10 @@ buildcommands {
}
```

### Symbolic Links and Windows

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.

## Tokens and Filters

Tokens are not expanded in filters. See [issue 1306](https://github.com/premake/premake-core/issues/1036#issuecomment-379685035) for some illustrative examples.
18 changes: 18 additions & 0 deletions website/docs/os/os.linkdir.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
Creates a new symbolic link to a directory.

```lua
os.linkdir("src", "dst")
```

### Parameters ###

`src` is the path of the directory to create a symbolic link to.
`dst` is the path to the created symbolic link.

### Return Value ###

True if successful, otherwise nil and an error message.

### Availability ###

Premake 5.0-beta4 or later.
18 changes: 18 additions & 0 deletions website/docs/os/os.linkfile.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
Creates a new symbolic link to a file.

```lua
os.linkfile("src", "dst")
```

### Parameters ###

`src` is the path of the file to create a symbolic link to.
`dst` is the path to the created symbolic link.

### Return Value ###

True if successful, otherwise nil and an error message.

### Availability ###

Premake 5.0-beta4 or later.
2 changes: 2 additions & 0 deletions website/sidebars.js
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,8 @@ module.exports = {
'os/os.isfile',
'os/os.islink',
'os/os.istarget',
'os/os.linkdir',
'os/os.linkfile',
'os/os.locate',
'os/os.matchdirs',
'os/os.matchfiles',
Expand Down

0 comments on commit 7f2a0c8

Please sign in to comment.