← Alle Beitrage

Xcode 27 entfernt ld64 und verlangt eindeutige Modulnamen

Einen Linker hat Apple in einem einzigen Satz beerdigt: „Der Linker ld64 wurde entfernt, und die Option -ld_classic wird nicht mehr unterstützt.”1 Verschwunden ist damit ausgerechnet jenes Flag, dessen Einsatz Apple den Entwicklern selbst empfohlen hat. In Apples eigenen Release Notes zu Xcode 15 stand -Wl,-ld_classic als Workaround für zwei Linker-Fehler.3

TL;DR

  • Xcode 27 entfernt ld64 und akzeptiert -ld_classic nicht mehr.1 Apples Xcode-15-Notes verordneten das Flag gegen Abstürze bei schwach definierten Symbolen und gegen fehlerhafte LTO-Importe, Xcode 16 erklärte es für veraltet, und Xcode 26 schwieg dazu vollständig.3410
  • Ein zweiter Bruch steckt im Swift-Compiler. Apple hat das Scannen von Abhängigkeiten zu einer gemeinsamen Aktion zusammengefasst, deshalb muss „jedes Clang-Modul, das aus einer einzelnen Swift-Abhängigkeitsscan-Aktion erreichbar ist, einen eindeutigen Modulnamen haben”.2 Gleich zweimal relativiert Apple: Der Scan „meldet möglicherweise einen Fehler”, und zuvor habe der Scanner doppelte Namen „möglicherweise toleriert”.2
  • Beides greift beim Toolchain-Update und nicht bei der Wahl von Deployment Target oder SDK, und keines von beiden hat einen Laufzeitanteil. Betroffen ist, wer mitgelieferte Binaries, CocoaPods oder eine große gemischte Codebasis aus Swift, Objective-C und C++ pflegt.
  • Apples „möglicherweise” ist keine redaktionelle Vorsicht. Unter Xcode 26.6 habe ich zwei Module Maps mit demselben Namen auf einen einzigen Suchpfad gelegt und den Scanner 20-mal laufen lassen: Neun Läufe stürzten mit SIGSEGV ab, fünf brachen ab, sechs liefen sauber durch.8
  • Eine mitgelieferte Module Map, die ein SDK-Modul erneut deklariert – genau der von Apple genannte Fall –, kompiliert heute klaglos und verdeckt das echte Modul. Mein mitgelieferter SQLite3-Shim lief mit Exit-Code 0 durch die Typprüfung, und sqlite3_open existierte anschließend nicht mehr.8
  • In sieben geprüften Projekten: null -ld_classic, OTHER_LDFLAGS überall ungesetzt und null doppelte Modulnamen unter 391 Module Maps – weil kein einziges Repository eine handgeschriebene enthält.9

Beide Hinweise stehen in den Release Notes zu Xcode 27 Beta 4, zusammen mit Swift 6.4 und den 27er-SDKs.11 Lesen Sie sie als Beta-Text.

Das Flag, das Apple Ihnen empfohlen hat

Die Geschichte beginnt mit einer Neuentwicklung des Linkers. Xcode 15 kündigte einen neuen Linker an, machte ihn zum Standard „für alle macOS-, iOS-, tvOS- und visionOS-Binaries” und legte die Bedingungen der Abschaffung in einem einzigen Nebensatz fest: „Der klassische Linker kann weiterhin ausdrücklich über -ld64 angefordert werden und wird in einem künftigen Release entfernt.”3

Der neue Linker kam mit Fehlern, und Apple dokumentierte den Notausstieg gleich zweimal auf derselben Seite. Das erste Known Issue: „Binaries, die Symbole mit einer schwachen Definition verwenden, stürzen zur Laufzeit unter iOS 14/macOS 12 oder älter ab. Das betrifft vor allem C++-Projekte, weil sie schwache Symbole ausgiebig nutzen.” Apples Workaround lautete, das Deployment Target anzuheben „oder -Wl,-ld_classic zur Build-Einstellung OTHER_LDFLAGS hinzuzufügen”.3 Das zweite betraf LTO-Objektdateien, die schwache Symbolimporte als nicht schwach linkten, und bot in derselben Einstellung „-Wl,-weak_reference_mismatches,weak oder -Wl,-ld_classic” an.3

Beide Fehler trafen C++-Projekte am härtesten, was erklärt, wer das Flag bis heute mit sich herumträgt. Niemand fasst eine Einstellung noch einmal an, die einen Absturz beseitigt hat.

Xcode 16 kündigte das Ende in einem Satz an: „Die Linker-Option -ld_classic ist veraltet und wird in einem künftigen Release entfernt.”4 Danach Schweigen. Ich habe die Release Notes zu Xcode 26 nach ld_classic, ld64 und dem klassischen Linker durchsucht und nichts gefunden.10

Die aktuelle Toolchain akzeptiert das Flag noch und beschwert sich weiterhin darüber. Unter Xcode 26.6 habe ich damit ein triviales C-Programm gelinkt: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 null. Das Binary wird gelinkt. Ersetzt man das Flag durch -Wl,-ld64, erscheint dieselbe Warnung, die -ld_classic nennt; beide Schreibweisen, die Apple verwendet hat, laufen heute also in denselben Codepfad.7 Apples Hinweis zu Xcode 27 nennt nur -ld_classic, und ob -ld64 auf dieselbe Weise scheitert, konnte ich nicht prüfen.

