Skip to content

fix: parent relative paths, and rework on the whole path extraction mechanics #429

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
May 30, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/CI-unixish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ jobs:

- name: integration test
run: |
python3 -m pytest integration_test.py
python3 -m pytest integration_test.py -vv

- name: Run CMake
run: |
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/CI-windows.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,5 +57,5 @@ jobs:
- name: integration test
run: |
set SIMPLECPP_EXE_PATH=.\${{ matrix.config }}\simplecpp.exe
python -m pytest integration_test.py || exit /b !errorlevel!
python -m pytest integration_test.py -vv || exit /b !errorlevel!

103 changes: 88 additions & 15 deletions integration_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,40 +35,48 @@ def __test_relative_header_create_source(dir, include1, include2, is_include1_sy

@pytest.mark.parametrize("with_pragma_once", (False, True))
@pytest.mark.parametrize("is_sys", (False, True))
def test_relative_header_1(tmpdir, with_pragma_once, is_sys):
def test_relative_header_1(record_property, tmpdir, with_pragma_once, is_sys):
_, double_include_error = __test_relative_header_create_header(tmpdir, with_pragma_once=with_pragma_once)

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

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

_, _, stderr = simplecpp(args, cwd=tmpdir)
_, stdout, stderr = simplecpp(args, cwd=tmpdir)
record_property("stdout", stdout)
record_property("stderr", stderr)

if with_pragma_once:
assert stderr == ''
else:
assert double_include_error in stderr

@pytest.mark.parametrize("with_pragma_once", (False, True))
@pytest.mark.parametrize("inv", (False, True))
@pytest.mark.parametrize("source_relative", (False, True))
def test_relative_header_2(tmpdir, inv, source_relative):
header_file, _ = __test_relative_header_create_header(tmpdir)
def test_relative_header_2(record_property, tmpdir, with_pragma_once, inv, source_relative):
header_file, double_include_error = __test_relative_header_create_header(tmpdir, with_pragma_once=with_pragma_once)

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

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

_, stdout, stderr = simplecpp(args, cwd=tmpdir)
assert stderr == ''
if source_relative and not inv:
assert '#line 8 "test.h"' in stdout
record_property("stdout", stdout)
record_property("stderr", stderr)
if with_pragma_once:
assert stderr == ''
if inv:
assert f'#line 8 "{pathlib.PurePath(tmpdir).as_posix()}/test.h"' in stdout
else:
assert '#line 8 "test.h"' in stdout
else:
assert f'#line 8 "{pathlib.PurePath(tmpdir).as_posix()}/test.h"' in stdout
assert double_include_error in stderr

@pytest.mark.parametrize("is_sys", (False, True))
@pytest.mark.parametrize("inv", (False, True))
@pytest.mark.parametrize("source_relative", (False, True))
def test_relative_header_3(tmpdir, is_sys, inv, source_relative):
def test_relative_header_3(record_property, tmpdir, is_sys, inv, source_relative):
test_subdir = os.path.join(tmpdir, "test_subdir")
os.mkdir(test_subdir)
header_file, _ = __test_relative_header_create_header(test_subdir)
Expand All @@ -78,20 +86,23 @@ def test_relative_header_3(tmpdir, is_sys, inv, source_relative):
args = ["test.c" if source_relative else test_file]

_, stdout, stderr = simplecpp(args, cwd=tmpdir)
record_property("stdout", stdout)
record_property("stderr", stderr)

if is_sys:
assert "missing header: Header not found" in stderr
else:
assert stderr == ''
if source_relative and not inv:
assert '#line 8 "test_subdir/test.h"' in stdout
else:
if inv:
assert f'#line 8 "{pathlib.PurePath(test_subdir).as_posix()}/test.h"' in stdout
else:
assert '#line 8 "test_subdir/test.h"' in stdout

@pytest.mark.parametrize("use_short_path", (False, True))
@pytest.mark.parametrize("relative_include_dir", (False, True))
@pytest.mark.parametrize("is_sys", (False, True))
@pytest.mark.parametrize("inv", (False, True))
def test_relative_header_4(tmpdir, use_short_path, is_sys, inv):
def test_relative_header_4(record_property, tmpdir, use_short_path, relative_include_dir, is_sys, inv):
test_subdir = os.path.join(tmpdir, "test_subdir")
os.mkdir(test_subdir)
header_file, _ = __test_relative_header_create_header(test_subdir)
Expand All @@ -100,7 +111,69 @@ def test_relative_header_4(tmpdir, use_short_path, is_sys, inv):

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

args = [format_include_path_arg(test_subdir), test_file]
args = [format_include_path_arg("test_subdir" if relative_include_dir else test_subdir), test_file]

_, _, stderr = simplecpp(args, cwd=tmpdir)
_, stdout, stderr = simplecpp(args, cwd=tmpdir)
record_property("stdout", stdout)
record_property("stderr", stderr)
assert stderr == ''
if (use_short_path and not inv) or (relative_include_dir and inv):
assert '#line 8 "test_subdir/test.h"' in stdout
else:
assert f'#line 8 "{pathlib.PurePath(test_subdir).as_posix()}/test.h"' in stdout

@pytest.mark.parametrize("with_pragma_once", (False, True))
@pytest.mark.parametrize("relative_include_dir", (False, True))
@pytest.mark.parametrize("is_sys", (False, True))
@pytest.mark.parametrize("inv", (False, True))
def test_relative_header_5(record_property, tmpdir, with_pragma_once, relative_include_dir, is_sys, inv): # test relative paths with ..
## 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
header_file, double_include_error = __test_relative_header_create_header(tmpdir, with_pragma_once=with_pragma_once)
if is_sys:
header_file_second_path = "test.h"
else:
header_file_second_path = "../test.h"

test_subdir = os.path.join(tmpdir, "test_subdir")
os.mkdir(test_subdir)
test_file = __test_relative_header_create_source(test_subdir, header_file, header_file_second_path, is_include2_sys=is_sys, inv=inv)

args = ([format_include_path_arg(".." if relative_include_dir else tmpdir)] if is_sys else []) + ["test.c"]

_, stdout, stderr = simplecpp(args, cwd=test_subdir)
record_property("stdout", stdout)
record_property("stderr", stderr)
if with_pragma_once:
assert stderr == ''
if (relative_include_dir or not is_sys) and inv:
assert '#line 8 "../test.h"' in stdout
else:
assert f'#line 8 "{pathlib.PurePath(tmpdir).as_posix()}/test.h"' in stdout
else:
assert double_include_error in stderr

@pytest.mark.parametrize("with_pragma_once", (False, True))
@pytest.mark.parametrize("relative_include_dir", (False, True))
@pytest.mark.parametrize("is_sys", (False, True))
@pytest.mark.parametrize("inv", (False, True))
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
## 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
header_file, double_include_error = __test_relative_header_create_header(tmpdir, with_pragma_once=with_pragma_once)

test_subdir = os.path.join(tmpdir, "test_subdir")
os.mkdir(test_subdir)
test_file = __test_relative_header_create_source(tmpdir, header_file, "../test.h", is_include2_sys=is_sys, inv=inv)

args = [format_include_path_arg("test_subdir" if relative_include_dir else test_subdir), "test.c"]

_, stdout, stderr = simplecpp(args, cwd=tmpdir)
record_property("stdout", stdout)
record_property("stderr", stderr)
if with_pragma_once:
assert stderr == ''
if relative_include_dir and inv:
assert '#line 8 "test.h"' in stdout
else:
assert f'#line 8 "{pathlib.PurePath(tmpdir).as_posix()}/test.h"' in stdout
else:
assert double_include_error in stderr
98 changes: 69 additions & 29 deletions simplecpp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2719,14 +2719,42 @@ static std::string toAbsolutePath(const std::string& path) {
return simplecpp::simplifyPath(path);
}

static std::pair<std::string, bool> extractRelativePathFromAbsolute(const std::string& absolutepath) {
static const std::string prefix = currentDirectory() + "/";
if (startsWith_(absolutepath, prefix)) {
const std::size_t size = prefix.size();
return std::make_pair(absolutepath.substr(size, absolutepath.size() - size), true);
static std::string dirPath(const std::string& path, bool withTrailingSlash=true) {
const std::size_t lastSlash = path.find_last_of("\\/");
if (lastSlash == std::string::npos) {
return "";
}
// otherwise
return std::make_pair("", false);
return path.substr(0, lastSlash + (withTrailingSlash ? 1U : 0U));
}

static std::string omitPathTrailingSlash(const std::string& path) {
if (endsWith(path, "/")) {
return path.substr(0, path.size() - 1U);
}
return path;
}

static std::string extractRelativePathFromAbsolute(const std::string& absoluteSimplifiedPath, const std::string& prefixSimplifiedAbsoluteDir = currentDirectory()) {
const std::string normalizedAbsolutePath = omitPathTrailingSlash(absoluteSimplifiedPath);
std::string currentPrefix = omitPathTrailingSlash(prefixSimplifiedAbsoluteDir);
std::string leadingParenting;
while (!startsWith_(normalizedAbsolutePath, currentPrefix)) {
leadingParenting = "../" + leadingParenting;
currentPrefix = dirPath(currentPrefix, false);
}
const std::size_t size = currentPrefix.size();
std::string relativeFromMeetingPath = normalizedAbsolutePath.substr(size, normalizedAbsolutePath.size() - size);
if (currentPrefix.empty() && !(startsWith_(absoluteSimplifiedPath, "/") && startsWith_(prefixSimplifiedAbsoluteDir, "/"))) {
// In the case that there is no common prefix path,
// and at not both of the paths start with `/` (can happen only in Windows paths on distinct partitions),
// return the absolute simplified path as is because no relative path can match.
return absoluteSimplifiedPath;
}
if (startsWith_(relativeFromMeetingPath, "/")) {
// omit the leading slash
relativeFromMeetingPath = relativeFromMeetingPath.substr(1, relativeFromMeetingPath.size());
}
return leadingParenting + relativeFromMeetingPath;
}

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

static std::string getRelativeFileName(const std::string &baseFile, const std::string &header)
{
std::string path;
if (baseFile.find_first_of("\\/") != std::string::npos)
path = baseFile.substr(0, baseFile.find_last_of("\\/") + 1U) + header;
else
path = header;
return simplecpp::simplifyPath(path);
const std::string baseFileSimplified = simplecpp::simplifyPath(baseFile);
const std::string baseFileAbsolute = isAbsolutePath(baseFileSimplified) ?
baseFileSimplified :
simplecpp::simplifyPath(currentDirectory() + "/" + baseFileSimplified);

const std::string headerSimplified = simplecpp::simplifyPath(header);
const std::string path = isAbsolutePath(headerSimplified) ?
headerSimplified :
simplecpp::simplifyPath(dirPath(baseFileAbsolute) + headerSimplified);

return extractRelativePathFromAbsolute(path);
}

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

static std::string openHeaderIncludePath(std::ifstream &f, const simplecpp::DUI &dui, const std::string &header)
Expand Down Expand Up @@ -3210,22 +3244,18 @@ static std::string findPathInMapBothRelativeAndAbsolute(const std::map<std::stri
if (filedata.find(path) != filedata.end()) {// try first to respect the exact match
return path;
}

// otherwise - try to use the normalize to the correct representation
std::string alternativePath;
if (isAbsolutePath(path)) {
const std::pair<std::string, bool> relativeExtractedResult = extractRelativePathFromAbsolute(path);
if (relativeExtractedResult.second) {
const std::string relativePath = relativeExtractedResult.first;
if (filedata.find(relativePath) != filedata.end()) {
return relativePath;
}
}
alternativePath = extractRelativePathFromAbsolute(simplecpp::simplifyPath(path));
} else {
const std::string absolutePath = toAbsolutePath(path);
if (filedata.find(absolutePath) != filedata.end()) {
return absolutePath;
}
alternativePath = toAbsolutePath(path);
}

if (filedata.find(alternativePath) != filedata.end()) {
return alternativePath;
}
// otherwise
return "";
}

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

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)
{
const bool inserted = filedata.insert(std::make_pair(header2, tokens)).second;
if (!inserted) {
std::cerr << "error in " << contextDesc << " - attempt to add a tokenized file to the file map, but this file is already in the map! Details:" <<
"header: " << header << " header2: " << header2 << " source: " << sourcefile << " systemheader: " << systemheader << std::endl;
std::abort();
}
}

std::map<std::string, simplecpp::TokenList*> simplecpp::load(const simplecpp::TokenList &rawtokens, std::vector<std::string> &filenames, const simplecpp::DUI &dui, simplecpp::OutputList *outputList)
{
#ifdef SIMPLECPP_WINDOWS
Expand Down Expand Up @@ -3343,7 +3383,7 @@ std::map<std::string, simplecpp::TokenList*> simplecpp::load(const simplecpp::To
TokenList *tokens = new TokenList(header2, filenames, outputList);
if (dui.removeComments)
tokens->removeComments();
ret[header2] = tokens;
safeInsertTokenListToMap(ret, header2, tokens, header, rawtok->location.file(), systemheader, "simplecpp::load");
if (tokens->front())
filelist.push_back(tokens->front());
}
Expand Down Expand Up @@ -3630,7 +3670,7 @@ void simplecpp::preprocess(simplecpp::TokenList &output, const simplecpp::TokenL
TokenList * const tokens = new TokenList(header2, files, outputList);
if (dui.removeComments)
tokens->removeComments();
filedata[header2] = tokens;
safeInsertTokenListToMap(filedata, header2, tokens, header, rawtok->location.file(), systemheader, "simplecpp::preprocess");
}
}
if (header2.empty()) {
Expand Down
Loading
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy