Skip to content

Commit ac90945

Browse files
authored
Clean before building when framework headers change (#177512)
Starting with Xcode 26, when a precompiled file changes (like a header file in the Flutter framework), it throws an error. This PR adds a fingerprinter to track changes to the headers. If the fingerprinting detects a change, it cleans before building. This only applies to Xcode 26 and incremental builds. Fresh builds should not clean. Fixes flutter/flutter#176462. ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. **Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). Comments from the `gemini-code-assist` bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed. <!-- Links --> [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
1 parent f25a04f commit ac90945

File tree

5 files changed

+497
-11
lines changed

5 files changed

+497
-11
lines changed

packages/flutter_tools/lib/src/ios/mac.dart

Lines changed: 82 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,13 @@ import 'package:unified_analytics/unified_analytics.dart';
1010

1111
import '../artifacts.dart';
1212
import '../base/file_system.dart';
13+
import '../base/fingerprint.dart';
1314
import '../base/io.dart';
1415
import '../base/logger.dart';
1516
import '../base/process.dart';
1617
import '../base/project_migrator.dart';
1718
import '../base/utils.dart';
19+
import '../base/version.dart';
1820
import '../build_info.dart';
1921
import '../cache.dart';
2022
import '../darwin/darwin.dart';
@@ -270,6 +272,16 @@ Future<XcodeBuildResult> buildXcodeProject({
270272
'file version field before submitting to the App Store.',
271273
);
272274
}
275+
final XcodeSdk sdk = environmentType == EnvironmentType.physical
276+
? XcodeSdk.IPhoneOS
277+
: XcodeSdk.IPhoneSimulator;
278+
final String buildDirectoryPath = getIosBuildDirectory();
279+
final Directory buildDirectory = globals.fs.directory(buildDirectoryPath);
280+
final bool incrementalBuild =
281+
buildDirectory.existsSync() &&
282+
buildDirectory.listSync().any(
283+
(FileSystemEntity entity) => entity.basename.contains(sdk.platformName),
284+
);
273285

274286
Map<String, String>? autoSigningConfigs;
275287

@@ -310,7 +322,7 @@ Future<XcodeBuildResult> buildXcodeProject({
310322
);
311323
}
312324
}
313-
await processPodsIfNeeded(project.ios, getIosBuildDirectory(), buildInfo.mode);
325+
await processPodsIfNeeded(project.ios, buildDirectoryPath, buildInfo.mode);
314326
if (configOnly) {
315327
return XcodeBuildResult(success: true);
316328
}
@@ -322,6 +334,26 @@ Future<XcodeBuildResult> buildXcodeProject({
322334
configuration,
323335
];
324336

337+
// Check the public headers before checking Xcode version so headers fingerprinter is created
338+
// regardless of Xcode version.
339+
final bool headersChanged = publicHeadersChanged(
340+
environmentType: environmentType,
341+
mode: buildInfo.mode,
342+
buildDirectory: buildDirectoryPath,
343+
artifacts: globals.artifacts,
344+
fileSystem: globals.fs,
345+
logger: globals.logger,
346+
);
347+
final Version? xcodeVersion = globals.xcode?.currentVersion;
348+
if (headersChanged &&
349+
incrementalBuild &&
350+
(xcodeVersion != null && xcodeVersion >= Version(26, 0, 0))) {
351+
// Xcode 26 changed the way headers are pre-compiled and will throw an error if the headers
352+
// have changed since the last time they were compiled. To avoid this error, clean before
353+
// building if headers have changed.
354+
buildCommands.addAll(<String>['clean', 'build']);
355+
}
356+
325357
if (globals.logger.isVerbose) {
326358
// An environment variable to be passed to xcode_backend.sh determining
327359
// whether to echo back executed commands.
@@ -351,7 +383,7 @@ Future<XcodeBuildResult> buildXcodeProject({
351383
scheme,
352384
if (buildAction !=
353385
XcodeBuildAction.archive) // dSYM files aren't copied to the archive if BUILD_DIR is set.
354-
'BUILD_DIR=${globals.fs.path.absolute(getIosBuildDirectory())}',
386+
'BUILD_DIR=${globals.fs.path.absolute(buildDirectoryPath)}',
355387
]);
356388
}
357389

@@ -373,20 +405,14 @@ Future<XcodeBuildResult> buildXcodeProject({
373405
return XcodeBuildResult(success: false);
374406
}
375407
} else {
376-
if (environmentType == EnvironmentType.physical) {
377-
buildCommands.addAll(<String>['-sdk', XcodeSdk.IPhoneOS.platformName]);
378-
} else {
379-
buildCommands.addAll(<String>['-sdk', XcodeSdk.IPhoneSimulator.platformName]);
380-
}
408+
buildCommands.addAll(<String>['-sdk', sdk.platformName]);
381409
}
382410

383411
buildCommands.add('-destination');
384412
if (deviceID != null) {
385413
buildCommands.add('id=$deviceID');
386-
} else if (environmentType == EnvironmentType.physical) {
387-
buildCommands.add(XcodeSdk.IPhoneOS.genericPlatform);
388414
} else {
389-
buildCommands.add(XcodeSdk.IPhoneSimulator.genericPlatform);
415+
buildCommands.add(sdk.genericPlatform);
390416
}
391417

392418
if (activeArch != null) {
@@ -628,6 +654,52 @@ Future<XcodeBuildResult> buildXcodeProject({
628654
}
629655
}
630656

657+
/// Check if the Flutter framework's public headers have changed since last built.
658+
bool publicHeadersChanged({
659+
required BuildMode mode,
660+
required EnvironmentType environmentType,
661+
required String buildDirectory,
662+
required Artifacts? artifacts,
663+
required FileSystem fileSystem,
664+
required Logger logger,
665+
}) {
666+
final String? basePath = artifacts?.getArtifactPath(
667+
Artifact.flutterFramework,
668+
platform: TargetPlatform.ios,
669+
mode: mode,
670+
environmentType: environmentType,
671+
);
672+
if (basePath == null) {
673+
return false;
674+
}
675+
final Directory headersDirectory = fileSystem.directory(
676+
fileSystem.path.join(basePath, 'Headers'),
677+
);
678+
if (!headersDirectory.existsSync()) {
679+
return false;
680+
}
681+
final List<String> files = headersDirectory
682+
.listSync()
683+
.map<String>((FileSystemEntity header) => header.path)
684+
.toList();
685+
686+
final String fingerprintPath = fileSystem.path.join(
687+
buildDirectory,
688+
'framework_public_headers.fingerprint',
689+
);
690+
final fingerprinter = Fingerprinter(
691+
fingerprintPath: fingerprintPath,
692+
paths: files,
693+
fileSystem: fileSystem,
694+
logger: logger,
695+
);
696+
final bool headersChanged = !fingerprinter.doesFingerprintMatch();
697+
if (headersChanged) {
698+
fingerprinter.writeFingerprint();
699+
}
700+
return headersChanged;
701+
}
702+
631703
/// Extended attributes applied by Finder can cause code signing errors. Remove them.
632704
/// https://developer.apple.com/library/archive/qa/qa1940/_index.html
633705
Future<void> removeFinderExtendedAttributes(

packages/flutter_tools/test/commands.shard/hermetic/build_ios_test.dart

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import 'package:args/command_runner.dart';
66
import 'package:file/memory.dart';
77
import 'package:flutter_tools/src/android/android_sdk.dart';
8+
import 'package:flutter_tools/src/artifacts.dart';
89
import 'package:flutter_tools/src/base/common.dart';
910
import 'package:flutter_tools/src/base/file_system.dart';
1011
import 'package:flutter_tools/src/base/logger.dart';
@@ -314,6 +315,7 @@ void main() {
314315
]),
315316
Platform: () => macosPlatform,
316317
XcodeProjectInterpreter: () => FakeXcodeProjectInterpreterWithBuildSettings(),
318+
Artifacts: () => Artifacts.test(),
317319
},
318320
);
319321