Ein Detail verkompliziert das Wort „entfernt”. Fragt man den Linker von Xcode 26.6 mit xcrun ld -v nach sich selbst, meldet er, welche Architekturen er weiterreicht: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 existiert als echtes Binary in der Toolchain, mitsamt eigener Man-Page, und der Linker leitet nach wie vor acht Architekturen dorthin weiter.7 Keine davon überlebt in einem aktuellen App-SDK: iPhoneOS 26.5 deklariert ausschließlich arm64e und arm64, das watchOS-SDK ergänzt arm64_32.7 Die Liste besteht aus 32-Bit-ARM, i386 und eingebetteten Cortex-M-Zielen. Dass ihr Fehlen in den SDKs der Grund dafür ist, warum die Entfernung für App-Entwickler ungefährlich bleibt, ist meine Schlussfolgerung und keine Aussage von Apple.

Das Flag finden, ohne grep zu trauen

-ld_classic steckt in OTHER_LDFLAGS. Fragen Sie deshalb das Build-System, worauf die Einstellung aufgelöst wird, statt Dateien nach Text zu durchsuchen:

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

Beim Projekt Reps kommt genau eine Zeile zurück:9

    SUPPORTED_PLATFORMS = appletvos appletvsimulator iphoneos iphonesimulator macosx

OTHER_LDFLAGS taucht überhaupt nicht auf, und genau das ist die Antwort: Die Einstellung ist nicht gesetzt, also gelangen von dort keine Linker-Flags in den Link-Schritt. Ein Fehlen in den aufgelösten Build-Einstellungen trägt Information, ein Fehlen in einer Textsuche nicht. Auch die Plattformfrage lesen Sie an SUPPORTED_PLATFORMS ab, niemals an einem *_DEPLOYMENT_TARGET, das Xcode unabhängig davon schreibt, ob ein Ziel real existiert.

Drei Fallen haben während des Audits vermeintlich saubere Ergebnisse erzeugt; alle drei geben nichts aus und beenden sich erfolgreich. timeout existiert auf einem unveränderten macOS nicht; wickelt man xcodebuild darin ein, kommen Exit-Code 127 und eine leere Ausgabe zurück, die aussieht wie ein Projekt ohne Linker-Flags. Unter zsh wird ein nicht quotiertes --include=*.pbxproj per Glob expandiert, bevor grep es überhaupt sieht, sodass der Befehl mit „no matches found” abbricht, statt null Treffer zu melden. Und find folgt einem Symlink nicht, wenn dieser als Startpunkt angegeben ist – was hier zählt, weil xcrun --sdk iphoneos --show-sdk-path genau einen zurückgibt: find "$SDK" -name '*.modulemap' findet nichts, find -H "$SDK" dagegen 266 Dateien.9 Setzen Sie Ihre Muster in Anführungszeichen, übergeben Sie -H, und vergewissern Sie sich, dass der Befehl etwas findet, von dem Sie wissen, dass es da ist, bevor Sie einer Null glauben.

Ein von Hand gepflegtes Target hält das Flag in project.pbxproj oder einer .xcconfig; ein CocoaPods-Projekt kann es zusätzlich aus einem post_install-Hook erhalten, den keine von Entwicklern bearbeitete Datei festhält. Die von mir geprüfte Flotte hat weder das eine noch das andere, dieser letzte Pfad blieb also ungeprüft.9

Die Regel für eindeutige Modulnamen und Apples zwei Einschränkungen

Der zweite Bruch kommt als Leistungsgewinn verkleidet daher, einsortiert unter New Features statt unter Deprecations. Apples Eintrag zum Swift-Compiler, vollständig:

Der Swift-Abhängigkeitsscanner wurde optimiert, um beim Nachschlagen von Clang-Modulen innerhalb einer einzelnen Abhängigkeitsscan-Aktion redundante Einrichtungsarbeit und Header-Suchen zu vermeiden, was die Scan-Leistung erheblich verbessert. Als Folge dieser Änderung muss jedes Clang-Modul, das aus einer einzelnen Swift-Abhängigkeitsscan-Aktion erreichbar ist, einen eindeutigen Modulnamen haben. Wenn zwei für denselben Scan sichtbare Module Maps ein Clang-Modul mit demselben Namen deklarieren, meldet der Scan möglicherweise einen Fehler. Zuvor hat der Scanner doppelte Namen möglicherweise toleriert. Die häufigsten Fälle sind Projekte oder SDKs, die denselben Clang-Modulnamen von mehr als einer Stelle im Header-Suchpfad anbieten, sowie mitgelieferte Drittanbieter-Quellen, die eine module.modulemap ausliefern, die ein SDK-Modul erneut deklariert.2

Vier Dinge darin verdienen es, auseinandergehalten zu werden. Apple bezieht die Anforderung auf das, was ein einzelner Scan erreichen kann, nicht auf Ihre gesamte Festplatte. Die Folge formuliert Apple als „meldet möglicherweise einen Fehler” und relativiert das alte Verhalten auf dieselbe Weise. Apple benennt die zwei auslösenden Konstellationen: ein Modulname, der von zwei Stellen im Header-Suchpfad angeboten wird, und eine mitgelieferte module.modulemap, die ein SDK-Modul erneut deklariert. Und der Auslöser ist die Toolchain, denn nichts verweist auf ein Deployment Target oder eine SDK-Version.

