← Wszystkie wpisy

Xcode 27 usuwa ld64 i wymaga unikalnych nazw modułów

Apple wycofało linker jednym zdaniem: „Linker ld64 został usunięty, a opcja -ld_classic nie jest już obsługiwana.”1 Flaga, która właśnie zniknęła, to flaga, której dodanie Apple samo zalecało. W informacjach o wydaniu Xcode 15 -Wl,-ld_classic figurowało jako obejście dwóch błędów linkera.3

TL;DR

  • Xcode 27 usuwa ld64 i przestaje akceptować -ld_classic.1 Notatki Apple do Xcode 15 zalecały tę flagę przy awariach związanych ze słabymi symbolami i przy błędach importu w LTO, Xcode 16 oznaczył ją jako przestarzałą, a Xcode 26 nie powiedział o niej ani słowa.3410
  • Drugie pęknięcie tkwi w kompilatorze Swift. Apple uczyniło skanowanie zależności jedną wspólną operacją, więc „każdy moduł Clang osiągalny z pojedynczej operacji skanowania zależności Swift musi mieć unikalną nazwę.”2 Apple asekuruje się dwukrotnie: skanowanie „może zgłosić błąd”, a wcześniej skaner „mógł tolerować powtarzające się nazwy.”2
  • Oba ujawniają się przy aktualizacji toolchaina, a nie przy zmianie deployment targetu czy wyborze SDK, i żadne nie ma komponentu w środowisku wykonawczym. Narażona populacja: dostarczone binaria, CocoaPods albo duża, mieszana baza kodu Swift/Objective-C/C++.
  • To „może” u Apple nie jest ostrożnością redakcyjną. Na Xcode 26.6 umieściłem na jednej ścieżce wyszukiwania dwie mapy modułów deklarujące tę samą nazwę i uruchomiłem skaner 20 razy: dziewięć przebiegów zakończyło się awarią SIGSEGV, pięć przerwaniem, a sześć powodzeniem.8
  • Dostarczona mapa modułu redeklarująca moduł z SDK — przypadek, który Apple wymienia z nazwy — kompiluje się dziś po cichu i przesłania prawdziwy moduł. Moja podkładka SQLite3 przeszła typecheck z kodem wyjścia 0, a sqlite3_open przestało istnieć.8
  • W siedmiu przeanalizowanych projektach: zero -ld_classic, OTHER_LDFLAGS nigdzie nieustawione i zero zduplikowanych nazw modułów wśród 391 map modułów — bo żadne repozytorium nie zawiera mapy pisanej ręcznie.9

Obie notatki trafiły do informacji o wydaniu Xcode 27 beta 4, obok Swift 6.4 i SDK z cyklu 27.11 Należy je czytać jako tekst bety.

Flaga, którą Apple kazało dodać

Historia zaczyna się od napisania linkera od nowa. Xcode 15 ogłosił nowy linker, uczynił go domyślnym „dla wszystkich binariów macOS, iOS, tvOS i visionOS” i w jednym zdaniu podrzędnym ustalił warunki wycofania poprzednika: „O klasyczny linker nadal można poprosić jawnie za pomocą -ld64; zostanie on usunięty w przyszłym wydaniu.”3

Nowy linker przyszedł z błędami, a Apple udokumentowało wyjście awaryjne dwukrotnie na tej samej stronie. Pierwszy wpis w Known Issues: „Binaria używające symboli ze słabą definicją ulegają awarii w czasie działania na iOS 14/macOS 12 lub starszych. Dotyczy to przede wszystkim projektów C++ ze względu na intensywne wykorzystanie słabych symboli.” Obejściem proponowanym przez Apple było podniesienie deployment targetu „albo dodanie -Wl,-ld_classic do ustawienia kompilacji OTHER_LDFLAGS.”3 Drugi, dotyczący plików obiektowych LTO, które linkowały słabe importy symboli jako niesłabe, oferował w tym samym ustawieniu „-Wl,-weak_reference_mismatches,weak lub -Wl,-ld_classic.”3

Oba błędy najmocniej uderzały w projekty C++ i to tłumaczy, kto nadal nosi tę flagę. Nikt nie wraca do ustawienia, które zatrzymało awarię.

Xcode 16 uprzedził jednym zdaniem: „Opcja linkera -ld_classic jest przestarzała i zostanie usunięta w przyszłym wydaniu.”4 Potem cisza. Przeszukałem informacje o wydaniu Xcode 26 pod kątem ld_classic, ld64 i klasycznego linkera — nie ma tam nic.10

Obecny toolchain wciąż akceptuje flagę i wciąż na nią narzeka. Na Xcode 26.6 zlinkowałem z nią trywialny program w C:7

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

Kod wyjścia zero. Plik binarny się linkuje. Podstawienie -Wl,-ld64 daje identyczne ostrzeżenie z nazwą -ld_classic, więc oba zapisy, których Apple używało, trafiają dziś w tę samą ścieżkę kodu.7 Notatka Apple do Xcode 27 wymienia wyłącznie -ld_classic, a tego, czy -ld64 zawodzi tak samo, nie mogłem sprawdzić.

