Unverified Commit 72888c7f authored by stuartmorgan's avatar stuartmorgan Committed by GitHub

Harden macOS build use of Xcode project getInfo (#40375)

- Makes build_macos.dart handle the case where there is only one Xcode
  project in the macos/ directory, but it's not called Runner.xcodeproj
- Makes getInfo throw a tool exit when trying to get project info and it
  can't find a project, since that is a configuration error by the user
  rather than a tool bug.
parent 576a455a
......@@ -7,6 +7,7 @@ import 'dart:async';
import 'package:meta/meta.dart';
import '../artifacts.dart';
import '../base/common.dart';
import '../base/context.dart';
import '../base/file_system.dart';
import '../base/io.dart';
......@@ -331,6 +332,10 @@ class XcodeProjectInterpreter {
}
Future<XcodeProjectInfo> getInfo(String projectPath, {String projectFilename}) async {
// The exit code returned by 'xcodebuild -list' when either:
// * -project is passed and the given project isn't there, or
// * no -project is passed and there isn't a project.
const int missingProjectExitCode = 66;
final RunResult result = await processUtils.run(
<String>[
_executable,
......@@ -338,8 +343,12 @@ class XcodeProjectInterpreter {
if (projectFilename != null) ...<String>['-project', projectFilename],
],
throwOnError: true,
whiteListFailures: (int c) => c == missingProjectExitCode,
workingDirectory: projectPath,
);
if (result.exitCode == missingProjectExitCode) {
throwToolExit('Unable to get Xcode project information:\n ${result.stderr}');
}
return XcodeProjectInfo.fromXcodeBuildOutput(result.toString());
}
}
......
......@@ -46,9 +46,13 @@ Future<void> buildMacOS({
final Directory xcodeProject = flutterProject.macos.xcodeProject;
// If the standard project exists, specify it to getInfo to handle the case where there are
// other Xcode projects in the macos/ directory. Otherwise pass no name, which will work
// regardless of the project name so long as there is exactly one project.
final String xcodeProjectName = xcodeProject.existsSync() ? xcodeProject.basename : null;
final XcodeProjectInfo projectInfo = await xcodeProjectInterpreter.getInfo(
xcodeProject.parent.path,
projectFilename: xcodeProject.basename,
projectFilename: xcodeProjectName,
);
final String scheme = projectInfo.schemeFor(buildInfo);
if (scheme == null) {
......
......@@ -173,6 +173,53 @@ void main() {
ProcessManager: () => mockProcessManager,
});
});
group('xcodebuild -list', () {
mocks.MockProcessManager mockProcessManager;
FakePlatform macOS;
FileSystem fs;
setUp(() {
mockProcessManager = mocks.MockProcessManager();
macOS = fakePlatform('macos');
fs = MemoryFileSystem();
fs.file(xcodebuild).createSync(recursive: true);
});
void testUsingOsxContext(String description, dynamic testMethod()) {
testUsingContext(description, testMethod, overrides: <Type, Generator>{
ProcessManager: () => mockProcessManager,
Platform: () => macOS,
FileSystem: () => fs,
});
}
testUsingOsxContext('getInfo returns something when xcodebuild -list succeeds', () async {
const String workingDirectory = '/';
when(mockProcessManager.run(<String>[xcodebuild, '-list'],
environment: anyNamed('environment'),
workingDirectory: workingDirectory)).thenAnswer((_) {
return Future<ProcessResult>.value(ProcessResult(1, 0, '', ''));
});
final XcodeProjectInterpreter xcodeProjectInterpreter = XcodeProjectInterpreter();
expect(await xcodeProjectInterpreter.getInfo(workingDirectory), isNotNull);
});
testUsingOsxContext('getInfo throws a tool exit when it is unable to find a project', () async {
const String workingDirectory = '/';
const String stderr = 'Useful Xcode failure message about missing project.';
when(mockProcessManager.run(<String>[xcodebuild, '-list'],
environment: anyNamed('environment'),
workingDirectory: workingDirectory)).thenAnswer((_) {
return Future<ProcessResult>.value(ProcessResult(1, 66, '', stderr));
});
final XcodeProjectInterpreter xcodeProjectInterpreter = XcodeProjectInterpreter();
expect(
() async => await xcodeProjectInterpreter.getInfo(workingDirectory),
throwsToolExit(message: stderr));
});
});
group('Xcode project properties', () {
test('properties from default project can be parsed', () {
const String output = '''
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment