diff --git a/Scripts/patch-icon-composer.sh b/Scripts/patch-icon-composer.sh index 5eb17a1..657c64a 100755 --- a/Scripts/patch-icon-composer.sh +++ b/Scripts/patch-icon-composer.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# XcodeGen 2.43 expands AppIcon.icon into a PBXGroup and adds icon.json / svg +# XcodeGen expands AppIcon.icon into a PBXGroup and adds icon.json / svg # files to Copy Bundle Resources. Icon Composer bundles must be a single # PBXFileReference (folder.iconcomposer.icon) linked to the target so actool # compiles them together with Assets.xcassets. @@ -32,62 +32,49 @@ if "/* AppIcon.icon in Resources */" in text and "folder.iconcomposer.icon" in t print("AppIcon.icon already patched") sys.exit(0) -icon_uuid_match = re.search( - r"([A-F0-9]{24}) /\* AppIcon\.icon \*/ = \{", +icon_ref_match = re.search( + r"(?P[ \t]*)(?P[A-F0-9]{24}) /\* AppIcon\.icon \*/ = \{", text, ) -if not icon_uuid_match: +if not icon_ref_match: print("error: AppIcon.icon not found in project.pbxproj", file=sys.stderr) sys.exit(1) -icon_uuid = icon_uuid_match.group(1) -group_pattern = re.compile( - rf"(?P\t\t){icon_uuid} /\* AppIcon\.icon \*/ = \{{\n" - r"\t\t\tisa = PBXGroup;\n" - r"\t\t\tchildren = \(\n" - r"(?:\t\t\t\t[A-F0-9]{24} /\* .* \*/,\n)*" - r"\t\t\t\);\n" - r"\t\t\tpath = AppIcon\.icon;\n" - r"\t\t\tsourceTree = \"\";\n" - r"\t\t\};", - re.MULTILINE, +icon_uuid = icon_ref_match.group("uuid") +indent = icon_ref_match.group("indent") +block_start = icon_ref_match.start() + +# Brace-match the PBX object so we tolerate XcodeGen format drift. +brace_open = text.find("{", icon_ref_match.end() - 1) +depth = 0 +block_end = None +for index in range(brace_open, len(text)): + char = text[index] + if char == "{": + depth += 1 + elif char == "}": + depth -= 1 + if depth == 0: + # Include trailing semicolon when present. + block_end = index + 1 + if block_end < len(text) and text[block_end] == ";": + block_end += 1 + break + +if block_end is None: + print("error: could not parse AppIcon.icon PBX block", file=sys.stderr) + sys.exit(1) + +inner_indent = indent + "\t" +replacement = ( + f"{indent}{icon_uuid} /* AppIcon.icon */ = {{\n" + f"{inner_indent}isa = PBXFileReference;\n" + f"{inner_indent}lastKnownFileType = folder.iconcomposer.icon;\n" + f"{inner_indent}path = AppIcon.icon;\n" + f"{inner_indent}sourceTree = \"\";\n" + f"{indent}}};" ) - -group_match = group_pattern.search(text) -if group_match: - indent = group_match.group("indent") - replacement = ( - f"{indent}{icon_uuid} /* AppIcon.icon */ = {{\n" - f"\t\t\tisa = PBXFileReference;\n" - f"\t\t\tlastKnownFileType = folder.iconcomposer.icon;\n" - f"\t\t\tpath = AppIcon.icon;\n" - f"\t\t\tsourceTree = \"\";\n" - f"\t\t}};" - ) - text = text[: group_match.start()] + replacement + text[group_match.end() :] -else: - file_ref_pattern = re.compile( - rf"\t\t{icon_uuid} /\* AppIcon\.icon \*/ = \{{\n" - r"\t\t\tisa = PBXFileReference;\n" - r"\t\t\tlastKnownFileType = [^;]+;\n" - r"\t\t\tpath = AppIcon\.icon;\n" - r"\t\t\tsourceTree = \"\";\n" - r"\t\t\};", - re.MULTILINE, - ) - file_ref_match = file_ref_pattern.search(text) - if not file_ref_match: - print("error: AppIcon.icon entry has unexpected shape", file=sys.stderr) - sys.exit(1) - replacement = ( - f"\t\t{icon_uuid} /* AppIcon.icon */ = {{\n" - f"\t\t\tisa = PBXFileReference;\n" - f"\t\t\tlastKnownFileType = folder.iconcomposer.icon;\n" - f"\t\t\tpath = AppIcon.icon;\n" - f"\t\t\tsourceTree = \"\";\n" - f"\t\t}};" - ) - text = text[: file_ref_match.start()] + replacement + text[file_ref_match.end() :] +text = text[:block_start] + replacement + text[block_end:] nested_resource_names = ("icon.json", "App Icon Template.svg") lines = text.splitlines(keepends=True) @@ -107,31 +94,37 @@ build_entry = ( f"\t\t{build_uuid} /* AppIcon.icon in Resources */ = " f"{{isa = PBXBuildFile; fileRef = {icon_uuid} /* AppIcon.icon */; }};\n" ) -text = text.replace("/* Begin PBXBuildFile section */\n", "/* Begin PBXBuildFile section */\n" + build_entry, 1) +if f"{build_uuid} /* AppIcon.icon in Resources */" not in text: + text = text.replace( + "/* Begin PBXBuildFile section */\n", + "/* Begin PBXBuildFile section */\n" + build_entry, + 1, + ) -resources_phase = re.search( - r"\t\t(?P[A-F0-9]{24}) /\* Resources \*/ = \{\n" - r"\t\t\tisa = PBXResourcesBuildPhase;\n" - r"\t\t\tbuildActionMask = 2147483647;\n" - r"\t\t\tfiles = \(\n" - r"(?P.*?Assets\.xcassets in Resources.*?\n)" - r"(?P.*?)" - r"\t\t\t\);\n" - r"\t\t\trunOnlyForDeploymentPostprocessing = 0;\n" - r"\t\t\};", - text, - re.DOTALL, -) -if not resources_phase: - print("error: OSGKeyboard Resources phase not found", file=sys.stderr) - sys.exit(1) +if f"{build_uuid} /* AppIcon.icon in Resources */," not in text: + resources_phase = re.search( + r"\t\t(?P[A-F0-9]{24}) /\* Resources \*/ = \{\n" + r"\t\t\tisa = PBXResourcesBuildPhase;\n" + r"\t\t\tbuildActionMask = 2147483647;\n" + r"\t\t\tfiles = \(\n" + r"(?P.*?Assets\.xcassets in Resources.*?\n)" + r"(?P.*?)" + r"\t\t\t\);\n" + r"\t\t\trunOnlyForDeploymentPostprocessing = 0;\n" + r"\t\t\};", + text, + re.DOTALL, + ) + if not resources_phase: + print("error: OSGKeyboard Resources phase not found", file=sys.stderr) + sys.exit(1) -insert_at = resources_phase.end("body") -text = ( - text[:insert_at] - + f"\t\t\t\t{build_uuid} /* AppIcon.icon in Resources */,\n" - + text[insert_at:] -) + insert_at = resources_phase.end("body") + text = ( + text[:insert_at] + + f"\t\t\t\t{build_uuid} /* AppIcon.icon in Resources */,\n" + + text[insert_at:] + ) pbxproj.write_text(text) print("Patched AppIcon.icon -> folder.iconcomposer.icon (target Resources)")