Jeden szczegół komplikuje słowo „usunięty”. Linker z Xcode 26.6, poproszony o przedstawienie się przez xcrun ld -v, raportuje, które architektury deleguje dalej: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 istnieje w toolchainie jako prawdziwe binarium, z własną stroną man, a linker nadal kieruje do niego osiem architektur.7 Żadna z nich nie przetrwała w aktualnym SDK dla aplikacji: iPhoneOS 26.5 deklaruje wyłącznie arm64e i arm64, a SDK watchOS dokłada arm64_32.7 Ta lista to 32-bitowe ARM-y, i386 oraz wbudowane cele Cortex-M. Odczytanie ich nieobecności w SDK jako powodu, dla którego usunięcie jest bezpieczne dla twórców aplikacji, to mój wniosek, a nie twierdzenie Apple.

Jak znaleźć flagę, nie ufając grepowi

-ld_classic mieszka w OTHER_LDFLAGS, więc zamiast przeszukiwać pliki pod kątem tekstu, warto zapytać system budowania, do czego to ustawienie się rozwija:

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

W projekcie Reps wraca jedna linia:9

    SUPPORTED_PLATFORMS = appletvos appletvsimulator iphoneos iphonesimulator macosx

OTHER_LDFLAGS nie pojawia się wcale i to właśnie jest odpowiedź: ustawienie nie ma żadnej wartości, więc nie trafiają z niego do etapu linkowania żadne flagi. Nieobecność w rozwiniętych ustawieniach kompilacji niesie informację, której nieobecność w wyszukiwaniu tekstowym nie niesie. Pytanie o platformy również warto rozstrzygać przez SUPPORTED_PLATFORMS, nigdy przez *_DEPLOYMENT_TARGET, które Xcode zapisuje niezależnie od tego, czy dany cel jest realny.

W trakcie audytu trzy pułapki dały fałszywie czyste wyniki, a każda z nich nic nie wypisuje i kończy się powodzeniem. timeout nie istnieje w standardowym macOS, więc opakowanie w niego xcodebuild zwraca kod 127 i puste wyjście, które czyta się jak projekt bez żadnych flag linkera. W zsh nieujęte w cudzysłów --include=*.pbxproj zostaje rozwinięte przez globbing, zanim zobaczy je grep, więc polecenie umiera z „no matches found” zamiast zaraportować zero trafień. A find nie podąża za dowiązaniem symbolicznym podanym jako punkt startowy, co ma znaczenie, bo xcrun --sdk iphoneos --show-sdk-path zwraca właśnie dowiązanie: find "$SDK" -name '*.modulemap' nie znajduje nic, podczas gdy find -H "$SDK" znajduje 266 plików.9 Wzorce trzeba ujmować w cudzysłów, przekazywać -H i upewnić się, że polecenie trafia w coś, o czym wiadomo, że tam jest, zanim zaufa się zeru.

Ręcznie utrzymywany target trzyma flagę w project.pbxproj albo w .xcconfig; projekt CocoaPods może ją dostać także z punktu zaczepienia post_install, którego nie odnotowuje żaden plik edytowany przez programistę. Flota, którą audytowałem, nie ma ani jednego, ani drugiego, więc ta ostatnia ścieżka pozostała niesprawdzona.9

Reguła unikalnych nazw modułów i dwa zastrzeżenia Apple

Drugie pęknięcie przychodzi w przebraniu zysku wydajnościowego, wpisane do New Features, a nie do Deprecations. Wpis Apple w sekcji Swift Compiler, w całości:

Skaner zależności Swift został zoptymalizowany tak, by unikać zbędnej pracy konfiguracyjnej i zbędnego wyszukiwania nagłówków przy odnajdywaniu modułów Clang w ramach jednej operacji skanowania zależności, co istotnie poprawia wydajność skanowania. W konsekwencji tej zmiany każdy moduł Clang osiągalny z pojedynczej operacji skanowania zależności Swift musi mieć unikalną nazwę. Jeżeli dwie mapy modułów widoczne dla tego samego skanowania deklarują moduł Clang o tej samej nazwie, skanowanie może zgłosić błąd. Wcześniej skaner mógł tolerować powtarzające się nazwy. Najczęstsze przypadki to projekty lub SDK, które udostępniają tę samą nazwę modułu Clang z więcej niż jednej lokalizacji na ścieżce wyszukiwania nagłówków, oraz dostarczane źródła firm trzecich, które zawierają module.modulemap redeklarujący moduł z SDK.2

Cztery rzeczy warto tu rozdzielić. Apple ogranicza wymóg do tego, co osiągalne w ramach jednego skanowania, a nie do całego dysku. Konsekwencję formułuje jako „może zgłosić błąd” i tak samo asekuruje opis dawnego zachowania. Wymienia z nazwy dwa kształty wyzwalające problem: jedną nazwę modułu udostępnianą z dwóch lokalizacji na ścieżce wyszukiwania nagłówków oraz dostarczony module.modulemap redeklarujący moduł z SDK. Wyzwalaczem jest zaś toolchain, skoro nic w tym wpisie nie odwołuje się do deployment targetu ani do wersji SDK.