Die Regel ist älter als die Optimierung. Die Module-Map-Sprache von Clang formuliert sie unmissverständlich: „Jedes Modul darf nur eine einzige Definition haben.”6 Was die Dokumentation nirgends festhält, ist die Folge eines Regelbruchs, und dieses Schweigen erweist sich als wohlbegründet: Die Toolchain antwortet nicht mit einem Verhalten, sondern mit mehreren.

Was eine Kollision tatsächlich anrichtet

Apples Einschränkungen haben mich neugierig auf das Scheitern gemacht, also habe ich die kleinstmögliche Version gebaut: zwei Verzeichnisse, jedes mit einer module.modulemap, die dasselbe Modul deklariert.8

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

Liegen beide Verzeichnisse im Suchpfad, scheitert eine schlichte Typprüfung jedes Mal auf dieselbe Weise, in 20 von 20 Läufen: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 ist die Zeichenfolge, nach der Sie im Build-Log suchen, und der clang von Xcode 26.6 gibt sie bereits aus. Entfernt man einen der beiden Suchpfade, gelingt derselbe Kompiliervorgang – der Auslöser ist also die Sichtbarkeit für einen einzelnen Kompiliervorgang und nicht die bloße Existenz auf der Festplatte.8

Der Abhängigkeitsscanner verhält sich anders, und dieser Unterschied ist der ganze Grund, warum Apple „möglicherweise” geschrieben hat. Schickt man dieselben zwei Module Maps 20-mal durch swiftc -scan-dependencies, ergeben sich drei verschiedene Ausgänge: neun Abstürze mit SIGSEGV, fünf Abbrüche und sechs saubere Durchläufe.8 Die Stacktraces der abgestürzten Läufe führen durch performParallelClangModuleLookup, was zu einer Wettlaufsituation in genau jener parallelen Suche passt, deren Ablösung Apple beschreibt. Dass ich den Nichtdeterminismus dieser Wettlaufsituation zuschreibe, ist meine Lesart dieser Spur und keine Aussage von Apple.

Der zweite von Apple genannte Fall ist der leise. Ich habe eine mitgelieferte Module Map geschrieben, die SQLite3 deklariert – ein echtes Modul im iPhoneOS-SDK –, sie in den Suchpfad gelegt und dagegen kompiliert:8

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

Der Kompiliervorgang gelang. Exit 0, keine Warnung, kein Hinweis, keinerlei Diagnose. Dann habe ich dieselbe Konfiguration nach sqlite3_open gefragt:8

error: cannot find 'sqlite3_open' in scope

Ohne das mitgelieferte Verzeichnis im Suchpfad kompiliert dieselbe Datei anstandslos. Die mitgelieferte Module Map hat das SQLite3 des SDK vollständig verdeckt, und die Toolchain sagte dazu nichts. Der Fall der mitgelieferten Neudeklaration scheitert heute also nicht lautstark, sondern daran, dass eine API in einem Modul fehlt, das Sie importiert zu haben glaubten. Apples Hinweis besagt, dass der Scan in Xcode 27 dort „möglicherweise einen Fehler meldet” – das würde ein stilles Verdecken in einen Build-Fehler verwandeln.

Ich baue mit Xcode 26.6 (Build 17F113), sämtliche Diagnosen oben stammen daher aus der vorherigen Toolchain.78 Den Fehlertext von Xcode 27 kann ich nicht wiedergeben, und ich habe keinen erfunden. Die Diagnosemaschinerie, die Zeichenfolge, nach der zu greppen ist, und jene Toleranz, deren Abschaffung Apple beschreibt, existieren allesamt schon heute.

Auf doppelte Modulnamen prüfen

Deklarierte Modulnamen aufzuzählen sieht nach einem einzeiligen grep aus, doch zwei Konstrukte der Module-Map-Sprache liefern falsche Antworten.

extern module Foo "Foo.modulemap" ist eine Vorwärtsreferenz und keine Definition, und Apples SDK verwendet 79 davon in einer einzigen Module Map.7 Eine naive Suche zählt die Referenz und die echte Definition als zwei Deklarationen von Foo. Zweitens erweitert module Darwin.C { ... } über eine punktierte Modul-ID ein anderswo deklariertes Modul; 13 Module Maps des SDK öffnen module Darwin.something, und wer die erste Komponente als Deklaration auf oberster Ebene liest, wirft alle 13 mit der echten Darwin-Deklaration zu einer Kollision über 14 Dateien zusammen.7 Beide Falschmeldungen standen in meinem ersten Entwurf.

Was übrig bleibt, kommt mit Kommentaren zurecht, zählt die Klammertiefe mit, damit verschachtelte explicit module-Submodule draußen bleiben, und verkraftet Pfade mit Leerzeichen:

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

