blob: 0191d71476cb419ecadfd333cc4b88729750636c [file] [log] [blame]
// Copyright 2020 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:io';
import 'package:path/path.dart' as path;
class DevToolsRepo {
DevToolsRepo._create(this.repoPath);
/// The path to the DevTools repository root.
final String repoPath;
/// The path to the DevTools 'tool' directory.
String get toolDirectoryPath => path.join(repoPath, 'tool');
/// The path to the 'tool/flutter-sdk' directory.
String get toolFlutterSdkPath =>
path.join(toolDirectoryPath, sdkDirectoryName);
/// The name of the Flutter SDK directory.
String get sdkDirectoryName => 'flutter-sdk';
/// The path to the DevTools 'devtools_app' directory.
String get devtoolsAppDirectoryPath =>
path.join(repoPath, 'packages', 'devtools_app');
@override
String toString() => '[DevTools $repoPath]';
/// This returns the DevToolsRepo instance based on the current working
/// directory.
///
/// Throws if the current working directory is not contained within a git
/// checkout of DevTools.
static DevToolsRepo getInstance() {
final repoPath = _findRepoRoot(Directory.current);
if (repoPath == null) {
throw Exception(
'devtools_tool must be run from inside of the DevTools repository directory',
);
}
return DevToolsRepo._create(repoPath);
}
List<Package> getPackages() {
final result = <Package>[];
final repoDir = Directory(repoPath);
for (FileSystemEntity entity in repoDir.listSync()) {
final name = path.basename(entity.path);
if (entity is Directory && !name.startsWith('.')) {
_collectPackages(entity, result);
}
}
result.sort((a, b) => a.packagePath.compareTo(b.packagePath));
return result;
}
static String? _findRepoRoot(Directory dir) {
// Look for README.md, packages, tool.
if (_fileExists(dir, 'README.md') &&
_dirExists(dir, 'packages') &&
_dirExists(dir, 'tool')) {
return dir.path;
}
if (dir.path == dir.parent.path) {
return null;
} else {
return _findRepoRoot(dir.parent);
}
}
void _collectPackages(Directory dir, List<Package> result) {
// Do not collect packages from the Flutter SDK that is stored in the tool/
// directory.
if (dir.path.contains('flutter-sdk/')) return;
// Do not include the top level devtools/packages directory in the results
// even though it has a pubspec.yaml file.
if (_fileExists(dir, 'pubspec.yaml') &&
!dir.path.endsWith('/devtools/packages')) {
result.add(Package._(this, dir.path));
}
for (FileSystemEntity entity in dir.listSync(followLinks: false)) {
final name = path.basename(entity.path);
if (entity is Directory && !name.startsWith('.') && name != 'build') {
_collectPackages(entity, result);
}
}
}
/// Reads the file at [uri], which should be a relative path from [repoPath].
String readFile(Uri uri) {
return File(path.join(repoPath, uri.path)).readAsStringSync();
}
}
class FlutterSdk {
FlutterSdk._(this.sdkPath);
static FlutterSdk? _current;
/// The current located Flutter SDK.
///
/// Tries to locate from the running Dart VM. If not found, will print a
/// warning and use Flutter from PATH.
static FlutterSdk get current {
if (_current == null) {
throw Exception(
'Cannot use FlutterSdk.current before SDK has been selected.'
'SDK selection is done by DevToolsCommandRunner.runCommand().',
);
}
return _current!;
}
/// Sets the active Flutter SDK to the one that contains the Dart VM being
/// used to run this script.
///
/// Throws if the current VM is not inside a Flutter SDK.
static void useFromCurrentVm() {
_current = findFromCurrentVm();
}
/// Sets the active Flutter SDK to the one found in the `PATH` environment
/// variable (by running which/where).
///
/// Throws if an SDK is not found on PATH.
static void useFromPathEnvironmentVariable() {
_current = findFromPathEnvironmentVariable();
}
/// Finds the Flutter SDK that contains the Dart VM being used to run this
/// script.
///
/// Throws if the current VM is not inside a Flutter SDK.
static FlutterSdk findFromCurrentVm() {
// Look for it relative to the current Dart process.
final dartVmPath = Platform.resolvedExecutable;
final pathSegments = path.split(dartVmPath);
// TODO(dantup): Should we add tool/flutter-sdk to the front here, to
// ensure we _only_ ever use this one, to avoid potentially updating a
// different Flutter if the user runs explicitly with another Flutter?
final expectedSegments = path.posix.split('bin/cache/dart-sdk/bin/dart');
if (pathSegments.length >= expectedSegments.length) {
// Remove the trailing 'dart'.
pathSegments.removeLast();
expectedSegments.removeLast();
while (expectedSegments.isNotEmpty) {
if (expectedSegments.last == pathSegments.last) {
pathSegments.removeLast();
expectedSegments.removeLast();
} else {
break;
}
}
if (expectedSegments.isEmpty) {
final flutterSdkRoot = path.joinAll(pathSegments);
return FlutterSdk._(flutterSdkRoot);
}
}
throw Exception(
'Unable to locate the Flutter SDK from the current running Dart VM:\n'
'${Platform.resolvedExecutable}',
);
}
/// Finds a Flutter SDK in the `PATH` environment variable
/// (by running which/where).
///
/// Throws if an SDK is not found on PATH.
static FlutterSdk findFromPathEnvironmentVariable() {
final whichCommand = Platform.isWindows ? 'where.exe' : 'which';
final result = Process.runSync(whichCommand, ['flutter']);
if (result.exitCode == 0) {
final sdkPath = result.stdout.toString().split('\n').first.trim();
// 'flutter/bin'
if (path.basename(path.dirname(sdkPath)) == 'bin') {
return FlutterSdk._(path.dirname(path.dirname(sdkPath)));
}
}
throw Exception(
'Unable to locate the Flutter SDK on PATH',
);
}
final String sdkPath;
static String get flutterExecutableName =>
Platform.isWindows ? 'flutter.bat' : 'flutter';
/// On windows, 'dart' is fine for running the .exe from the Dart SDK directly
/// but the wrapper in the Flutter bin folder is a .bat and needs an explicit
/// extension.
static String get dartWrapperExecutableName =>
Platform.isWindows ? 'dart.bat' : 'dart';
String get flutterExePath =>
path.join(sdkPath, 'bin', flutterExecutableName);
String get dartExePath =>
path.join(sdkPath, 'bin', dartWrapperExecutableName);
String get dartSdkPath => path.join(sdkPath, 'bin', 'cache', 'dart-sdk');
String get pubToolPath => path.join(dartSdkPath, 'bin', 'pub');
@override
String toString() => '[Flutter sdk: $sdkPath]';
}
class Package {
Package._(this.repo, this.packagePath);
final DevToolsRepo repo;
final String packagePath;
String get relativePath => path.relative(packagePath, from: repo.repoPath);
bool get hasAnyDartCode {
final dartFiles = <String>[];
_collectDartFiles(Directory(packagePath), dartFiles);
return dartFiles.isNotEmpty;
}
void _collectDartFiles(Directory dir, List<String> result) {
for (FileSystemEntity entity in dir.listSync(followLinks: false)) {
final name = path.basename(entity.path);
if (entity is Directory && !name.startsWith('.') && name != 'build') {
_collectDartFiles(entity, result);
} else if (entity is File && name.endsWith('.dart')) {
result.add(entity.path);
}
}
}
@override
String toString() => '[Package $relativePath]';
}
bool _fileExists(Directory parent, String name) {
return FileSystemEntity.isFileSync(path.join(parent.path, name));
}
bool _dirExists(Directory parent, String name) {
return FileSystemEntity.isDirectorySync(path.join(parent.path, name));
}