Sama reguła jest starsza od optymalizacji. Język map modułów Clanga stawia sprawę wprost: „Każdy moduł musi mieć pojedynczą definicję.”6 Dokumentacja nigdzie nie mówi, co się dzieje po złamaniu tej reguły, a to milczenie okazuje się w pełni zasłużone: odpowiedzią toolchaina nie jest jedno zachowanie, lecz kilka.

Co faktycznie robi kolizja

Zastrzeżenia Apple sprawiły, że chciałem tę awarię zobaczyć, więc zbudowałem jej najmniejszą wersję: dwa katalogi, w każdym module.modulemap deklarujący ten sam moduł.8

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

Gdy oba katalogi znajdą się na ścieżce wyszukiwania, zwykły typecheck zawodzi za każdym razem tak samo, w 20 przebiegach na 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 to ciąg, którego należy szukać w logu kompilacji — clang z Xcode 26.6 już go wypisuje. Usunięcie jednej ścieżki wyszukiwania sprawia, że ta sama kompilacja się udaje, co potwierdza, że wyzwalaczem jest widoczność w obrębie jednej kompilacji, a nie obecność na dysku.8

Skaner zależności zachowuje się inaczej i właśnie ta różnica jest całym powodem, dla którego Apple napisało „może”. Przepuszczenie tych samych dwóch map modułów przez swiftc -scan-dependencies 20 razy dało trzy wyniki: dziewięć awarii z SIGSEGV, pięć przerwań i sześć czystych powodzeń.8 Ślady stosu z przebiegów zakończonych awarią prowadzą przez performParallelClangModuleLookup, co pasuje do wyścigu w równoległym wyszukiwaniu, którego wymianę Apple opisuje. Przypisanie tego niedeterminizmu właśnie takiemu wyścigowi to mój odczyt śladu, a nie twierdzenie Apple.

Drugi przypadek wymieniony przez Apple jest tym cichym. Napisałem mapę modułu taką, jaką dostarcza zależność zewnętrzna — deklarującą SQLite3, prawdziwy moduł w SDK iPhoneOS — umieściłem ją na ścieżce wyszukiwania i skompilowałem względem niej kod:8

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

Kompilacja się powiodła. Kod wyjścia 0, żadnego ostrzeżenia, żadnej noty, żadnej diagnostyki jakiegokolwiek rodzaju. Potem poprosiłem tę samą konfigurację o sqlite3_open:8

error: cannot find 'sqlite3_open' in scope

Bez dostarczonego katalogu na ścieżce wyszukiwania identyczny plik kompiluje się bez problemu. Dostarczona mapa modułu całkowicie przesłoniła SQLite3 z SDK, a toolchain nie powiedział ani słowa. Dziś zatem przypadek redeklaracji w dostarczonych źródłach nie zawodzi głośno — zawodzi jako brakujące API w module, który rzekomo został zaimportowany. Notatka Apple mówi, że skanowanie w Xcode 27 „może zgłosić błąd”, co zamieniłoby ciche przesłonięcie w błąd kompilacji.

Buduję na Xcode 26.6 (build 17F113), więc każda powyższa diagnostyka pochodzi z poprzedniego toolchaina.78 Nie mogę podać treści błędu z Xcode 27 i żadnej nie wymyśliłem. Maszyneria diagnostyczna, ciąg do wyszukania i tolerancja, której wycofanie Apple opisuje, istnieją już dziś.

Audyt zduplikowanych nazw modułów

Wyliczenie zadeklarowanych nazw modułów wygląda na jednolinijkowego grepa, ale dwie konstrukcje języka map modułów dają błędne odpowiedzi.

extern module Foo "Foo.modulemap" to odwołanie zapowiadające, a nie definicja, a SDK Apple używa 79 takich odwołań w jednej mapie modułów.7 Naiwne skanowanie liczy odwołanie i prawdziwą definicję jako dwie deklaracje Foo. Po drugie, module Darwin.C { ... } używa kropkowanego identyfikatora modułu, żeby rozszerzyć moduł zadeklarowany gdzie indziej; 13 map modułów z SDK otwiera module Darwin.something, a odczytanie pierwszego członu jako deklaracji najwyższego poziomu wrzuca całą trzynastkę do jednego worka z prawdziwą deklaracją Darwin jako kolizję obejmującą 14 plików.7 Oba fałszywe alarmy pojawiły się w moim pierwszym podejściu.

Wersja, która przetrwała, radzi sobie z komentarzami, z głębokością nawiasów klamrowych (dzięki czemu zagnieżdżone podmoduły explicit module zostają poza wynikiem) oraz ze ścieżkami zawierającymi spacje:

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

Najmocniejszą dostępną kontrolą negatywną jest własny SDK Apple, który musi spełniać tę regułę, żeby toolchain w ogóle działał. Skierowane na SDK iPhoneOS 26.5 polecenie znajduje 1099 deklaracji najwyższego poziomu w usr/include i 205 w mapach modułów frameworków — w obu przypadkach zero duplikatów.7 Skierowane na dane testowe zbudowane tak, by kolidowały, wskazuje duplikat i oba pliki.8

Wykluczenia mają swoją wagę. -not -path '*/build/*' nie wyklucza .build/, bo wzorzec potrzebuje dosłownej nazwy katalogu, a SwiftPM zapisuje wygenerowane mapy modułów do .build tuzinami. Pozostawienie .build dało jedyne „duplikaty” w siedmiu projektach: GrappleCore w sześciu plikach i GrappleRender w trzech, wszystkie będące wyjściem SwiftPM, obejmujące dwa targety Swift w różnych katalogach kompilacji.9 Ta dziewiątka nie jest nawet identyczna bajt po bajcie, a powód jest pouczający: każdy plik opakowuje tę samą nazwę modułu wokół bezwzględnej ścieżki do wygenerowanego nagłówka we własnym katalogu kompilacji, więc różnią się jednym ciągiem znaków i niczym, co ma znaczenie. Audyt, który nazywa je kolizjami, wyczerpuje swoją wiarygodność, zanim dotrze do czegokolwiek prawdziwego.

Xcode produkuje ten sam fałszywy alarm w sposób jeszcze mniej dwuznaczny. W obrębie jednego drzewa DerivedData zapisuje wygenerowaną mapę modułu każdego pakietu Swift dwukrotnie — do GeneratedModuleMaps-iphonesimulator/ oraz do plików pośrednich samego targetu — za każdym razem ze względną ścieżką nagłówka. W drzewie jednego projektu 15 nazw pojawia się po dwa razy, a wszystkie 15 par jest identycznych bajt po bajcie.9 Audyt należy zawęzić do źródeł, nie do wyjścia kompilacji.

Co zawiera siedem projektów

Oba audyty przepuściłem przez siedem projektów Xcode na Xcode 26.6. Uczciwym wynikiem jest czyste zero.9

Projekt Pliki Swift Objective-C / C++ / C Zależności -ld_classic Mapy modułów w źródłach
Reps 77 0 2 lokalne SPM 0 0
Return 57 0 brak 0 0
Banana List 55 0 brak 0 0
Ace Citizenship 26 0 brak zewnętrznych 0 0
Water 34 0 (2 .metal) brak 0 0
ResumeGeni 71 0 3 zdalne SPM, 9 przypięć 0 0
Yawara 143 0 2 lokalne SPM 0 0
Razem 463 0 tylko SPM 0 0

OTHER_LDFLAGS jest nieustawione we wszystkich siedmiu projektach, w całej flocie nie ma nigdzie pliku .xcconfig, nie ma też CocoaPods, Carthage ani dostarczonych .framework czy .xcframework.9 Audyt nazw modułów objął 391 map modułów — 204 w katalogach projektów i 187 we współdzielonym DerivedData, gdzie lądują kompilacje z GUI Xcode — i każda z nich jest wyjściem kompilacji.9 Ani jedno repozytorium nie zawiera ręcznie napisanej mapy modułu.

Oba zera mają tę samą przyczynę i to właśnie ten wniosek warto zabrać ze sobą: narażoną populacją są projekty mieszające języki programowania, a te takie nie są. W 463 plikach Swift flota nie ma ani jednego źródła Objective-C, C++ czy C — jedynym skompilowanym kodem spoza Swifta są dwa shadery Metal w projekcie Water.9 Zerowy wynik z bazy kodu, która nigdy nie była zagrożona, jest słabym dowodem na temat samych zagrożeń, a mocnym na temat tego, kto powinien przeprowadzić audyt.

Dwa szczegóły znaczą więcej niż same zera. Jedyne ręcznie pisane mapy modułów w rozwiązanych pakietach należą do swift-crypto, które udostępnia cztery podkładki w C o nazwach CCryptoBoringSSL, CCryptoBoringSSLShims, CXKCP i CXKCPShims — wszystkie różne.9 Żadna nazwa deklarowana przez flotę nie pojawia się też nigdzie w przestrzeni nazw modułów Clang z SDK, co sprawdzono wobec wszystkich 1318 nazw najwyższego poziomu, jakie polecenie znajduje w SDK iPhoneOS 26.5.9 Nie trafiają nawet te generyczne, przychodzące przez Supabase: SDK deklaruje moduły Clang o nazwach Foundation, UIKit i SQLite3, a żadnego o nazwie Crypto, Storage czy Auth.

Próg deployment targetu dla C++, który przesuwa się pod spodem

Jedna sąsiadująca zmiana uderza w te same projekty C++, które trzymają -ld_classic. Apple podniosło próg i wymieniło przy tym tylko jedną platformę — macOS: „Minimalny obsługiwany deployment target na macOS dla biblioteki standardowej C++ został podniesiony do 11.0.”5