Die stärkste verfügbare Negativkontrolle ist Apples eigenes SDK, das die Regel erfüllen muss, damit die Toolchain überhaupt funktioniert. Auf das iPhoneOS-26.5-SDK angesetzt, findet der Befehl 1.099 Deklarationen auf oberster Ebene unter usr/include und 205 über die Framework-Module-Maps hinweg, in beiden Fällen ohne eine einzige Dublette.7 Auf absichtlich kollidierende Testfälle angesetzt, benennt er den doppelten Namen und beide Dateien.8

Die Ausschlüsse haben Gewicht. -not -path '*/build/*' schließt .build/ nicht aus, weil das Muster den wörtlichen Verzeichnisnamen braucht, und SwiftPM schreibt generierte Module Maps dutzendweise nach .build. .build mit im Ergebnis zu lassen erzeugte meine einzigen „Dubletten” über sieben Projekte hinweg: GrappleCore in sechs Dateien und GrappleRender in drei, allesamt SwiftPM-Ausgabe, verteilt auf zwei Swift-Targets über verschiedene Build-Roots.9 Die neun sind nicht einmal byte-identisch, und der Grund ist lehrreich: Jede legt denselben Modulnamen um einen absoluten Pfad zum generierten Header ihres eigenen Build-Roots, sie unterscheiden sich also in genau einer Zeichenkette und in nichts, worauf es ankommt. Ein Audit, das sie Kollisionen nennt, verspielt seine Glaubwürdigkeit, bevor es auf irgendetwas Echtes trifft.

Xcode erzeugt dieselbe Falschmeldung mit weniger Mehrdeutigkeit. Innerhalb eines einzigen DerivedData-Baums schreibt es die generierte Module Map jedes Swift-Packages zweimal, nach GeneratedModuleMaps-iphonesimulator/ und in die Zwischenprodukte des Targets, beide Male mit relativem Header-Pfad. Im Baum eines Projekts tauchen 15 Namen je zweimal auf, und alle 15 Paare sind byte-identisch.9 Beschränken Sie das Audit auf den Quellcode, nicht auf Build-Ausgaben.

Was sieben Projekte enthalten

Ich habe beide Prüfungen auf sieben Xcode-Projekte unter Xcode 26.6 angesetzt. Das ehrliche Ergebnis ist eine saubere Null.9

Projekt Swift-Dateien Objective-C / C++ / C Abhängigkeiten -ld_classic Module Maps im Quellcode
Reps 77 0 2 lokale SPM 0 0
Return 57 0 keine 0 0
Banana List 55 0 keine 0 0
Ace Citizenship 26 0 keine externen 0 0
Water 34 0 (2 .metal) keine 0 0
ResumeGeni 71 0 3 externe SPM, 9 Pins 0 0
Yawara 143 0 2 lokale SPM 0 0
Gesamt 463 0 nur SPM 0 0

OTHER_LDFLAGS ist in allen sieben Projekten ungesetzt, nirgends in der Flotte existiert eine .xcconfig-Datei, und es gibt weder CocoaPods noch Carthage noch ein mitgeliefertes .framework oder .xcframework.9 Das Modulnamen-Audit untersuchte 391 Module Maps, 204 innerhalb der Projektverzeichnisse und 187 im gemeinsamen DerivedData, wo die GUI-Builds von Xcode landen, und jede einzelne davon ist Build-Ausgabe.9 Nicht ein Repository enthält eine handgeschriebene Module Map.

Beide Nullen haben dieselbe Ursache, und darin liegt die Erkenntnis, die es mitzunehmen lohnt: Betroffen sind Projekte mit gemischten Sprachen, und das sind diese nicht. Über 463 Swift-Dateien hinweg enthält die Flotte null Objective-C-, null C++- und null C-Quellen; die beiden Metal-Shader in Water sind der einzige kompilierte Code außerhalb von Swift.9 Ein Nullbefund aus einer Codebasis, die nie gefährdet war, sagt wenig über die Gefahren aus und viel darüber, wer prüfen muss.

Zwei Details wiegen schwerer als die Nullen. Die einzigen handgeschriebenen Module Maps in den aufgelösten Packages gehören zu swift-crypto, das vier C-Shims mit den Namen CCryptoBoringSSL, CCryptoBoringSSLShims, CXKCP und CXKCPShims anbietet, allesamt verschieden.9 Und kein Name, den die Flotte deklariert, taucht irgendwo im Clang-Modulnamensraum des SDK auf, geprüft gegen alle 1.318 Namen auf oberster Ebene, die der Befehl im iPhoneOS-26.5-SDK findet.9 Selbst die generischen Namen, die über Supabase hereinkommen, gehen daneben: Das SDK deklariert Clang-Module namens Foundation, UIKit und SQLite3, aber keines namens Crypto, Storage oder Auth.

Die C++-Untergrenze, die sich darunter verschiebt

Eine benachbarte Änderung trifft genau jene C++-Projekte, die -ld_classic mit sich führen. Apple hat eine Untergrenze angehoben und dabei ausschließlich macOS genannt, keine andere Plattform: „Das minimal unterstützte Deployment Target unter macOS für die C++-Standardbibliothek wurde auf 11.0 angehoben.”5

