Repository navigation
Conversation
Contributor
|
Did you test Xcode UI performance? I had this implemented internally and adding / deleting files in larger modules caused Xcode to hang for 5-6 seconds. |
Contributor
Author
|
I tested first on a big bug fake project I generated. The re-save stopped happening after I was able to match the codegen to what Xcode actually produces - but it may differ on a bigger real-world project. I just tested now on my actual project, and I'm not seeing any re-save or performance issue. Files changes reflects correctly. |
This was referenced Oct 9, 2026
Adds the `xcodeproj_synchronized_folders` aspect hint, which shows a target's source folders in Xcode as synchronized folders (Xcode 16's buildable folders, `PBXFileSystemSynchronizedRootGroup`), so files added to them appear in the target without regenerating the project. Bazel keeps compiling the target's `srcs` (typically a `glob` of the folder), so no rules changes are needed. - The aspect reads the hint's folders (relative to the target's package), records them as `synchronized_folders` inputs (carried through target merging), and drops the target's sources inside them from its file list. - files_and_groups creates a `PBXFileSystemSynchronizedRootGroup` for each folder, with an identifier derived only from its path, and skips other paths inside it; pbxnativetargets references the same identifiers in the target's `fileSystemSynchronizedGroups`. Targets with folders don't need the compile stub. - Projects with synchronized folders use `objectVersion = 70`, which is what Xcode writes for them; other projects keep their version. Also writes `project.pbxproj` exactly as Xcode serializes it. When the contents of a synchronized folder change, Xcode rewrites the whole project file if its serialization differs from the file on disk, which for large projects means re-saving tens of megabytes after each generation. - A final assembly step replaces `cat`: it groups objects into `/* Begin <isa> section */` blocks sorted by identifier and sorts each object's attributes, as Xcode does. - Comments match Xcode's: the project's build configuration list includes the project name, and groups are commented with their name before their path. - Files with unknown extensions get `lastKnownFileType = text`, which Xcode infers for text files. - `TargetAttributes` are sorted by identifier. Signed-off-by: Ernesto Cambuston <e.cambuston@gmail.com>
erneestoc
force-pushed
the
ec/synchronized-folders
branch
from
October 9, 2026 16:51
235429f to
6d01b88
Compare
This was referenced Oct 9, 2026
Contributor
Author
|
Split into smaller PRs that can be reviewed and merged independently:
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds support for Xcode 16's buildable folders (
PBXFileSystemSynchronizedRootGroup) through a newxcodeproj_synchronized_foldersaspect hint, and makes the generatedproject.pbxprojbyte-for-byte what Xcode writes, so Xcode doesn't rewrite it when a synchronized folder's contents change.This takes a similar approach to #3315, with a few differences described below, including a cause for the slowdown reported there.
Usage
Files added to
Sources/(in Xcode or on disk) appear in the target without regenerating the project, and Bazel builds them through theglob. No changes to rules_swift or other rules are needed.Synchronized folders
synchronized_folders(carried through target merging, so a test's library folder ends up on the test target), and drops the target's sources inside them from its per-file list. A target with folders counts as having sources (no compile stub).PBXFileSystemSynchronizedRootGroupfor each folder and skips any other path inside it (e.g. extra files), so a folder never also appears as a group of files.fileSystemSynchronizedGroupson the target. Both generators compute the folder's identifier from its path alone (Identifiers.FilesAndGroups.synchronizedRootGroup).objectVersion = 70, which is what Xcode writes for them with ourcompatibilityVersion; other projects keep their current version.Writing project.pbxproj in Xcode's form
When a synchronized folder's contents change, Xcode serializes the project and rewrites
project.pbxprojif its bytes differ from the file on disk. With rules_xcodeproj's output that happened on the first change after every generation: a full re-save of the project file, which for large projects is tens of megabytes. Once the file matches Xcode's serialization, adding and removing files causes no writes at all.The only content difference was
lastKnownFileTypefor unknown extensions; the rest was layout:assemble_project_pbxproj.sh, a streaming awk/sort pipeline) replacescat. It groups objects into/* Begin <isa> section */blocks sorted by identifier and sorts each multi-line object's attributes withisafirst (thePBXProjectis written by several generators). It's idempotent and fails on malformed input.pbxproj_prefixgets--project-name), and groups are commented with theirnamebefore theirpath(external repositories were commented with their resolved cache path).lastKnownFileType = textinstead offile. Xcode infers the type from contents, which the generator can't see; unknown files in source trees (.graphql, scripts,LICENSE) are almost always text. A binary file with an unknown extension would still differ.TargetAttributesare sorted by identifier, as Xcode does.This changes the layout of every generated project, not just ones with synchronized folders: the first regeneration produces a large diff, after which the file matches what Xcode writes. The assembly step takes ~0.16 s for a 1 MB project file and ~3.8 s for a 43 MB one (almost all in macOS awk), once per generation.
Performance
On a synthetic project (200 targets, 3,000 files, 200 synchronized folders, 1 MB
project.pbxproj) with Xcode 26.2 open:The 5–6 s hangs reported in #3315 are likely a mix of both; this PR removes the rewrite part.
Testing
fileSystemSynchronizedGroups, the object version, the hint rule, and the assembly script (objects split across partials,PBXProjectwritten in parts, idempotence, malformed input).bazel test //tools/... //test/...passes (103 tests), as does//docs:diff_test.xcodebuild testpasses;SwiftFileListand is built by Bazel without regenerating;project.pbxprojis identical to Xcode's own serialization, and adding/removing files leaves it untouched.Limitations
srcs, so they should cover the folder (e.g.glob(["Sources/**/*.swift"])); a file added in Xcode outside theglobappears in the target but isn't built.