Skip to content

Commit

Permalink
feat(shorebird_cli): add xcodeVersion method to xcodebuild wrapper (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
bryanoltman authored Oct 5, 2023
1 parent 37944ac commit 9925017
Show file tree
Hide file tree
Showing 2 changed files with 133 additions and 0 deletions.
38 changes: 38 additions & 0 deletions packages/shorebird_cli/lib/src/executables/xcodebuild.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import 'dart:io';

import 'package:mason_logger/mason_logger.dart';
import 'package:path/path.dart' as p;
import 'package:pub_semver/pub_semver.dart';
import 'package:scoped/scoped.dart';
import 'package:shorebird_cli/src/process.dart';

Expand Down Expand Up @@ -106,4 +107,41 @@ class XcodeBuild {
schemes: schemes,
);
}

/// Gets the currently installed version of Xcode.
///
/// Invokes `xcodebuild -version` and parses the output.
/// Output is expected to be of the form:
///
/// $ /usr/bin/xcodebuild -version
/// Xcode 15.0
/// Build version 15A240d
Future<Version> xcodeVersion() async {
const arguments = ['-version'];
final result = await process.run(
executable,
arguments,
);

if (result.exitCode != ExitCode.success.code) {
throw ProcessException(executable, arguments, '${result.stderr}');
}

final lines = LineSplitter.split('${result.stdout}').map((e) => e.trim());
var versionString = lines.firstOrNull?.split(' ').lastOrNull;
if (versionString == null) {
throw FormatException(
'Could not parse Xcode version from output: "${result.stdout}".',
);
}

// [Version.parse] requires a patch number. If Xcode does not report a patch
// number (e.g. "12.0"), add a patch number of 0 (e.g. "12.0.0")
final noPachNumberRegex = RegExp(r'^\d+\.\d+$');
if (noPachNumberRegex.hasMatch(versionString)) {
versionString += '.0';
}

return Version.parse(versionString);
}
}
95 changes: 95 additions & 0 deletions packages/shorebird_cli/test/src/executables/xcodebuild_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import 'dart:io';
import 'package:mason_logger/mason_logger.dart';
import 'package:mocktail/mocktail.dart';
import 'package:path/path.dart' as p;
import 'package:pub_semver/pub_semver.dart';
import 'package:scoped/scoped.dart';
import 'package:shorebird_cli/src/executables/executables.dart';
import 'package:shorebird_cli/src/process.dart';
Expand All @@ -13,6 +14,7 @@ import '../mocks.dart';
void main() {
group(XcodeBuild, () {
late ShorebirdProcess process;
late ShorebirdProcessResult processResult;
late XcodeBuild xcodeBuild;

R runWithOverrides<R>(R Function() body) {
Expand All @@ -32,6 +34,7 @@ void main() {

setUp(() {
process = MockShorebirdProcess();
processResult = MockShorebirdProcessResult();
xcodeBuild = runWithOverrides(XcodeBuild.new);
});

Expand Down Expand Up @@ -132,5 +135,97 @@ To add iOS, run "flutter create . --platforms ios"''',
).called(1);
});
});

group('xcodeVersion', () {
late ExitCode exitCode;
late String stdout;

setUp(() {
when(() => process.run(XcodeBuild.executable, ['-version']))
.thenAnswer((_) async => processResult);
when(() => processResult.exitCode).thenAnswer((_) => exitCode.code);
when(() => processResult.stdout).thenAnswer((_) => stdout);
});

group('when a non-zero exit code is returned', () {
const errorMessage = 'An unexpected error occurred.';
setUp(() {
stdout = '';
exitCode = ExitCode.cantCreate;
when(() => processResult.stderr).thenReturn(errorMessage);
});

test('throws a ProcessException', () async {
expect(
() => runWithOverrides(xcodeBuild.xcodeVersion),
throwsA(
isA<ProcessException>()
.having((e) => e.message, 'message', errorMessage),
),
);
});
});

group('when stdout contains unexpected output', () {
setUp(() {
exitCode = ExitCode.success;
});

test('throws FormatException if output is empty', () async {
stdout = '';
expect(
() => runWithOverrides(xcodeBuild.xcodeVersion),
throwsA(
isA<FormatException>().having(
(e) => e.message,
'message',
'Could not parse Xcode version from output: "".',
),
),
);
});

test('throws FormatException if output does not contain version',
() async {
stdout = 'unexpected output';
expect(
() => runWithOverrides(xcodeBuild.xcodeVersion),
throwsA(
isA<FormatException>().having(
(e) => e.message,
'message',
'Could not parse "output".',
),
),
);
});
});

group('when stdout contains valid output', () {
setUp(() {
exitCode = ExitCode.success;
});

test('returns correct version with major, minor, and build numbers',
() async {
stdout = '''
Xcode 14.3.1
Build version 14E300c
''';
final version = await runWithOverrides(xcodeBuild.xcodeVersion);
expect(version, Version(14, 3, 1));
});

test('returns correct version with only major and minor numbers',
() async {
stdout = '''
Xcode 15.0
Build version 15A240d
''';
final version = await runWithOverrides(xcodeBuild.xcodeVersion);
expect(version, Version(15, 0, 0));
});
});
});
});
}

0 comments on commit 9925017

Please sign in to comment.