@@ -350,6 +352,7 @@ void main() {
350352
Pub: ThrowingPub.new,
351353
Platform: () => macosPlatform,
352354
XcodeProjectInterpreter: () => FakeXcodeProjectInterpreterWithBuildSettings(),
355+
Artifacts: () => Artifacts.test(),
353356
},
354357
);
355358

@@ -387,6 +390,7 @@ void main() {
387390
Pub: ThrowingPub.new,
388391
Platform: () => macosPlatform,
389392
XcodeProjectInterpreter: () => FakeXcodeProjectInterpreterWithBuildSettings(),
393+
Artifacts: () => Artifacts.test(),
390394
},
391395
);
392396

@@ -423,6 +427,7 @@ void main() {
423427
Pub: ThrowingPub.new,
424428
Platform: () => macosPlatform,
425429
XcodeProjectInterpreter: () => FakeXcodeProjectInterpreterWithBuildSettings(),
430+
Artifacts: () => Artifacts.test(),
426431
},
427432
);
428433

@@ -470,6 +475,7 @@ void main() {
470475
Pub: ThrowingPub.new,
471476
Platform: () => macosPlatform,
472477
XcodeProjectInterpreter: () => FakeXcodeProjectInterpreterWithBuildSettings(),
478+
Artifacts: () => Artifacts.test(),
473479
},
474480
);
475481

