Skip to content

Write project.pbxproj in Xcode's layout - #3375

Open
erneestoc wants to merge 2 commits into
MobileNativeFoundation:mainfrom
erneestoc:ec/assemble-pbxproj-xcode-layout
Open

erneestoc wants to merge 2 commits into
MobileNativeFoundation:mainfrom
erneestoc:ec/assemble-pbxproj-xcode-layout

Conversation

@erneestoc

@erneestoc erneestoc commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

Depends on #3381, which makes pbxproj_prefix write the whole PBXProject.

The partials are concatenated with cat, so objects come in generator order without the /* Begin <isa> section */ blocks Xcode writes, and Xcode rewrites the whole file the next time it saves the project.

cat can't produce those: markers are only written for non-empty sections, and objects of the same isa come from several generators with interleaved identifiers. A small project_pbxproj generator replaces cat. It finds each object's bounds and isa, stable sorts the objects by (isa, identifier), and writes them with the section markers, copying each object as is (the generators already write attributes in Xcode's order). Malformed partials (no objects = { line, an object without an isa, or an unterminated object) are an error.

On a 102 MB project.pbxproj (median of 11 runs):

Time
cat 0.02 s
project_pbxproj 0.07 s

That's about 0.3 s for a 417 MB project. The action is still no-cache/no-remote/no-sandbox with the same inputs, so only this step reruns on regeneration.

The output matches Xcode byte for byte: a test project generated with this on top of the synchronized folders PRs (#3376–#3378) is identical to the file Xcode saved for it, and rules_xcodeproj's own project is unchanged from the previous version of this PR.

@brentleyjones

Copy link
Copy Markdown
Contributor

Before even looking at the PR, I have concerns. We chose the cat model specifically because a 0.16 s per MB speed is too slow. Our project is 417MB. We can't have the last part of project generation take 66s.

The `PBXProject` object was split across four partials: `pbxproj_prefix`
wrote its start with `attributes` last, and the `TargetAttributes`,
`knownRegions`, and `targets` partials were concatenated after it. This
put `attributes` after `projectRoot` and `knownRegions` after
`attributes`, which isn't the order Xcode writes.

`pbxproj_prefix` already runs after `pbxtargetdependencies` and
`files_and_groups`, so it now takes those three partials as inputs and
writes the whole object in Xcode's order. The final `cat` no longer needs
them.

Signed-off-by: Ernesto Cambuston <e.cambuston@gmail.com>
The partials were concatenated as is, so objects were in generator
order, without the `/* Begin <isa> section */` blocks Xcode writes. Xcode
rewrites the whole file the next time it saves the project.

`cat` can't do this: markers are only written for non-empty sections,
and objects of the same `isa` come from different generators with
interleaved identifiers. The new `project_pbxproj` generator replaces
`cat`: it finds each object's bounds and `isa`, stable sorts the objects
by `isa` and identifier, and writes them with the section markers. Each
object is copied as is, since the generators already write attributes in
Xcode's order.

Depends on `pbxproj_prefix` writing the whole `PBXProject` (previous
commit), as it's the only object that was split across partials.

On a 102 MB `project.pbxproj` it takes 0.07 s (`cat`: 0.02 s), so about
0.3 s for 417 MB.

It is still a separate `no-cache` action with the same inputs, so only
this step reruns on regeneration.

Signed-off-by: Ernesto Cambuston <e.cambuston@gmail.com>
@erneestoc
erneestoc force-pushed the ec/assemble-pbxproj-xcode-layout branch from 803f7ee to d1c962b Compare October 9, 2026 21:50
@erneestoc
erneestoc requested a review from a team as a code owner October 9, 2026 21:50
@erneestoc erneestoc changed the title Write project.pbxproj in Xcode's layout Write project.pbxproj in Xcode's layout Oct 9, 2026
@erneestoc

erneestoc commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor Author

Agreed, the script was far too slow. I've reworked it:

  • Write the whole PBXProject in pbxproj_prefix #3381 makes pbxproj_prefix write the whole PBXProject in Xcode's order. It's the only object that was split across partials, so nothing else needs reordering.
  • This PR is now a Swift generator that only finds object bounds, sorts them by (isa, identifier), and writes them with the section markers. Objects are copied as is. Its own commit is about 965 lines: about 265 of logic, 255 of tests, and the rest the usual generator setup (BUILD files, README, wiring). Until Write the whole PBXProject in pbxproj_prefix #3381 merges, the diff here also includes that PR's commit.

On a 102 MB project.pbxproj, it takes 0.07 s (cat takes 0.02 s). That's about 0.3 s for your 417 MB project. The output is byte-identical to the previous version and to an Xcode-saved project.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants