Skip to content

Commit 3a900a2

Browse files
committed
feat: Darwin-style framework search (-F, -iframework) with ordered path lookup
This change teaches simplecpp to resolve headers from Apple-style Framework directories while preserving the left-to-right order of interleaved -I/-F/-iframework search paths (like GCC/Clang on Darwin). - Add `DUI::SearchPath` with `PathKind {Include, Framework, SystemFramework}`. - If `DUI::searchPaths` is non-empty, use it verbatim (interleaved -I/-F/-iframework). Otherwise preserve back-compat by mirroring `includePaths` as Include paths. - Update `openHeader()` to consult typed paths, and only rewrite `<Pkg/Hdr.h>` to `Pkg.framework/{Headers,PrivateHeaders}/Hdr.h` when a package prefix exists. - Implement `toAppleFrameworkRelatives()` returning prioritized candidates (Headers first, then PrivateHeaders). - Update CLI: support `-F<dir>` and `-iframework<dir>` (and keep `-I` as before). - Tests use `PathKind::Framework` when checking framework layout. Behavior notes - The order of -I/-F/-iframework is preserved exactly as provided. - `Framework` vs `SystemFramework` differ only in diagnostic semantics (not lookup). - Legacy users who only set `DUI::includePaths` see identical behavior. This brings simplecpp closer to GCC/Clang behavior on macOS and enables robust resolution of framework headers like `Foundation/Foundation.h`. Suggested-by: glankk@users.noreply.github.com
1 parent f69d51c commit 3a900a2

File tree

6 files changed

+353
-63
lines changed

6 files changed

+353
-63
lines changed

integration_test.py

Lines changed: 193 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,15 @@
44
import pathlib
55
import platform
66
import pytest
7-
from testutils import simplecpp, format_include_path_arg, format_include
7+
from testutils import (
8+
simplecpp,
9+
format_include_path_arg,
10+
format_framework_path_arg,
11+
format_iframework_path_arg,
12+
format_include,
13+
mk_framework,
14+
mk_framework_private,
15+
)
816

917
def __test_relative_header_create_header(dir, with_pragma_once=True):
1018
header_file = os.path.join(dir, 'test.h')
@@ -236,6 +244,190 @@ def test_same_name_header(record_property, tmpdir):
236244
assert "OK" in stdout
237245
assert stderr == ""
238246

247+
def test_framework_basic_F_headers(record_property, tmpdir):
248+
# Arrange framework: <tmp>/FwRoot/MyKit.framework/Headers/MyKit.h
249+
fw_root = os.path.join(tmpdir, "FwRoot")
250+
mk_framework(tmpdir, "FwRoot", "MyKit", "MyKit.h",
251+
'#define ORIGIN "FROM_FRAMEWORK"\n'
252+
'int __force_line_emission; /* anything non-preprocessor */\n')
253+
254+
# Source uses the macro defined in the framework header
255+
source = os.path.join(tmpdir, "t.c")
256+
with open(source, "wt") as f:
257+
f.write("""
258+
#include <MyKit/MyKit.h>
259+
ORIGIN
260+
""")
261+
262+
args = [format_framework_path_arg(fw_root), source]
263+
print("*" * 10, args)
264+
_, stdout, stderr = simplecpp(args, cwd=tmpdir)
265+
record_property("stdout", stdout)
266+
record_property("stderr", stderr)
267+
268+
assert stderr == ""
269+
assert 'FROM_FRAMEWORK' in stdout
270+
# And we should see a #line to the physical framework header path:
271+
assert ("/MyKit.framework/Headers/MyKit.h" in stdout.replace("\\", "/"))
272+
273+
def test_framework_privateheaders_when_no_headers(record_property, tmpdir):
274+
# Arrange framework with only PrivateHeaders
275+
fw_root = os.path.join(tmpdir, "FwRoot2")
276+
mk_framework_private(tmpdir, "FwRoot2", "MyKit", "Component.h",
277+
'#define WHERE "PRIVATE"\n'
278+
'int __force_line_emission; /* anything non-preprocessor */\n')
279+
# No Headers/Component.h
280+
281+
source = os.path.join(tmpdir, "t.c")
282+
with open(source, "wt") as f:
283+
f.write("""
284+
#include <MyKit/Component.h>
285+
WHERE
286+
""")
287+
288+
args = [format_framework_path_arg(fw_root), source]
289+
_, stdout, stderr = simplecpp(args, cwd=tmpdir)
290+
record_property("stdout", stdout)
291+
record_property("stderr", stderr)
292+
293+
assert stderr == ""
294+
assert "PRIVATE" in stdout
295+
# confirm we actually found PrivateHeaders:
296+
assert ("/MyKit.framework/PrivateHeaders/Component.h" in stdout.replace("\\", "/"))
297+
298+
def test_order_I_then_F_prefers_I(record_property, tmpdir):
299+
incdir = os.path.join(tmpdir, "inc")
300+
fw_root = os.path.join(tmpdir, "Fw")
301+
302+
os.mkdir(incdir)
303+
304+
# Normal include tree: inc/MyKit/MyKit.h
305+
mykit_mykit_h = os.path.join(incdir, "MyKit", "MyKit.h")
306+
os.mkdir(os.path.join(incdir, "MyKit"))
307+
with open(mykit_mykit_h, "wt") as f:
308+
f.write("""
309+
#define ORIGIN "FROM_INCLUDE"
310+
int __force_line_emission; /* anything non-preprocessor */
311+
""")
312+
313+
# Framework: Fw/MyKit.framework/Headers/MyKit.h
314+
mk_framework(tmpdir, "Fw", "MyKit", "MyKit.h",
315+
'#define ORIGIN "FROM_FRAMEWORK"\n'
316+
'int __force_line_emission; /* anything non-preprocessor */\n')
317+
318+
source = os.path.join(tmpdir, "t.c")
319+
with open(source, "wt") as f:
320+
f.write("""
321+
#include <MyKit/MyKit.h>
322+
ORIGIN
323+
""")
324+
325+
# ORDER: -I first, then -F
326+
args = [format_include_path_arg(incdir), format_framework_path_arg(fw_root), source]
327+
_, stdout, stderr = simplecpp(args, cwd=tmpdir)
328+
record_property("stdout", stdout)
329+
record_property("stderr", stderr)
330+
331+
assert stderr == ""
332+
assert "FROM_INCLUDE" in stdout
333+
assert "FROM_FRAMEWORK" not in stdout
334+
# path should point to the include tree:
335+
assert (f'{pathlib.PurePath(incdir).as_posix()}/MyKit/MyKit.h') in stdout
336+
337+
def test_order_F_then_I_prefers_F(record_property, tmpdir):
338+
fw_root = os.path.join(tmpdir, "Fw")
339+
incdir = os.path.join(tmpdir, "inc")
340+
341+
os.mkdir(incdir)
342+
343+
mk_framework(tmpdir, "Fw", "MyKit", "MyKit.h",
344+
'#define ORIGIN "FROM_FRAMEWORK"\n'
345+
'int __force_line_emission; /* anything non-preprocessor */\n')
346+
347+
mykit_mykit_h = os.path.join(incdir, "MyKit", "MyKit.h")
348+
os.mkdir(os.path.join(incdir, "MyKit"))
349+
with open(mykit_mykit_h, "wt") as f:
350+
f.write("""
351+
#define ORIGIN "FROM_INCLUDE"
352+
int __force_line_emission; /* anything non-preprocessor */
353+
""")
354+
355+
source = os.path.join(tmpdir, "t.c")
356+
with open(source, "wt") as f:
357+
f.write("""
358+
#include <MyKit/MyKit.h>
359+
ORIGIN
360+
""")
361+
362+
# ORDER: -F first, then -I
363+
args = [format_framework_path_arg(fw_root), format_include_path_arg(incdir), source]
364+
_, stdout, stderr = simplecpp(args, cwd=tmpdir)
365+
record_property("stdout", stdout)
366+
record_property("stderr", stderr)
367+
368+
assert stderr == ""
369+
assert "FROM_FRAMEWORK" in stdout
370+
assert "FROM_INCLUDE" not in stdout
371+
assert ("/MyKit.framework/Headers/MyKit.h" in stdout.replace("\\", "/"))
372+
373+
def test_interleaved_multiple_paths(record_property, tmpdir):
374+
# Layout:
375+
# I1/MyKit/MyKit.h -> "I1"
376+
# F1/MyKit.framework/... -> "F1"
377+
# I2/MyKit/MyKit.h -> "I2"
378+
# F2/MyKit.framework/... -> "F2"
379+
380+
I1 = os.path.join(tmpdir, "I1")
381+
F1 = os.path.join(tmpdir, "F1")
382+
I2 = os.path.join(tmpdir, "I2")
383+
F2 = os.path.join(tmpdir, "F2")
384+
385+
os.mkdir(I1)
386+
os.mkdir(I2)
387+
388+
i1_mykit_mykit_h = os.path.join(I1, "MyKit", "MyKit.h")
389+
os.mkdir(os.path.join(I1, "MyKit"))
390+
with open(i1_mykit_mykit_h, "wt") as f:
391+
f.write("""
392+
#define ORIGIN "I1"
393+
""")
394+
395+
mk_framework(tmpdir, "F1", "MyKit", "MyKit.h", '#define ORIGIN "F1"\n')
396+
397+
i2_mykit_mykit_h = os.path.join(I2, "MyKit", "MyKit.h")
398+
os.mkdir(os.path.join(I2, "MyKit"))
399+
with open(i2_mykit_mykit_h, "wt") as f:
400+
f.write("""
401+
#define ORIGIN "I2"
402+
""")
403+
404+
mk_framework(tmpdir, "F2", "MyKit", "MyKit.h", '#define ORIGIN "F2"\n')
405+
406+
source = os.path.join(tmpdir, "t.c")
407+
with open(source, "wt") as f:
408+
f.write("""
409+
#include <MyKit/MyKit.h>
410+
ORIGIN
411+
""")
412+
413+
# Choose an ordering to validate precedence:
414+
# e.g. I1, F1, I2, F2 -> expect I1
415+
args = [
416+
format_include_path_arg(I1),
417+
format_framework_path_arg(F1),
418+
format_include_path_arg(I2),
419+
format_framework_path_arg(F2),
420+
source
421+
]
422+
_, stdout, stderr = simplecpp(args, cwd=tmpdir)
423+
record_property("stdout", stdout)
424+
record_property("stderr", stderr)
425+
426+
assert stderr == ""
427+
assert '"I1"' in stdout
428+
for other in ("\"F1\"", "\"I2\"", "\"F2\""):
429+
assert other not in stdout
430+
239431
def test_pragma_once_matching(record_property, tmpdir):
240432
test_dir = os.path.join(tmpdir, "test_dir")
241433
test_subdir = os.path.join(test_dir, "test_subdir")

