Skip to content

Commit 57a9a9b

Browse files
[tool] Run a config-only build before Xcode analyze (#9075)
Currently xcode-analyze relies on the native project files already having been generated. This is unreliably locally, and now is problematic on CI as well since Xcode builds now (as of flutter/flutter#165916) must match the last build mode, so analysis will fail if the previous CI step built in release mode (as is currently the case). This adds a config-only build call in debug mode before analyzing. Since running a config-only build is a common operation in the tool, this extracts a helper to abstract the logic. Unblocks the flutter/flutter->flutter/packages roller.
1 parent 4389067 commit 57a9a9b

8 files changed

+200
-84
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'package:platform/platform.dart';
6+
7+
import 'process_runner.dart';
8+
import 'repository_package.dart';
9+
10+
/// Runs the appropriate `flutter build --config-only` command for the given
11+
/// target platform and build mode, to ensure that all of the native build files
12+
/// are present for that mode.
13+
///
14+
/// If [streamOutput] is false, output will only be printed if the command
15+
/// fails.
16+
Future<bool> runConfigOnlyBuild(
17+
RepositoryPackage package,
18+
ProcessRunner processRunner,
19+
Platform platform,
20+
FlutterPlatform targetPlatform, {
21+
bool buildDebug = false,
22+
List<String> extraArgs = const <String>[],
23+
}) async {
24+
final String flutterCommand = platform.isWindows ? 'flutter.bat' : 'flutter';
25+
26+
final String target = switch (targetPlatform) {
27+
FlutterPlatform.android => 'apk',
28+
FlutterPlatform.ios => 'ios',
29+
FlutterPlatform.linux => 'linux',
30+
FlutterPlatform.macos => 'macos',
31+
FlutterPlatform.web => 'web',
32+
FlutterPlatform.windows => 'windows',
33+
};
34+
35+
final int exitCode = await processRunner.runAndStream(
36+
flutterCommand,
37+
<String>[
38+
'build',
39+
target,
40+
if (buildDebug) '--debug',
41+
'--config-only',
42+
...extraArgs,
43+
],
44+
workingDir: package.directory);
45+
return exitCode == 0;
46+
}

script/tool/lib/src/fetch_deps_command.dart

+15-15
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// found in the LICENSE file.
44

55
import 'common/core.dart';
6+
import 'common/flutter_command_utils.dart';
67
import 'common/gradle.dart';
78
import 'common/output_utils.dart';
89
import 'common/package_looping_command.dart';
@@ -179,12 +180,9 @@ class FetchDepsCommand extends PackageLoopingCommand {
179180
processRunner: processRunner, platform: platform);
180181

181182
if (!gradleProject.isConfigured()) {
182-
final int exitCode = await processRunner.runAndStream(
183-
flutterCommand,
184-
<String>['build', 'apk', '--config-only'],
185-
workingDir: example.directory,
186-
);
187-
if (exitCode != 0) {
183+
final bool buildSuccess = await runConfigOnlyBuild(
184+
example, processRunner, platform, FlutterPlatform.android);
185+
if (!buildSuccess) {
188186
printError('Unable to configure Gradle project.');
189187
return PackageResult.fail(<String>['Unable to configure Gradle.']);
190188
}
@@ -203,23 +201,25 @@ class FetchDepsCommand extends PackageLoopingCommand {
203201
}
204202

205203
Future<PackageResult> _fetchDarwinDeps(
206-
RepositoryPackage package, final String platform) async {
207-
if (!pluginSupportsPlatform(platform, package,
204+
RepositoryPackage package, final String platformString) async {
205+
if (!pluginSupportsPlatform(platformString, package,
208206
requiredMode: PlatformSupport.inline)) {
209207
// Convert from the flag (lower case ios/macos) to the actual name.
210-
final String displayPlatform = platform.replaceFirst('os', 'OS');
208+
final String displayPlatform = platformString.replaceFirst('os', 'OS');
211209
return PackageResult.skip(
212210
'Package does not have native $displayPlatform dependencies.');
213211
}
214212

215213
for (final RepositoryPackage example in package.getExamples()) {
216214
// Create the necessary native build files, which will run pub get and pod install if needed.
217-
final int exitCode = await processRunner.runAndStream(
218-
flutterCommand,
219-
<String>['build', platform, '--config-only'],
220-
workingDir: example.directory,
221-
);
222-
if (exitCode != 0) {
215+
final bool buildSuccess = await runConfigOnlyBuild(
216+
example,
217+
processRunner,
218+
platform,
219+
platformString == platformIOS
220+
? FlutterPlatform.ios
221+
: FlutterPlatform.macos);
222+
if (!buildSuccess) {
223223
printError('Unable to prepare native project files.');
224224
return PackageResult.fail(<String>['Unable to configure project.']);
225225
}

script/tool/lib/src/firebase_test_lab_command.dart

+14-17
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import 'package:file/file.dart';
88
import 'package:uuid/uuid.dart';
99

1010
import 'common/core.dart';
11+
import 'common/flutter_command_utils.dart';
1112
import 'common/gradle.dart';
1213
import 'common/output_utils.dart';
1314
import 'common/package_looping_command.dart';
@@ -182,7 +183,7 @@ class FirebaseTestLabCommand extends PackageLoopingCommand {
182183
// Ensures that gradle wrapper exists
183184
final GradleProject project = GradleProject(example,
184185
processRunner: processRunner, platform: platform);
185-
if (!await _ensureGradleWrapperExists(project)) {
186+
if (!await _ensureGradleWrapperExists(example, project)) {
186187
return PackageResult.fail(<String>['Unable to build example apk']);
187188
}
188189

@@ -245,26 +246,22 @@ class FirebaseTestLabCommand extends PackageLoopingCommand {
245246
/// Flutter build to generate it.
246247
///
247248
/// Returns true if either gradlew was already present, or the build succeeds.
248-
Future<bool> _ensureGradleWrapperExists(GradleProject project) async {
249+
Future<bool> _ensureGradleWrapperExists(
250+
RepositoryPackage package, GradleProject project) async {
249251
// Unconditionally re-run build with --debug --config-only, to ensure that
250252
// the project is in a debug state even if it was previously configured.
251253
print('Running flutter build apk...');
252254
final String experiment = getStringArg(kEnableExperiment);
253-
final int exitCode = await processRunner.runAndStream(
254-
flutterCommand,
255-
<String>[
256-
'build',
257-
'apk',
258-
'--debug',
259-
'--config-only',
260-
if (experiment.isNotEmpty) '--enable-experiment=$experiment',
261-
],
262-
workingDir: project.androidDirectory);
263-
264-
if (exitCode != 0) {
265-
return false;
266-
}
267-
return true;
255+
return runConfigOnlyBuild(
256+
package,
257+
processRunner,
258+
platform,
259+
FlutterPlatform.android,
260+
buildDebug: true,
261+
extraArgs: <String>[
262+
if (experiment.isNotEmpty) '--enable-experiment=$experiment',
263+
],
264+
);
268265
}
269266

270267
/// Runs [test] from [example] as a Firebase Test Lab test, returning true if

script/tool/lib/src/lint_android_command.dart

+4-6
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// found in the LICENSE file.
44

55
import 'common/core.dart';
6+
import 'common/flutter_command_utils.dart';
67
import 'common/gradle.dart';
78
import 'common/output_utils.dart';
89
import 'common/package_looping_command.dart';
@@ -41,12 +42,9 @@ class LintAndroidCommand extends PackageLoopingCommand {
4142
processRunner: processRunner, platform: platform);
4243

4344
if (!project.isConfigured()) {
44-
final int exitCode = await processRunner.runAndStream(
45-
flutterCommand,
46-
<String>['build', 'apk', '--config-only'],
47-
workingDir: example.directory,
48-
);
49-
if (exitCode != 0) {
45+
final bool buildSuccess = await runConfigOnlyBuild(
46+
example, processRunner, platform, FlutterPlatform.android);
47+
if (!buildSuccess) {
5048
printError('Unable to configure Gradle project.');
5149
return PackageResult.fail(<String>['Unable to configure Gradle.']);
5250
}

script/tool/lib/src/native_test_command.dart

+7-5
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import 'package:meta/meta.dart';
99

1010
import 'common/cmake.dart';
1111
import 'common/core.dart';
12+
import 'common/flutter_command_utils.dart';
1213
import 'common/gradle.dart';
1314
import 'common/output_utils.dart';
1415
import 'common/package_looping_command.dart';
@@ -330,12 +331,13 @@ this command.
330331
platform: platform,
331332
);
332333
if (!project.isConfigured()) {
333-
final int exitCode = await processRunner.runAndStream(
334-
flutterCommand,
335-
<String>['build', 'apk', '--config-only'],
336-
workingDir: example.directory,
334+
final bool buildSuccess = await runConfigOnlyBuild(
335+
example,
336+
processRunner,
337+
platform,
338+
FlutterPlatform.android,
337339
);
338-
if (exitCode != 0) {
340+
if (!buildSuccess) {
339341
printError('Unable to configure Gradle project.');
340342
failed = true;
341343
continue;

script/tool/lib/src/xcode_analyze_command.dart

+37-16
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// found in the LICENSE file.
44

55
import 'common/core.dart';
6+
import 'common/flutter_command_utils.dart';
67
import 'common/output_utils.dart';
78
import 'common/package_looping_command.dart';
89
import 'common/plugin_utils.dart';
@@ -74,19 +75,21 @@ class XcodeAnalyzeCommand extends PackageLoopingCommand {
7475

7576
final List<String> failures = <String>[];
7677
if (testIOS &&
77-
!await _analyzePlugin(package, 'iOS', extraFlags: <String>[
78-
'-destination',
79-
'generic/platform=iOS Simulator',
80-
if (minIOSVersion.isNotEmpty)
81-
'IPHONEOS_DEPLOYMENT_TARGET=$minIOSVersion',
82-
])) {
78+
!await _analyzePlugin(package, FlutterPlatform.ios,
79+
extraFlags: <String>[
80+
'-destination',
81+
'generic/platform=iOS Simulator',
82+
if (minIOSVersion.isNotEmpty)
83+
'IPHONEOS_DEPLOYMENT_TARGET=$minIOSVersion',
84+
])) {
8385
failures.add('iOS');
8486
}
8587
if (testMacOS &&
86-
!await _analyzePlugin(package, 'macOS', extraFlags: <String>[
87-
if (minMacOSVersion.isNotEmpty)
88-
'MACOSX_DEPLOYMENT_TARGET=$minMacOSVersion',
89-
])) {
88+
!await _analyzePlugin(package, FlutterPlatform.macos,
89+
extraFlags: <String>[
90+
if (minMacOSVersion.isNotEmpty)
91+
'MACOSX_DEPLOYMENT_TARGET=$minMacOSVersion',
92+
])) {
9093
failures.add('macOS');
9194
}
9295

@@ -101,22 +104,40 @@ class XcodeAnalyzeCommand extends PackageLoopingCommand {
101104
/// Analyzes [plugin] for [targetPlatform], returning true if it passed analysis.
102105
Future<bool> _analyzePlugin(
103106
RepositoryPackage plugin,
104-
String targetPlatform, {
107+
FlutterPlatform targetPlatform, {
105108
List<String> extraFlags = const <String>[],
106109
}) async {
110+
final String platformString =
111+
targetPlatform == FlutterPlatform.ios ? 'iOS' : 'macOS';
107112
bool passing = true;
108113
for (final RepositoryPackage example in plugin.getExamples()) {
114+
// Unconditionally re-run build with --debug --config-only, to ensure that
115+
// the project is in a debug state even if it was previously configured.
116+
print('Running flutter build --config-only...');
117+
final bool buildSuccess = await runConfigOnlyBuild(
118+
example,
119+
processRunner,
120+
platform,
121+
targetPlatform,
122+
buildDebug: true,
123+
);
124+
if (!buildSuccess) {
125+
printError('Unable to prepare native project files.');
126+
passing = false;
127+
continue;
128+
}
129+
109130
// Running tests and static analyzer.
110131
final String examplePath = getRelativePosixPath(example.directory,
111132
from: plugin.directory.parent);
112-
print('Running $targetPlatform tests and analyzer for $examplePath...');
133+
print('Running $platformString tests and analyzer for $examplePath...');
113134
final int exitCode = await _xcode.runXcodeBuild(
114135
example.directory,
115-
targetPlatform,
136+
platformString,
116137
// Clean before analyzing to remove cached swiftmodules from previous
117138
// runs, which can cause conflicts.
118139
actions: <String>['clean', 'analyze'],
119-
workspace: '${targetPlatform.toLowerCase()}/Runner.xcworkspace',
140+
workspace: '${platformString.toLowerCase()}/Runner.xcworkspace',
120141
scheme: 'Runner',
121142
configuration: 'Debug',
122143
hostPlatform: platform,
@@ -126,9 +147,9 @@ class XcodeAnalyzeCommand extends PackageLoopingCommand {
126147
],
127148
);
128149
if (exitCode == 0) {
129-
printSuccess('$examplePath ($targetPlatform) passed analysis.');
150+
printSuccess('$examplePath ($platformString) passed analysis.');
130151
} else {
131-
printError('$examplePath ($targetPlatform) failed analysis.');
152+
printError('$examplePath ($platformString) failed analysis.');
132153
passing = false;
133154
}
134155
}

script/tool/test/firebase_test_lab_command_test.dart

+5-25
Original file line numberDiff line numberDiff line change
@@ -167,11 +167,7 @@ public class MainActivityTest {
167167
ProcessCall(
168168
'flutter',
169169
const <String>['build', 'apk', '--debug', '--config-only'],
170-
plugin1
171-
.getExamples()
172-
.first
173-
.platformDirectory(FlutterPlatform.android)
174-
.path),
170+
plugin1.getExamples().first.directory.path),
175171
ProcessCall(
176172
'gcloud',
177173
'auth activate-service-account --key-file=/path/to/key'
@@ -196,11 +192,7 @@ public class MainActivityTest {
196192
ProcessCall(
197193
'flutter',
198194
const <String>['build', 'apk', '--debug', '--config-only'],
199-
plugin2
200-
.getExamples()
201-
.first
202-
.platformDirectory(FlutterPlatform.android)
203-
.path),
195+
plugin2.getExamples().first.directory.path),
204196
ProcessCall(
205197
'/packages/plugin2/example/android/gradlew',
206198
'app:assembleAndroidTest -Pverbose=true'.split(' '),
@@ -264,11 +256,7 @@ public class MainActivityTest {
264256
ProcessCall(
265257
'flutter',
266258
const <String>['build', 'apk', '--debug', '--config-only'],
267-
plugin
268-
.getExamples()
269-
.first
270-
.platformDirectory(FlutterPlatform.android)
271-
.path),
259+
plugin.getExamples().first.directory.path),
272260
ProcessCall(
273261
'/packages/plugin/example/android/gradlew',
274262
'app:assembleAndroidTest -Pverbose=true'.split(' '),
@@ -694,11 +682,7 @@ class MainActivityTest {
694682
ProcessCall(
695683
'flutter',
696684
'build apk --debug --config-only'.split(' '),
697-
plugin
698-
.getExamples()
699-
.first
700-
.platformDirectory(FlutterPlatform.android)
701-
.path,
685+
plugin.getExamples().first.directory.path,
702686
),
703687
ProcessCall(
704688
'/packages/plugin/example/android/gradlew',
@@ -878,11 +862,7 @@ class MainActivityTest {
878862
'--config-only',
879863
'--enable-experiment=exp1'
880864
],
881-
plugin
882-
.getExamples()
883-
.first
884-
.platformDirectory(FlutterPlatform.android)
885-
.path),
865+
plugin.getExamples().first.directory.path),
886866
ProcessCall(
887867
'/packages/plugin/example/android/gradlew',
888868
'app:assembleAndroidTest -Pverbose=true -Pextra-front-end-options=--enable-experiment%3Dexp1 -Pextra-gen-snapshot-options=--enable-experiment%3Dexp1'

0 commit comments

Comments
 (0)