Ten sam wpis oferuje wyjście awaryjne i w następnym zdaniu datuje jego usunięcie. libc++ zmieniło wynik lower_bound i upper_bound na std::map oraz std::set dla komparatorów niebędących ścisłym porządkiem słabym, a zdefiniowanie _LIBCPP_ENABLE_LEGACY_TREE_LOWER_UPPER_BOUND „przywróci historyczną implementację tych operacji”. A dalej: „To wyjście awaryjne zostanie usunięte w jednym z nadchodzących wydań (prawdopodobnie w kolejnym).”5 Powiązana zmiana sprawia, że multimap::find i multiset::find nie muszą już zwracać pierwszego równego elementu — zachowanie, o którym Apple pisze, że „nigdy nie było gwarantowane przez Standard”, choć libc++ zawsze je zapewniało — i wchodzi w życie bez możliwości rezygnacji.5 Makro warto traktować jak zgłoszenie migracyjne, a nie jak poprawkę.

FAQ

Dlaczego mój projekt zawiera -ld_classic?

Niemal na pewno dlatego, że zalecały ją informacje o wydaniu Xcode 15 od Apple. Dwa wpisy Known Issues zalecały tam dodanie -Wl,-ld_classic do OTHER_LDFLAGS: jeden dotyczył binariów używających słabo zdefiniowanych symboli, które ulegały awarii w czasie działania na iOS 14 i macOS 12 lub starszych — co, jak zaznaczyło Apple, „dotyczy przede wszystkim projektów C++” — a drugi plików obiektowych LTO linkujących słabe importy symboli jako niesłabe.3 Apple oznaczyło opcję jako przestarzałą w Xcode 16, a w Xcode 27 usunęło stojący za nią linker.14 Jeśli flaga tam jest, przed szukaniem zamiennika warto potwierdzić, że pierwotny błąd nadal się odtwarza.

Jak znaleźć zduplikowane nazwy modułów Clang w projekcie?

Należy wyliczyć deklaracje modułów najwyższego poziomu we wszystkich mapach modułów, poszukać nazwy występującej w dwóch plikach i usunąć z wyników katalogi kompilacji. Trzy rzeczy sprawiają, że to więcej niż grep: extern module Foo "path" jest odwołaniem, a nie definicją; module Foo.Bar rozszerza moduł zadeklarowany gdzie indziej, zamiast deklarować Foo; a zagnieżdżone podmoduły explicit module nie kolidują z nazwami najwyższego poziomu.6 .build, build i DerivedData trzeba wykluczyć jawnie, bo -not -path '*/build/*' nie łapie .build, a zarówno SwiftPM, jak i Xcode rutynowo duplikują generowane mapy modułów.9

Jak wygląda ten błąd w Xcode 27?

Nie potrafię powiedzieć i nie potrafi tego nikt, kto buduje na Xcode 26. Na moim komputerze działa Xcode 26.6 (build 17F113), więc nie raportuję żadnego wyjścia z Xcode 27.78 Dla dwóch map modułów deklarujących jedną nazwę Xcode 26.6 wypisuje error: redefinition of module 'Widget' wraz z note: previously defined here, i to przy każdym przebiegu zwykłego typechecku.8 Apple pisze o Xcode 27, że skanowanie „może zgłosić błąd”, więc w logach warto szukać redefinition of module, a nie ciągu, który ktoś zgadł.

Czy awaria związana z unikalnością nazw modułów zależy od deployment targetu?

Nie. Apple formułuje ten wymóg wobec pojedynczej operacji skanowania zależności Swift i nie wymienia we wpisie żadnej wersji systemu, żadnego SDK ani deployment targetu.2 Zmiana w linkerze czyta się tak samo — jako usunięcie z toolchaina.1 Oba trafiają w pierwszą kompilację w Xcode 27, razem z makrem @State, a nie razem z wymogami z tego samego cyklu wyzwalanymi przez SDK.

Najważniejsze wnioski

Dla twórców aplikacji na iOS: - Zamiast szukać -ld_classic grepem, warto odpytać OTHER_LDFLAGS przez xcodebuild -showBuildSettings -configuration Release -sdk iphoneos. Nieobecność w rozwiniętych ustawieniach oznacza, że ustawienie nie ma żadnej wartości; nieobecność w wyszukiwaniu tekstowym nie oznacza nic.9 - W logach kompilacji należy szukać redefinition of module — diagnostyki, którą Xcode 26.6 już wypisuje — a nie zmyślonej treści błędu z Xcode 27.8

Dla zespołów z dostarczanymi zależnościami C lub C++: - Dostarczone pliki module.modulemap warto najpierw sprawdzić wobec przestrzeni nazw SDK: Apple wymienia ten przypadek z nazwy, a dziś zawodzi on po cichu. Moja dostarczona podkładka SQLite3 skompilowała się z kodem wyjścia 0 i sprawiła, że sqlite3_open zniknęło.8 - Audyt należy zawęzić do źródeł i wykluczyć .build, build oraz DerivedData. Każdy pozorny duplikat wśród 391 map modułów okazał się wygenerowanym wyjściem kompilacji dla pojedynczego targetu Swift.9

Dla osób odpowiedzialnych za wydania: - Obie zmiany warto traktować jako wyzwalane przez toolchain i zaplanować na pierwszą kompilację w Xcode 27, a nie na migrację SDK. Żadna nie odwołuje się do deployment targetu ani nie ma komponentu w środowisku wykonawczym.12 - Zastrzeżenia Apple warto zachować w zgłoszeniu. Skanowanie „może zgłosić błąd”, a 20 przebiegów jednej kolizji dało trzy różne wyniki — jedna udana kompilacja nie dowodzi więc niczego.28


