Skip to content
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
21 changes: 19 additions & 2 deletions scripts/wire-app-sources.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,12 +109,29 @@ def parse(text: str, directory: str = "Sources", target: str = "cmux") -> Projec
setting(match.group("rest"), "sourceTree"),
match.span("children"),
)
# `directory` is a top-level folder: its group is a child of the
# project's main group (which has no comment, so GROUP skips it).
if "/" in directory.strip("/"):
raise SystemExit(f"wire-app-sources: --dir must be a top-level directory, not {directory!r}")
directory = directory.strip("/")
main_group = re.search(r"\bmainGroup = ([0-9A-Za-z]+);", text)
main_children = re.search(
(re.escape(main_group.group(1)) if main_group else "(?!)")
+ r"(?: /\* [^\n]*? \*/)? = \{\n\t+isa = PBXGroup;\n\t+children = \(\n(.*?)\t+\);",
text,
re.S,
)
top_level = set(CHILD_ID.findall(main_children.group(1))) if main_children else set()
root_id = next(
(gid for gid, (_, path, tree, _) in raw_groups.items() if path == directory and tree == '<group>'),
(
gid
for gid, (_, path, tree, _) in raw_groups.items()
if gid in top_level and path == directory and tree == "<group>"
),
None,
)
if root_id is None:
raise SystemExit(f"wire-app-sources: the {directory} group was not found")
raise SystemExit(f"wire-app-sources: no top-level {directory} group in the project")

groups: dict[str, Group] = {}
ref_paths: dict[str, str] = {}
Expand Down
53 changes: 53 additions & 0 deletions tests/test_wire_app_sources.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,29 @@
/* End PBXFileReference section */

/* Begin PBXGroup section */
M0000000000000000000000M = {
isa = PBXGroup;
children = (
G0000000000000000000000S /* Sources */,
G0000000000000000000000U /* UITests */,
);
sourceTree = "<group>";
};
G0000000000000000000000U /* UITests */ = {
isa = PBXGroup;
children = (
G0000000000000000000000N /* Sources */,
);
path = UITests;
sourceTree = "<group>";
};
G0000000000000000000000N /* Sources */ = {
isa = PBXGroup;
children = (
);
path = Sources;
sourceTree = "<group>";
};
G0000000000000000000000C /* Cloud */ = {
isa = PBXGroup;
children = (
Expand All @@ -54,6 +77,13 @@
};
/* End PBXGroup section */

/* Begin PBXProject section */
P0000000000000000000000P /* Project object */ = {
isa = PBXProject;
mainGroup = M0000000000000000000000M;
};
/* End PBXProject section */

/* Begin PBXNativeTarget section */
N0000000000000000000000A /* cmux */ = {
isa = PBXNativeTarget;
Expand Down Expand Up @@ -98,6 +128,12 @@ def group_of(text, rel):
return next(group.directory for group in project.groups.values() if ref in group.children)


def group_of_in(text, rel, directory):
project = wire_app_sources.parse(text, directory=directory, target="cmuxTests")
ref = next(ref for ref, path in project.ref_paths.items() if path == rel)
return next(group.directory for group in project.groups.values() if ref in group.children)


class WireAppSourcesTests(unittest.TestCase):
def test_resolves_paths_through_nested_groups_and_source_root(self):
project = wire_app_sources.parse(PROJECT)
Expand Down Expand Up @@ -161,6 +197,23 @@ def test_another_target_gets_the_file_in_its_own_phase(self):
self.assertIn("Sources/Top.swift", wire_app_sources.parse(text, target="cmuxTests").wired_paths)
self.assertNotIn("Sources/Top.swift", wire_app_sources.parse(text).wired_paths)

def test_another_top_level_directory_uses_its_own_group(self):
text = wire_app_sources.wire(PROJECT, "UITests/NewUITests.swift", directory="UITests", target="cmuxTests")
self.assertEqual(group_of_in(text, "UITests/NewUITests.swift", "UITests"), "UITests")
self.assertIn("path = NewUITests.swift;", text)

def test_the_directory_group_is_the_top_level_one_not_a_nested_namesake(self):
# UITests/Sources is also a group with `path = Sources`; wiring into
# Sources must still resolve from the top-level Sources group.
project = wire_app_sources.parse(PROJECT)
self.assertIn("Sources/Sidebar/Wired.swift", project.wired_paths)
self.assertEqual(min(group.directory for group in project.groups.values()), "Sources")

def test_nested_or_missing_directories_are_errors(self):
for directory in ["Sources/Sidebar", "Nope"]:
with self.subTest(directory=directory), self.assertRaises(SystemExit):
wire_app_sources.parse(PROJECT, directory=directory)

def test_wiring_is_idempotent_and_ids_are_stable(self):
once = wire_app_sources.wire(PROJECT, "Sources/Top.swift")
self.assertEqual(wire_app_sources.wire(once, "Sources/Top.swift"), once)
Expand Down
Loading