Derselbe Eintrag bietet einen Notausstieg an und datiert dessen Entfernung schon im nächsten Satz. libc++ hat das Ergebnis von lower_bound und upper_bound auf std::map und std::set für Vergleichsfunktionen geändert, die keine strenge schwache Ordnung bilden; wer _LIBCPP_ENABLE_LEGACY_TREE_LOWER_UPPER_BOUND definiert, „kehrt zur historischen Implementierung dieser Operationen zurück”. Und dann: „Dieser Notausstieg wird in einem kommenden Release entfernt (wahrscheinlich im nächsten Release).”5 Eine verwandte Änderung sorgt dafür, dass multimap::find und multiset::find nicht mehr zwingend das erste gleiche Element zurückgeben – ein Verhalten, das laut Apple „vom Standard nie garantiert war”, obwohl libc++ es stets bot, und sie kommt ohne Opt-out.5 Behandeln Sie das Makro als Migrationsticket, nicht als Lösung.

FAQ

Warum steckt -ld_classic in meinem Projekt?

Mit ziemlicher Sicherheit, weil Apples Release Notes zu Xcode 15 es verordnet haben. Zwei Known Issues empfahlen dort, -Wl,-ld_classic zu OTHER_LDFLAGS hinzuzufügen: eines, bei dem Binaries mit schwach definierten Symbolen unter iOS 14 und macOS 12 oder älter zur Laufzeit abstürzten, was laut Apple „vor allem C++-Projekte betrifft”, und eines, bei dem LTO-Objektdateien schwache Symbolimporte als nicht schwach linkten.3 In Xcode 16 hat Apple die Option für veraltet erklärt und in Xcode 27 den Linker dahinter entfernt.14 Wenn das Flag da ist, prüfen Sie zuerst, ob sich der ursprüngliche Fehler überhaupt noch reproduzieren lässt, bevor Sie nach Ersatz suchen.

Wie finde ich doppelte Clang-Modulnamen in meinem Projekt?

Zählen Sie über alle Module Maps hinweg die Deklarationen auf oberster Ebene auf, suchen Sie nach einem Namen, der in zwei Dateien auftaucht, und schließen Sie Build-Verzeichnisse aus dem Ergebnis aus. Drei Dinge machen daraus mehr als ein grep: extern module Foo "path" ist eine Referenz und keine Definition, module Foo.Bar erweitert ein anderswo deklariertes Modul, statt Foo zu deklarieren, und verschachtelte explicit module-Submodule kollidieren nicht mit Namen auf oberster Ebene.6 Schließen Sie .build, build und DerivedData ausdrücklich aus, denn -not -path '*/build/*' verfehlt .build, und sowohl SwiftPM als auch Xcode duplizieren generierte Module Maps routinemäßig.9

Wie sieht der Fehler unter Xcode 27 aus?

Das kann ich nicht sagen, und niemand kann es, der auf Xcode 26 baut. Auf meiner Maschine läuft Xcode 26.6 (Build 17F113), ich berichte daher überhaupt keine Ausgabe von Xcode 27.78 Was Xcode 26.6 bei zwei Module Maps mit demselben Namen ausgibt, ist error: redefinition of module 'Widget' samt note: previously defined here, und zwar bei jedem Lauf einer schlichten Typprüfung.8 Apples Formulierung für Xcode 27 lautet, dass der Scan „möglicherweise einen Fehler meldet” – durchsuchen Sie Logs also nach redefinition of module und nicht nach einer geratenen Zeichenfolge.

Hängt ein Fehler wegen nicht eindeutiger Modulnamen von meinem Deployment Target ab?

Nein. Apple formuliert die Anforderung gegen eine einzelne Swift-Abhängigkeitsscan-Aktion und nennt im Eintrag weder eine OS-Version noch ein SDK oder ein Deployment Target.2 Die Linker-Änderung liest sich genauso, nämlich als Entfernung aus der Toolchain.1 Beides schlägt beim ersten Build unter Xcode 27 zu, gemeinsam mit dem @State-Makro und nicht mit den SDK-getriebenen Anforderungen desselben Zyklus.

Die wichtigsten Erkenntnisse

Für iOS-Entwickler: - Fragen Sie OTHER_LDFLAGS über xcodebuild -showBuildSettings -configuration Release -sdk iphoneos ab, statt nach -ld_classic zu greppen. Ein Fehlen in den aufgelösten Einstellungen heißt „nicht gesetzt”, ein Fehlen in einer Textsuche heißt gar nichts.9 - Durchsuchen Sie Build-Logs nach redefinition of module, der Diagnose, die Xcode 26.6 bereits ausgibt, und nicht nach erfundenem Fehlertext aus Xcode 27.8

Für Teams mit mitgelieferten C- oder C++-Abhängigkeiten: - Prüfen Sie mitgelieferte module.modulemap-Dateien zuerst gegen den Namensraum des SDK: Genau diesen Fall nennt Apple, und er scheitert heute lautlos. Mein mitgelieferter SQLite3-Shim kompilierte mit Exit-Code 0 und ließ sqlite3_open verschwinden.8 - Beschränken Sie das Audit auf den Quellcode und schließen Sie .build, build und DerivedData aus. Jede scheinbare Dublette unter 391 Module Maps war generierte Build-Ausgabe für ein einzelnes Swift-Target.9

