Skip to content

Commit 76df97f

Browse files
authored
fix: parent relative paths, and rework on the whole path extraction mechanics (#429)
1 parent 2bbb496 commit 76df97f

File tree

4 files changed

+159
-46
lines changed

4 files changed

+159
-46
lines changed

.github/workflows/CI-unixish.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ jobs:
5353
5454
- name: integration test
5555
run: |
56-
python3 -m pytest integration_test.py
56+
python3 -m pytest integration_test.py -vv
5757
5858
- name: Run CMake
5959
run: |

.github/workflows/CI-windows.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,5 +57,5 @@ jobs:
5757
- name: integration test
5858
run: |
5959
set SIMPLECPP_EXE_PATH=.\${{ matrix.config }}\simplecpp.exe
60-
python -m pytest integration_test.py || exit /b !errorlevel!
60+
python -m pytest integration_test.py -vv || exit /b !errorlevel!
6161

integration_test.py

Lines changed: 88 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -35,40 +35,48 @@ def __test_relative_header_create_source(dir, include1, include2, is_include1_sy
3535

3636
@pytest.mark.parametrize("with_pragma_once", (False, True))
3737
@pytest.mark.parametrize("is_sys", (False, True))
38-
def test_relative_header_1(tmpdir, with_pragma_once, is_sys):
38+
def test_relative_header_1(record_property, tmpdir, with_pragma_once, is_sys):
3939
_, double_include_error = __test_relative_header_create_header(tmpdir, with_pragma_once=with_pragma_once)
4040

4141
test_file = __test_relative_header_create_source(tmpdir, "test.h", "test.h", is_include1_sys=is_sys, is_include2_sys=is_sys)
4242

4343
args = ([format_include_path_arg(tmpdir)] if is_sys else []) + [test_file]
4444

45-
_, _, stderr = simplecpp(args, cwd=tmpdir)
45+
_, stdout, stderr = simplecpp(args, cwd=tmpdir)
46+
record_property("stdout", stdout)
47+
record_property("stderr", stderr)
4648

4749
if with_pragma_once:
4850
assert stderr == ''
4951
else:
5052
assert double_include_error in stderr
5153

54+
@pytest.mark.parametrize("with_pragma_once", (False, True))
5255
@pytest.mark.parametrize("inv", (False, True))
5356
@pytest.mark.parametrize("source_relative", (False, True))
54-
def test_relative_header_2(tmpdir, inv, source_relative):
55-
header_file, _ = __test_relative_header_create_header(tmpdir)
57+
def test_relative_header_2(record_property, tmpdir, with_pragma_once, inv, source_relative):
58+
header_file, double_include_error = __test_relative_header_create_header(tmpdir, with_pragma_once=with_pragma_once)
5659

5760
test_file = __test_relative_header_create_source(tmpdir, "test.h", header_file, inv=inv)
5861

5962
args = ["test.c" if source_relative else test_file]
6063

6164
_, stdout, stderr = simplecpp(args, cwd=tmpdir)
62-
assert stderr == ''
63-
if source_relative and not inv:
64-
assert '#line 8 "test.h"' in stdout
65+
record_property("stdout", stdout)
66+
record_property("stderr", stderr)
67+
if with_pragma_once:
68+
assert stderr == ''
69+
if inv:
70+
assert f'#line 8 "{pathlib.PurePath(tmpdir).as_posix()}/test.h"' in stdout
71+
else:
72+
assert '#line 8 "test.h"' in stdout
6573
else:
66-
assert f'#line 8 "{pathlib.PurePath(tmpdir).as_posix()}/test.h"' in stdout
74+
assert double_include_error in stderr
6775

6876
@pytest.mark.parametrize("is_sys", (False, True))
6977
@pytest.mark.parametrize("inv", (False, True))
7078
@pytest.mark.parametrize("source_relative", (False, True))
71-
def test_relative_header_3(tmpdir, is_sys, inv, source_relative):
79+
def test_relative_header_3(record_property, tmpdir, is_sys, inv, source_relative):
7280
test_subdir = os.path.join(tmpdir, "test_subdir")
7381
os.mkdir(test_subdir)
7482
header_file, _ = __test_relative_header_create_header(test_subdir)
@@ -78,20 +86,23 @@ def test_relative_header_3(tmpdir, is_sys, inv, source_relative):
7886
args = ["test.c" if source_relative else test_file]
7987

8088
_, stdout, stderr = simplecpp(args, cwd=tmpdir)
89+
record_property("stdout", stdout)
90+
record_property("stderr", stderr)
8191

8292
if is_sys:
8393
assert "missing header: Header not found" in stderr
8494
else:
8595
assert stderr == ''
86-
if source_relative and not inv:
87-
assert '#line 8 "test_subdir/test.h"' in stdout
88-
else:
96+
if inv:
8997
assert f'#line 8 "{pathlib.PurePath(test_subdir).as_posix()}/test.h"' in stdout
98+
else:
99+
assert '#line 8 "test_subdir/test.h"' in stdout
90100

91101
@pytest.mark.parametrize("use_short_path", (False, True))
102+
@pytest.mark.parametrize("relative_include_dir", (False, True))
92103
@pytest.mark.parametrize("is_sys", (False, True))
93104
@pytest.mark.parametrize("inv", (False, True))
94-
def test_relative_header_4(tmpdir, use_short_path, is_sys, inv):
105+
def test_relative_header_4(record_property, tmpdir, use_short_path, relative_include_dir, is_sys, inv):
95106
test_subdir = os.path.join(tmpdir, "test_subdir")
96107
os.mkdir(test_subdir)
97108
header_file, _ = __test_relative_header_create_header(test_subdir)
@@ -100,7 +111,69 @@ def test_relative_header_4(tmpdir, use_short_path, is_sys, inv):
100111

101112
test_file = __test_relative_header_create_source(tmpdir, header_file, "test.h", is_include2_sys=is_sys, inv=inv)
102113

103-
args = [format_include_path_arg(test_subdir), test_file]
114+
args = [format_include_path_arg("test_subdir" if relative_include_dir else test_subdir), test_file]
104115

105-
_, _, stderr = simplecpp(args, cwd=tmpdir)
116+
_, stdout, stderr = simplecpp(args, cwd=tmpdir)
117+
record_property("stdout", stdout)
118+
record_property("stderr", stderr)
106119
assert stderr == ''
120+
if (use_short_path and not inv) or (relative_include_dir and inv):
121+
assert '#line 8 "test_subdir/test.h"' in stdout
122+
else:
123+
assert f'#line 8 "{pathlib.PurePath(test_subdir).as_posix()}/test.h"' in stdout
124+
125+
@pytest.mark.parametrize("with_pragma_once", (False, True))
126+
@pytest.mark.parametrize("relative_include_dir", (False, True))
127+
@pytest.mark.parametrize("is_sys", (False, True))
128+
@pytest.mark.parametrize("inv", (False, True))
129+
def test_relative_header_5(record_property, tmpdir, with_pragma_once, relative_include_dir, is_sys, inv): # test relative paths with ..
130+
## in this test, the subdir role is the opposite then the previous - it contains the test.c file, while the parent tmpdir contains the header file
131+
header_file, double_include_error = __test_relative_header_create_header(tmpdir, with_pragma_once=with_pragma_once)
132+
if is_sys:
133+
header_file_second_path = "test.h"
134+
else:
135+
header_file_second_path = "../test.h"
136+
137+
test_subdir = os.path.join(tmpdir, "test_subdir")
138+
os.mkdir(test_subdir)
139+
test_file = __test_relative_header_create_source(test_subdir, header_file, header_file_second_path, is_include2_sys=is_sys, inv=inv)
140+
141+
args = ([format_include_path_arg(".." if relative_include_dir else tmpdir)] if is_sys else []) + ["test.c"]
142+
143+
_, stdout, stderr = simplecpp(args, cwd=test_subdir)
144+
record_property("stdout", stdout)
145+
record_property("stderr", stderr)
146+
if with_pragma_once:
147+
assert stderr == ''
148+
if (relative_include_dir or not is_sys) and inv:
149+
assert '#line 8 "../test.h"' in stdout
150+
else:
151+
assert f'#line 8 "{pathlib.PurePath(tmpdir).as_posix()}/test.h"' in stdout
152+
else:
153+
assert double_include_error in stderr
154+
155+
@pytest.mark.parametrize("with_pragma_once", (False, True))
156+
@pytest.mark.parametrize("relative_include_dir", (False, True))
157+
@pytest.mark.parametrize("is_sys", (False, True))
158+
@pytest.mark.parametrize("inv", (False, True))
159+
def test_relative_header_6(record_property, tmpdir, with_pragma_once, relative_include_dir, is_sys, inv): # test relative paths with .. that is resolved only by an include dir
160+
## in this test, both the header and the source file are at the same dir, but there is a dummy inclusion dir as a subdir
161+
header_file, double_include_error = __test_relative_header_create_header(tmpdir, with_pragma_once=with_pragma_once)
162+
163+
test_subdir = os.path.join(tmpdir, "test_subdir")
164+
os.mkdir(test_subdir)
165+
test_file = __test_relative_header_create_source(tmpdir, header_file, "../test.h", is_include2_sys=is_sys, inv=inv)
166+
167+
args = [format_include_path_arg("test_subdir" if relative_include_dir else test_subdir), "test.c"]
168+
169+
_, stdout, stderr = simplecpp(args, cwd=tmpdir)
170+
record_property("stdout", stdout)
171+
record_property("stderr", stderr)
172+
if with_pragma_once:
173+
assert stderr == ''
174+
if relative_include_dir and inv:
175+
assert '#line 8 "test.h"' in stdout
176+
else:
177+
assert f'#line 8 "{pathlib.PurePath(tmpdir).as_posix()}/test.h"' in stdout
178+
else:
179+
assert double_include_error in stderr

simplecpp.cpp

Lines changed: 69 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -2719,14 +2719,42 @@ static std::string toAbsolutePath(const std::string& path) {
27192719
return simplecpp::simplifyPath(path);
27202720
}
27212721

2722-
static std::pair<std::string, bool> extractRelativePathFromAbsolute(const std::string& absolutepath) {
2723-
static const std::string prefix = currentDirectory() + "/";
2724-
if (startsWith_(absolutepath, prefix)) {
2725-
const std::size_t size = prefix.size();
2726-
return std::make_pair(absolutepath.substr(size, absolutepath.size() - size), true);
2722+
static std::string dirPath(const std::string& path, bool withTrailingSlash=true) {
2723+
const std::size_t lastSlash = path.find_last_of("\\/");
2724+
if (lastSlash == std::string::npos) {
2725+
return "";
27272726
}
2728-
// otherwise
2729-
return std::make_pair("", false);
2727+
return path.substr(0, lastSlash + (withTrailingSlash ? 1U : 0U));
2728+
}
2729+
2730+
static std::string omitPathTrailingSlash(const std::string& path) {
2731+
if (endsWith(path, "/")) {
2732+
return path.substr(0, path.size() - 1U);
2733+
}
2734+
return path;
2735+
}
2736+
2737+
static std::string extractRelativePathFromAbsolute(const std::string& absoluteSimplifiedPath, const std::string& prefixSimplifiedAbsoluteDir = currentDirectory()) {
2738+
const std::string normalizedAbsolutePath = omitPathTrailingSlash(absoluteSimplifiedPath);
2739+
std::string currentPrefix = omitPathTrailingSlash(prefixSimplifiedAbsoluteDir);
2740+
std::string leadingParenting;
2741+
while (!startsWith_(normalizedAbsolutePath, currentPrefix)) {
2742+
leadingParenting = "../" + leadingParenting;
2743+
currentPrefix = dirPath(currentPrefix, false);
2744+
}
2745+
const std::size_t size = currentPrefix.size();
2746+
std::string relativeFromMeetingPath = normalizedAbsolutePath.substr(size, normalizedAbsolutePath.size() - size);
2747+
if (currentPrefix.empty() && !(startsWith_(absoluteSimplifiedPath, "/") && startsWith_(prefixSimplifiedAbsoluteDir, "/"))) {
2748+
// In the case that there is no common prefix path,
2749+
// and at not both of the paths start with `/` (can happen only in Windows paths on distinct partitions),
2750+
// return the absolute simplified path as is because no relative path can match.
2751+
return absoluteSimplifiedPath;
2752+
}
2753+
if (startsWith_(relativeFromMeetingPath, "/")) {
2754+
// omit the leading slash
2755+
relativeFromMeetingPath = relativeFromMeetingPath.substr(1, relativeFromMeetingPath.size());
2756+
}
2757+
return leadingParenting + relativeFromMeetingPath;
27302758
}
27312759

27322760
static std::string openHeader(std::ifstream &f, const simplecpp::DUI &dui, const std::string &sourcefile, const std::string &header, bool systemheader);
@@ -3147,12 +3175,17 @@ static std::string openHeader(std::ifstream &f, const std::string &path)
31473175

31483176
static std::string getRelativeFileName(const std::string &baseFile, const std::string &header)
31493177
{
3150-
std::string path;
3151-
if (baseFile.find_first_of("\\/") != std::string::npos)
3152-
path = baseFile.substr(0, baseFile.find_last_of("\\/") + 1U) + header;
3153-
else
3154-
path = header;
3155-
return simplecpp::simplifyPath(path);
3178+
const std::string baseFileSimplified = simplecpp::simplifyPath(baseFile);
3179+
const std::string baseFileAbsolute = isAbsolutePath(baseFileSimplified) ?
3180+
baseFileSimplified :
3181+
simplecpp::simplifyPath(currentDirectory() + "/" + baseFileSimplified);
3182+
3183+
const std::string headerSimplified = simplecpp::simplifyPath(header);
3184+
const std::string path = isAbsolutePath(headerSimplified) ?
3185+
headerSimplified :
3186+
simplecpp::simplifyPath(dirPath(baseFileAbsolute) + headerSimplified);
3187+
3188+
return extractRelativePathFromAbsolute(path);
31563189
}
31573190

31583191
static std::string openHeaderRelative(std::ifstream &f, const std::string &sourcefile, const std::string &header)
@@ -3174,8 +3207,9 @@ static std::string getIncludePathFileName(const std::string &includePath, const
31743207
std::string basePath = toAbsolutePath(includePath);
31753208
if (!basePath.empty() && basePath[basePath.size()-1U]!='/' && basePath[basePath.size()-1U]!='\\')
31763209
basePath += '/';
3177-
const std::string absolutesimplifiedHeaderPath = basePath + simplifiedHeader;
3178-
return extractRelativePathFromAbsolute(absolutesimplifiedHeaderPath).first;
3210+
const std::string absoluteSimplifiedHeaderPath = simplecpp::simplifyPath(basePath + simplifiedHeader);
3211+
// preserve absoluteness/relativieness of the including dir
3212+
return isAbsolutePath(includePath) ? absoluteSimplifiedHeaderPath : extractRelativePathFromAbsolute(absoluteSimplifiedHeaderPath);
31793213
}
31803214

31813215
static std::string openHeaderIncludePath(std::ifstream &f, const simplecpp::DUI &dui, const std::string &header)
@@ -3210,22 +3244,18 @@ static std::string findPathInMapBothRelativeAndAbsolute(const std::map<std::stri
32103244
if (filedata.find(path) != filedata.end()) {// try first to respect the exact match
32113245
return path;
32123246
}
3247+
32133248
// otherwise - try to use the normalize to the correct representation
3249+
std::string alternativePath;
32143250
if (isAbsolutePath(path)) {
3215-
const std::pair<std::string, bool> relativeExtractedResult = extractRelativePathFromAbsolute(path);
3216-
if (relativeExtractedResult.second) {
3217-
const std::string relativePath = relativeExtractedResult.first;
3218-
if (filedata.find(relativePath) != filedata.end()) {
3219-
return relativePath;
3220-
}
3221-
}
3251+
alternativePath = extractRelativePathFromAbsolute(simplecpp::simplifyPath(path));
32223252
} else {
3223-
const std::string absolutePath = toAbsolutePath(path);
3224-
if (filedata.find(absolutePath) != filedata.end()) {
3225-
return absolutePath;
3226-
}
3253+
alternativePath = toAbsolutePath(path);
3254+
}
3255+
3256+
if (filedata.find(alternativePath) != filedata.end()) {
3257+
return alternativePath;
32273258
}
3228-
// otherwise
32293259
return "";
32303260
}
32313261

@@ -3267,6 +3297,16 @@ static bool hasFile(const std::map<std::string, simplecpp::TokenList *> &filedat
32673297
return !getFileIdPath(filedata, sourcefile, header, dui, systemheader).empty();
32683298
}
32693299

3300+
static void safeInsertTokenListToMap(std::map<std::string, simplecpp::TokenList *> &filedata, const std::string &header2, simplecpp::TokenList *tokens, const std::string &header, const std::string &sourcefile, bool systemheader, const char* contextDesc)
3301+
{
3302+
const bool inserted = filedata.insert(std::make_pair(header2, tokens)).second;
3303+
if (!inserted) {
3304+
std::cerr << "error in " << contextDesc << " - attempt to add a tokenized file to the file map, but this file is already in the map! Details:" <<
3305+
"header: " << header << " header2: " << header2 << " source: " << sourcefile << " systemheader: " << systemheader << std::endl;
3306+
std::abort();
3307+
}
3308+
}
3309+
32703310
std::map<std::string, simplecpp::TokenList*> simplecpp::load(const simplecpp::TokenList &rawtokens, std::vector<std::string> &filenames, const simplecpp::DUI &dui, simplecpp::OutputList *outputList)
32713311
{
32723312
#ifdef SIMPLECPP_WINDOWS
@@ -3343,7 +3383,7 @@ std::map<std::string, simplecpp::TokenList*> simplecpp::load(const simplecpp::To
33433383
TokenList *tokens = new TokenList(header2, filenames, outputList);
33443384
if (dui.removeComments)
33453385
tokens->removeComments();
3346-
ret[header2] = tokens;
3386+
safeInsertTokenListToMap(ret, header2, tokens, header, rawtok->location.file(), systemheader, "simplecpp::load");
33473387
if (tokens->front())
33483388
filelist.push_back(tokens->front());
33493389
}
@@ -3630,7 +3670,7 @@ void simplecpp::preprocess(simplecpp::TokenList &output, const simplecpp::TokenL
36303670
TokenList * const tokens = new TokenList(header2, files, outputList);
36313671
if (dui.removeComments)
36323672
tokens->removeComments();
3633-
filedata[header2] = tokens;
3673+
safeInsertTokenListToMap(filedata, header2, tokens, header, rawtok->location.file(), systemheader, "simplecpp::preprocess");
36343674
}
36353675
}
36363676
if (header2.empty()) {

0 commit comments

Comments
 (0)