Skip to content

Commit c4b6e37

Browse files
Tal500Tal Hadad
and
Tal Hadad
authored
fix: use both absolute and relative header paths in header matching (#362)
Co-authored-by: Tal Hadad <[email protected]>
1 parent a853253 commit c4b6e37

File tree

6 files changed

+286
-24
lines changed

6 files changed

+286
-24
lines changed

.github/workflows/CI-unixish.yml

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,18 @@ jobs:
3030
run: |
3131
sudo apt-get update
3232
sudo apt-get install libc++-18-dev
33-
33+
34+
- name: Install missing software on macos
35+
if: contains(matrix.os, 'macos')
36+
run: |
37+
brew install python3
38+
39+
- name: Install missing Python packages
40+
run: |
41+
python3 -m pip config set global.break-system-packages true
42+
python3 -m pip install pip --upgrade
43+
python3 -m pip install pytest
44+
3445
- name: make simplecpp
3546
run: make -j$(nproc)
3647

@@ -41,6 +52,10 @@ jobs:
4152
run: |
4253
make -j$(nproc) selfcheck
4354
55+
- name: integration test
56+
run: |
57+
python3 -m pytest integration_test.py
58+
4459
- name: Run CMake
4560
run: |
4661
cmake -S . -B cmake.output

.github/workflows/CI-windows.yml

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,18 @@ jobs:
2626

2727
- name: Setup msbuild.exe
2828
uses: microsoft/setup-msbuild@v2
29-
29+
30+
- name: Set up Python 3.13
31+
uses: actions/setup-python@v5
32+
with:
33+
python-version: '3.13'
34+
check-latest: true
35+
36+
- name: Install missing Python packages
37+
run: |
38+
python -m pip install pip --upgrade || exit /b !errorlevel!
39+
python -m pip install pytest || exit /b !errorlevel!
40+
3041
- name: Run cmake
3142
if: matrix.os == 'windows-2019'
3243
run: |
@@ -48,4 +59,9 @@ jobs:
4859
- name: Selfcheck
4960
run: |
5061
.\${{ matrix.config }}\simplecpp.exe simplecpp.cpp -e || exit /b !errorlevel!
62+
63+
- name: integration test
64+
run: |
65+
set SIMPLECPP_EXE_PATH=.\${{ matrix.config }}\simplecpp.exe
66+
python -m pytest integration_test.py || exit /b !errorlevel!
5167

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,6 @@ testrunner
3232
# CLion
3333
/.idea
3434
/cmake-build-*
35+
36+
# python
37+
__pycache__/

integration_test.py

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
## test with python -m pytest integration_test.py
2+
3+
import os
4+
import pytest
5+
from testutils import simplecpp, format_include_path_arg, format_include
6+
7+
def __test_relative_header_create_header(dir, with_pragma_once=True):
8+
header_file = os.path.join(dir, 'test.h')
9+
with open(header_file, 'wt') as f:
10+
f.write(f"""
11+
{"#pragma once" if with_pragma_once else ""}
12+
#ifndef TEST_H_INCLUDED
13+
#define TEST_H_INCLUDED
14+
#else
15+
#error header_was_already_included
16+
#endif
17+
""")
18+
return header_file, "error: #error header_was_already_included"
19+
20+
def __test_relative_header_create_source(dir, include1, include2, is_include1_sys=False, is_include2_sys=False, inv=False):
21+
if inv:
22+
return __test_relative_header_create_source(dir, include1=include2, include2=include1, is_include1_sys=is_include2_sys, is_include2_sys=is_include1_sys)
23+
## otherwise
24+
25+
src_file = os.path.join(dir, 'test.c')
26+
with open(src_file, 'wt') as f:
27+
f.write(f"""
28+
#undef TEST_H_INCLUDED
29+
#include {format_include(include1, is_include1_sys)}
30+
#include {format_include(include2, is_include2_sys)}
31+
""")
32+
return src_file
33+
34+
@pytest.mark.parametrize("with_pragma_once", (False, True))
35+
@pytest.mark.parametrize("is_sys", (False, True))
36+
def test_relative_header_1(tmpdir, with_pragma_once, is_sys):
37+
_, double_include_error = __test_relative_header_create_header(tmpdir, with_pragma_once=with_pragma_once)
38+
39+
test_file = __test_relative_header_create_source(tmpdir, "test.h", "test.h", is_include1_sys=is_sys, is_include2_sys=is_sys)
40+
41+
args = ([format_include_path_arg(tmpdir)] if is_sys else []) + [test_file]
42+
43+
_, _, stderr = simplecpp(args, cwd=tmpdir)
44+
45+
if with_pragma_once:
46+
assert stderr == ''
47+
else:
48+
assert double_include_error in stderr
49+
50+
@pytest.mark.parametrize("inv", (False, True))
51+
def test_relative_header_2(tmpdir, inv):
52+
header_file, _ = __test_relative_header_create_header(tmpdir)
53+
54+
test_file = __test_relative_header_create_source(tmpdir, "test.h", header_file, inv=inv)
55+
56+
args = [test_file]
57+
58+
_, _, stderr = simplecpp(args, cwd=tmpdir)
59+
assert stderr == ''
60+
61+
@pytest.mark.parametrize("is_sys", (False, True))
62+
@pytest.mark.parametrize("inv", (False, True))
63+
def test_relative_header_3(tmpdir, is_sys, inv):
64+
test_subdir = os.path.join(tmpdir, "test_subdir")
65+
os.mkdir(test_subdir)
66+
header_file, _ = __test_relative_header_create_header(test_subdir)
67+
68+
test_file = __test_relative_header_create_source(tmpdir, "test_subdir/test.h", header_file, is_include1_sys=is_sys, inv=inv)
69+
70+
args = [test_file]
71+
72+
_, _, stderr = simplecpp(args, cwd=tmpdir)
73+
74+
if is_sys:
75+
assert "missing header: Header not found" in stderr
76+
else:
77+
assert stderr == ''
78+
79+
@pytest.mark.parametrize("use_short_path", (False, True))
80+
@pytest.mark.parametrize("is_sys", (False, True))
81+
@pytest.mark.parametrize("inv", (False, True))
82+
def test_relative_header_4(tmpdir, use_short_path, is_sys, inv):
83+
test_subdir = os.path.join(tmpdir, "test_subdir")
84+
os.mkdir(test_subdir)
85+
header_file, _ = __test_relative_header_create_header(test_subdir)
86+
if use_short_path:
87+
header_file = "test_subdir/test.h"
88+
89+
test_file = __test_relative_header_create_source(tmpdir, header_file, "test.h", is_include2_sys=is_sys, inv=inv)
90+
91+
args = [format_include_path_arg(test_subdir), test_file]
92+
93+
_, _, stderr = simplecpp(args, cwd=tmpdir)
94+
assert stderr == ''

simplecpp.cpp

Lines changed: 99 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@
4343
#ifdef SIMPLECPP_WINDOWS
4444
#include <windows.h>
4545
#undef ERROR
46+
#else
47+
#include <unistd.h>
4648
#endif
4749

4850
#if __cplusplus >= 201103L
@@ -147,6 +149,11 @@ static unsigned long long stringToULL(const std::string &s)
147149
return ret;
148150
}
149151