main.cpp

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,13 @@ int main(int argc, char **argv)
4343
}
4444
case 'I': { // include path
4545
const char * const value = arg[2] ? (argv[i] + 2) : argv[++i];
46-
dui.includePaths.push_back(value);
46+
dui.searchPaths.push_back({value, simplecpp::DUI::PathKind::Include});
47+
found = true;
48+
break;
49+
}
50+
case 'F': { // framework path
51+
const char * const value = arg[2] ? (argv[i] + 2) : argv[++i];
52+
dui.searchPaths.push_back({value, simplecpp::DUI::PathKind::Framework});
4753
found = true;
4854
break;
4955
}
@@ -54,6 +60,9 @@ int main(int argc, char **argv)
5460
} else if (std::strncmp(arg, "-is",3)==0) {
5561
use_istream = true;
5662
found = true;
63+
} else if (std::strncmp(arg, "-iframework", 11)==0) {
64+
dui.searchPaths.push_back({arg + 11, simplecpp::DUI::PathKind::SystemFramework});
65+
found = true;
5766
}
5867
break;
5968
case 's':
@@ -100,6 +109,8 @@ int main(int argc, char **argv)
100109
std::cout << "simplecpp [options] filename" << std::endl;
101110
std::cout << " -DNAME Define NAME." << std::endl;
102111
std::cout << " -IPATH Include path." << std::endl;
112+
std::cout << " -FPATH Framework path." << std::endl;
113+
std::cout << " -iframeworkPATH System framework path." << std::endl;
103114
std::cout << " -include=FILE Include FILE." << std::endl;
104115
std::cout << " -UNAME Undefine NAME." << std::endl;
105116
std::cout << " -std=STD Specify standard." << std::endl;

0 commit comments

Comments
 (0)