Commit 77efc38b authored by Jakob Andersen's avatar Jakob Andersen Committed by GitHub

Teach flutter tools to find gradle (#8241)

* Teach flutter tools to find gradle

Flutter tools will now use Gradle from Android Studio, which is now found automatically.

flutter doctor will verify that Android Studio has been installed, and that the included Gradle is at least version 2.14.1.

It is still possible to manually configure the path to Android Studio (flutter config --android-studio-dir=XXX) or Gradle (flutter config --gradle-dir=XXX), but this should only be necessary if they're installed somewhere non-standard.

Only tested on Linux and macOS for now.

Fixes #8131
parent df149c0b
......@@ -106,6 +106,7 @@ Future<Null> main(List<String> args) async {
context.putIfAbsent(FileSystem, () => new LocalFileSystem());
context.putIfAbsent(ProcessManager, () => new LocalProcessManager());
context.putIfAbsent(Logger, () => platform.isWindows ? new WindowsStdoutLogger() : new StdoutLogger());
context.putIfAbsent(Config, () => new Config());
// Order-independent context entries
context.putIfAbsent(DeviceManager, () => new DeviceManager());
......@@ -114,7 +115,6 @@ Future<Null> main(List<String> args) async {
context.putIfAbsent(HotRunnerConfig, () => new HotRunnerConfig());
context.putIfAbsent(Cache, () => new Cache());
context.putIfAbsent(Artifacts, () => new CachedArtifacts());
context.putIfAbsent(Config, () => new Config());
context.putIfAbsent(OperatingSystemUtils, () => new OperatingSystemUtils());
context.putIfAbsent(Xcode, () => new Xcode());
context.putIfAbsent(IOSSimulatorUtils, () => new IOSSimulatorUtils());
......
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:pub_semver/pub_semver.dart';
import '../base/common.dart';
import '../base/context.dart';
import '../base/file_system.dart';
import '../base/os.dart';
import '../base/platform.dart';
import '../base/process_manager.dart';
import '../globals.dart';
import '../ios/plist_utils.dart';
AndroidStudio get androidStudio =>
context.putIfAbsent(AndroidStudio, AndroidStudio.latestValid);
// Android Studio layout:
// Linux/Windows:
// $HOME/.AndroidStudioX.Y/system/.home
// macOS:
// /Applications/Android Studio.app/Contents/
// $HOME/Applications/Android Studio.app/Contents/
// $STUDIO_HOME/gradle/gradle-X.Y.Z/bin/gradle
final Version minGradleVersion = new Version(2, 14, 1);
/// Locate Gradle.
String get gradleExecutable {
// See if the user has explicitly configured gradle-dir.
String gradleDir = config.getValue('gradle-dir');
if (gradleDir != null) {
if (fs.isFileSync(gradleDir))
return gradleDir;
return fs.path.join(gradleDir, 'bin', 'gradle');
}
return androidStudio?.gradleExecutable ?? os.which('gradle')?.path;
}
class AndroidStudio implements Comparable<AndroidStudio> {
AndroidStudio(this.directory, {this.version = '0.0', this.configured}) {
_init();
}
final String directory;
final String version;
final String configured;
String _gradlePath;
bool _isValid = false;
List<String> _validationMessages = <String>[];
factory AndroidStudio.fromMacOSBundle(String bundlePath) {
String studioPath = fs.path.join(bundlePath, 'Contents');
String plistFile = fs.path.join(studioPath, 'Info.plist');
String version =
getValueFromFile(plistFile, kCFBundleShortVersionStringKey);
return new AndroidStudio(studioPath, version: version);
}
factory AndroidStudio.fromHomeDot(Directory homeDotDir) {
String version = homeDotDir.basename.substring('.AndroidStudio'.length);
String installPath;
try {
installPath = fs
.file(fs.path.join(homeDotDir.path, 'system', '.home'))
.readAsStringSync();
} catch (e) {
// ignored
}
if (installPath != null && fs.isDirectorySync(installPath)) {
return new AndroidStudio(installPath, version: version);
}
return null;
}
String get gradlePath => _gradlePath;
String get gradleExecutable => fs.path
.join(_gradlePath, 'bin', platform.isWindows ? 'gradle.bat' : 'gradle');
bool get isValid => _isValid;
List<String> get validationMessages => _validationMessages;
@override
int compareTo(AndroidStudio other) {
int result = version.compareTo(other.version);
if (result == 0)
return directory.compareTo(other.directory);
return result;
}
/// Locates the newest, valid version of Android Studio.
static AndroidStudio latestValid() {
String configuredStudio = config.getValue('android-studio-dir');
if (configuredStudio != null) {
String configuredStudioPath = configuredStudio;
if (os.isMacOS && !configuredStudioPath.endsWith('Contents'))
configuredStudioPath = fs.path.join(configuredStudioPath, 'Contents');
return new AndroidStudio(configuredStudioPath,
configured: configuredStudio);
}
// Find all available Studio installations.
List<AndroidStudio> studios = allInstalled();
if (studios.isEmpty) {
return null;
}
studios.sort();
return studios.lastWhere((AndroidStudio s) => s.isValid,
orElse: () => null);
}
static List<AndroidStudio> allInstalled() =>
platform.isMacOS ? _allMacOS() : _allLinuxOrWindows();
static List<AndroidStudio> _allMacOS() {
List<FileSystemEntity> candidatePaths = <FileSystemEntity>[];
void _checkForStudio(String path) {
List<Directory> directories = fs
.directory(path)
.listSync()
.where((FileSystemEntity e) => e is Directory);
for (Directory directory in directories) {
if (directory.basename == 'Android Studio.app') {
candidatePaths.add(directory);
} else if (!directory.path.endsWith('.app')) {
_checkForStudio(directory.path);
}
}
}
_checkForStudio('/Applications');
_checkForStudio(fs.path.join(homeDirPath, 'Applications'));
String configuredStudioDir = config.getValue('android-studio-dir');
if (configuredStudioDir != null) {
FileSystemEntity configuredStudio = fs.file(configuredStudioDir);
if (configuredStudio.basename == 'Contents') {
configuredStudio = configuredStudio.parent;
}
if (!candidatePaths
.any((FileSystemEntity e) => e.path == configuredStudio.path)) {
candidatePaths.add(configuredStudio);
}
}
return candidatePaths
.map((FileSystemEntity e) => new AndroidStudio.fromMacOSBundle(e.path))
.where((AndroidStudio s) => s != null)
.toList();
}
static List<AndroidStudio> _allLinuxOrWindows() {
List<AndroidStudio> studios = <AndroidStudio>[];
bool _hasStudioAt(String path, {String newerThan}) {
return studios.any((AndroidStudio studio) {
if (studio.directory != path) return false;
if (newerThan != null) {
return studio.version.compareTo(newerThan) >= 0;
}
return true;
});
}
// Read all $HOME/AndroidStudio*/system/.home files. There may be several
// pointing to the same installation, so we grab only the latest one.
for (FileSystemEntity entity in fs.directory(homeDirPath).listSync()) {
if (entity is Directory && entity.basename.startsWith('.AndroidStudio')) {
AndroidStudio studio = new AndroidStudio.fromHomeDot(entity);
if (studio != null &&
!_hasStudioAt(studio.directory, newerThan: studio.version)) {
studios.removeWhere(
(AndroidStudio other) => other.directory == studio.directory);
studios.add(studio);
}
}
}
String configuredStudioDir = config.getValue('android-studio-dir');
if (configuredStudioDir != null && !_hasStudioAt(configuredStudioDir)) {
studios.add(new AndroidStudio(configuredStudioDir,
configured: configuredStudioDir));
}
if (platform.isLinux) {
void _checkWellKnownPath(String path) {
if (fs.isDirectorySync(path) && !_hasStudioAt(path)) {
studios.add(new AndroidStudio(path));
}
}
// Add /opt/android-studio and $HOME/android-studio, if they exist.
_checkWellKnownPath('/opt/android-studio');
_checkWellKnownPath('$homeDirPath/android-studio');
}
return studios;
}
void _init() {
_isValid = false;
_validationMessages.clear();
if (configured != null) {
_validationMessages.add('android-studio-dir = $configured');
}
if (!fs.isDirectorySync(directory)) {
_validationMessages.add('Android Studio not found at $directory');
return;
}
Version latestGradleVersion;
List<FileSystemEntity> gradlePaths;
try {
gradlePaths = fs.directory(fs.path.join(directory, 'gradle')).listSync();
for (FileSystemEntity entry in gradlePaths.where((FileSystemEntity e) =>
e.basename.startsWith('gradle-') && e is Directory)) {
Version version =
new Version.parse(entry.basename.substring('gradle-'.length));
if (latestGradleVersion == null || version > latestGradleVersion) {
latestGradleVersion = version;
if (version >= minGradleVersion) {
_gradlePath = entry.path;
}
}
}
} catch (e) {
printTrace('Unable to determine Gradle version: $e');
}
if (latestGradleVersion == null) {
_validationMessages.add('Gradle not found.');
} else if (_gradlePath == null) {
_validationMessages.add('Gradle version $minGradleVersion required. '
'Found version $latestGradleVersion.');
} else if (processManager.canRun(gradleExecutable)) {
_isValid = true;
_validationMessages.add('Gradle version $latestGradleVersion');
} else {
_validationMessages.add(
'Gradle version $latestGradleVersion at $_gradlePath is not executable.');
}
}
@override
String toString() =>
version == '0.0' ? 'Android Studio (unknown)' : 'Android Studio $version';
}
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import 'package:pub_semver/pub_semver.dart';
import '../base/file_system.dart';
import '../base/io.dart';
import '../doctor.dart';
import '../globals.dart';
import '../base/platform.dart';
import '../base/process_manager.dart';
import 'android_studio.dart';
class AndroidStudioValidator extends DoctorValidator {
final AndroidStudio _studio;
AndroidStudioValidator(this._studio) : super('Android Studio');
static List<DoctorValidator> get allValidators {
List<DoctorValidator> validators = <DoctorValidator>[];
List<AndroidStudio> studios = AndroidStudio.allInstalled();
if (studios.isEmpty) {
validators.add(new NoAndroidStudioValidator());
} else {
validators.addAll(studios
.map((AndroidStudio studio) => new AndroidStudioValidator(studio)));
}
String cfgGradleDir = config.getValue('gradle-dir');
if (cfgGradleDir != null) {
validators.add(new ConfiguredGradleValidator(cfgGradleDir));
}
return validators;
}
@override
Future<ValidationResult> validate() async {
List<ValidationMessage> messages = <ValidationMessage>[];
ValidationType type = ValidationType.missing;
String studioVersionText = _studio.version == '0.0'
? 'unknown version'
: 'version ${_studio.version}';
messages
.add(new ValidationMessage('Android Studio at ${_studio.directory}'));
if (_studio.isValid) {
type = ValidationType.installed;
messages.addAll(_studio.validationMessages
.map((String m) => new ValidationMessage(m)));
} else {
type = ValidationType.partial;
messages.addAll(_studio.validationMessages
.map((String m) => new ValidationMessage.error(m)));
messages.add(new ValidationMessage(
'Try updating or re-installing Android Studio.'));
if (_studio.configured != null) {
messages.add(new ValidationMessage(
'Consider removing the android-studio-dir setting.'));
}
}
return new ValidationResult(type, messages, statusInfo: studioVersionText);
}
}
class NoAndroidStudioValidator extends DoctorValidator {
NoAndroidStudioValidator() : super('Android Studio');
@override
Future<ValidationResult> validate() async {
List<ValidationMessage> messages = <ValidationMessage>[];
String cfgAndroidStudio = config.getValue('android-studio-dir');
if (cfgAndroidStudio != null) {
messages.add(
new ValidationMessage.error('android-studio-dir = $cfgAndroidStudio\n'
'but Android Studio not found at this location.'));
}
messages.add(new ValidationMessage(
'Android Studio not found. Download from https://developer.android.com/studio/index.html\n'
'(or visit https://flutter.io/setup/#android-setup for detailed instructions).'));
return new ValidationResult(ValidationType.missing, messages,
statusInfo: 'not installed');
}
}
class ConfiguredGradleValidator extends DoctorValidator {
final String cfgGradleDir;
ConfiguredGradleValidator(this.cfgGradleDir) : super('Gradle');
@override
Future<ValidationResult> validate() async {
ValidationType type = ValidationType.missing;
List<ValidationMessage> messages = <ValidationMessage>[];
messages.add(new ValidationMessage('gradle-dir = $cfgGradleDir'));
String gradleExecutable = cfgGradleDir;
if (!fs.isFileSync(cfgGradleDir)) {
gradleExecutable = fs.path.join(
cfgGradleDir, 'bin', platform.isWindows ? 'gradle.bat' : 'gradle');
}
String version;
if (processManager.canRun(gradleExecutable)) {
type = ValidationType.partial;
ProcessResult result =
processManager.runSync(<String>[gradleExecutable, '--version']);
if (result.exitCode == 0) {
version = result.stdout
.toString()
.split('\n')
.firstWhere((String s) => s.startsWith('Gradle '))
.substring('Gradle '.length);
if (new Version.parse(version) >= minGradleVersion) {
type = ValidationType.installed;
} else {
messages.add(new ValidationMessage.error(
'Gradle version $minGradleVersion required. Found version $version.'));
}
} else {
messages
.add(new ValidationMessage('Unable to determine Gradle version.'));
}
} else {
messages
.add(new ValidationMessage('Gradle not found at $gradleExecutable'));
}
messages.add(new ValidationMessage(
'Consider removing the gradle-dir setting to use Gradle from Android Studio.'));
return new ValidationResult(type, messages, statusInfo: version);
}
}
......@@ -35,11 +35,11 @@ class AndroidWorkflow extends DoctorValidator implements Workflow {
String androidHomeDir = platform.environment[kAndroidHome];
messages.add(new ValidationMessage.error(
'$kAndroidHome = $androidHomeDir\n'
'but Android Studio / Android SDK not found at this location.'
'but Android SDK not found at this location.'
));
} else {
messages.add(new ValidationMessage.error(
'Android Studio / Android SDK not found. Download from https://developer.android.com/sdk/\n'
'Android SDK not found. Download from https://developer.android.com/sdk/\n'
'(or visit https://flutter.io/setup/#android-setup for detailed instructions).'
));
}
......@@ -63,7 +63,7 @@ class AndroidWorkflow extends DoctorValidator implements Workflow {
}
List<String> validationResult = androidSdk.validateSdkWellFormed();
// Empty result means SDK is well formated.
// Empty result means SDK is well formed.
if (validationResult.isEmpty) {
const String _kJdkDownload = 'https://www.oracle.com/technetwork/java/javase/downloads/';
......
......@@ -15,6 +15,7 @@ import '../build_info.dart';
import '../cache.dart';
import '../globals.dart';
import 'android_sdk.dart';
import 'android_studio.dart';
const String gradleManifestPath = 'android/app/src/main/AndroidManifest.xml';
const String gradleAppOutV1 = 'android/app/build/outputs/apk/app-debug.apk';
......@@ -67,7 +68,7 @@ String get gradleAppOut {
}
String locateSystemGradle({ bool ensureExecutable: true }) {
String gradle = _locateSystemGradle();
String gradle = gradleExecutable;
if (ensureExecutable && gradle != null) {
File file = fs.file(gradle);
if (file.existsSync())
......@@ -76,51 +77,6 @@ String locateSystemGradle({ bool ensureExecutable: true }) {
return gradle;
}
String _locateSystemGradle() {
// See if the user has explicitly configured gradle-dir.
String gradleDir = config.getValue('gradle-dir');
if (gradleDir != null) {
if (fs.isFileSync(gradleDir))
return gradleDir;
return fs.path.join(gradleDir, 'bin', 'gradle');
}
// Look relative to Android Studio.
String studioPath = config.getValue('android-studio-dir');
if (studioPath == null && os.isMacOS) {
final String kDefaultMacPath = '/Applications/Android Studio.app';
if (fs.isDirectorySync(kDefaultMacPath))
studioPath = kDefaultMacPath;
}
if (studioPath != null) {
// '/Applications/Android Studio.app/Contents/gradle/gradle-2.10/bin/gradle'
if (os.isMacOS && !studioPath.endsWith('Contents'))
studioPath = fs.path.join(studioPath, 'Contents');
Directory dir = fs.directory(fs.path.join(studioPath, 'gradle'));
if (dir.existsSync()) {
// We find the first valid gradle directory.
for (FileSystemEntity entity in dir.listSync()) {
if (entity is Directory && fs.path.basename(entity.path).startsWith('gradle-')) {
String executable = fs.path.join(entity.path, 'bin', 'gradle');
if (fs.isFileSync(executable))
return executable;
}
}
}
}
// Use 'which'.
File file = os.which('gradle');
if (file != null)
return file.path;
// We couldn't locate gradle.
return null;
}
String locateProjectGradlew({ bool ensureExecutable: true }) {
final String path = 'android/gradlew';
......@@ -133,43 +89,16 @@ String locateProjectGradlew({ bool ensureExecutable: true }) {
}
}
Future<String> ensureGradlew() async {
String gradlew = locateProjectGradlew();
if (gradlew == null) {
String gradle = locateSystemGradle();
Future<String> ensureGradle() async {
String gradle = locateProjectGradlew();
if (gradle == null) {
gradle = locateSystemGradle();
if (gradle == null) {
throwToolExit(
'Unable to locate gradle. Please configure the path to gradle using \'flutter config --gradle-dir\'.'
);
} else {
printTrace('Using gradle from $gradle.');
throwToolExit('Unable to locate gradle. Please install Android Studio.');
}
// Run 'gradle wrapper'.
List<String> command = logger.isVerbose
? <String>[gradle, 'wrapper']
: <String>[gradle, '-q', 'wrapper'];
try {
Status status = logger.startProgress('Running \'gradle wrapper\'...', expectSlowOperation: true);
int exitcode = await runCommandAndStreamOutput(
command,
workingDirectory: 'android',
allowReentrantFlutter: true
);
status.stop();
if (exitcode != 0)
throwToolExit('Gradle failed: $exitcode', exitCode: exitcode);
} catch (error) {
throwToolExit('$error');
}
gradlew = locateProjectGradlew();
if (gradlew == null)
throwToolExit('Unable to build android/gradlew.');
}
return gradlew;
printTrace('Using gradle from $gradle.');
return gradle;
}
Future<Null> buildGradleProject(BuildMode buildMode) async {
......@@ -190,48 +119,48 @@ Future<Null> buildGradleProject(BuildMode buildMode) async {
settings.values['flutter.buildMode'] = buildModeName;
settings.writeContents(localProperties);
String gradlew = await ensureGradlew();
String gradle = await ensureGradle();
switch (flutterPluginVersion) {
case FlutterPluginVersion.none:
// Fall through. Pretend it's v1, and just go for it.
case FlutterPluginVersion.v1:
return buildGradleProjectV1(gradlew);
return buildGradleProjectV1(gradle);
case FlutterPluginVersion.managed:
// Fall through. Managed plugin builds the same way as plugin v2.
case FlutterPluginVersion.v2:
return buildGradleProjectV2(gradlew, buildModeName);
return buildGradleProjectV2(gradle, buildModeName);
}
}
String _escapePath(String path) => platform.isWindows ? path.replaceAll('\\', '\\\\') : path;
Future<Null> buildGradleProjectV1(String gradlew) async {
// Run 'gradlew build'.
Status status = logger.startProgress('Running \'gradlew build\'...', expectSlowOperation: true);
Future<Null> buildGradleProjectV1(String gradle) async {
// Run 'gradle build'.
Status status = logger.startProgress('Running \'gradle build\'...', expectSlowOperation: true);
int exitcode = await runCommandAndStreamOutput(
<String>[fs.file(gradlew).absolute.path, 'build'],
<String>[fs.file(gradle).absolute.path, 'build'],
workingDirectory: 'android',
allowReentrantFlutter: true
);
status.stop();
if (exitcode != 0)
throwToolExit('Gradlew failed: $exitcode', exitCode: exitcode);
throwToolExit('Gradle build failed: $exitcode', exitCode: exitcode);
File apkFile = fs.file(gradleAppOutV1);
printStatus('Built $gradleAppOutV1 (${getSizeAsMB(apkFile.lengthSync())}).');
}
Future<Null> buildGradleProjectV2(String gradlew, String buildModeName) async {
Future<Null> buildGradleProjectV2(String gradle, String buildModeName) async {
String assembleTask = "assemble${toTitleCase(buildModeName)}";
// Run 'gradlew assemble<BuildMode>'.
Status status = logger.startProgress('Running \'gradlew $assembleTask\'...', expectSlowOperation: true);
String gradlewPath = fs.file(gradlew).absolute.path;
// Run 'gradle assemble<BuildMode>'.
Status status = logger.startProgress('Running \'gradle $assembleTask\'...', expectSlowOperation: true);
String gradlePath = fs.file(gradle).absolute.path;
List<String> command = logger.isVerbose
? <String>[gradlewPath, assembleTask]
: <String>[gradlewPath, '-q', assembleTask];
? <String>[gradlePath, assembleTask]
: <String>[gradlePath, '-q', assembleTask];
int exitcode = await runCommandAndStreamOutput(
command,
workingDirectory: 'android',
......@@ -240,7 +169,7 @@ Future<Null> buildGradleProjectV2(String gradlew, String buildModeName) async {
status.stop();
if (exitcode != 0)
throwToolExit('Gradlew failed: $exitcode', exitCode: exitcode);
throwToolExit('Gradle build failed: $exitcode', exitCode: exitcode);
String apkFilename = 'app-$buildModeName.apk';
File apkFile = fs.file('$gradleAppOutDir/$apkFilename');
......
......@@ -8,6 +8,7 @@ import 'dart:convert' show UTF8;
import 'package:archive/archive.dart';
import 'android/android_workflow.dart';
import 'android/android_studio_validator.dart';
import 'base/common.dart';
import 'base/context.dart';
import 'base/file_system.dart';
......@@ -15,6 +16,7 @@ import 'base/platform.dart';
import 'device.dart';
import 'globals.dart';
import 'ios/ios_workflow.dart';
import 'ios/plist_utils.dart';
import 'version.dart';
Doctor get doctor => context[Doctor];
......@@ -32,24 +34,8 @@ String osName() {
class Doctor {
Doctor() {
_validators.add(new _FlutterValidator());
_androidWorkflow = new AndroidWorkflow();
if (_androidWorkflow.appliesToHostPlatform)
_validators.add(_androidWorkflow);
_iosWorkflow = new IOSWorkflow();
if (_iosWorkflow.appliesToHostPlatform)
_validators.add(_iosWorkflow);
List<DoctorValidator> ideValidators = <DoctorValidator>[];
ideValidators.addAll(IntelliJValidator.installedValidators);
if (ideValidators.isNotEmpty)
_validators.addAll(ideValidators);
else
_validators.add(new NoIdeValidator());
_validators.add(new DeviceValidator());
}
IOSWorkflow _iosWorkflow;
......@@ -60,10 +46,34 @@ class Doctor {
AndroidWorkflow get androidWorkflow => _androidWorkflow;
List<DoctorValidator> _validators = <DoctorValidator>[];
List<DoctorValidator> _validators;
List<DoctorValidator> get validators {
if (_validators == null) {
_validators = <DoctorValidator>[];
_validators.add(new _FlutterValidator());
if (_androidWorkflow.appliesToHostPlatform)
_validators.add(_androidWorkflow);
if (_iosWorkflow.appliesToHostPlatform)
_validators.add(_iosWorkflow);
List<DoctorValidator> ideValidators = <DoctorValidator>[];
ideValidators.addAll(AndroidStudioValidator.allValidators);
ideValidators.addAll(IntelliJValidator.installedValidators);
if (ideValidators.isNotEmpty)
_validators.addAll(ideValidators);
else
_validators.add(new NoIdeValidator());
_validators.add(new DeviceValidator());
}
return _validators;
}
List<Workflow> get workflows {
return new List<Workflow>.from(_validators.where((DoctorValidator validator) => validator is Workflow));
return new List<Workflow>.from(validators.where((DoctorValidator validator) => validator is Workflow));
}
/// Print a summary of the state of the tooling, as well as how to get more info.
......@@ -76,7 +86,7 @@ class Doctor {
bool allGood = true;
for (DoctorValidator validator in _validators) {
for (DoctorValidator validator in validators) {
ValidationResult result = await validator.validate();
buffer.write('${result.leadingBox} ${validator.title} is ');
if (result.type == ValidationType.missing)
......@@ -108,7 +118,7 @@ class Doctor {
bool firstLine = true;
bool doctorResult = true;
for (DoctorValidator validator in _validators) {
for (DoctorValidator validator in validators) {
if (!firstLine)
printStatus('');
firstLine = false;
......@@ -434,23 +444,8 @@ class IntelliJValidatorOnMac extends IntelliJValidator {
@override
String get version {
if (_version == null) {
String plist;
try {
plist = fs.file(fs.path.join(installPath, 'Contents', 'Info.plist')).readAsStringSync();
int index = plist.indexOf('CFBundleShortVersionString');
if (index > 0) {
int start = plist.indexOf('<string>', index);
if (start > 0) {
int end = plist.indexOf('</string>', start);
if (end > 0) {
_version = plist.substring(start + 8, end);
}
}
}
} on FileSystemException catch (_) {
// ignored
}
_version ??= 'unknown';
String plistFile = fs.path.join(installPath, 'Contents', 'Info.plist');
_version = getValueFromFile(plistFile, kCFBundleShortVersionStringKey) ?? 'unknown';
}
return _version;
}
......
......@@ -6,6 +6,7 @@ import '../base/file_system.dart';
import '../base/process.dart';
const String kCFBundleIdentifierKey = "CFBundleIdentifier";
const String kCFBundleShortVersionStringKey = "CFBundleShortVersionString";
String getValueFromFile(String plistFilePath, String key) {
// TODO(chinmaygarde): For now, we only need to read from plist files on a mac
......
......@@ -46,6 +46,7 @@ void testUsingContext(String description, dynamic testMethod(), {
testContext.putIfAbsent(FileSystem, () => new LocalFileSystem());
testContext.putIfAbsent(ProcessManager, () => new LocalProcessManager());
testContext.putIfAbsent(Logger, () => new BufferLogger());
testContext.putIfAbsent(Config, () => new Config());
// Order-independent context entries
testContext.putIfAbsent(DeviceManager, () => new MockDeviceManager());
......@@ -54,7 +55,6 @@ void testUsingContext(String description, dynamic testMethod(), {
testContext.putIfAbsent(HotRunnerConfig, () => new HotRunnerConfig());
testContext.putIfAbsent(Cache, () => new Cache());
testContext.putIfAbsent(Artifacts, () => new CachedArtifacts());
testContext.putIfAbsent(Config, () => new Config());
testContext.putIfAbsent(OperatingSystemUtils, () {
MockOperatingSystemUtils os = new MockOperatingSystemUtils();
when(os.isWindows).thenReturn(false);
......
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