[core] path:join can now be deferred for those paths that execute lua

prior to this change, paths that begin with ${ } were sometimes assumed to
be either absolute or relative.  new functions have been added in order
to reduce the number of systems that can break.

new functions are

path:absolutetype -> similair to isabsolute but can return an unknown or maybe result
path:deferredjoin -> similair to path:join but creates a string for unknown absolute
	paths which we except to be generated at bake time
path:hasdeferredjoin -> predicate to determine if a path needs to be evaled for
    	a deferred join
path:resolvedeferredjoin -> resolves a deferredjoin path to an actual path
    	this is to be called after any %{} replacements occur.

right now all api path types use the deferredjoin code path and
detoken, solution:makerelative and project:makerelative have been updated
to use resolvedeferredjoin.

unit tests have been created to test the four new path functions.
added unit tests
This commit is contained in:
R. Blaine Whittle 2017-09-20 14:32:51 -07:00 committed by Tom van Dijck
parent e569700eb7
commit b88d4eff3e
9 changed files with 407 additions and 115 deletions

View File

@ -1048,10 +1048,7 @@
premake.field.kind("path", {
paths = true,
store = function(field, current, value, processor)
if string.sub(value, 1, 2) == "%{" then
return value
end
return path.getabsolute(value)
return path.deferredjoin(os.getcwd(), value)
end,
compare = function(field, a, b, processor)
return (a == b)

View File

@ -107,6 +107,9 @@
-- a NON-path value, I need to make it relative to the project that
-- will contain it. Otherwise I ended up with an absolute path in
-- the generated project, and it can no longer be moved around.
if path.hasdeferredjoin(result) then
result = path.resolvedeferredjoin(result)
end
isAbs = path.isabsolute(result)
if isAbs and not field.paths and basedir and not dontMakeRelative then
result = path.getrelative(basedir, result)

View File

@ -246,7 +246,11 @@
return result
else
if filename then
return path.getrelative(prj.location, filename)
local result = filename
if path.hasdeferredjoin(result) then
result = path.resolvedeferredjoin(result)
end
return path.getrelative(prj.location, result)
end
end
end

View File

@ -187,7 +187,11 @@
return result
else
if filename then
return path.getrelative(self.location, filename)
local result = filename
if path.hasdeferredjoin(result) then
result = path.resolvedeferredjoin(result)
end
return path.getrelative(self.location, result)
end
end
end

View File

@ -4,11 +4,118 @@
* \author Copyright (c) 2002-2016 Jason Perkins and the Premake project
*/
/**
* \file path_isabsolute.c
* \brief Determines if a path is absolute or relative.
* \author Copyright (c) 2002-2016 Jason Perkins and the Premake project
*/
#include "premake.h"
#include <ctype.h>
#include <string.h>
#if PLATFORM_WINDOWS
#define strncasecmp _strnicmp
#endif
#define JOIN_RELATIVE 0
#define JOIN_ABSOLUTE 1
#define JOIN_MAYBE_ABSOLUTE 2
int do_absolutetype(const char* path)
{
char c;
const char* closing;
size_t length;
while (path[0] == '"' || path[0] == '!')
path++;
if (path[0] == '/' || path[0] == '\\')
return JOIN_ABSOLUTE;
if (isalpha(path[0]) && path[1] == ':')
return JOIN_ABSOLUTE;
// $(foo) and %(foo)
if ((path[0] == '%' || path[0] == '$') && path[1] == '(')
{
char delimiter = path[0];
closing = strchr(path + 2, ')');
if (closing == NULL)
return JOIN_RELATIVE;
path += 2;
// special case VS macros %(filename) and %(extension) as normal text
if (delimiter == '%')
{
length = closing - path;
switch (length) {
case 8:
if (strncasecmp(path, "Filename)", length) == 0)
return JOIN_RELATIVE;
break;
case 9:
if (strncasecmp(path, "Extension)", length) == 0)
return JOIN_RELATIVE;
break;
default:
break;
}
}
// only alpha, digits, _ and . allowed inside $()
while (path < closing) {
c = *path++;
if (!isalpha(c) && !isdigit(c) && c != '_' && c != '.')
return JOIN_RELATIVE;
}
return JOIN_ABSOLUTE;
}
// $ORIGIN.
if (path[0] == '$')
return JOIN_ABSOLUTE;
// either %ORIGIN% or %{<lua code>}
if (path[0] == '%')
{
if (path[1] == '{') //${foo} need to defer join until after detokenization
{
closing = strchr(path + 2, '}');
if (closing != NULL)
{
return JOIN_MAYBE_ABSOLUTE;
}
}
// find the second closing %
path += 1;
closing = strchr(path, '%');
if (closing == NULL)
return JOIN_RELATIVE;
// need at least one character between the %%
if (path == closing)
return JOIN_RELATIVE;
// only alpha, digits and _ allowed inside %..%
while (path < closing) {
c = *path++;
if (!isalpha(c) && !isdigit(c) && c != '_')
return JOIN_RELATIVE;
}
return JOIN_ABSOLUTE;
}
return JOIN_RELATIVE;
}
int do_isabsolute(const char* path)
{
// backwards compatibility
return (do_absolutetype(path) == 1) ? 1 : 0;
}
int path_isabsolute(lua_State* L)
{
const char* path = luaL_checkstring(L, -1);
@ -16,62 +123,9 @@ int path_isabsolute(lua_State* L)
return 1;
}
int do_isabsolute(const char* path)
int path_absolutetype(lua_State* L)
{
char c;
const char* closing;
if (path[0] == '/' || path[0] == '\\')
return 1;
if (isalpha(path[0]) && path[1] == ':')
return 1;
if (path[0] == '"' || path[0] == '!')
return do_isabsolute(path + 1);
// $(foo) and %(foo)
if ((path[0] == '%' || path[0] == '$') && path[1] == '(')
{
path += 2;
closing = strchr(path, ')');
if (closing == NULL)
return 0;
// only alpha, digits, _ and . allowed inside $()
while (path < closing) {
c = *path++;
if (!isalpha(c) && !isdigit(c) && c != '_' && c != '.')
return 0;
}
return 1;
}
// $ORIGIN.
if (path[0] == '$')
return 1;
// %foo%
if (path[0] == '%')
{
// find the second closing %
path += 1;
closing = strchr(path, '%');
if (closing == NULL)
return 0;
// need at least one character between the %%
if (path == closing)
return 0;
// only alpha, digits and _ allowed inside %..%
while (path < closing) {
c = *path++;
if (!isalpha(c) && !isdigit(c) && c != '_')
return 0;
}
return 1;
}
return 0;
const char* path = luaL_checkstring(L, -1);
lua_pushinteger(L, do_absolutetype(path));
return 1;
}

View File

@ -1,62 +1,56 @@
/**
* \file path_join.c
* \brief Join two or more pieces of a file system path.
* \author Copyright (c) 2002-2013 Jason Perkins and the Premake project
*/
* \file path_join.c
* \brief Join two or more pieces of a file system path.
* \author Copyright (c) 2002-2013 Jason Perkins and the Premake project
*/
#include "premake.h"
#include <string.h>
#define JOIN_RELATIVE 0
#define JOIN_ABSOLUTE 1
#define JOIN_MAYBE_ABSOLUTE 2
#define DEFERRED_JOIN_DELIMITER '|'
int path_join(lua_State* L)
char* path_join_single(char* buffer, char* ptr, const char* part, int allowDeferredJoin)
{
int i;
size_t len;
const char* part;
char buffer[0x4000];
char* ptr = buffer;
int absoluteType;
size_t len = strlen(part);
/* remove leading "./" */
while (strncmp(part, "./", 2) == 0) {
part += 2;
len -= 2;
}
/* for each argument... */
int argc = lua_gettop(L);
for (i = 1; i <= argc; ++i) {
/* if next argument is nil, skip it */
if (lua_isnil(L, i)) {
continue;
}
/* remove trailing slashes */
while (len > 1 && part[len - 1] == '/') {
--len;
}
/* grab the next argument */
part = luaL_checkstring(L, i);
len = strlen(part);
/* ignore empty segments and "." */
if (len == 0 || (len == 1 && part[0] == '.')) {
return ptr;
}
/* remove leading "./" */
while (strncmp(part, "./", 2) == 0) {
part += 2;
len -= 2;
}
/* remove trailing slashes */
while (len > 1 && part[len - 1] == '/') {
--len;
}
/* ignore empty segments and "." */
if (len == 0 || (len == 1 && part[0] == '.')) {
continue;
}
/* if I encounter an absolute path, restart my result */
if (do_isabsolute(part)) {
ptr = buffer;
}
absoluteType = do_absolutetype(part);
if (!allowDeferredJoin && absoluteType == JOIN_MAYBE_ABSOLUTE)
absoluteType = JOIN_RELATIVE;
/* if I encounter an absolute path, restart my result */
switch (absoluteType) {
case JOIN_ABSOLUTE:
ptr = buffer;
break;
case JOIN_RELATIVE:
/* if source has a .. prefix then take off last dest path part
note that this doesn't guarantee a normalized result as this
code doesn't check for .. in the mid path, however .. occurring
mid path are much more likely to occur during path joins
and its faster if we handle here as we don't have to remove
substrings from the middle of the string. */
note that this doesn't guarantee a normalized result as this
code doesn't check for .. in the mid path, however .. occurring
mid path are much more likely to occur during path joins
and its faster if we handle here as we don't have to remove
substrings from the middle of the string. */
while (ptr != buffer && len >= 2 && part[0] == '.' && part[1] == '.') {
while (ptr != buffer && len >= 2 && part[0] == '.' && part[1] == '.')
{
/* locate start of previous segment */
char* start = strrchr(buffer, '/');
if (!start) {
@ -94,12 +88,106 @@ int path_join(lua_State* L)
*(ptr++) = '/';
}
/* append new part */
strncpy(ptr, part, len);
ptr += len;
*ptr = '\0';
break;
case JOIN_MAYBE_ABSOLUTE:
*ptr = DEFERRED_JOIN_DELIMITER;
ptr++;
break;
}
/* append new part */
strncpy(ptr, part, len);
ptr += len;
*ptr = '\0';
return ptr;
}
int path_join_internal(lua_State* L, int allowDeferredJoin)
{
int i;
const char* part;
char buffer[0x4000];
char* ptr = buffer;
/* for each argument... */
int argc = lua_gettop(L);
for (i = 1; i <= argc; ++i) {
/* if next argument is nil, skip it */
if (lua_isnil(L, i)) {
continue;
}
/* grab the next argument */
part = luaL_checkstring(L, i);
ptr = path_join_single(buffer, ptr, part, allowDeferredJoin);
}
lua_pushstring(L, buffer);
return 1;
}
int path_join(lua_State* L)
{
return path_join_internal(L, 0);
}
int path_deferred_join(lua_State* L)
{
return path_join_internal(L, 1);
}
int do_path_has_deferred_join(const char* path)
{
return (strchr(path, DEFERRED_JOIN_DELIMITER) != NULL);
}
int path_has_deferred_join(lua_State* L)
{
const char* path = luaL_checkstring(L, -1);
lua_pushboolean(L, do_path_has_deferred_join(path));
return 1;
}
int path_resolve_deferred_join(lua_State* L)
{
const char* path = luaL_checkstring(L, -1);
char inBuffer[0x4000];
char outBuffer[0x4000];
char* ptr = outBuffer;
char* nextPart;
size_t len = strlen(path);
int i;
int numParts = 0;
strncpy(inBuffer, path, len);
inBuffer[len] = '\0';
char *parts[0x200];
// break up the string into parts and index the start of each part
nextPart = strchr(inBuffer, DEFERRED_JOIN_DELIMITER);
if (nextPart == NULL) // nothing to do
{
lua_pushlstring(L, inBuffer, len);
return 1;
}
parts[numParts++] = inBuffer;
while (nextPart != NULL)
{
*nextPart = '\0';
nextPart++;
parts[numParts++] = nextPart;
nextPart = strchr(nextPart, DEFERRED_JOIN_DELIMITER);
}
/* for each part... */
for (i = 0; i < numParts; ++i) {
nextPart = parts[i];
ptr = path_join_single(outBuffer, ptr, nextPart, 0);
}
lua_pushstring(L, outBuffer);
return 1;
}

View File

@ -1,4 +1,4 @@
/**
/**
* \file premake.c
* \brief Program entry point.
* \author Copyright (c) 2002-2017 Jason Perkins and the Premake project
@ -49,6 +49,9 @@ static const luaL_Reg path_functions[] = {
{ "getrelative", path_getrelative },
{ "isabsolute", path_isabsolute },
{ "join", path_join },
{ "deferredjoin", path_deferred_join },
{ "hasdeferredjoin", path_has_deferred_join },
{ "resolvedeferredjoin", path_resolve_deferred_join },
{ "normalize", path_normalize },
{ "translate", path_translate },
{ "wildcards", path_wildcards },

View File

@ -1,4 +1,4 @@
/**
/**
* \file premake.h
* \brief Program-wide constants and definitions.
* \author Copyright (c) 2002-2015 Jason Perkins and the Premake project
@ -89,6 +89,7 @@ unsigned long do_hash(const char* str, int seed);
void do_getabsolute(char* result, const char* value, const char* relative_to);
int do_getcwd(char* buffer, size_t size);
int do_isabsolute(const char* path);
int do_absolutetype(const char* path);
int do_isfile(lua_State* L, const char* filename);
int do_locate(lua_State* L, const char* filename, const char* path);
void do_normalize(lua_State* L, char* buffer, const char* path);
@ -108,6 +109,9 @@ int path_getabsolute(lua_State* L);
int path_getrelative(lua_State* L);
int path_isabsolute(lua_State* L);
int path_join(lua_State* L);
int path_deferred_join(lua_State* L);
int path_has_deferred_join(lua_State* L);
int path_resolve_deferred_join(lua_State* L);
int path_normalize(lua_State* L);
int path_translate(lua_State* L);
int path_wildcards(lua_State* L);

View File

@ -93,6 +93,141 @@
test.isequal("$ORIGIN/../../libs", path.getabsolute("$ORIGIN/../../libs"))
end
--
-- path.deferred_join() tests
--
function suite.deferred_join_OnMaybeAbsolutePath()
test.isequal("p1|%{foo}", path.deferredjoin("p1", "%{foo}"))
end
function suite.deferred_join_OnValidParts()
test.isequal("p1/p2", path.deferredjoin("p1", "p2"))
end
function suite.deferred_join_OnAbsoluteath()
test.isequal("/p2", path.deferredjoin("p1", "/p2"))
end
--
-- path.has_deferred_join() tests
--
function suite.has_deferred_join_true()
test.istrue(path.hasdeferredjoin("p1|%{foo}"))
end
function suite.deferred_join_OnValidParts()
test.isfalse(path.hasdeferredjoin("p1/p2"))
end
--
-- path.resolvedeferredjoin() tests
--
function suite.resolve_deferred_join_OnNoDelimiter()
test.isequal("p1", path.resolvedeferredjoin("p1"))
end
function suite.resolve_deferred_join_OnValidParts()
test.isequal("p1/p2", path.resolvedeferredjoin("p1|p2"))
end
function suite.resolve_deferred_join_OnAbsoluteWindowsPath()
test.isequal("C:/p2", path.resolvedeferredjoin("p1|C:/p2"))
end
function suite.resolve_deferred_join_OnCurrentDirectory()
test.isequal("p2", path.resolvedeferredjoin(".|p2"))
end
function suite.resolve_deferred_join_OnBackToBasePath()
test.isequal("", path.resolvedeferredjoin("p1/p2/|../../"))
end
function suite.resolve_deferred_join_OnBackToBasePathWithoutFinalSlash()
test.isequal("", path.resolvedeferredjoin("p1/p2/|../.."))
end
function suite.resolve_deferred_join_OnBothUpTwoFolders()
test.isequal("../../../../foo", path.resolvedeferredjoin("../../|../../foo"))
end
function suite.resolve_deferred_join_OnUptwoFolders()
test.isequal("p1/foo", path.resolvedeferredjoin("p1/p2/p3|../../foo"))
end
function suite.resolve_deferred_join_OnUptoBase()
test.isequal("foo", path.resolvedeferredjoin("p1/p2/p3|../../../foo"))
end
function suite.resolve_deferred_join_ignoreLeadingDots()
test.isequal("p1/p2/foo", path.resolvedeferredjoin("p1/p2|././foo"))
end
function suite.resolve_deferred_join_OnUptoParentOfBase()
test.isequal("../../p1", path.resolvedeferredjoin("p1/p2/p3/p4/p5/p6/p7/|../../../../../../../../../p1"))
end
function suite.resolve_deferred_join_onMoreThanTwoParts()
test.isequal("p1/p2/p3", path.resolvedeferredjoin("p1|p2|p3"))
end
function suite.resolve_deferred_join_removesExtraInternalSlashes()
test.isequal("p1/p2", path.resolvedeferredjoin("p1/|p2"))
end
function suite.resolve_deferred_join_removesTrailingSlash()
test.isequal("p1/p2", path.resolvedeferredjoin("p1|p2/"))
end
function suite.resolve_deferred_join_ignoresEmptyParts()
test.isequal("p2", path.resolvedeferredjoin("|p2|"))
end
function suite.resolve_deferred_join_canJoinBareSlash()
test.isequal("/Users", path.resolvedeferredjoin("/|Users"))
end
function suite.resolve_deferred_join_keepsLeadingEnvVar()
test.isequal("$(ProjectDir)/../../Bin", path.resolvedeferredjoin("$(ProjectDir)|../../Bin"))
end
function suite.resolve_deferred_join_keepsInternalEnvVar()
test.isequal("$(ProjectDir)/$(TargetName)/../../Bin", path.resolvedeferredjoin("$(ProjectDir)/$(TargetName)|../../Bin"))
end
function suite.resolve_deferred_join_keepsComplexInternalEnvVar()
test.isequal("$(ProjectDir)/myobj_$(Arch)/../../Bin", path.resolvedeferredjoin("$(ProjectDir)/myobj_$(Arch)|../../Bin"))
end
function suite.resolve_deferred_join_keepsRecursivePattern()
test.isequal("p1/**.lproj/../p2", path.resolvedeferredjoin("p1/**.lproj|../p2"))
end
function suite.resolve_deferred_join_keepsVSMacros()
test.isequal("p1/%(Filename).ext", path.resolvedeferredjoin("p1|%(Filename).ext"))
end
function suite.resolve_deferred_join_noCombineSingleDot()
test.isequal("p1/./../p2", path.resolvedeferredjoin("p1/.|../p2"))
end
function suite.resolve_deferred_join_absolute_second_part()
test.isequal("$ORIGIN", path.resolvedeferredjoin("foo/bar|$ORIGIN"))
end
function suite.resolve_deferred_join_absolute_second_part1()
test.isequal("$(FOO)/bar", path.resolvedeferredjoin("foo/bar|$(FOO)/bar"))
end
function suite.resolve_deferred_join_absolute_second_part2()
test.isequal("%ROOT%/foo", path.resolvedeferredjoin("foo/bar|%ROOT%/foo"))
end
function suite.resolve_deferred_join_token_in_second_part()
test.isequal("foo/bar/%{test}/foo", path.resolvedeferredjoin("foo/bar|%{test}/foo"))
end
--
-- path.getbasename() tests