Migrate mac.dart to null safety (#88846)

This commit is contained in:
Jenn Magder 2021-08-25 13:31:03 -07:00 committed by GitHub
parent a1cd3f45e0
commit 125451bc2e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 167 additions and 87 deletions

View file

@ -2,8 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// @dart = 2.8
import 'package:meta/meta.dart';
import 'package:process/process.dart';
@ -36,10 +34,10 @@ import 'xcodeproj.dart';
class IMobileDevice {
IMobileDevice({
@required Artifacts artifacts,
@required Cache cache,
@required ProcessManager processManager,
@required Logger logger,
required Artifacts artifacts,
required Cache cache,
required ProcessManager processManager,
required Logger logger,
}) : _idevicesyslogPath = artifacts.getHostArtifact(HostArtifact.idevicesyslog).path,
_idevicescreenshotPath = artifacts.getHostArtifact(HostArtifact.idevicescreenshot).path,
_dyLdLibEntry = cache.dyLdLibEntry,
@ -53,7 +51,7 @@ class IMobileDevice {
final ProcessUtils _processUtils;
bool get isInstalled => _isInstalled ??= _processManager.canRun(_idevicescreenshotPath);
bool _isInstalled;
bool? _isInstalled;
/// Starts `idevicesyslog` and returns the running process.
Future<Process> startLogger(String deviceID) {
@ -93,13 +91,13 @@ class IMobileDevice {
}
Future<XcodeBuildResult> buildXcodeProject({
BuildableIOSApp app,
BuildInfo buildInfo,
String targetOverride,
required BuildableIOSApp app,
required BuildInfo buildInfo,
required String targetOverride,
EnvironmentType environmentType = EnvironmentType.physical,
DarwinArch activeArch,
DarwinArch? activeArch,
bool codesign = true,
String deviceID,
String? deviceID,
bool configOnly = false,
XcodeBuildAction buildAction = XcodeBuildAction.build,
}) async {
@ -126,12 +124,16 @@ Future<XcodeBuildResult> buildXcodeProject({
await removeFinderExtendedAttributes(app.project.parent.directory, globals.processUtils, globals.logger);
final XcodeProjectInfo projectInfo = await app.project.projectInfo();
final String scheme = projectInfo.schemeFor(buildInfo);
final XcodeProjectInfo? projectInfo = await app.project.projectInfo();
if (projectInfo == null) {
globals.printError('Xcode project not found.');
return XcodeBuildResult(success: false);
}
final String? scheme = projectInfo.schemeFor(buildInfo);
if (scheme == null) {
projectInfo.reportFlavorNotFoundAndExit();
}
final String configuration = projectInfo.buildConfigurationFor(buildInfo, scheme);
final String? configuration = projectInfo.buildConfigurationFor(buildInfo, scheme);
if (configuration == null) {
globals.printError('');
globals.printError('The Xcode project defines build configurations: ${projectInfo.buildConfigurations.join(', ')}');
@ -159,14 +161,14 @@ Future<XcodeBuildResult> buildXcodeProject({
}
final FlutterManifest manifest = app.project.parent.manifest;
final String buildName = parsedBuildName(manifest: manifest, buildInfo: buildInfo);
final String? buildName = parsedBuildName(manifest: manifest, buildInfo: buildInfo);
final bool buildNameIsMissing = buildName == null || buildName.isEmpty;
if (buildNameIsMissing) {
globals.printStatus('Warning: Missing build name (CFBundleShortVersionString).');
}
final String buildNumber = parsedBuildNumber(manifest: manifest, buildInfo: buildInfo);
final String? buildNumber = parsedBuildNumber(manifest: manifest, buildInfo: buildInfo);
final bool buildNumberIsMissing = buildNumber == null || buildNumber.isEmpty;
if (buildNumberIsMissing) {
@ -177,7 +179,7 @@ Future<XcodeBuildResult> buildXcodeProject({
'file version field before submitting to the App Store.');
}
Map<String, String> autoSigningConfigs;
Map<String, String>? autoSigningConfigs;
final Map<String, String> buildSettings = await app.project.buildSettingsForBuildInfo(
buildInfo,
@ -206,7 +208,7 @@ Future<XcodeBuildResult> buildXcodeProject({
}
final List<String> buildCommands = <String>[
...globals.xcode.xcrunCommand(),
...globals.xcode!.xcrunCommand(),
'xcodebuild',
'-configuration',
configuration,
@ -291,18 +293,18 @@ Future<XcodeBuildResult> buildXcodeProject({
);
}
Status buildSubStatus;
Status initialBuildStatus;
Directory tempDir;
Status? buildSubStatus;
Status? initialBuildStatus;
Directory? tempDir;
File scriptOutputPipeFile;
File? scriptOutputPipeFile;
if (globals.logger.hasTerminal) {
tempDir = globals.fs.systemTempDirectory.createTempSync('flutter_build_log_pipe.');
scriptOutputPipeFile = tempDir.childFile('pipe_to_stdout');
globals.os.makePipe(scriptOutputPipeFile.path);
Future<void> listenToScriptOutputLine() async {
final List<String> lines = await scriptOutputPipeFile.readAsLines();
final List<String> lines = await scriptOutputPipeFile!.readAsLines();
for (final String line in lines) {
if (line == 'done' || line == 'all done') {
buildSubStatus?.stop();
@ -347,7 +349,7 @@ Future<XcodeBuildResult> buildXcodeProject({
final Stopwatch sw = Stopwatch()..start();
initialBuildStatus = globals.logger.startProgress('Running Xcode build...');
final RunResult buildResult = await _runBuildWithRetries(buildCommands, app);
final RunResult? buildResult = await _runBuildWithRetries(buildCommands, app);
// Notifies listener that no more output is coming.
scriptOutputPipeFile?.writeAsStringSync('all done');
@ -361,7 +363,7 @@ Future<XcodeBuildResult> buildXcodeProject({
);
globals.flutterUsage.sendTiming(xcodeBuildActionToString(buildAction), 'xcode-ios', Duration(milliseconds: sw.elapsedMilliseconds));
if (buildResult.exitCode != 0) {
if (buildResult != null && buildResult.exitCode != 0) {
globals.printStatus('Failed to build iOS app');
if (buildResult.stderr.isNotEmpty) {
globals.printStatus('Error output from Xcode build:\n');
@ -383,18 +385,22 @@ Future<XcodeBuildResult> buildXcodeProject({
),
);
} else {
String outputDir;
String? outputDir;
if (buildAction == XcodeBuildAction.build) {
// If the app contains a watch companion target, the sdk argument of xcodebuild has to be omitted.
// For some reason this leads to TARGET_BUILD_DIR always ending in 'iphoneos' even though the
// actual directory will end with 'iphonesimulator' for simulator builds.
// The value of TARGET_BUILD_DIR is adjusted to accommodate for this effect.
String targetBuildDir = buildSettings['TARGET_BUILD_DIR'];
String? targetBuildDir = buildSettings['TARGET_BUILD_DIR'];
if (targetBuildDir == null) {
globals.printError('Xcode build is missing expected TARGET_BUILD_DIR build setting.');
return XcodeBuildResult(success: false);
}
if (hasWatchCompanion && environmentType == EnvironmentType.simulator) {
globals.printTrace('Replacing iphoneos with iphonesimulator in TARGET_BUILD_DIR.');
targetBuildDir = targetBuildDir.replaceFirst('iphoneos', 'iphonesimulator');
}
final String appBundle = buildSettings['WRAPPER_NAME'];
final String? appBundle = buildSettings['WRAPPER_NAME'];
final String expectedOutputDirectory = globals.fs.path.join(
targetBuildDir,
appBundle,
@ -463,11 +469,11 @@ Future<void> removeFinderExtendedAttributes(Directory projectDirectory, ProcessU
}
}
Future<RunResult> _runBuildWithRetries(List<String> buildCommands, BuildableIOSApp app) async {
Future<RunResult?> _runBuildWithRetries(List<String> buildCommands, BuildableIOSApp app) async {
int buildRetryDelaySeconds = 1;
int remainingTries = 8;
RunResult buildResult;
RunResult? buildResult;
while (remainingTries > 0) {
remainingTries--;
buildRetryDelaySeconds *= 2;
@ -507,13 +513,14 @@ return result.exitCode != 0 &&
}
Future<void> diagnoseXcodeBuildFailure(XcodeBuildResult result, Usage flutterUsage, Logger logger) async {
if (result.xcodeBuildExecution != null &&
result.xcodeBuildExecution.environmentType == EnvironmentType.physical &&
result.stdout?.toUpperCase()?.contains('BITCODE') == true) {
final XcodeBuildExecution? xcodeBuildExecution = result.xcodeBuildExecution;
if (xcodeBuildExecution != null &&
xcodeBuildExecution.environmentType == EnvironmentType.physical &&
result.stdout?.toUpperCase().contains('BITCODE') == true) {
BuildEvent('xcode-bitcode-failure',
type: 'ios',
command: result.xcodeBuildExecution.buildCommands.toString(),
settings: result.xcodeBuildExecution.buildSettings.toString(),
command: xcodeBuildExecution.buildCommands.toString(),
settings: xcodeBuildExecution.buildSettings.toString(),
flutterUsage: flutterUsage,
).send();
}
@ -531,9 +538,8 @@ Future<void> diagnoseXcodeBuildFailure(XcodeBuildResult result, Usage flutterUsa
logger.printError(' flutter clean');
return;
}
if (result.xcodeBuildExecution != null &&
result.xcodeBuildExecution.environmentType == EnvironmentType.physical &&
if (xcodeBuildExecution != null &&
xcodeBuildExecution.environmentType == EnvironmentType.physical &&
result.stdout?.contains('BCEROR') == true &&
// May need updating if Xcode changes its outputs.
result.stdout?.contains("Xcode couldn't find a provisioning profile matching") == true) {
@ -543,16 +549,16 @@ Future<void> diagnoseXcodeBuildFailure(XcodeBuildResult result, Usage flutterUsa
// Make sure the user has specified one of:
// * DEVELOPMENT_TEAM (automatic signing)
// * PROVISIONING_PROFILE (manual signing)
if (result.xcodeBuildExecution != null &&
result.xcodeBuildExecution.environmentType == EnvironmentType.physical &&
if (xcodeBuildExecution != null &&
xcodeBuildExecution.environmentType == EnvironmentType.physical &&
!<String>['DEVELOPMENT_TEAM', 'PROVISIONING_PROFILE'].any(
result.xcodeBuildExecution.buildSettings.containsKey)) {
xcodeBuildExecution.buildSettings.containsKey)) {
logger.printError(noDevelopmentTeamInstruction, emphasis: true);
return;
}
if (result.xcodeBuildExecution != null &&
result.xcodeBuildExecution.environmentType == EnvironmentType.physical &&
result.xcodeBuildExecution.buildSettings['PRODUCT_BUNDLE_IDENTIFIER']?.contains('com.example') == true) {
if (xcodeBuildExecution != null &&
xcodeBuildExecution.environmentType == EnvironmentType.physical &&
xcodeBuildExecution.buildSettings['PRODUCT_BUNDLE_IDENTIFIER']?.contains('com.example') == true) {
logger.printError('');
logger.printError('It appears that your application still contains the default signing identifier.');
logger.printError("Try replacing 'com.example' with your signing id in Xcode:");
@ -589,7 +595,7 @@ String xcodeBuildActionToString(XcodeBuildAction action) {
class XcodeBuildResult {
XcodeBuildResult({
@required this.success,
required this.success,
this.output,
this.stdout,
this.stderr,
@ -597,20 +603,20 @@ class XcodeBuildResult {
});
final bool success;
final String output;
final String stdout;
final String stderr;
final String? output;
final String? stdout;
final String? stderr;
/// The invocation of the build that resulted in this result instance.
final XcodeBuildExecution xcodeBuildExecution;
final XcodeBuildExecution? xcodeBuildExecution;
}
/// Describes an invocation of a Xcode build command.
class XcodeBuildExecution {
XcodeBuildExecution({
@required this.buildCommands,
@required this.appDirectory,
@required this.environmentType,
@required this.buildSettings,
required this.buildCommands,
required this.appDirectory,
required this.environmentType,
required this.buildSettings,
});
/// The original list of Xcode build commands used to produce this build result.
@ -627,12 +633,13 @@ bool _checkXcodeVersion() {
if (!globals.platform.isMacOS) {
return false;
}
if (!globals.xcodeProjectInterpreter.isInstalled) {
final XcodeProjectInterpreter? xcodeProjectInterpreter = globals.xcodeProjectInterpreter;
if (xcodeProjectInterpreter?.isInstalled != true) {
globals.printError('Cannot find "xcodebuild". $_xcodeRequirement');
return false;
}
if (!globals.xcode.isRequiredVersionSatisfactory) {
globals.printError('Found "${globals.xcodeProjectInterpreter.versionText}". $_xcodeRequirement');
if (globals.xcode?.isRequiredVersionSatisfactory != true) {
globals.printError('Found "${xcodeProjectInterpreter?.versionText}". $_xcodeRequirement');
return false;
}
return true;
@ -649,10 +656,10 @@ bool upgradePbxProjWithFlutterAssets(IosProject project, Logger logger) {
final Set<String> printedStatuses = <String>{};
for (final String line in lines) {
final Match match = oldAssets.firstMatch(line);
final Match? match = oldAssets.firstMatch(line);
if (match != null) {
if (printedStatuses.add(match.group(1))) {
logger.printStatus('Removing obsolete reference to ${match.group(1)} from ${project.xcodeProject?.basename}');
if (printedStatuses.add(match.group(1)!)) {
logger.printStatus('Removing obsolete reference to ${match.group(1)} from ${project.xcodeProject.basename}');
}
} else {
buffer.writeln(line);

View file

@ -373,7 +373,7 @@ class XcodeProjectBuildContext {
///
/// Represents the output of `xcodebuild -list`.
class XcodeProjectInfo {
XcodeProjectInfo(
const XcodeProjectInfo(
this.targets,
this.buildConfigurations,
this.schemes,

View file

@ -86,18 +86,88 @@ void main() {
BufferLogger logger;
Xcode xcode;
FakeXcodeProjectInterpreter fakeXcodeProjectInterpreter;
XcodeProjectInfo projectInfo;
setUp(() {
logger = BufferLogger.test();
fileSystem = MemoryFileSystem.test();
processManager = FakeProcessManager.empty();
fakeXcodeProjectInterpreter = FakeXcodeProjectInterpreter();
projectInfo = XcodeProjectInfo(
<String>['Runner'],
<String>['Debug', 'Release'],
<String>['Runner'],
logger,
);
fakeXcodeProjectInterpreter = FakeXcodeProjectInterpreter(projectInfo: projectInfo);
xcode = Xcode.test(processManager: FakeProcessManager.any(), xcodeProjectInterpreter: fakeXcodeProjectInterpreter);
fileSystem.file('foo/.packages')
..createSync(recursive: true)
..writeAsStringSync('\n');
});
testUsingContext('missing TARGET_BUILD_DIR', () async {
final IOSDevice iosDevice = setUpIOSDevice(
fileSystem: fileSystem,
processManager: processManager,
logger: logger,
artifacts: artifacts,
);
setUpIOSProject(fileSystem);
final FlutterProject flutterProject = FlutterProject.fromDirectory(fileSystem.currentDirectory);
final BuildableIOSApp buildableIOSApp = BuildableIOSApp(flutterProject.ios, 'flutter', 'My Super Awesome App.app');
processManager.addCommand(FakeCommand(command: _xattrArgs(flutterProject)));
processManager.addCommand(const FakeCommand(command: kRunReleaseArgs));
final LaunchResult launchResult = await iosDevice.startApp(
buildableIOSApp,
debuggingOptions: DebuggingOptions.disabled(BuildInfo.release),
platformArgs: <String, Object>{},
);
expect(launchResult.started, false);
expect(logger.errorText, contains('Xcode build is missing expected TARGET_BUILD_DIR build setting'));
expect(processManager, hasNoRemainingExpectations);
}, overrides: <Type, Generator>{
ProcessManager: () => processManager,
FileSystem: () => fileSystem,
Logger: () => logger,
Platform: () => macPlatform,
XcodeProjectInterpreter: () => FakeXcodeProjectInterpreter(buildSettings: const <String, String>{
'WRAPPER_NAME': 'My Super Awesome App.app',
'DEVELOPMENT_TEAM': '3333CCCC33',
}, projectInfo: projectInfo),
Xcode: () => xcode,
});
testUsingContext('missing project info', () async {
final IOSDevice iosDevice = setUpIOSDevice(
fileSystem: fileSystem,
processManager: FakeProcessManager.any(),
logger: logger,
artifacts: artifacts,
);
setUpIOSProject(fileSystem);
final FlutterProject flutterProject = FlutterProject.fromDirectory(fileSystem.currentDirectory);
final BuildableIOSApp buildableIOSApp = BuildableIOSApp(flutterProject.ios, 'flutter', 'My Super Awesome App.app');
final LaunchResult launchResult = await iosDevice.startApp(
buildableIOSApp,
debuggingOptions: DebuggingOptions.disabled(BuildInfo.release),
platformArgs: <String, Object>{},
);
expect(launchResult.started, false);
expect(logger.errorText, contains('Xcode project not found'));
}, overrides: <Type, Generator>{
ProcessManager: () => FakeProcessManager.any(),
FileSystem: () => fileSystem,
Logger: () => logger,
Platform: () => macPlatform,
XcodeProjectInterpreter: () => FakeXcodeProjectInterpreter(projectInfo: null),
Xcode: () => xcode,
});
testUsingContext('with buildable app', () async {
final IOSDevice iosDevice = setUpIOSDevice(
fileSystem: fileSystem,
@ -244,7 +314,8 @@ IOSDevice setUpIOSDevice({
);
logger ??= BufferLogger.test();
return IOSDevice('123',
return IOSDevice(
'123',
name: 'iPhone 1',
sdkVersion: sdkVersion,
fileSystem: fileSystem ?? MemoryFileSystem.test(),
@ -270,12 +341,27 @@ IOSDevice setUpIOSDevice({
}
class FakeXcodeProjectInterpreter extends Fake implements XcodeProjectInterpreter {
FakeXcodeProjectInterpreter({
@required this.projectInfo,
this.buildSettings = const <String, String>{
'TARGET_BUILD_DIR': 'build/ios/Release-iphoneos',
'WRAPPER_NAME': 'My Super Awesome App.app',
'DEVELOPMENT_TEAM': '3333CCCC33',
},
});
final Map<String, String> buildSettings;
final XcodeProjectInfo projectInfo;
@override
final bool isInstalled = true;
@override
final Version version = Version(1000, 0, 0);
@override
String get versionText => version.toString();
@override
List<String> xcrunCommand() => <String>['xcrun'];
@ -283,23 +369,12 @@ class FakeXcodeProjectInterpreter extends Fake implements XcodeProjectInterprete
Future<XcodeProjectInfo> getInfo(
String projectPath, {
String projectFilename,
}) async =>
XcodeProjectInfo(
<String>['Runner'],
<String>['Debug', 'Release'],
<String>['Runner'],
BufferLogger.test(),
);
}) async => projectInfo;
@override
Future<Map<String, String>> getBuildSettings(
String projectPath, {
@required XcodeProjectBuildContext buildContext,
Duration timeout = const Duration(minutes: 1),
}) async =>
<String, String>{
'TARGET_BUILD_DIR': 'build/ios/Release-iphoneos',
'WRAPPER_NAME': 'My Super Awesome App.app',
'DEVELOPMENT_TEAM': '3333CCCC33',
};
}) async => buildSettings;
}

View file

@ -2,8 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// @dart = 2.8
import 'package:file/file.dart';
import 'package:file/memory.dart';
import 'package:flutter_tools/src/artifacts.dart';
@ -23,15 +21,15 @@ import '../../src/fake_process_manager.dart';
import '../../src/fakes.dart';
void main() {
BufferLogger logger;
late BufferLogger logger;
setUp(() {
logger = BufferLogger.test();
});
group('IMobileDevice', () {
Artifacts artifacts;
Cache cache;
late Artifacts artifacts;
late Cache cache;
setUp(() {
artifacts = Artifacts.test();
@ -44,8 +42,8 @@ void main() {
});
group('screenshot', () {
FakeProcessManager fakeProcessManager;
File outputFile;
late FakeProcessManager fakeProcessManager;
late File outputFile;
setUp(() {
fakeProcessManager = FakeProcessManager.empty();
@ -131,8 +129,8 @@ void main() {
});
group('Diagnose Xcode build failure', () {
Map<String, String> buildSettings;
TestUsage testUsage;
late Map<String, String> buildSettings;
late TestUsage testUsage;
setUp(() {
buildSettings = <String, String>{
@ -445,7 +443,7 @@ Exited (sigterm)''',
});
group('remove Finder extended attributes', () {
Directory projectDirectory;
late Directory projectDirectory;
setUp(() {
final MemoryFileSystem fs = MemoryFileSystem.test();
projectDirectory = fs.directory('flutter_project');
@ -494,5 +492,5 @@ class FakeIosProject extends Fake implements IosProject {
Future<String> hostAppBundleName(BuildInfo buildInfo) async => 'UnitTestRunner.app';
@override
final Directory xcodeProject = null;
Directory get xcodeProject => xcodeProjectInfoFile.fileSystem.directory('Runner.xcodeproj');
}