Files
buzz/scripts/test-ios-pbxproj-semantics.py
Tom Brow d69b89c43c feat: add internal iOS push MVP
Signed-off-by: Tom Brow <tomb@squareup.com>
2026-08-18 16:23:53 -07:00

88 lines
3.9 KiB
Python
Executable File

#!/usr/bin/env python3
"""Validate the semantic iOS target/configuration table via macOS plutil."""
import json
import subprocess
import sys
from pathlib import Path
PBXPROJ = Path("mobile/ios/Runner.xcodeproj/project.pbxproj")
# This asserts plutil-resolved pbxproj declarations, not object-key uniqueness or
# the final xcconfig-expanded bundle identity.
EXPECTED_ROWS = """\
NotificationService Debug Flutter/Debug.xcconfig $(BUZZ_DEVELOPMENT_TEAM) NotificationService/NotificationService.entitlements $(BUNDLE_IDENTIFIER).NotificationService
NotificationService Profile Flutter/Release.xcconfig $(BUZZ_DEVELOPMENT_TEAM) NotificationService/NotificationService.entitlements $(BUNDLE_IDENTIFIER).NotificationService
NotificationService Release Flutter/Release.xcconfig $(BUZZ_DEVELOPMENT_TEAM) NotificationService/NotificationService.entitlements $(BUNDLE_IDENTIFIER).NotificationService
Runner Debug Flutter/Debug.xcconfig $(BUZZ_DEVELOPMENT_TEAM) $(BUZZ_CODE_SIGN_ENTITLEMENTS) $(BUNDLE_IDENTIFIER)
Runner Profile Flutter/Release.xcconfig $(BUZZ_DEVELOPMENT_TEAM) $(BUZZ_CODE_SIGN_ENTITLEMENTS) $(BUNDLE_IDENTIFIER)
Runner Release Flutter/Release.xcconfig $(BUZZ_DEVELOPMENT_TEAM) $(BUZZ_CODE_SIGN_ENTITLEMENTS) $(BUNDLE_IDENTIFIER)
RunnerTests Debug Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig - - $(BUNDLE_IDENTIFIER).RunnerTests
RunnerTests Profile Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig - - $(BUNDLE_IDENTIFIER).RunnerTests
RunnerTests Release Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig - - $(BUNDLE_IDENTIFIER).RunnerTests
""".splitlines()
def parse_project(path: Path) -> dict:
result = subprocess.run(
["plutil", "-convert", "json", "-o", "-", str(path)],
capture_output=True,
text=True,
)
if result.returncode != 0:
detail = result.stderr.strip() or "no error detail"
raise RuntimeError(f"plutil failed to parse {path}: {detail}")
if not result.stdout:
raise RuntimeError(f"plutil returned no JSON for {path}")
return json.loads(result.stdout)
def semantic_rows(project: dict) -> list[str]:
objects = project["objects"]
root = objects[project["rootObject"]]
rows = []
for target_id in root["targets"]:
target = objects[target_id]
if target.get("isa") != "PBXNativeTarget":
continue
configuration_list = objects[target["buildConfigurationList"]]
for configuration_id in configuration_list["buildConfigurations"]:
configuration = objects[configuration_id]
settings = configuration.get("buildSettings", {})
base_reference = configuration.get("baseConfigurationReference")
base_path = objects.get(base_reference, {}).get("path", "-")
rows.append(
" ".join(
[
target.get("name", "-"),
configuration.get("name", "-"),
base_path,
settings.get("DEVELOPMENT_TEAM", "-"),
settings.get("CODE_SIGN_ENTITLEMENTS", "-"),
settings.get("PRODUCT_BUNDLE_IDENTIFIER", "-"),
]
)
)
return sorted(rows)
def main() -> int:
path = Path(sys.argv[1]) if len(sys.argv) > 1 else PBXPROJ
try:
actual = semantic_rows(parse_project(path))
except (KeyError, TypeError, json.JSONDecodeError, RuntimeError) as error:
print(f"FAIL: {error}", file=sys.stderr)
return 1
if actual != EXPECTED_ROWS:
print("FAIL: unexpected iOS target configuration semantics", file=sys.stderr)
print("expected:", *EXPECTED_ROWS, sep="\n", file=sys.stderr)
print("actual:", *actual, sep="\n", file=sys.stderr)
return 1
print("iOS target configuration semantics match")
return 0
if __name__ == "__main__":
raise SystemExit(main())