Cykl 27 zawodzi wciąż w innych miejscach: klucz ekranu startowego zatrzymuje wysyłkę do sklepu, obowiązkowy cykl życia sceny zatrzymuje uruchomienie aplikacji, a makro @State zatrzymuje kompilację na poziomie kodu źródłowego. Usunięcie linkera i reguła unikalnych nazw modułów zatrzymują ją warstwę niżej — w tych częściach toolchaina, których nikt nie konfiguruje celowo. Centrum całej serii to Seria Ekosystem Apple.

Źródła


  1. Apple, Xcode 27 Release Notes, sekcja Linking, Deprecations in Xcode 27 Beta (radar 165165518). Źródło informacji o usunięciu, cytowane w całości: „Linker ld64 został usunięty, a opcja -ld_classic nie jest już obsługiwana.” Zweryfikowane wobec JSON dokumentacji Apple 26 lipca 2026, ponieważ strona HTML renderuje swoją treść przez JavaScript. Tytuł strony w tym dniu brzmi „Xcode 27 Beta 4 Release Notes”. 

  2. Apple, Xcode 27 Release Notes, sekcja Swift Compiler, New Features in Xcode 27 Beta (radar 136303612). Źródło wpisu o skanerze zależności, cytowanego dosłownie i w całości w treści tego artykułu, wraz z oboma zastrzeżeniami („skanowanie może zgłosić błąd” oraz „Wcześniej skaner mógł tolerować powtarzające się nazwy”) i dwoma wymienionymi przypadkami. Zweryfikowane wobec JSON dokumentacji Apple 26 lipca 2026. Odnotowane jako wpisane do New Features, a nie do Deprecations czy Known Issues. 

  3. Apple, Xcode 15 Release Notes, sekcja Linking. New Features (radar 108915312) jest źródłem zdania: „Napisano nowy linker, który istotnie przyspiesza statyczne linkowanie. Jest domyślny dla wszystkich binariów macOS, iOS, tvOS i visionOS oraz dla każdego, kto korzysta z funkcji »Mergeable Libraries«. O klasyczny linker nadal można poprosić jawnie za pomocą -ld64; zostanie on usunięty w przyszłym wydaniu.” Known Issues jest źródłem obu obejść zalecających tę flagę: radar 114813650 (FB13097713), „Binaria używające symboli ze słabą definicją ulegają awarii w czasie działania na iOS 14/macOS 12 lub starszych. Dotyczy to przede wszystkim projektów C++ ze względu na intensywne wykorzystanie słabych symboli”, z obejściem polegającym na podniesieniu deployment targetu „albo dodaniu -Wl,-ld_classic do ustawienia kompilacji OTHER_LDFLAGS”; oraz radar 115521975 (FB13171424), „Słabe importy symboli są linkowane jako importy niesłabe, gdy pochodzą z plików obiektowych LTO”, z obejściem „Dodaj opcje -Wl,-weak_reference_mismatches,weak lub -Wl,-ld_classic do ustawienia kompilacji OTHER_LDFLAGS.” Zweryfikowane wobec JSON dokumentacji Apple 26 lipca 2026. 

  4. Apple, Xcode 16 Release Notes, sekcja Linking, Deprecations (radar 128502299): „Opcja linkera -ld_classic jest przestarzała i zostanie usunięta w przyszłym wydaniu.” Zweryfikowane wobec JSON dokumentacji Apple 26 lipca 2026. 

  5. Apple, Xcode 27 Release Notes, sekcja C++ Standard Library, Deprecations in Xcode 27 Beta. Apple podaje jeden radar dla całego bloku, 178191050, umieszczony za jego ostatnim punktem, a nie przy każdym z osobna. Źródło zdania „Minimalny obsługiwany deployment target na macOS dla biblioteki standardowej C++ został podniesiony do 11.0”, zmiany w multi{map,set}::find („kod polegający na tym, że find zwraca pierwszy element, przestanie działać; zamiast tego należy użyć lower_bound lub equal_range”) oraz wyjścia awaryjnego i terminu jego wygaśnięcia: „Ponieważ w niektórych przypadkach obejście tego może być trudne, w tym wydaniu udostępniono wyjście awaryjne: zdefiniowanie _LIBCPP_ENABLE_LEGACY_TREE_LOWER_UPPER_BOUND przywróci historyczną implementację tych operacji. To wyjście awaryjne zostanie usunięte w jednym z nadchodzących wydań (prawdopodobnie w kolejnym).” Zweryfikowane wobec JSON dokumentacji Apple 26 lipca 2026. 

  6. Zespół Clang, dokumentacja Clang Modules, Module Map Language. Źródło zdania „Każdy moduł musi mieć pojedynczą definicję”, reguły dotyczącej identyfikatora modułu i deklaracji extern module, zdania „Kwalifikator explicit można zastosować wyłącznie do podmodułu, czyli modułu zagnieżdżonego w innym module”, a także zasady odnajdywania map modułów po nazwie pliku module.modulemap, przy czym module.map jest wyszukiwany ze względu na zgodność wsteczną. Cytowane jako własna dokumentacja toolchaina dla języka map modułów, a nie jako dokument dla deweloperów Apple; clang od Apple wywodzi się z tej implementacji. 

  7. Testy autora na macOS 26.5.2 (build 25F84) z Xcode 26.6 (build 17F113), Apple clang 21.0.0, 26 lipca 2026. Wyjście poleceń odtworzone dosłownie. xcrun clang hello.c -o hello -Wl,-ld_classic wypisuje „ld: warning: -ld_classic is deprecated and will be removed in a future release” i kończy się kodem 0; podstawienie -Wl,-ld64 wypisuje identyczne ostrzeżenie z nazwą -ld_classic. xcrun ld -v raportuje PROJECT:ld-1267 oraz cytowane wyżej listy architektur. ld-classic jest obecny w /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/ld-classic, a obok niego leży strona man. Architektury SDK pochodzą z SDKSettings.plist: iPhoneOS 26.5 deklaruje arm64e i arm64, WatchOS 26.5 deklaruje arm64, arm64e i arm64_32. Liczby map modułów w SDK (1099 deklaracji najwyższego poziomu w usr/include, 205 w System/Library/Frameworks, w obu przypadkach zero duplikatów) pochodzą z uruchomienia opublikowanego wyżej polecenia na SDK iPhoneOS 26.5; 79 deklaracji extern module znajduje się w usr/include/module.modulemap, a 13 map modułów w tym samym katalogu otwiera kropkowany module Darwin.* (bank, Darwin_C, Darwin_Mach, Darwin_Mach_machine, Darwin_machine, Darwin_POSIX, Darwin_sys, device, mach_debug, net, netinet, netinet6 oraz uuid), które odczyt po pierwszym członie grupuje z prawdziwą deklaracją Darwin w Darwin.modulemap jako jedną kolizję obejmującą 14 plików. Zachowania pod Xcode 27 nie testowano, ponieważ Xcode 27 nie był zainstalowany na użytej maszynie. 

  8. Reprodukcja autora na tej samej maszynie i tym samym toolchainie, 26 lipca 2026. Dwa katalogi, z których każdy zawiera module.modulemap deklarujący module Widget, skompilowane przez swiftc z oboma katalogami na ścieżce wyszukiwania. -typecheck dawało error: redefinition of module 'Widget' wraz z note: previously defined here w 20 przebiegach na 20, kod wyjścia 1; usunięcie jednej ścieżki -I sprawiało, że ten sam plik kompilował się z kodem wyjścia 0. -scan-dependencies na tym samym wejściu w 20 przebiegach dało dziewięć zakończeń sygnałem 11 (SIGSEGV), pięć sygnałem 6 (SIGABRT) i sześć zakończeń ze statusem 0, przy czym ślady stosu z przebiegów zakończonych awarią prowadziły przez swift::ModuleDependencyScanner::performParallelClangModuleLookup. Osobno: dostarczona mapa modułu deklarująca SQLite3 (moduł obecny w SDK iPhoneOS 26.5), umieszczona na ścieżce wyszukiwania, przeszła typecheck z kodem wyjścia 0 i bez żadnej diagnostyki, podczas gdy plik wywołujący sqlite3_open z SDK przy tej samej konfiguracji zawiódł z komunikatem „error: cannot find ‘sqlite3_open’ in scope” i skompilował się czysto, gdy tylko dostarczony katalog usunięto ze ścieżki wyszukiwania. Wśród danych testowych znalazła się ścieżka ze spacją, aby zweryfikować opublikowane polecenie. Polecenie to porównuje nazwy pomiędzy plikami, więc nazwa zadeklarowana dwa razy w obrębie jednej mapy modułu z założenia leży poza jego zakresem. Nigdzie w tym artykule nie raportuję wyjścia z Xcode 27. 

  9. Audyt autora obejmujący siedem projektów Xcode (Reps, Return, Banana List, Ace Citizenship, Water, ResumeGeni i Yawara) na macOS 26.5.2 z Xcode 26.6 (build 17F113), 26 lipca 2026. -ld_classic i ld64 nie występują w żadnym drzewie roboczym ani razu, a OTHER_LDFLAGS jest nieustawione w każdym projekcie; nie ma plików .xcconfig, nie ma Podfile, nie ma Carthage ani dostarczonych .framework czy .xcframework nigdzie we flocie. Wyszukiwanie objęło wyłącznie bieżące drzewa robocze, nie historię git ani zarchiwizowane logi kompilacji, więc wynik brzmi „nieobecne teraz”, a nie „nigdy nieużywane”. Cytowana dla projektu Reps linia SUPPORTED_PLATFORMS pochodzi z xcodebuild -showBuildSettings -configuration Release -sdk iphoneos. Sumy map modułów: przebadano 391, z czego 204 w katalogach siedmiu projektów i 187 w drzewach tych siedmiu projektów we współdzielonym ~/Library/Developer/Xcode/DerivedData (cały współdzielony katalog zawiera znacznie więcej, należących do innych projektów); wszystkie są wyjściem kompilacji, a w żadnym z siedmiu repozytoriów nie ma map modułów pisanych ręcznie ani dostarczonych. Pozorne duplikaty sprawdzono skrótami zawartości i okazało się, że oba generatory zachowują się inaczej. Piętnaście par nazw wygenerowanych przez Xcode w audytowanym drzewie ResumeGeni (wewnątrzprojektowy build/DerivedData), zapisanych raz w GeneratedModuleMaps-iphonesimulator/ i raz w plikach pośrednich targetu, jest identycznych bajt po bajcie 15 razy na 15, ponieważ Xcode zapisuje względną ścieżkę nagłówka. Liczba dotyczy drzewa, nie projektu: drzewo tej samej aplikacji we współdzielonym ~/Library/Developer/Xcode/DerivedData zawiera 16 par, a jego wariant Index.noindex — 22. Uogólnia się tu mechanizm, nie liczba. Kopie SwiftPM identyczne nie są: sześć plików GrappleCore i trzy GrappleRender w katalogach .build projektu Yawara mają odpowiednio sześć i trzy różne skróty oraz rozmiary od 171 do 185 bajtów, ponieważ każdy osadza bezwzględną ścieżkę do wygenerowanego -Swift.h we własnym katalogu kompilacji, a poza tym jest identyczny. Żadna nazwa modułu deklarowana w całej flocie nie występuje wśród 1318 odrębnych nazw modułów Clang najwyższego poziomu, jakie opublikowane polecenie znajduje po skierowaniu go na cały SDK iPhoneOS 26.5, z czego 1099 leży w usr/include, a 205 w System/Library/Frameworks; przecięcie z nazwami deklarowanymi przez flotę jest puste. To samo skanowanie raportuje zero duplikatów w całym SDK, co jest kontrolą negatywną, jakiej ta reguła wymaga. CryptoKit nie występuje na tej liście, ponieważ jest dostarczany jako framework wyłącznie Swiftowy, bez mapy modułu Clang, więc brak kolizji z Crypto wynika z tego, że SDK nie deklaruje modułu Clang o takiej nazwie, a nie z tego, że nazwę tę zajmuje CryptoKit. swift-crypto 4.4.0 dostarcza jedyne ręcznie pisane mapy modułów w rozwiązanym grafie zależności (CCryptoBoringSSL, CCryptoBoringSSLShims, CXKCP, CXKCPShims, po jednej deklaracji każda). Warianty SymbolKit — narzędziowy i targetowy — znajdują się we współdzielonym pakiecie lokalnym 941Kit, a nie w siedmiu aplikacjach. Liczniki plików źródłowych pomijają virtualenvy Python, i ta korekta miała znaczenie: naiwne zliczanie przypisało projektowi Reps pliki C i nagłówki, które w całości okazały się leżeć w .venv i site-packages i nie są kompilowane przez żaden target Xcode. Water nie ma lokalnego drzewa kompilacji, więc jego zerowa liczba map modułów odzwierciedla projekt niezbudowany, a nie zweryfikowany jako czysty graf kompilacji. Każdą z trzech pułapek fałszywej czystości potwierdzono bezpośrednio: timeout nie występuje w standardowym macOS i kończy się kodem 127; zsh rozwija nieujęte w cudzysłów --include=*.pbxproj i przerywa z „no matches found”; a xcrun --sdk iphoneos --show-sdk-path zwraca dowiązanie symboliczne, wobec którego find bez -H raportuje zero map modułów, podczas gdy find -H raportuje ich 266. 

  10. Apple, Xcode 26 Release Notes. Przeszukane 26 lipca 2026 pod kątem ld_classic, ld64 oraz jakiegokolwiek wpisu opisującego klasyczny linker; nie występuje żaden, więc pomiędzy zapowiedzią wycofania w Xcode 16 a usunięciem w Xcode 27 nie ma żadnego powtórzenia komunikatu. 

  11. Apple, Xcode 27 Release Notes, Overview: „Xcode 27 beta 4 zawiera Swift 6.4 oraz SDK dla iOS 27, iPadOS 27, tvOS 27, watchOS 27, macOS 27 i visionOS 27.” Te same notatki odnotowują w sekcji Intel Deprecation (radar 162138432), że „Xcode 27 będzie się instalował i uruchamiał wyłącznie na Macach z układami Apple silicon”. Sprawdzone 26 lipca 2026. 

Powiązane artykuły

Makro @State: co przestaje się kompilować w Xcode 27

Xcode 27 przepisuje @State ze SwiftUI na makro Swift. Łamie to zgodność przy zmianie toolchainu, nie deployment target; …

16 min czytania

Xcode 27 porzuca Intela: co się kończy, a co nadal da się wydać

Xcode 27 wymaga Apple silicon, nadal wydaje aplikacje Universal do macOS 12 i po cichu usuwa x86_64 z ARCHS_STANDARD prz…

20 min czytania