@@ -508,6 +514,7 @@ void main() {
508514
Pub: ThrowingPub.new,
509515
Platform: () => macosPlatform,
510516
XcodeProjectInterpreter: () => FakeXcodeProjectInterpreterWithBuildSettings(),
517+
Artifacts: () => Artifacts.test(),
511518
},
512519
);
513520

@@ -545,6 +552,7 @@ void main() {
545552
Pub: ThrowingPub.new,
546553
Platform: () => macosPlatform,
547554
XcodeProjectInterpreter: () => FakeXcodeProjectInterpreterWithBuildSettings(),
555+
Artifacts: () => Artifacts.test(),
548556
},
549557
);
550558

@@ -580,6 +588,7 @@ void main() {
580588
Pub: ThrowingPub.new,
581589
Platform: () => macosPlatform,
582590
XcodeProjectInterpreter: () => FakeXcodeProjectInterpreterWithBuildSettings(),
591+
Artifacts: () => Artifacts.test(),
583592
},
584593
);
585594

@@ -642,6 +651,7 @@ void main() {
642651
FileSystemUtils: () => FileSystemUtils(fileSystem: fileSystem, platform: macosPlatform),
643652
Analytics: () => fakeAnalytics,
644653
XcodeProjectInterpreter: () => FakeXcodeProjectInterpreterWithBuildSettings(),
654+
Artifacts: () => Artifacts.test(),
645655
},
646656
);
647657

@@ -703,6 +713,7 @@ void main() {
703713
XcodeProjectInterpreter: () => FakeXcodeProjectInterpreterWithBuildSettings(),
704714
Pub: ThrowingPub.new,
705715
Analytics: () => fakeAnalytics,
716+
Artifacts: () => Artifacts.test(),
706717
},
707718
);
708719

@@ -766,6 +777,7 @@ void main() {
766777
),
767778
Pub: ThrowingPub.new,
768779
Analytics: () => fakeAnalytics,
780+
Artifacts: () => Artifacts.test(),
769781
},
770782
);
771783
});
@@ -809,6 +821,7 @@ void main() {
809821
Pub: ThrowingPub.new,
810822
Platform: () => macosPlatform,
811823
XcodeProjectInterpreter: () => FakeXcodeProjectInterpreterWithBuildSettings(),
824+
Artifacts: () => Artifacts.test(),
812825
},
813826
);
814827

@@ -857,6 +870,7 @@ void main() {
857870
Pub: ThrowingPub.new,
858871
Platform: () => macosPlatform,
859872
XcodeProjectInterpreter: () => FakeXcodeProjectInterpreterWithBuildSettings(),
873+
Artifacts: () => Artifacts.test(),
860874
},
861875
);
862876

@@ -910,6 +924,7 @@ void main() {
910924
Pub: ThrowingPub.new,
911925
Platform: () => macosPlatform,
912926
XcodeProjectInterpreter: () => FakeXcodeProjectInterpreterWithBuildSettings(),
927+
Artifacts: () => Artifacts.test(),
913928
},
914929
);
915930

@@ -948,6 +963,7 @@ void main() {
948963
Pub: ThrowingPub.new,
949964
Platform: () => macosPlatform,
950965
XcodeProjectInterpreter: () => FakeXcodeProjectInterpreterWithBuildSettings(),
966+
Artifacts: () => Artifacts.test(),
951967
},
952968
);
953969

@@ -1004,6 +1020,7 @@ void main() {
10041020
Pub: ThrowingPub.new,
10051021
Platform: () => macosPlatform,
10061022
XcodeProjectInterpreter: () => FakeXcodeProjectInterpreterWithBuildSettings(),
1023+
Artifacts: () => Artifacts.test(),
10071024
},
10081025
);
10091026

@@ -1051,6 +1068,7 @@ void main() {
10511068
Pub: ThrowingPub.new,
10521069
Platform: () => macosPlatform,
10531070
XcodeProjectInterpreter: () => FakeXcodeProjectInterpreterWithBuildSettings(),
1071+
Artifacts: () => Artifacts.test(),
10541072
},
10551073
);
10561074

@@ -1092,6 +1110,7 @@ void main() {
10921110
Pub: ThrowingPub.new,
10931111
Platform: () => macosPlatform,
10941112
XcodeProjectInterpreter: () => FakeXcodeProjectInterpreterWithBuildSettings(),
1113+
Artifacts: () => Artifacts.test(),
10951114
},
10961115
);
10971116