Für Release-Verantwortliche: - Behandeln Sie beide Änderungen als Toolchain-getrieben und planen Sie sie für den ersten Xcode-27-Build ein, nicht für die SDK-Migration. Keine von beiden verweist auf ein Deployment Target oder hat einen Laufzeitanteil.12 - Halten Sie Apples Einschränkungen im Ticket fest. Der Scan „meldet möglicherweise einen Fehler”, und 20 Läufe ein und derselben Kollision ergaben drei verschiedene Ausgänge – ein einmal erfolgreicher Build beweist also nichts.28


Der 27er-Zyklus scheitert immer wieder an anderer Stelle: Der Schlüssel für den Launch Screen verhindert eine Einreichung, die Pflicht zum Szenen-Lebenszyklus verhindert einen Start, und das @State-Makro verhindert einen Build auf Quellcode-Ebene. Die Entfernung des Linkers und die Regel für Modulnamen setzen eine Schicht tiefer an, in jenen Teilen der Toolchain, die niemand absichtlich konfiguriert. Der Sammelpunkt der gesamten Reihe ist die Apple-Ecosystem-Serie.

Quellen


  1. Apple, Xcode 27 Release Notes, Abschnitt „Linking”, Deprecations in Xcode 27 Beta (Radar 165165518). Quelle der Entfernung, vollständig zitiert: „Der Linker ld64 wurde entfernt, und die Option -ld_classic wird nicht mehr unterstützt.” Am 26. Juli 2026 gegen JSON der Apple-Dokumentation geprüft, da die HTML-Seite ihre Inhalte über JavaScript rendert. Der Seitentitel lautet zu diesem Datum „Xcode 27 Beta 4 Release Notes”. 

  2. Apple, Xcode 27 Release Notes, Abschnitt „Swift Compiler”, New Features in Xcode 27 Beta (Radar 136303612). Quelle des Eintrags zum Abhängigkeitsscanner, im Fließtext dieses Artikels wörtlich und vollständig zitiert, einschließlich beider Einschränkungen („der Scan meldet möglicherweise einen Fehler” und „Zuvor hat der Scanner doppelte Namen möglicherweise toleriert”) sowie der zwei benannten Fälle. Am 26. Juli 2026 gegen JSON der Apple-Dokumentation geprüft. Festgehalten sei, dass der Eintrag unter New Features steht und nicht unter Deprecations oder Known Issues. 

  3. Apple, Xcode 15 Release Notes, Abschnitt „Linking”. New Features (Radar 108915312) ist die Quelle von „Ein neuer Linker wurde geschrieben, um das statische Linken erheblich zu beschleunigen. Er ist der Standard für alle macOS-, iOS-, tvOS- und visionOS-Binaries sowie für alle, die die Funktion ‚Mergeable Libraries’ nutzen. Der klassische Linker kann weiterhin ausdrücklich über -ld64 angefordert werden und wird in einem künftigen Release entfernt.” Known Issues ist die Quelle beider Workarounds, die das Flag empfehlen: Radar 114813650 (FB13097713), „Binaries, die Symbole mit einer schwachen Definition verwenden, stürzen zur Laufzeit unter iOS 14/macOS 12 oder älter ab. Das betrifft vor allem C++-Projekte, weil sie schwache Symbole ausgiebig nutzen”, mit dem Workaround, das Deployment Target anzuheben „oder -Wl,-ld_classic zur Build-Einstellung OTHER_LDFLAGS hinzuzufügen”; und Radar 115521975 (FB13171424), „Schwache Symbolimporte werden als nicht schwache Importe gelinkt, wenn sie aus LTO-Objektdateien stammen”, mit dem Workaround „Fügen Sie der Build-Einstellung OTHER_LDFLAGS die Optionen -Wl,-weak_reference_mismatches,weak oder -Wl,-ld_classic hinzu.” Am 26. Juli 2026 gegen JSON der Apple-Dokumentation geprüft. 

  4. Apple, Xcode 16 Release Notes, Abschnitt „Linking”, Deprecations (Radar 128502299): „Die Linker-Option -ld_classic ist veraltet und wird in einem künftigen Release entfernt.” Am 26. Juli 2026 gegen JSON der Apple-Dokumentation geprüft. 

  5. Apple, Xcode 27 Release Notes, Abschnitt „C++ Standard Library”, Deprecations in Xcode 27 Beta. Apple druckt für den gesamten Block eine einzige Radar-Nummer, 178191050, nachgestellt hinter dem letzten Punkt statt neben jedem einzelnen. Quelle von „Das minimal unterstützte Deployment Target unter macOS für die C++-Standardbibliothek wurde auf 11.0 angehoben”, der Änderung an multi{map,set}::find („Code, der sich darauf verlässt, dass find das erste Element zurückgibt, wird brechen; stattdessen sollten lower_bound oder equal_range verwendet werden”) sowie des Notausstiegs und seines Ablaufs: „Da sich das in manchen Fällen schwer umgehen lässt, wird in diesem Release ein Notausstieg bereitgestellt: Wer _LIBCPP_ENABLE_LEGACY_TREE_LOWER_UPPER_BOUND definiert, kehrt zur historischen Implementierung dieser Operationen zurück. Dieser Notausstieg wird in einem kommenden Release entfernt (wahrscheinlich im nächsten Release).” Am 26. Juli 2026 gegen JSON der Apple-Dokumentation geprüft. 

  6. Clang-Team, Clang Modules documentation, Module Map Language. Quelle von „Jedes Modul darf nur eine einzige Definition haben”, der Regel zur Modul-ID und der extern module-Deklaration, von „Der Qualifizierer explicit kann nur auf ein Submodul angewendet werden, also auf ein Modul, das in einem anderen Modul verschachtelt ist”, sowie der Auffindung von Module Maps über den Dateinamen module.modulemap, wobei module.map aus Kompatibilitätsgründen mitgesucht wird. Zitiert als Referenz der Toolchain selbst für die Module-Map-Sprache und nicht als Apple-Entwicklerdokument; Apples clang stammt von dieser Implementierung ab. 

  7. Tests des Autors unter macOS 26.5.2 (Build 25F84) mit Xcode 26.6 (Build 17F113), Apple clang 21.0.0, 26. Juli 2026. Befehlsausgaben wörtlich wiedergegeben. xcrun clang hello.c -o hello -Wl,-ld_classic gibt „ld: warning: -ld_classic is deprecated and will be removed in a future release” aus und endet mit 0; ersetzt man das Flag durch -Wl,-ld64, erscheint dieselbe Warnung, die -ld_classic nennt. xcrun ld -v meldet PROJECT:ld-1267 und die oben zitierten Architekturlisten. ld-classic liegt unter /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/ld-classic mit einer Man-Page daneben. Die SDK-Architekturen stammen aus SDKSettings.plist: iPhoneOS 26.5 deklariert arm64e und arm64, WatchOS 26.5 deklariert arm64, arm64e und arm64_32. Die Module-Map-Zahlen des SDK (1.099 Deklarationen auf oberster Ebene über usr/include, 205 über System/Library/Frameworks, in beiden Fällen null Dubletten) stammen aus dem oben veröffentlichten Befehl, angesetzt auf das iPhoneOS-26.5-SDK; die 79 extern module-Deklarationen stehen in usr/include/module.modulemap, und 13 Module Maps im selben Verzeichnis öffnen ein punktiertes module Darwin.* (bank, Darwin_C, Darwin_Mach, Darwin_Mach_machine, Darwin_machine, Darwin_POSIX, Darwin_sys, device, mach_debug, net, netinet, netinet6 und uuid), die eine Auswertung der ersten Komponente mit der echten Darwin-Deklaration in Darwin.modulemap zu einer einzigen Kollision über 14 Dateien zusammenfasst. Das Verhalten unter Xcode 27 wurde nicht getestet, weil Xcode 27 auf der verwendeten Maschine nicht installiert war. 

  8. Reproduktion des Autors auf derselben Maschine und Toolchain, 26. Juli 2026. Zwei Verzeichnisse mit je einer module.modulemap, die module Widget deklariert, mit beiden Verzeichnissen im Suchpfad über swiftc kompiliert. -typecheck erzeugte in 20 von 20 Läufen error: redefinition of module 'Widget' samt note: previously defined here, Exit 1; das Entfernen eines -I-Pfads ließ dieselbe Datei mit Exit 0 kompilieren. -scan-dependencies auf derselben Eingabe ergab über 20 Läufe neunmal Beendigung mit Signal 11 (SIGSEGV), fünfmal mit Signal 6 (SIGABRT) und sechsmal Status 0, wobei die Stacktraces der abgestürzten Läufe durch swift::ModuleDependencyScanner::performParallelClangModuleLookup führten. Separat davon lief eine mitgelieferte Module Map, die SQLite3 deklariert (ein Modul im iPhoneOS-26.5-SDK) und im Suchpfad lag, mit Exit 0 und ohne jede Diagnose durch die Typprüfung, während eine Datei, die unter derselben Konfiguration sqlite3_open des SDK aufruft, mit „error: cannot find ‘sqlite3_open’ in scope” scheiterte und sauber kompilierte, sobald das mitgelieferte Verzeichnis aus dem Suchpfad entfernt war. Zu den Testfällen gehörte ein Pfad mit Leerzeichen, um den veröffentlichten Befehl zu prüfen. Dieser Befehl vergleicht Namen zwischen Dateien; ein Name, der innerhalb einer einzelnen Module Map zweimal deklariert ist, liegt daher konstruktionsbedingt außerhalb seines Geltungsbereichs. Nirgends in diesem Artikel wird eine Ausgabe von Xcode 27 berichtet. 

  9. Audit des Autors an sieben Xcode-Projekten (Reps, Return, Banana List, Ace Citizenship, Water, ResumeGeni und Yawara) unter macOS 26.5.2 mit Xcode 26.6 (Build 17F113), 26. Juli 2026. -ld_classic und ld64 kommen in keinem Arbeitsbaum vor, und OTHER_LDFLAGS ist in jedem Projekt ungesetzt; es gibt keine .xcconfig-Dateien, kein Podfile, kein Carthage und nirgends in der Flotte ein mitgeliefertes .framework oder .xcframework. Die Suche erfasste ausschließlich die aktuellen Arbeitsbäume, nicht die Git-Historie oder archivierte Build-Logs; das Ergebnis lautet also „derzeit nicht vorhanden” und nicht „nie verwendet”. Die für Reps zitierte SUPPORTED_PLATFORMS-Zeile stammt aus xcodebuild -showBuildSettings -configuration Release -sdk iphoneos. Module-Map-Gesamtzahlen: 391 untersucht, 204 unterhalb der sieben Projektverzeichnisse und 187 in den eigenen Bäumen dieser sieben Projekte unterhalb des gemeinsamen ~/Library/Developer/Xcode/DerivedData (das gesamte gemeinsame Verzeichnis enthält weit mehr, das zu anderen Projekten gehört), allesamt Build-Ausgaben, mit null handgeschriebenen oder mitgelieferten Module Maps in allen sieben Repositories. Scheinbare Dubletten wurden per Inhalts-Hash überprüft, und die beiden Erzeuger verhalten sich unterschiedlich. Die 15 Namenspaare von Xcode im geprüften ResumeGeni-Baum (dem projektinternen build/DerivedData), einmal unter GeneratedModuleMaps-iphonesimulator/ und einmal unter den Zwischenprodukten des Targets geschrieben, sind 15-mal von 15 byte-identisch, weil Xcode einen relativen Header-Pfad ausgibt. Die Zahl gilt pro Baum und nicht pro Projekt: Der Baum derselben App unter dem gemeinsamen ~/Library/Developer/Xcode/DerivedData enthält 16, seine Index.noindex-Variante 22. Verallgemeinern lässt sich der Mechanismus, nicht die Zahl. Die Kopien von SwiftPM sind nicht identisch: Die sechs GrappleCore- und drei GrappleRender-Dateien unter Yawaras .build-Verzeichnissen tragen sechs bzw. drei verschiedene Hashes und sind zwischen 171 und 185 Byte groß, weil jede einen absoluten Pfad zur generierten -Swift.h innerhalb ihres eigenen Build-Roots einbettet und ansonsten gleich ist. Kein in der Flotte deklarierter Modulname taucht unter den 1.318 verschiedenen Clang-Modulnamen auf oberster Ebene auf, die der veröffentlichte Befehl findet, wenn man ihn auf das gesamte iPhoneOS-26.5-SDK ansetzt; davon liegen 1.099 unter usr/include und 205 unter System/Library/Frameworks. Die Schnittmenge mit den deklarierten Namen der Flotte ist leer. Derselbe Scan meldet über das gesamte SDK hinweg null Dubletten – die Negativkontrolle, die diese Regel verlangt. CryptoKit fehlt in dieser Liste, weil es als reines Swift-Framework ohne Clang-Module-Map ausgeliefert wird; das Ausbleiben einer Crypto-Kollision beruht also darauf, dass das SDK kein Clang-Modul dieses Namens deklariert, und nicht darauf, dass CryptoKit den Namen belegt. swift-crypto 4.4.0 liefert die einzigen handgeschriebenen Module Maps im aufgelösten Abhängigkeitsgraphen (CCryptoBoringSSL, CCryptoBoringSSLShims, CXKCP, CXKCPShims, je eine Deklaration). Die Host-Tool- und Target-Varianten von SymbolKit liegen im gemeinsamen lokalen Package 941Kit und nicht in den sieben Apps. Die Quellzählungen schließen Python-virtualenvs aus – eine Korrektur, auf die es ankam: Eine naive Zählung schrieb Reps C- und Header-Dateien zu, die sämtlich in .venv und site-packages lagen und von keinem Xcode-Target kompiliert werden. Water hat keinen lokalen Build-Baum, seine Module-Map-Zahl von null spiegelt daher ein nicht gebautes Projekt wider und keinen nachweislich sauberen Build-Graphen. Die drei Fallen für vermeintlich saubere Ergebnisse wurden jeweils direkt bestätigt: timeout fehlt auf einem unveränderten macOS und endet mit 127; zsh expandiert ein nicht quotiertes --include=*.pbxproj und bricht mit „no matches found” ab; und xcrun --sdk iphoneos --show-sdk-path liefert einen Symlink, gegen den find ohne -H null Module Maps meldet, während find -H 266 meldet. 

  10. Apple, Xcode 26 Release Notes. Am 26. Juli 2026 nach ld_classic, ld64 und jedem Eintrag zum klassischen Linker durchsucht; nichts davon taucht auf, sodass zwischen Apples Veralterungshinweis in Xcode 16 und der Entfernung in Xcode 27 keine erneute Erwähnung liegt. 

  11. Apple, Xcode 27 Release Notes, Overview: „Xcode 27 Beta 4 enthält Swift 6.4 sowie SDKs für iOS 27, iPadOS 27, tvOS 27, watchOS 27, macOS 27 und visionOS 27.” Dieselben Notes halten unter Intel Deprecation (Radar 162138432) fest, dass „Xcode 27 sich ausschließlich auf Macs mit Apple Silicon installieren und ausführen lässt”. Geprüft am 26. Juli 2026. 

Verwandte Beiträge

Das @State-Makro: Was Xcode 27 nicht mehr kompiliert

Xcode 27 implementiert SwiftUIs @State als Swift-Makro neu. Der Bruch kommt mit der Toolchain, nicht dem Deployment Targ…

16 Min. Lesezeit

Xcode 27 lässt Intel fallen: Was endet und was weiterhin ausgeliefert wird

Xcode 27 läuft nur auf Apple Silicon, liefert Universal-Apps bis macOS 12 aus und entfernt bei Deployment-Target 27.0 he…

20 Min. Lesezeit