Xcode 27 Removes ld64 and Requires Unique Module Names

Apple retired a linker in one sentence: “The ld64 linker has been removed and the -ld_classic option is no longer supported.”1 The flag now gone is a flag Apple told developers to add. Xcode 15’s own release notes offered -Wl,-ld_classic as the workaround for two linker bugs.3

TL;DR

  • Xcode 27 removes ld64. On the GA toolchain, -ld_classic is ignored with a warning and the link exits 0, while the other spelling Apple once documented, -ld64, fails the link outright.112 Apple’s Xcode 15 notes prescribed the flag for weak-symbol crashes and LTO import bugs, Xcode 16 deprecated it, and Xcode 26 said nothing at all.3410
  • A second break sits in the Swift compiler. Apple made dependency scanning one shared action, so “every Clang module reachable from a single Swift dependency-scan action must have a unique module name.”2 Apple hedges twice: the scan “may report an error,” and previously the scanner “may have tolerated duplicating names.”2
  • Both fire on toolchain upgrade rather than a deployment target or SDK choice, and neither has a runtime component. The exposed population: vendored binaries, CocoaPods, or a large mixed Swift/Objective-C/C++ codebase.
  • Apple’s “may” is not editorial caution. Under Xcode 26.6 I put two module maps declaring one name on a single search path and ran the scanner 20 times: nine runs crashed with SIGSEGV, five aborted, and six finished cleanly.8 On the GA toolchain, 20 runs of the same input print the redefinition error every time and exit 0 every time, while the vendored module map in the next bullet prints nothing; the second September 15 update has the detail.13
  • A vendored module map redeclaring an SDK module, the case Apple names, compiles silently today and hides the real module. My vendored SQLite3 shim typechecked with exit 0, and sqlite3_open stopped existing.8
  • Across seven audited projects: zero -ld_classic, OTHER_LDFLAGS unset everywhere, and zero duplicate module names among 391 module maps, because no repository holds a hand-authored one.9

Both notes first shipped in the Xcode 27 beta 4 release notes, alongside Swift 6.4 and the 27 SDKs, and both survive unchanged in the GA notes of September 14.1112

Update, September 15: what the GA toolchain does with each flag

Xcode 27 (27A266a) shipped on September 14, and the linker inside it settles the question this post could not test in July. xcrun ld -v now reports:12

