Skip to content

Commit cdd5fc2

Browse files
committed
feat: add Darwin-style framework search paths (-F, -iframework)
- Introduce DUI::SearchPath with PathKind {Include, Framework, SystemFramework} to model GCC/Clang behavior on Darwin. - Extend openHeader() to search left-to-right across mixed -I/-F/-iframework paths, preserving order. - Implement toAppleFrameworkRelatives() returning both Headers and PrivateHeaders candidates for a <Pkg/Hdr.h> include. - Preserve backward compatibility: if searchPaths is empty, legacy includePaths are mirrored as Include paths. - Update tests to use PathKind::Framework for framework-based includes. This brings simplecpp closer to GCC/Clang behavior on macOS and enables robust resolution of framework headers like Foundation/Foundation.h. Suggested-by: glank <[email protected]>
1 parent 2c718ab commit cdd5fc2

File tree

3 files changed

+73
-25
lines changed

3 files changed

+73
-25
lines changed

simplecpp.cpp

Lines changed: 34 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include "simplecpp.h"
1414

1515
#include <algorithm>
16+
#include <array>
1617
#include <cassert>
1718
#include <cctype>
1819
#include <climits>
@@ -2432,16 +2433,18 @@ static bool isAbsolutePath(const std::string &path)
24322433
#endif
24332434

24342435
namespace {
2435-
// "<Pkg/Hdr.h>" -> "<Pkg.framework/Headers/Hdr.h>"
2436-
static inline std::string
2437-
toAppleFrameworkRelative(const std::string& header)
2436+
// "<Pkg/Hdr.h>" -> "<Pkg.framework/Headers/Hdr.h>" (and PrivateHeaders variant).
2437+
// Returns candidates in priority order (Headers, then PrivateHeaders).
2438+
static inline std::array<std::string,2>
2439+
toAppleFrameworkRelatives(const std::string& header)
24382440
{
24392441
const std::size_t slash = header.find('/');
24402442
if (slash == std::string::npos)
2441-
return header; // no transformation applicable
2443+
return { header, header }; // no transformation applicable
24422444
const std::string pkg = header.substr(0, slash);
24432445
const std::string tail = header.substr(slash); // includes '/'
2444-
return pkg + ".framework/Headers" + tail;
2446+
return { pkg + ".framework/Headers" + tail,
2447+
pkg + ".framework/PrivateHeaders" + tail };
24452448
}
24462449
}
24472450

@@ -3015,23 +3018,34 @@ static std::string openHeader(std::ifstream &f, const simplecpp::DUI &dui, const
30153018
}
30163019
}
30173020