@@ -1150,6 +1169,7 @@ void main() {
11501169
Pub: ThrowingPub.new,
11511170
Platform: () => macosPlatform,
11521171
XcodeProjectInterpreter: () => FakeXcodeProjectInterpreterWithBuildSettings(),
1172+
Artifacts: () => Artifacts.test(),
11531173
},
11541174
);
11551175

@@ -1194,6 +1214,7 @@ Runner requires a provisioning profile. Select a provisioning profile in the Sig
11941214
Pub: ThrowingPub.new,
11951215
Platform: () => macosPlatform,
11961216
XcodeProjectInterpreter: () => FakeXcodeProjectInterpreterWithBuildSettings(),
1217+
Artifacts: () => Artifacts.test(),
11971218
},
11981219
);
11991220

@@ -1236,6 +1257,7 @@ Runner requires a provisioning profile. Select a provisioning profile in the Sig
12361257
Platform: () => macosPlatform,
12371258
XcodeProjectInterpreter: () =>
12381259
FakeXcodeProjectInterpreterWithBuildSettings(developmentTeam: null),
1260+
Artifacts: () => Artifacts.test(),
12391261
},
12401262
);
12411263

@@ -1279,6 +1301,7 @@ Runner requires a provisioning profile. Select a provisioning profile in the Sig
12791301
EnvironmentType: () => EnvironmentType.physical,
12801302
Platform: () => macosPlatform,
12811303
XcodeProjectInterpreter: () => FakeXcodeProjectInterpreterWithBuildSettings(),
1304+
Artifacts: () => Artifacts.test(),
12821305
},
12831306
);
12841307

@@ -1320,6 +1343,7 @@ Runner requires a provisioning profile. Select a provisioning profile in the Sig
13201343
Platform: () => macosPlatform,
13211344
XcodeProjectInterpreter: () =>
13221345
FakeXcodeProjectInterpreterWithBuildSettings(developmentTeam: null),
1346+
Artifacts: () => Artifacts.test(),
13231347
},
13241348
);
13251349

@@ -1363,6 +1387,7 @@ Runner requires a provisioning profile. Select a provisioning profile in the Sig
13631387
Platform: () => macosPlatform,
13641388
XcodeProjectInterpreter: () =>
13651389
FakeXcodeProjectInterpreterWithBuildSettings(developmentTeam: null),
1390+
Artifacts: () => Artifacts.test(),
13661391
},
13671392
);
13681393

@@ -1413,6 +1438,7 @@ Runner requires a provisioning profile. Select a provisioning profile in the Sig
14131438
Platform: () => macosPlatform,
14141439
XcodeProjectInterpreter: () =>
14151440
FakeXcodeProjectInterpreterWithBuildSettings(developmentTeam: null),
1441+
Artifacts: () => Artifacts.test(),
14161442
},
14171443
);
14181444
});
@@ -1459,6 +1485,7 @@ Runner requires a provisioning profile. Select a provisioning profile in the Sig
14591485
Pub: ThrowingPub.new,
14601486
Platform: () => macosPlatform,
14611487
XcodeProjectInterpreter: () => FakeXcodeProjectInterpreterWithBuildSettings(),
1488+
Artifacts: () => Artifacts.test(),
14621489
},
14631490
);
14641491

@@ -1507,6 +1534,7 @@ Runner requires a provisioning profile. Select a provisioning profile in the Sig
15071534
Pub: ThrowingPub.new,
15081535
Platform: () => macosPlatform,
15091536
XcodeProjectInterpreter: () => FakeXcodeProjectInterpreterWithBuildSettings(),
1537+
Artifacts: () => Artifacts.test(),
15101538
},
15111539
);
15121540

@@ -1564,6 +1592,7 @@ Runner requires a provisioning profile. Select a provisioning profile in the Sig
15641592
Pub: ThrowingPub.new,
15651593
Platform: () => macosPlatform,
15661594
XcodeProjectInterpreter: () => FakeXcodeProjectInterpreterWithBuildSettings(),
1595+
Artifacts: () => Artifacts.test(),
15671596
},
15681597
);
15691598

@@ -1605,6 +1634,7 @@ Runner requires a provisioning profile. Select a provisioning profile in the Sig
16051634
Pub: ThrowingPub.new,
16061635
Platform: () => macosPlatform,
16071636
XcodeProjectInterpreter: () => FakeXcodeProjectInterpreterWithBuildSettings(),
1637+
Artifacts: () => Artifacts.test(),
16081638
},
16091639
);
16101640
});

0 commit comments

Comments
 (0)