@(#)PROGRAM:ld PROJECT:ld-27037.1
BUILD 21:17:51 Aug 25 2026
configured to support archs: armv6 armv7 armv7s arm64 arm64e arm64_32 i386 x86_64 x86_64h armv6m armv7k armv7m armv7em armv8m.main armv8.1m.main
LTO support using: LLVM version 21.0.0 (static support for 30, runtime is 30)

The “will use ld-classic for:” line that Xcode 26.6 printed is gone, and so is the ld-classic binary: the toolchain’s usr/bin holds a single linker, ld. The two spellings that reached one code path in July now diverge. With the same one-line hello.c:12

xcrun clang hello.c -o hello -Wl,-ld_classic
ld: warning: -ld_classic is no longer supported and will be ignored

Exit status zero, and the binary links. Then:

xcrun clang hello.c -o hello -Wl,-ld64
ld: library 'd64' not found
clang: error: linker command failed with exit code 1 (use -v to see invocation)

Exit status one, no binary. The new linker no longer recognizes -ld64 as a linker selector, so it parses it as -l d64, a request for a library named d64, and fails when none exists. So the July sentence “stops accepting -ld_classic” was half right: the flag Apple named is tolerated with a warning, and the flag Apple named in Xcode 15, -ld64, is the one that breaks the build. A project carrying -Wl,-ld64 in OTHER_LDFLAGS fails on upgrade with an error message that never mentions the linker; a project carrying -Wl,-ld_classic keeps building and gains a warning on every link. The Xcode 27 release post places both beside the other changes that fire on upgrade.

Update, September 15: the GA scanner on the same two collisions

The module-map experiments below ran on Xcode 26.6 in July. On 27A266a, with the same two directories declaring module Widget on one search path, swiftc -typecheck still fails 20 runs out of 20 with error: redefinition of module 'Widget' and the previously defined here note, so the compile still stops. The scanner is what changed. swiftc -scan-dependencies on the same input exits 0 on all 20 runs, prints the identical redefinition of module 'Widget' error and note to stderr on all 20, and writes a complete dependency graph on all 20 that resolves Widget to the first -I directory’s module map. Where Xcode 26.6 crashed nine times and aborted five, the shipped scanner reports the collision every time and finishes anyway. The July reading of Apple’s “may” as cover for an unstable scanner does not hold on GA.813 On GA the scanner prints the error and exits 0, which means a build step that keys on that status sees a successful scan and the failure arrives one step later, at compile. I did not test how Xcode’s own build system treats a printed error from a zero-exit scan.13

The vendored case stays quiet. A Vendor/module.modulemap redeclaring SQLite3 on the search path scans with exit 0 and nothing on stderr, 20 runs out of 20, and the graph resolves SQLite3 to the vendored map rather than the SDK’s usr/include/SQLite3.modulemap, the file that usr/include/module.modulemap line 7 references as extern module SQLite3 "SQLite3.modulemap"; a file importing the module typechecks clean, and a file calling sqlite3_open still fails with error: cannot find 'sqlite3_open' in scope until the vendored directory leaves the search path. On the toolchain that ships, the second shape Apple names produces no diagnostic that names the collision.13

The same two module maps declaring module Widget, scanned 20 times: Xcode 26.6 crashed 9 times, aborted 5, and succeeded 6; Xcode 27 printed the redefinition error and exited 0 on all 20; typecheck failed on all 20 with both toolchains.

The flag Apple told you to add

The story starts with a linker rewrite. Xcode 15 announced a new linker, made it the default “for all macOS, iOS, tvOS and visionOS binaries,” and set the terms of the retirement in one clause: “The classic linker can still be explicitly requested using -ld64, and will be removed in a future release.”3

The new linker arrived with bugs, and Apple documented the escape hatch twice on the same page. The first Known Issue: “Binaries using symbols with a weak definition crash at runtime on iOS 14/macOS 12 or older. This impacts primarily C++ projects due to their extensive use of weak symbols.” Apple’s workaround was to raise the deployment target “or add -Wl,-ld_classic to the OTHER_LDFLAGS build setting.”3 The second, covering LTO object files that linked weak symbol imports as non-weak, offered “-Wl,-weak_reference_mismatches,weak or -Wl,-ld_classic” in the same setting.3

Both bugs hit C++ projects hardest, which explains who still carries the flag. Nobody revisits a setting that stopped a crash.

Xcode 16 gave notice in one sentence: “-ld_classic linker option is deprecated and will be removed in a future release.”4 Then silence. I searched the Xcode 26 release notes for ld_classic, ld64, and the classic linker and found none.10

The current toolchain still accepts the flag and still complains. On Xcode 26.6 I linked a trivial C program with it:7

xcrun clang hello.c -o hello -Wl,-ld_classic
ld: warning: -ld_classic is deprecated and will be removed in a future release

Exit status zero. The binary links. Substituting -Wl,-ld64 produces the identical warning naming -ld_classic, so both spellings Apple used reach the same code path today.7 Apple’s Xcode 27 note names only -ld_classic. On the GA toolchain the two spellings part ways, as the update above shows: -ld_classic is ignored with a warning, and -ld64 fails the link.12

One detail complicates the word “removed.” Xcode 26.6’s linker, asked to identify itself with xcrun ld -v, reports which architectures it delegates:7

@(#)PROGRAM:ld PROJECT:ld-1267
BUILD 16:38:58 Jun  8 2026
configured to support archs: armv6 armv7 armv7s arm64 arm64e arm64_32 i386 x86_64 x86_64h armv6m armv7k armv7m armv7em armv8m.main armv8.1m.main
will use ld-classic for: armv6 armv7 armv7s i386 armv6m armv7k armv7m armv7em

ld-classic exists as a real binary in the toolchain, with its own man page, and the linker still routes eight architectures to it.7 None survives in a current app SDK: iPhoneOS 26.5 declares arm64e and arm64 only, and the watchOS SDK adds arm64_32.7 The list is 32-bit ARM, i386, and Cortex-M embedded targets. Reading their absence from the SDKs as the reason the removal is safe for app developers is my inference rather than Apple’s claim.

Finding the flag without trusting grep

-ld_classic lives in OTHER_LDFLAGS, so ask the build system what it resolves to rather than searching files for text:

xcodebuild -showBuildSettings \
  -project YourApp.xcodeproj \
  -configuration Release -sdk iphoneos 2>/dev/null \
  | grep -E "OTHER_LDFLAGS|SUPPORTED_PLATFORMS"

Against the Reps project one line comes back:9

    SUPPORTED_PLATFORMS = appletvos appletvsimulator iphoneos iphonesimulator macosx

OTHER_LDFLAGS does not appear at all, which is the answer: the setting is unset, so no linker flags reach the link step from it. Absence in resolved build settings carries information that absence in a text search does not. Read SUPPORTED_PLATFORMS for the platform question too, never a *_DEPLOYMENT_TARGET, which Xcode writes whether or not a destination is real.

Three traps produced false clean results during the audit, and all three print nothing and exit successfully. timeout does not exist on stock macOS, so wrapping xcodebuild in it returns exit 127 and empty output that reads like a project with no linker flags. Under zsh an unquoted --include=*.pbxproj gets glob-expanded before grep sees it, so the command dies with “no matches found” instead of reporting zero hits. And find does not follow a symlink given as its starting point, which matters because xcrun --sdk iphoneos --show-sdk-path returns one: find "$SDK" -name '*.modulemap' finds nothing, while find -H "$SDK" finds 266 files.9 Quote your patterns, pass -H, and confirm the command matches something you know is there before trusting a zero.

A hand-maintained target keeps the flag in project.pbxproj or an .xcconfig; a CocoaPods project can also receive it from a post_install hook, which no developer-edited file records. The fleet I audited has neither, so that last path went unverified.9

The module-name rule, and Apple’s two hedges

The second break arrives disguised as a performance win, filed under New Features rather than Deprecations. Apple’s Swift Compiler entry, in full:

The Swift dependency scanner has been optimized to avoid redundant setup work and header searches when looking up Clang modules during a single dependency-scan action, substantially improving scanning performance. As a consequence of this change, every Clang module reachable from a single Swift dependency-scan action must have a unique module name. If two module maps visible to the same scan declare a Clang module with the same name, the scan may report an error. Previously, the scanner may have tolerated duplicating names. The most common cases are projects or SDKs that vend the same Clang module name from more than one location on the header search path, and vendored third-party sources that ship a module.modulemap redeclaring an SDK module.2

Four things there deserve separating. Apple scopes the requirement to what one scan can reach, not your whole disk. Apple states the consequence as “may report an error,” and hedges the old behavior the same way. Apple names the two triggering shapes: one module name vended from two locations on the header search path, and a vendored module.modulemap redeclaring an SDK module. And the trigger is the toolchain, since nothing references a deployment target or SDK version.

The rule predates the optimization. Clang’s module map language states it plainly: “Each module shall have a single definition.”6 What the documentation never states is the consequence of breaking the rule, and that silence turns out to be well earned: the toolchain’s answer is not one behavior but several.

What a collision actually does

Apple’s hedges made me want to see the failure, so I built the smallest version: two directories, each with a module.modulemap declaring the same module.8

A/module.modulemap    B/module.modulemap
module Widget {       module Widget {
    header "widget.h"     header "widget.h"
    export *              export *
}                     }

With both directories on the search path, a plain typecheck fails the same way every time, 20 runs out of 20:8

B/module.modulemap:1:8: error: redefinition of module 'Widget'
1 | module Widget {
  |        `- error: redefinition of module 'Widget'
A/module.modulemap:1:8: note: previously defined here

redefinition of module is the string to search for in a build log, and Xcode 26.6’s clang already emits it. Dropping one search path makes the same compile succeed, confirming visibility to one compilation as the trigger rather than presence on disk.8

The dependency scanner behaves differently, and in July the difference looked like the whole reason Apple wrote “may”; the second September 15 update revisits that. Running the same two module maps through swiftc -scan-dependencies 20 times produced three outcomes: nine crashes with SIGSEGV, five aborts, and six clean successes.8 The crashing runs’ stack traces pass through performParallelClangModuleLookup, which fits a race in the parallel lookup Apple describes replacing. Attributing the nondeterminism to that race is my reading of the trace, not Apple’s statement.

Apple’s second named case is the quiet one. I wrote a vendored module map declaring SQLite3, a real module in the iPhoneOS SDK, put it on the search path, and compiled against it:8

Vendor/module.modulemap
module SQLite3 {
    header "shim.h"
    export *
}

The compile succeeded. Exit 0, no warning, no note, no diagnostic of any kind. Then I asked the same configuration for sqlite3_open:8

error: cannot find 'sqlite3_open' in scope

Without the vendored directory on the search path, the identical file compiles. The vendored module map shadowed the SDK’s SQLite3 completely, and the toolchain said nothing. So today the vendored-redeclaration case does not fail loudly; it fails as a missing API in a module you thought you imported. Apple’s note says the Xcode 27 scan “may report an error” there, which would convert a silent shadowing into a build failure.

I build on Xcode 26.6 (build 17F113), so every diagnostic above comes from the previous toolchain.78 I cannot report Xcode 27’s error text and have not invented any. The diagnostic machinery, the string to grep for, and the tolerance Apple describes retiring all exist today. (September 15: this paragraph, and the prediction that closes the paragraph above, describe July; on the GA toolchain the vendored case still fails silently, and the output for both shapes is in the second update at the top of this post.13)

Auditing for duplicate module names

Enumerating declared module names looks like a one-line grep, but two constructs in the module map language produce wrong answers.

extern module Foo "Foo.modulemap" is a forward reference, not a definition, and Apple’s SDK uses 79 of them in one module map.7 A naive scan counts the reference and the real definition as two declarations of Foo. Second, module Darwin.C { ... } uses a dotted module-id to extend a module declared elsewhere; 13 SDK module maps open module Darwin.something, and reading the first component as a top-level declaration lumps all 13 in with the real Darwin declaration as one 14-file collision.7 Both false positives appeared in my first draft.

What survives handles comments, brace depth so nested explicit module submodules stay out, and paths containing spaces:

find -H "${1:-.}" \( -name '*.modulemap' -o -name 'module.map' \) \
  -not -path '*/build/*' -not -path '*/.build/*' \
  -not -path '*/DerivedData*/*' -print0 |
while IFS= read -r -d '' map; do
  awk -v f="$map" '
    { line = $0; sub(/\/\/.*/, "", line)
      if (depth == 0 && line !~ /extern[[:space:]]+module/ &&
          match(line, /(^|[[:space:]])module[[:space:]]+[A-Za-z_][A-Za-z0-9_.]*/)) {
        n = substr(line, RSTART, RLENGTH); sub(/.*module[[:space:]]+/, "", n)
        if (n !~ /\./) print n "\t" f
      }
      for (i = 1; i <= length(line); i++) {
        c = substr(line, i, 1)
        if (c == "{") depth++; else if (c == "}") depth--
      }
    }' "$map"
done | sort -u > /tmp/modnames.txt

cut -f1 /tmp/modnames.txt | uniq -d | while read -r n; do
  echo "duplicate: $n"
  awk -F'\t' -v n="$n" '$1==n {print "  " $2}' /tmp/modnames.txt
done

The strongest available negative control is Apple’s own SDK, which must satisfy the rule for the toolchain to work at all. Pointed at the iPhoneOS 26.5 SDK the command finds 1,099 top-level declarations under usr/include and 205 across the framework module maps, with zero duplicates in either.7 Pointed at fixtures built to collide, it names the duplicate and both files.8

The exclusions carry weight. -not -path '*/build/*' does not exclude .build/, because the pattern needs the literal directory name, and SwiftPM writes generated module maps into .build by the dozen. Leaving .build in produced my only “duplicates” across seven projects: GrappleCore in six files and GrappleRender in three, all SwiftPM output, spanning two Swift targets across different build roots.9 The nine are not even byte-identical, and the reason is instructive: each wraps the same module name around an absolute path to its own build root’s generated header, so they differ in one string and in nothing that matters. An audit that calls them collisions exhausts its credibility before reaching anything real.

Xcode manufactures the same false positive with less ambiguity. Inside a single DerivedData tree it writes each Swift package’s generated module map twice, into GeneratedModuleMaps-iphonesimulator/ and into the target’s own intermediates, with a relative header path both times. In one project’s tree 15 names appear twice each, all 15 pairs byte-identical.9 Scope the audit to source, not build output.

What seven projects contain

I ran both audits against seven Xcode projects on Xcode 26.6. The honest result is a clean null.9

Project Swift files Objective-C / C++ / C Dependencies -ld_classic Source module maps
Reps 77 0 2 local SPM 0 0
Return 57 0 none 0 0
Banana List 55 0 none 0 0
Ace Citizenship 26 0 none external 0 0
Water 34 0 (2 .metal) none 0 0
ResumeGeni 71 0 3 remote SPM, 9 pins 0 0
Yawara 143 0 2 local SPM 0 0
Total 463 0 SPM only 0 0

OTHER_LDFLAGS is unset in all seven projects, no .xcconfig file exists anywhere in the fleet, and there is no CocoaPods, no Carthage, and no vendored .framework or .xcframework.9 The module-name audit examined 391 module maps, 204 inside the project directories and 187 in the shared DerivedData where Xcode’s GUI builds land, and every one is build output.9 Not one repository holds a hand-authored module map.

Both zeros have the same cause, and it is the finding worth carrying away: the exposed population is mixed-language projects, and these are not. Across 463 Swift files the fleet holds zero Objective-C, zero C++, and zero C sources, with two Metal shaders in Water as the only non-Swift compiled code.9 A null result from a codebase never at risk is weak evidence about the hazards and strong evidence about who needs to audit.

Two details matter more than the zeros. The only hand-written module maps in the resolved packages belong to swift-crypto, which vends four C shims named CCryptoBoringSSL, CCryptoBoringSSLShims, CXKCP, and CXKCPShims, all distinct.9 And no name the fleet declares appears anywhere in the SDK’s Clang module namespace, checked against all 1,318 top-level names the command finds in the iPhoneOS 26.5 SDK.9 Even the generic ones arriving through Supabase miss: the SDK declares Clang modules named Foundation, UIKit, and SQLite3, and none named Crypto, Storage, or Auth.

The C++ deployment floor moving underneath

One adjacent change hits the same C++ projects that hold -ld_classic. Apple raised a floor, naming macOS and no other platform: “The minimum supported deployment target on macOS for the C++ standard library has been increased to 11.0.”5

The same entry offers an escape hatch and dates its removal in the next sentence. libc++ changed the result of lower_bound and upper_bound on std::map and std::set for comparators that are not a strict weak order, and defining _LIBCPP_ENABLE_LEGACY_TREE_LOWER_UPPER_BOUND “will revert to the historical implementation of these operations.” Then: “That escape hatch will be removed in an upcoming release (likely in the next release).”5 A related change stops multimap::find and multiset::find necessarily returning the first equal element, behavior Apple notes “was never guaranteed by the Standard” though libc++ always provided it, and it ships with no opt-out.5 Treat the macro as a migration ticket, not a fix.

FAQ

Why does my project have -ld_classic in it?

Almost certainly because Apple’s Xcode 15 release notes prescribed it. Two Known Issues there recommended adding -Wl,-ld_classic to OTHER_LDFLAGS: one where binaries using weakly defined symbols crashed at runtime on iOS 14 and macOS 12 or older, which Apple noted “impacts primarily C++ projects,” and one where LTO object files linked weak symbol imports as non-weak.3 Apple deprecated the option in Xcode 16 and removed the linker behind it in Xcode 27.14 If the flag is there, confirm the original bug still reproduces before hunting a replacement.

How do I find duplicate Clang module names in my project?

Enumerate top-level module declarations across every module map, look for a name appearing in two files, and drop build directories from the results. Three things make the job more than a grep: extern module Foo "path" is a reference rather than a definition, module Foo.Bar extends a module declared elsewhere instead of declaring Foo, and nested explicit module submodules do not collide with top-level names.6 Exclude .build, build, and DerivedData explicitly, since -not -path '*/build/*' misses .build and both SwiftPM and Xcode duplicate generated module maps routinely.9

What does the error look like under Xcode 27?

On Xcode 27.0 (27A266a) the text is the one Xcode 26.6 already emits: error: redefinition of module 'Widget' with note: previously defined here. A plain -typecheck prints it and exits 1; -scan-dependencies prints it and exits 0.13 In July my machine ran Xcode 26.6 (build 17F113), so the July text reported no Xcode 27 output at all;78 the second September 15 update at the top adds it.13 Apple’s wording for Xcode 27 is that the scan “may report an error,” so search logs for redefinition of module rather than a string someone guessed.

Does a unique-module-name failure depend on my deployment target?

No. Apple frames the requirement against a single Swift dependency-scan action and names no OS version, SDK, or deployment target in the entry.2 The linker change reads the same way, as a removal from the toolchain.1 Both land on the first build in Xcode 27, alongside the @State macro rather than the SDK-triggered requirements in the same cycle.

Key Takeaways

For iOS developers:

  • Query OTHER_LDFLAGS through xcodebuild -showBuildSettings -configuration Release -sdk iphoneos instead of grepping for -ld_classic. Absence from resolved settings means unset; absence from a text search means nothing.9
  • Search build logs for redefinition of module, the diagnostic Xcode 26.6 already emits, not invented Xcode 27 error text.8

For teams with vendored C or C++ dependencies:

  • Audit vendored module.modulemap files against the SDK namespace first: Apple names that case, and it fails silently today. My vendored SQLite3 shim compiled with exit 0 and made sqlite3_open disappear.8
  • Scope the audit to source and exclude .build, build, and DerivedData. Every apparent duplicate across 391 module maps was generated build output for a single Swift target.9

For release managers:

  • Treat both changes as toolchain-triggered and schedule them for the first Xcode 27 build, not the SDK migration. Neither references a deployment target or has a runtime component.12
  • Keep Apple’s hedges in the ticket. The scan “may report an error,” and 20 runs of one collision on Xcode 26.6 gave three different outcomes, so a build passing once proves nothing.28 On GA the scan prints the error but exits 0, so a green scan step proves nothing either.13

The 27 cycle keeps failing in different places: the launch screen key stops a submission, the scene life cycle mandate stops a launch, and the @State macro stops a build at the source level. The linker removal and the module-name rule stop it one layer lower, in the parts of the toolchain nobody configures on purpose. The full series hub is the Apple Ecosystem Series.

References


  1. Apple, Xcode 27 Release Notes, Linking section, Deprecations in Xcode 27 Beta (radar 165165518). Source of the removal, quoted in full: “The ld64 linker has been removed and the -ld_classic option is no longer supported.” Verified against Apple’s documentation JSON on July 26, 2026, since the HTML page renders its content through JavaScript. The page title at that date is “Xcode 27 Beta 4 Release Notes.” ↩↩↩↩↩

  2. Apple, Xcode 27 Release Notes, Swift Compiler section, New Features in Xcode 27 Beta (radar 136303612). Source of the dependency-scanner entry, quoted verbatim and in full in the body of this article, including both hedges (“the scan may report an error” and “Previously, the scanner may have tolerated duplicating names”) and the two named cases. Verified against Apple’s documentation JSON on July 26, 2026. Noted as filed under New Features rather than Deprecations or Known Issues. ↩↩↩↩↩↩

  3. Apple, Xcode 15 Release Notes, Linking section. New Features (radar 108915312) is the source of “A new linker has been written to significantly speed up static linking. It’s the default for all macOS, iOS, tvOS and visionOS binaries and anyone using the ‘Mergeable Libraries’ feature. The classic linker can still be explicitly requested using -ld64, and will be removed in a future release.” Known Issues is the source of both workarounds recommending the flag: radar 114813650 (FB13097713), “Binaries using symbols with a weak definition crash at runtime on iOS 14/macOS 12 or older. This impacts primarily C++ projects due to their extensive use of weak symbols,” with the workaround to raise the deployment target “or add -Wl,-ld_classic to the OTHER_LDFLAGS build setting”; and radar 115521975 (FB13171424), “Weak symbol imports are linked as non-weak imports, when used from LTO object files,” with the workaround “Add -Wl,-weak_reference_mismatches,weak or -Wl,-ld_classic options to the OTHER_LDFLAGS build setting.” Verified against Apple’s documentation JSON on July 26, 2026. ↩↩↩↩↩↩

  4. Apple, Xcode 16 Release Notes, Linking section, Deprecations (radar 128502299): “-ld_classic linker option is deprecated and will be removed in a future release.” Verified against Apple’s documentation JSON on July 26, 2026. ↩↩↩

  5. Apple, Xcode 27 Release Notes, C++ Standard Library section, Deprecations in Xcode 27 Beta. Apple prints a single radar for the whole block, 178191050, trailing its final item rather than beside each one. Source of “The minimum supported deployment target on macOS for the C++ standard library has been increased to 11.0,” of the multi{map,set}::find change (“code relying on the first element being returned from find will be broken, and lower_bound or equal_range should be used instead”), and of the escape hatch and its expiry: “Since this may be tricky to work around in some cases, an escape hatch is provided in this release: defining _LIBCPP_ENABLE_LEGACY_TREE_LOWER_UPPER_BOUND will revert to the historical implementation of these operations. That escape hatch will be removed in an upcoming release (likely in the next release).” Verified against Apple’s documentation JSON on July 26, 2026. ↩↩↩

  6. Clang team, Clang Modules documentation, Module Map Language. Source of “Each module shall have a single definition,” of the module-id rule and the extern module declaration, of “The explicit qualifier can only be applied to a submodule, i.e., a module that is nested within another module,” and of module map discovery by the filename module.modulemap with module.map searched for compatibility. Cited as the toolchain’s own reference for the module map language rather than as an Apple developer document; Apple’s clang derives from this implementation. ↩↩

  7. Author’s testing on macOS 26.5.2 (build 25F84) with Xcode 26.6 (build 17F113), Apple clang 21.0.0, July 26, 2026. Command output reproduced verbatim. xcrun clang hello.c -o hello -Wl,-ld_classic prints “ld: warning: -ld_classic is deprecated and will be removed in a future release” and exits 0; substituting -Wl,-ld64 prints the identical warning naming -ld_classic. xcrun ld -v reports PROJECT:ld-1267 and the architecture lists quoted above. ld-classic is present at /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/ld-classic with a man page alongside it. SDK architectures come from SDKSettings.plist: iPhoneOS 26.5 declares arm64e and arm64, WatchOS 26.5 declares arm64, arm64e, and arm64_32. The SDK module-map counts (1,099 top-level declarations across usr/include, 205 across System/Library/Frameworks, zero duplicates in either) come from running the command published above against the iPhoneOS 26.5 SDK; the 79 extern module declarations are in usr/include/module.modulemap, and 13 module maps in the same directory open a dotted module Darwin.* (bank, Darwin_C, Darwin_Mach, Darwin_Mach_machine, Darwin_machine, Darwin_POSIX, Darwin_sys, device, mach_debug, net, netinet, netinet6, and uuid), which a first-component reading groups with the real Darwin declaration in Darwin.modulemap as a single 14-file collision. Behavior under Xcode 27 was not tested, because Xcode 27 was not installed on the machine used. ↩↩↩↩↩↩↩↩↩↩

  8. Author’s reproduction on the same machine and toolchain, July 26, 2026. Two directories each containing a module.modulemap declaring module Widget, compiled with both directories on the search path via swiftc. -typecheck produced error: redefinition of module 'Widget' with note: previously defined here on 20 of 20 runs, exit 1; dropping one -I path made the same file compile with exit 0. -scan-dependencies on the same input across 20 runs produced nine exits with signal 11 (SIGSEGV), five with signal 6 (SIGABRT), and six exits with status 0, with the crashing runs’ stack traces passing through swift::ModuleDependencyScanner::performParallelClangModuleLookup. Separately, a vendored module map declaring SQLite3 (a module in the iPhoneOS 26.5 SDK) placed on the search path typechecked with exit 0 and no diagnostic, while a file calling the SDK’s sqlite3_open under the same configuration failed with “error: cannot find ‘sqlite3_open’ in scope” and compiled cleanly once the vendored directory was removed from the search path. Fixtures included a path containing a space to verify the published command. That command compares names between files, so a name declared twice inside one module map is out of its scope by design. The July text reported no Xcode 27 output; the second September 15 update carries it.13 ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩

  9. Author’s audit of seven Xcode projects (Reps, Return, Banana List, Ace Citizenship, Water, ResumeGeni, and Yawara) on macOS 26.5.2 with Xcode 26.6 (build 17F113), July 26, 2026. -ld_classic and ld64 appear zero times in any working tree, and OTHER_LDFLAGS is unset in every project, with no .xcconfig files, no Podfile, no Carthage, and no vendored .framework or .xcframework anywhere in the fleet; the search covered current working trees only, not git history or archived build logs, so the result is “absent now” rather than “never used.” The SUPPORTED_PLATFORMS line quoted for Reps comes from xcodebuild -showBuildSettings -configuration Release -sdk iphoneos. Module-map totals: 391 examined, 204 under the seven project directories and 187 in those seven projects’ own trees under the shared ~/Library/Developer/Xcode/DerivedData (the whole shared directory holds far more, belonging to other projects), all of them build output, with zero hand-authored or vendored module maps in any of the seven repositories. Apparent duplicates were checked by content hash, and the two generators behave differently. Xcode’s 15 name pairs in the audited ResumeGeni tree (the in-project build/DerivedData), written once under GeneratedModuleMaps-iphonesimulator/ and once under the target’s intermediates, are byte-identical 15 times out of 15, because Xcode emits a relative header path. The count is per tree rather than per project: the same app’s tree under the shared ~/Library/Developer/Xcode/DerivedData holds 16, and its Index.noindex variant 22. The mechanism is what generalizes, not the number. SwiftPM’s copies are not: the six GrappleCore and three GrappleRender files under Yawara’s .build directories carry six and three distinct hashes and range from 171 to 185 bytes, because each embeds an absolute path to the generated -Swift.h inside its own build root and is otherwise identical. No module name declared across the fleet appears among the 1,318 distinct top-level Clang module names the published command finds when pointed at the whole iPhoneOS 26.5 SDK, of which 1,099 sit under usr/include and 205 under System/Library/Frameworks; the intersection with the fleet’s declared names is empty. The same scan reports zero duplicates across the entire SDK, which is the negative control the rule demands. CryptoKit is absent from that list because it ships as a Swift-only framework with no Clang module map, so the absence of a Crypto collision rests on the SDK declaring no Clang module by that name rather than on CryptoKit occupying it. swift-crypto 4.4.0 vends the only hand-written module maps in the resolved dependency graph (CCryptoBoringSSL, CCryptoBoringSSLShims, CXKCP, CXKCPShims, one declaration each). The SymbolKit host-tool and target variants are in the shared local package 941Kit rather than in the seven apps. Source counts exclude Python virtualenvs, which is a correction that mattered: a naive count credited Reps with C and header files that all turned out to live inside .venv and site-packages and are compiled by no Xcode target. Water has no local build tree, so its zero module-map count reflects an unbuilt project rather than a verified-clean build graph. The three false-clean traps were each confirmed directly: timeout is absent from stock macOS and exits 127; zsh expands an unquoted --include=*.pbxproj and aborts with “no matches found”; and xcrun --sdk iphoneos --show-sdk-path returns a symlink, against which find without -H reports zero module maps where find -H reports 266. ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩

  10. Apple, Xcode 26 Release Notes. Searched for ld_classic, ld64, and any entry describing the classic linker on July 26, 2026; none appears, so Apple’s deprecation notice in Xcode 16 and the removal in Xcode 27 have no intervening restatement. ↩↩

  11. Apple, Xcode 27 Release Notes, Overview: “Xcode 27 beta 4 includes Swift 6.4 and SDKs for iOS 27, iPadOS 27, tvOS 27, watchOS 27, macOS 27, and visionOS 27.” The same notes record under Intel Deprecation (radar 162138432) that “Xcode 27 will only install and run on Apple silicon Macs.” Checked July 26, 2026. ↩

  12. Author’s testing on macOS 26.6.2 (build 25G83) with Xcode 27.0 (build 27A266a), Apple clang 21.0.0 (clang-2100.3.34.2), September 15, 2026, with DEVELOPER_DIR pointed at the Xcode 27 bundle. xcrun ld -v output reproduced verbatim above; ls of the toolchain’s usr/bin lists ld and no ld-classic. xcrun clang hello.c -o hello -Wl,-ld_classic with hello.c containing int main(void){return 0;} printed “ld: warning: -ld_classic is no longer supported and will be ignored” and exited 0; xcrun clang hello.c -o hello -Wl,-ld64 printed “ld: library ‘d64’ not found” and “clang: error: linker command failed with exit code 1 (use -v to see invocation)” and exited 1 with no output file. The GA Xcode 27 Release Notes, fetched the same day through Apple’s documentation JSON, carry the Linking deprecation (165165518) and the Swift Compiler dependency-scanner entry (136303612) with the wording quoted in this post, and the Overview now reads “Xcode 27 includes Swift 6.4 and SDKs for iOS 27, iPadOS 27, tvOS 27, watchOS 27, macOS 27, and visionOS 27.” ↩↩↩↩↩

  13. Author’s testing on macOS 26.6.2 (build 25G83) with Xcode 27.0 (build 27A266a), Apple Swift version 6.4 (swiftlang-6.4.0.34.1 clang-2100.3.34.1), September 15, 2026, against the iPhoneSimulator 27.0 SDK with -target arm64-apple-ios27.0-simulator. Fixtures: A/module.modulemap and B/module.modulemap each declaring module Widget { header "widget.h" export * }, a main.swift that imports Widget, and Vendor/module.modulemap declaring module SQLite3 { header "shim.h" export * }. With -I A -I B: swiftc -typecheck main.swift exited 1 on 20 of 20 runs with “error: redefinition of module ‘Widget’” and “note: previously defined here”; swiftc -scan-dependencies main.swift exited 0 on 20 of 20 runs, printed the same error and note to stderr on 20 of 20, and on 20 of 20 wrote a graph whose Widget entry carries moduleMapPath pointing at A/module.modulemap. With -I A alone both commands exited 0. With -I Vendor: swiftc -scan-dependencies of a file importing SQLite3 exited 0 with empty stderr on 20 of 20 runs and resolved SQLite3 to Vendor/module.modulemap; -typecheck of the import exited 0; -typecheck of a file calling sqlite3_open(":memory:", &db) exited 1 with “error: cannot find ‘sqlite3_open’ in scope”, and exited 0 without the -I Vendor argument. The SDK references the module at usr/include/module.modulemap line 7, extern module SQLite3 "SQLite3.modulemap", and defines it in usr/include/SQLite3.modulemap as module SQLite3 [system]; without -I Vendor the scan exited 0 with empty stderr and resolved SQLite3 to that file. With the two search-path arguments reversed, -I B -I A, the scan still exited 0, the error moved to A/module.modulemap, the note to B/module.modulemap, and the graph resolved Widget to B/module.modulemap. ↩↩↩↩↩↩↩↩↩

Related Posts

The @State Macro: What Xcode 27 Stops Compiling

Xcode 27 reimplements SwiftUI's @State as a Swift macro. The break lands on toolchain upgrade, not deployment target, an…

22 min read

Xcode 27 Drops Intel: What Stops and What Still Ships

Xcode 27 runs only on Apple silicon, still ships Universal apps back to macOS 12, and silently drops x86_64 from ARCHS_S…

27 min read

Pixel-Art Town Layout: Pallet to Pelican Town, Measured

Sixteen Pokémon towns measured in walk cells from source, plus Pelican Town and Narshe: first doors, signs, street width…

103 min read