3018-
// search the header on the include paths (provided by the flags "-I...")
3019-
for (const auto &includePath : dui.includePaths) {
3020-
std::string path = openHeaderDirect(f, simplecpp::simplifyPath(includePath + "/" + header));
3021-
if (!path.empty())
3022-
return path;
3021+
// Build an ordered, typed path list:
3022+
// - Prefer DUI::searchPaths when provided (interleaved -I/-F/-iframework).
3023+
// - Otherwise mirror legacy includePaths into Include entries (back-compat).
3024+
std::vector<simplecpp::DUI::SearchPath> searchPaths;
3025+
if (!dui.searchPaths.empty()) {
3026+
searchPaths = dui.searchPaths;
3027+
} else {
3028+
searchPaths.reserve(dui.includePaths.size());
3029+
for (const auto &includePath : dui.includePaths)
3030+
searchPaths.push_back({includePath, simplecpp::DUI::PathKind::Include});
30233031
}
30243032

3025-
// on Apple, try to find the header in the framework path
3026-
// Convert <includePath>/PKGNAME/myHeader -> <includePath>/PKGNAME.framework/Headers/myHeader
3027-
// Works on any platform, but only relevant when compiling against Apple SDKs.
3028-
const std::string appleFrameworkHeader = toAppleFrameworkRelative(header);
3029-
if (appleFrameworkHeader != header) {
3030-
for (const auto & includePath: dui.includePaths) {
3031-
const std::string frameworkCandidatePath = includePath + '/' + appleFrameworkHeader;
3032-
std::string simplePath = openHeaderDirect(f, simplecpp::simplifyPath(frameworkCandidatePath));
3033-
if (!simplePath.empty())
3034-
return simplePath;
3033+
// Search left-to-right, honoring path kinds.
3034+
for (const auto &searchPath : searchPaths) {
3035+
if (searchPath.kind == simplecpp::DUI::PathKind::Include) {
3036+
const std::string path = openHeaderDirect(f, simplecpp::simplifyPath(searchPath.path + "/" + header));
3037+
if (!path.empty())
3038+
return path;
3039+
} else {
3040+
// Framework & SystemFramework: try Headers then PrivateHeaders
3041+
const auto relatives = toAppleFrameworkRelatives(header);
3042+
if (relatives[0] != header) { // Skip if no framework rewrite was applied.
3043+
for (const auto &rel : relatives) {
3044+
const std::string frameworkPath = openHeaderDirect(f, simplecpp::simplifyPath(searchPath.path + "/" + rel));
3045+
if (!frameworkPath.empty())
3046+
return frameworkPath;
3047+
}
3048+
}
30353049
}
30363050
}
30373051

simplecpp.h

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -340,13 +340,43 @@ namespace simplecpp {
340340

341341
/**
342342
* Command line preprocessor settings.
343-
* On the command line these are configured by -D, -U, -I, --include, -std
343+
*
344+
* Mirrors typical compiler options:
345+
* -D <name>=<value> Add macro definition
346+
* -U <name> Undefine macro
347+
* -I <dir> Add include search directory
348+
* -F <dir> Add framework search directory (Darwin)
349+
* -iframework <dir> Add system framework search directory (Darwin)
350+
* --include <file> Force inclusion of a header
351+
* -std=<version> Select language standard (C++17, C23, etc.)
352+
*
353+
* Path search behavior:
354+
* - If searchPaths is non-empty, it is used directly, preserving the
355+
* left-to-right order and distinguishing between Include, Framework,
356+
* and SystemFramework kinds.
357+
* - If searchPaths is empty, legacy includePaths is used instead, and
358+
* each entry is treated as a normal Include path (for backward
359+
* compatibility).
344360
*/
345361
struct SIMPLECPP_LIB DUI {
346362
DUI() : clearIncludeCache(false), removeComments(false) {}
363+
364+
// Typed search path entry. Mirrors GCC behavior for -I, -F, -iframework.
365+
enum class PathKind { Include, Framework, SystemFramework };
366+
struct SearchPath {
367+
std::string path;
368+
PathKind kind;
369+
};
370+
347371
std::list<std::string> defines;
348372
std::set<std::string> undefined;
373+
374+
// Back-compat: legacy -I list. If searchPaths is empty at use time,
375+
// consumers should mirror includePaths -> searchPaths as Include.
349376
std::list<std::string> includePaths;
377+
// New: ordered, interleaved search paths with kind.
378+
std::vector<SearchPath> searchPaths;
379+
350380
std::list<std::string> includes;
351381
std::string std;
352382
bool clearIncludeCache;

test.cpp

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2148,9 +2148,11 @@ static void appleFrameworkIncludeTest()
21482148
simplecpp::TokenList tokens2(files);
21492149
simplecpp::DUI dui;
21502150
#ifdef SIMPLECPP_SOURCE_DIR
2151-
dui.includePaths.push_back(std::string(SIMPLECPP_SOURCE_DIR) + "/testsuite");
2151+
dui.searchPaths.push_back({std::string(SIMPLECPP_SOURCE_DIR) + "/testsuite",
2152+
simplecpp::DUI::PathKind::Framework
2153+
});
21522154
#else
2153-
dui.includePaths.push_back("./testsuite");
2155+
dui.searchPaths.push_back({"./testsuite", simplecpp::DUI::PathKind::Framework});
21542156
#endif
21552157
simplecpp::OutputList outputList;
21562158
simplecpp::preprocess(tokens2, rawtokens, files, cache, dui, &outputList);
@@ -2175,9 +2177,11 @@ static void appleFrameworkHasIncludeTest()
21752177
simplecpp::TokenList tokens2(files);
21762178
simplecpp::DUI dui;
21772179
#ifdef SIMPLECPP_SOURCE_DIR
2178-
dui.includePaths.push_back(std::string(SIMPLECPP_SOURCE_DIR) + "/testsuite");
2180+
dui.searchPaths.push_back({std::string(SIMPLECPP_SOURCE_DIR) + "/testsuite",
2181+
simplecpp::DUI::PathKind::Framework
2182+
});
21792183
#else
2180-
dui.includePaths.push_back("./testsuite");
2184+
dui.searchPaths.push_back({"./testsuite", simplecpp::DUI::PathKind::Framework});
21812185
#endif
21822186
dui.std = "c++17"; // enable __has_include
21832187

0 commit comments

Comments
 (0)