Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[flutter_tools] Remove Version.unknown #124771

Merged
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
97 changes: 58 additions & 39 deletions packages/flutter_tools/lib/src/android/android_studio.dart
Original file line number Diff line number Diff line change
Expand Up @@ -39,15 +39,16 @@ final RegExp _dotHomeStudioVersionMatcher =
// See https://github.com/flutter/flutter/issues/124252.
String? get javaPath => globals.androidStudio?.javaPath;

class AndroidStudio implements Comparable<AndroidStudio> {
class AndroidStudio {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did you move the comparison logic out of AndroidStudio.compareTo?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My instinct is to never implement Comparable unless the semantics of comparison are immediately obvious (i.e. do not require reading documentation or code). This makes sense for things like numbers and dates. It makes less sense for things like software installs where the version (or lack of it) and the directory name are involved.

I would consider consider the implementation if we had a need to compare AndroidStudio installs in multiple places in code, but we are only doing it in one place now.

I don't mind either way

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahh ok, if we were only comparing this once, this makes sense.

/// A [version] value of null represents an unknown version.
AndroidStudio(
this.directory, {
Version? version,
this.configured,
this.version,
this.configuredPath,
this.studioAppName = 'AndroidStudio',
this.presetPluginsPath,
}) : version = version ?? Version.unknown {
_init(version: version);
}) {
_initAndValidate();
}

static AndroidStudio? fromMacOSBundle(String bundlePath) {
Expand Down Expand Up @@ -147,8 +148,13 @@ class AndroidStudio implements Comparable<AndroidStudio> {

final String directory;
final String studioAppName;
final Version version;
final String? configured;

/// The version of Android Studio.
///
/// A null value represents an unknown version.
final Version? version;

final String? configuredPath;
final String? presetPluginsPath;

String? _javaPath;
Expand All @@ -163,8 +169,12 @@ class AndroidStudio implements Comparable<AndroidStudio> {
if (presetPluginsPath != null) {
return presetPluginsPath!;
}
final int major = version.major;
final int minor = version.minor;

// TODO(andrewkolos): This is a bug. We shouldn't treat an unknown
// version as equivalent to 0.0.
// See https://github.com/flutter/flutter/issues/121468.
final int major = version?.major ?? 0;
final int minor = version?.minor ?? 0;
final String? homeDirPath = globals.fsUtils.homeDirPath;
if (homeDirPath == null) {
return null;
Expand Down Expand Up @@ -217,25 +227,20 @@ class AndroidStudio implements Comparable<AndroidStudio> {

List<String> get validationMessages => _validationMessages;

@override
int compareTo(AndroidStudio other) {
final int result = version.compareTo(other.version);
if (result == 0) {
return directory.compareTo(other.directory);
}
return result;
}

/// Locates the newest, valid version of Android Studio.
///
/// In the case that `--android-studio-dir` is configured, the version of
/// Android Studio found at that location is always returned, even if it is
/// invalid.
static AndroidStudio? latestValid() {
final String? configuredStudio = globals.config.getValue('android-studio-dir') as String?;
if (configuredStudio != null) {
String configuredStudioPath = configuredStudio;
if (globals.platform.isMacOS && !configuredStudioPath.endsWith('Contents')) {
configuredStudioPath = globals.fs.path.join(configuredStudioPath, 'Contents');
final String? configuredStudioPath = globals.config.getValue('android-studio-dir') as String?;
if (configuredStudioPath != null) {
String correctedConfiguredStudioPath = configuredStudioPath;
if (globals.platform.isMacOS && !correctedConfiguredStudioPath.endsWith('Contents')) {
correctedConfiguredStudioPath = globals.fs.path.join(correctedConfiguredStudioPath, 'Contents');
}
return AndroidStudio(configuredStudioPath,
configured: configuredStudio);
return AndroidStudio(correctedConfiguredStudioPath,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know we used to do this, but should we not toolexit in the case where correctedConfiguredStudioPath does not exist on disk? This most likely means they either made a mistake setting their config value, or forgot they set that and then deleted Android Studio.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added this and a test

configuredPath: configuredStudioPath);
}

// Find all available Studio installations.
Expand All @@ -245,7 +250,18 @@ class AndroidStudio implements Comparable<AndroidStudio> {
}
AndroidStudio? newest;
for (final AndroidStudio studio in studios.where((AndroidStudio s) => s.isValid)) {
if (newest == null || studio.compareTo(newest) > 0) {
if (newest == null) {
newest = studio;
continue;
}

if (studio.version != null && newest.version == null) {
newest = studio;
} else if (studio.version == null && newest.version == null &&
studio.directory.compareTo(newest.directory) > 0) {

This comment was marked as outdated.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, yeesh, so we're just alphabetically comparing these strings?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let me file a tracking issue about investigating whether we should really be doing this.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a TODO with a link to #124880

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, yeesh, so we're just alphabetically comparing these strings?

Only if 1) both installations have equivalent versions or 2) neither installation have known versions. I wonder what the original inspiration for this was...perhaps it was just a way keep ordering consistent even if the discovery logic changed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, I might be slightly incorrect here. Let me re-examine the original code.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, reading this more, I can see this is a last-resort fallback.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I have the logic right. I added another test

newest = studio;
} else if (studio.version != null && newest.version != null &&
studio.version! > newest.version!) {
newest = studio;
}
}
Expand Down Expand Up @@ -331,13 +347,16 @@ class AndroidStudio implements Comparable<AndroidStudio> {
static List<AndroidStudio> _allLinuxOrWindows() {
final List<AndroidStudio> studios = <AndroidStudio>[];

bool hasStudioAt(String path, { Version? newerThan }) {
bool alreadyFoundStudioAt(String path, { Version? newerThan }) {
return studios.any((AndroidStudio studio) {
if (studio.directory != path) {
return false;
}
if (newerThan != null) {
return studio.version.compareTo(newerThan) >= 0;
if (studio.version == null) {
return false;
}
return studio.version!.compareTo(newerThan) >= 0;
}
return true;
});
Expand Down Expand Up @@ -373,7 +392,7 @@ class AndroidStudio implements Comparable<AndroidStudio> {

for (final Directory entity in entities) {
final AndroidStudio? studio = AndroidStudio.fromHomeDot(entity);
if (studio != null && !hasStudioAt(studio.directory, newerThan: studio.version)) {
if (studio != null && !alreadyFoundStudioAt(studio.directory, newerThan: studio.version)) {
studios.removeWhere((AndroidStudio other) => other.directory == studio.directory);
studios.add(studio);
}
Expand Down Expand Up @@ -404,7 +423,7 @@ class AndroidStudio implements Comparable<AndroidStudio> {
version: Version.parse(version),
studioAppName: title,
);
if (!hasStudioAt(studio.directory, newerThan: studio.version)) {
if (!alreadyFoundStudioAt(studio.directory, newerThan: studio.version)) {
studios.removeWhere((AndroidStudio other) => other.directory == studio.directory);
studios.add(studio);
}
Expand All @@ -415,14 +434,14 @@ class AndroidStudio implements Comparable<AndroidStudio> {
}

final String? configuredStudioDir = globals.config.getValue('android-studio-dir') as String?;
if (configuredStudioDir != null && !hasStudioAt(configuredStudioDir)) {
if (configuredStudioDir != null && !alreadyFoundStudioAt(configuredStudioDir)) {
studios.add(AndroidStudio(configuredStudioDir,
configured: configuredStudioDir));
configuredPath: configuredStudioDir));
}

if (globals.platform.isLinux) {
void checkWellKnownPath(String path) {
if (globals.fs.isDirectorySync(path) && !hasStudioAt(path)) {
if (globals.fs.isDirectorySync(path) && !alreadyFoundStudioAt(path)) {
studios.add(AndroidStudio(path));
}
}
Expand All @@ -438,12 +457,12 @@ class AndroidStudio implements Comparable<AndroidStudio> {
return keyMatcher.stringMatch(plistValue)?.split('=').last.trim().replaceAll('"', '');
}

void _init({Version? version}) {
void _initAndValidate() {
_isValid = false;
_validationMessages.clear();

if (configured != null) {
_validationMessages.add('android-studio-dir = $configured');
if (configuredPath != null) {
_validationMessages.add('android-studio-dir = $configuredPath');
}

if (!globals.fs.isDirectorySync(directory)) {
Expand All @@ -453,15 +472,15 @@ class AndroidStudio implements Comparable<AndroidStudio> {

final String javaPath;
if (globals.platform.isMacOS) {
if (version != null && version.major < 2020) {
if (version != null && version!.major < 2020) {
javaPath = globals.fs.path.join(directory, 'jre', 'jdk', 'Contents', 'Home');
} else if (version != null && version.major == 2022) {
} else if (version != null && version!.major == 2022) {
javaPath = globals.fs.path.join(directory, 'jbr', 'Contents', 'Home');
} else {
javaPath = globals.fs.path.join(directory, 'jre', 'Contents', 'Home');
}
} else {
if (version != null && version.major == 2022) {
if (version != null && version!.major == 2022) {
javaPath = globals.fs.path.join(directory, 'jbr');
} else {
javaPath = globals.fs.path.join(directory, 'jre');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import '../base/config.dart';
import '../base/file_system.dart';
import '../base/platform.dart';
import '../base/user_messages.dart';
import '../base/version.dart';
import '../doctor_validator.dart';
import '../intellij/intellij.dart';
import 'android_studio.dart';
Expand Down Expand Up @@ -46,7 +45,7 @@ class AndroidStudioValidator extends DoctorValidator {
final List<ValidationMessage> messages = <ValidationMessage>[];
ValidationType type = ValidationType.missing;

final String? studioVersionText = _studio.version == Version.unknown
final String? studioVersionText = _studio.version == null
? null
: userMessages.androidStudioVersion(_studio.version.toString());
messages.add(ValidationMessage(
Expand Down Expand Up @@ -83,7 +82,7 @@ class AndroidStudioValidator extends DoctorValidator {
(String m) => ValidationMessage.error(m),
));
messages.add(ValidationMessage(userMessages.androidStudioNeedsUpdate));
if (_studio.configured != null) {
if (_studio.configuredPath != null) {
messages.add(ValidationMessage(userMessages.androidStudioResetDir));
}
}
Expand Down
18 changes: 11 additions & 7 deletions packages/flutter_tools/lib/src/base/version.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,20 @@

import 'package:meta/meta.dart';

// TODO(reidbaker): Investigate using pub_semver instead of this class.
/// Represents the version of some piece of software.
///
/// While a [Version] object has fields resembling semver, it does not
/// necessarily represent a semver version.
@immutable
class Version implements Comparable<Version> {
/// Creates a new [Version] object.
factory Version(int? major, int? minor, int? patch, {String? text}) {
///
/// A null [minor] or [patch] version is logically equivalent to 0. Using null
/// for these parameters only affects the generation of [text], if no value
/// for it is provided.
factory Version(int major, int? minor, int? patch, {String? text}) {
if (text == null) {
text = major == null ? '0' : '$major';
text = '$major';
if (minor != null) {
text = '$text.$minor';
}
Expand All @@ -19,7 +26,7 @@ class Version implements Comparable<Version> {
}
}

return Version._(major ?? 0, minor ?? 0, patch ?? 0, text);
return Version._(major, minor ?? 0, patch ?? 0, text);
}

/// Public constant constructor when all fields are non-null, without default value fallbacks.
Expand Down Expand Up @@ -67,9 +74,6 @@ class Version implements Comparable<Version> {
return primary;
}


static Version get unknown => Version(0, 0, 0, text: 'unknown');

/// The major version number: "1" in "1.2.3".
final int major;

Expand Down
2 changes: 1 addition & 1 deletion packages/flutter_tools/lib/src/commands/config.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ class ConfigCommand extends FlutterCommand {
negatable: false,
help: 'Clear the saved development certificate choice used to sign apps for iOS device deployment.');
argParser.addOption('android-sdk', help: 'The Android SDK directory.');
argParser.addOption('android-studio-dir', help: 'The Android Studio install directory.');
argParser.addOption('android-studio-dir', help: 'The Android Studio install directory. If unset, flutter will search for valid installs at well-known locations.');
argParser.addOption('build-dir', help: 'The relative path to override a projects build directory.',
valueHelp: 'out/');
argParser.addFlag('machine',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,11 +125,6 @@ abstract class IntelliJValidator extends DoctorValidator {
}

void _validateIntelliJVersion(List<ValidationMessage> messages, Version minVersion) {
// Ignore unknown versions.
if (minVersion == Version.unknown) {
return;
}

final Version? installedVersion = Version.parse(version);
if (installedVersion == null) {
return;
Expand Down
7 changes: 3 additions & 4 deletions packages/flutter_tools/lib/src/vscode/vscode.dart
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,7 @@ const String extensionMarketplaceUrl =
'https://marketplace.visualstudio.com/items?itemName=$extensionIdentifier';

class VsCode {
VsCode._(this.directory, this.extensionDirectory, { Version? version, this.edition, required FileSystem fileSystem})
: version = version ?? Version.unknown {
VsCode._(this.directory, this.extensionDirectory, { this.version, this.edition, required FileSystem fileSystem}) {

if (!fileSystem.isDirectorySync(directory)) {
_validationMessages.add(ValidationMessage.error('VS Code not found at $directory'));
Expand Down Expand Up @@ -76,7 +75,7 @@ class VsCode {

final String directory;
final String extensionDirectory;
final Version version;
final Version? version;
final String? edition;

Version? _extensionVersion;
Expand Down Expand Up @@ -284,7 +283,7 @@ class VsCode {

@override
String toString() =>
'VS Code ($version)${_extensionVersion != Version.unknown ? ', Flutter ($_extensionVersion)' : ''}';
'VS Code ($version)${_extensionVersion != null ? ', Flutter ($_extensionVersion)' : ''}';

static String? _getVersionFromPackageJson(String packageJsonPath, FileSystem fileSystem) {
if (!fileSystem.isFileSync(packageJsonPath)) {
Expand Down
3 changes: 1 addition & 2 deletions packages/flutter_tools/lib/src/vscode/vscode_validator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import 'package:process/process.dart';
import '../base/file_system.dart';
import '../base/platform.dart';
import '../base/user_messages.dart';
import '../base/version.dart';
import '../doctor_validator.dart';
import 'vscode.dart';

Expand All @@ -24,7 +23,7 @@ class VsCodeValidator extends DoctorValidator {

@override
Future<ValidationResult> validate() async {
final String? vsCodeVersionText = _vsCode.version == Version.unknown
final String? vsCodeVersionText = _vsCode.version == null
? null
: userMessages.vsCodeVersion(_vsCode.version.toString());

Expand Down