152+
static bool startsWith(const std::string &s, const std::string &p)
153+
{
154+
return (s.size() >= p.size()) && std::equal(p.begin(), p.end(), s.begin());
155+
}
156+
150157
static bool endsWith(const std::string &s, const std::string &e)
151158
{
152159
return (s.size() >= e.size()) && std::equal(e.rbegin(), e.rend(), s.rbegin());
@@ -2680,6 +2687,46 @@ static bool isCpp17OrLater(const simplecpp::DUI &dui)
26802687
return !std_ver.empty() && (std_ver >= "201703L");
26812688
}
26822689

2690+
2691+
static std::string currentDirectoryOSCalc() {
2692+
#ifdef SIMPLECPP_WINDOWS
2693+
TCHAR NPath[MAX_PATH];
2694+
GetCurrentDirectory(MAX_PATH, NPath);
2695+
return NPath;
2696+
#else
2697+
const std::size_t size = 1024;
2698+
char the_path[size];
2699+
getcwd(the_path, size);
2700+
return the_path;
2701+
#endif
2702+
}
2703+
2704+
static const std::string& currentDirectory() {
2705+
static const std::string curdir = simplecpp::simplifyPath(currentDirectoryOSCalc());
2706+
return curdir;
2707+
}
2708+
2709+
static std::string toAbsolutePath(const std::string& path) {
2710+
if (path.empty()) {
2711+
return path;// preserve error file path that is indicated by an empty string
2712+
}
2713+
if (!isAbsolutePath(path)) {
2714+
return currentDirectory() + "/" + path;
2715+
}
2716+
// otherwise
2717+
return path;
2718+
}
2719+
2720+
static std::pair<std::string, bool> extractRelativePathFromAbsolute(const std::string& absolutepath) {
2721+
static const std::string prefix = currentDirectory() + "/";
2722+
if (startsWith(absolutepath, prefix)) {
2723+
const std::size_t size = prefix.size();
2724+
return std::make_pair(absolutepath.substr(size, absolutepath.size() - size), true);
2725+
}
2726+
// otherwise
2727+
return std::make_pair("", false);
2728+
}
2729+
26832730
static std::string openHeader(std::ifstream &f, const simplecpp::DUI &dui, const std::string &sourcefile, const std::string &header, bool systemheader);
26842731
static void simplifyHasInclude(simplecpp::TokenList &expr, const simplecpp::DUI &dui)
26852732
{
@@ -3098,9 +3145,12 @@ static std::string openHeader(std::ifstream &f, const std::string &path)
30983145

30993146
static std::string getRelativeFileName(const std::string &sourcefile, const std::string &header)
31003147
{
3148+
std::string path;
31013149
if (sourcefile.find_first_of("\\/") != std::string::npos)
3102-
return simplecpp::simplifyPath(sourcefile.substr(0, sourcefile.find_last_of("\\/") + 1U) + header);
3103-
return simplecpp::simplifyPath(header);
3150+
path = sourcefile.substr(0, sourcefile.find_last_of("\\/") + 1U) + header;
3151+
else
3152+
path = header;
3153+
return simplecpp::simplifyPath(path);
31043154
}
31053155

31063156
static std::string openHeaderRelative(std::ifstream &f, const std::string &sourcefile, const std::string &header)
@@ -3110,7 +3160,7 @@ static std::string openHeaderRelative(std::ifstream &f, const std::string &sourc
31103160

31113161
static std::string getIncludePathFileName(const std::string &includePath, const std::string &header)
31123162
{
3113-
std::string path = includePath;
3163+
std::string path = toAbsolutePath(includePath);
31143164
if (!path.empty() && path[path.size()-1U]!='/' && path[path.size()-1U]!='\\')
31153165
path += '/';
31163166
return path + header;
@@ -3119,9 +3169,9 @@ static std::string getIncludePathFileName(const std::string &includePath, const
31193169
static std::string openHeaderIncludePath(std::ifstream &f, const simplecpp::DUI &dui, const std::string &header)
31203170
{
31213171
for (std::list<std::string>::const_iterator it = dui.includePaths.begin(); it != dui.includePaths.end(); ++it) {
3122-
std::string simplePath = openHeader(f, getIncludePathFileName(*it, header));
3123-
if (!simplePath.empty())
3124-
return simplePath;
3172+
std::string path = openHeader(f, getIncludePathFileName(*it, header));
3173+
if (!path.empty())
3174+
return path;
31253175
}
31263176
return "";
31273177
}
@@ -3131,49 +3181,76 @@ static std::string openHeader(std::ifstream &f, const simplecpp::DUI &dui, const
31313181
if (isAbsolutePath(header))
31323182
return openHeader(f, header);
31333183

3134-
std::string ret;
3135-
31363184
if (systemheader) {
3137-
ret = openHeaderIncludePath(f, dui, header);
3138-
return ret;
3185+
// always return absolute path for systemheaders
3186+
return toAbsolutePath(openHeaderIncludePath(f, dui, header));
31393187
}
31403188

3189+
std::string ret;
3190+
31413191
ret = openHeaderRelative(f, sourcefile, header);
31423192
if (ret.empty())
3143-
return openHeaderIncludePath(f, dui, header);
3193+
return toAbsolutePath(openHeaderIncludePath(f, dui, header));// in a similar way to system headers
31443194
return ret;
31453195
}
31463196

3147-
static std::string getFileName(const std::map<std::string, simplecpp::TokenList *> &filedata, const std::string &sourcefile, const std::string &header, const simplecpp::DUI &dui, bool systemheader)
3197+
static std::string findPathInMapBothRelativeAndAbsolute(const std::map<std::string, simplecpp::TokenList *> &filedata, const std::string& path) {
3198+
// here there are two possibilities - either we match this from absolute path or from a relative one
3199+
if (filedata.find(path) != filedata.end()) {// try first to respect the exact match
3200+
return path;
3201+
}
3202+
// otherwise - try to use the normalize to the correct representation
3203+
if (isAbsolutePath(path)) {
3204+
const std::pair<std::string, bool> relativeExtractedResult = extractRelativePathFromAbsolute(path);
3205+
if (relativeExtractedResult.second) {
3206+
const std::string relativePath = relativeExtractedResult.first;
3207+
if (filedata.find(relativePath) != filedata.end()) {
3208+
return relativePath;
3209+
}
3210+
}
3211+
} else {
3212+
const std::string absolutePath = toAbsolutePath(path);
3213+
if (filedata.find(absolutePath) != filedata.end())
3214+
return absolutePath;
3215+
}
3216+
// otherwise
3217+
return "";
3218+
}
3219+
3220+
static std::string getFileIdPath(const std::map<std::string, simplecpp::TokenList *> &filedata, const std::string &sourcefile, const std::string &header, const simplecpp::DUI &dui, bool systemheader)
31483221
{
31493222
if (filedata.empty()) {
31503223
return "";
31513224
}
31523225
if (isAbsolutePath(header)) {
3153-
return (filedata.find(header) != filedata.end()) ? simplecpp::simplifyPath(header) : "";
3226+
const std::string simplifiedHeaderPath = simplecpp::simplifyPath(header);
3227+
return (filedata.find(simplifiedHeaderPath) != filedata.end()) ? simplifiedHeaderPath : "";
31543228
}
31553229

31563230
if (!systemheader) {
3157-
const std::string relativeFilename = getRelativeFileName(sourcefile, header);
3158-
if (filedata.find(relativeFilename) != filedata.end())
3159-
return relativeFilename;
3231+
const std::string relativeOrAbsoluteFilename = getRelativeFileName(sourcefile, header);// unknown if absolute or relative, but always simplified
3232+
const std::string match = findPathInMapBothRelativeAndAbsolute(filedata, relativeOrAbsoluteFilename);
3233+
if (!match.empty()) {
3234+
return match;
3235+
}
31603236
}
31613237

31623238
for (std::list<std::string>::const_iterator it = dui.includePaths.begin(); it != dui.includePaths.end(); ++it) {
3163-
std::string s = simplecpp::simplifyPath(getIncludePathFileName(*it, header));
3164-
if (filedata.find(s) != filedata.end())
3165-
return s;
3239+
const std::string match = findPathInMapBothRelativeAndAbsolute(filedata, simplecpp::simplifyPath(getIncludePathFileName(*it, header)));
3240+
if (!match.empty()) {
3241+
return match;
3242+
}
31663243
}
31673244

31683245
if (systemheader && filedata.find(header) != filedata.end())
3169-
return header;
3246+
return header;// system header that its file wasn't found in the included paths but alreasy in the filedata - return this as is
31703247

31713248
return "";
31723249
}
31733250

31743251
static bool hasFile(const std::map<std::string, simplecpp::TokenList *> &filedata, const std::string &sourcefile, const std::string &header, const simplecpp::DUI &dui, bool systemheader)
31753252
{
3176-
return !getFileName(filedata, sourcefile, header, dui, systemheader).empty();
3253+
return !getFileIdPath(filedata, sourcefile, header, dui, systemheader).empty();
31773254
}
31783255

31793256
std::map<std::string, simplecpp::TokenList*> simplecpp::load(const simplecpp::TokenList &rawtokens, std::vector<std::string> &filenames, const simplecpp::DUI &dui, simplecpp::OutputList *outputList)
@@ -3529,7 +3606,7 @@ void simplecpp::preprocess(simplecpp::TokenList &output, const simplecpp::TokenL
35293606

35303607
const bool systemheader = (inctok->str()[0] == '<');
35313608
const std::string header(realFilename(inctok->str().substr(1U, inctok->str().size() - 2U)));
3532-
std::string header2 = getFileName(filedata, rawtok->location.file(), header, dui, systemheader);
3609+
std::string header2 = getFileIdPath(filedata, rawtok->location.file(), header, dui, systemheader);
35333610
if (header2.empty()) {
35343611
// try to load file..
35353612
std::ifstream f;

0 commit comments

Comments
 (0)