blob: 24c0b1b353739c36add08596161bbcf8616da231 [file] [log] [blame]
// Copyright 2014 The Flutter 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:package_config/package_config.dart';
import '../artifacts.dart';
import '../base/common.dart';
import '../base/file_system.dart';
import '../base/logger.dart';
import '../base/platform.dart';
import '../base/user_messages.dart' hide userMessages;
import '../cache.dart';
import '../dart/package_map.dart';
/// A strategy for locating the `out/` directory of a local engine build.
///
/// The flutter tool can be run with the output files of one or more engine builds
/// replacing the cached artifacts. Typically this is done by setting the
/// `--local-engine` command line flag to the name of the desired engine variant
/// (e.g. "host_debug_unopt"). Provided that the `flutter/` and `engine/` directories
/// are located adjacent to one another, the output folder will be located
/// automatically.
///
/// For scenarios where the engine is not adjacent to flutter, the
/// `--local-engine-src-path` can be provided to give an exact path.
///
/// For more information on local engines, see README.md.
class LocalEngineLocator {
LocalEngineLocator({
required Platform platform,
required Logger logger,
required FileSystem fileSystem,
required String flutterRoot,
required UserMessages userMessages,
}) : _platform = platform,
_logger = logger,
_fileSystem = fileSystem,
_flutterRoot = flutterRoot,
_userMessages = userMessages;
final Platform _platform;
final Logger _logger;
final FileSystem _fileSystem;
final String _flutterRoot;
final UserMessages _userMessages;
/// Returns the engine build path of a local engine if one is located, otherwise `null`.
Future<EngineBuildPaths?> findEnginePath({
String? engineSourcePath,
String? localEngine,
String? localHostEngine,
String? localWebSdk,
String? packagePath,
}) async {
engineSourcePath ??= _platform.environment[kFlutterEngineEnvironmentVariableName];
if (engineSourcePath == null && localEngine == null && localWebSdk == null && packagePath == null) {
return null;
}
if (engineSourcePath == null) {
try {
if (localEngine != null) {
engineSourcePath = _findEngineSourceByBuildPath(localEngine);
}
if (localWebSdk != null) {
engineSourcePath ??= _findEngineSourceByBuildPath(localWebSdk);
}
engineSourcePath ??= await _findEngineSourceByPackageConfig(packagePath);
} on FileSystemException catch (e) {
_logger.printTrace('Local engine auto-detection file exception: $e');
engineSourcePath = null;
}
// If engineSourcePath is still not set, try to determine it by flutter root.
engineSourcePath ??= _tryEnginePath(
_fileSystem.path.join(_fileSystem.directory(_flutterRoot).parent.path, 'engine', 'src'),
);
}
if (engineSourcePath != null && _tryEnginePath(engineSourcePath) == null) {
throwToolExit(
_userMessages.runnerNoEngineBuildDirInPath(engineSourcePath),
exitCode: 2,
);
}
if (engineSourcePath != null) {
_logger.printTrace('Local engine source at $engineSourcePath');
return _findEngineBuildPath(
engineSourcePath: engineSourcePath,
localEngine: localEngine,
localWebSdk: localWebSdk,
localHostEngine: localHostEngine,
);
}
if (localEngine != null || localWebSdk != null) {
throwToolExit(
_userMessages.runnerNoEngineSrcDir(
kFlutterEnginePackageName,
kFlutterEngineEnvironmentVariableName,
),
exitCode: 2,
);
}
return null;
}
String? _findEngineSourceByBuildPath(String buildPath) {
// When the local engine is an absolute path to a variant inside the
// out directory, parse the engine source.
// --local-engine /path/to/cache/builder/src/out/host_debug_unopt
if (_fileSystem.path.isAbsolute(buildPath)) {
final Directory buildDirectory = _fileSystem.directory(buildPath);
final Directory outDirectory = buildDirectory.parent;
final Directory srcDirectory = outDirectory.parent;
if (buildDirectory.existsSync() && outDirectory.basename == 'out' && srcDirectory.basename == 'src') {
_logger.printTrace('Parsed engine source from local engine as ${srcDirectory.path}.');
return srcDirectory.path;
}
}
return null;
}
Future<String?> _findEngineSourceByPackageConfig(String? packagePath) async {
final PackageConfig packageConfig = await loadPackageConfigWithLogging(
_fileSystem.file(
// TODO(zanderso): update to package_config
packagePath ?? _fileSystem.path.join('.packages'),
),
logger: _logger,
throwOnError: false,
);
// Skip if sky_engine is the version in bin/cache.
Uri? engineUri = packageConfig[kFlutterEnginePackageName]?.packageUriRoot;
final String cachedPath = _fileSystem.path.join(_flutterRoot, 'bin', 'cache', 'pkg', kFlutterEnginePackageName, 'lib');
if (engineUri != null && _fileSystem.identicalSync(cachedPath, engineUri.path)) {
_logger.printTrace('Local engine auto-detection sky_engine in $packagePath is the same version in bin/cache.');
engineUri = null;
}
// If sky_engine is specified and the engineSourcePath not set, try to
// determine the engineSourcePath by sky_engine setting. A typical engine Uri
// looks like:
// file://flutter-engine-local-path/src/out/host_debug_unopt/gen/dart-pkg/sky_engine/lib/
String? engineSourcePath;
final String? engineUriPath = engineUri?.path;
if (engineUriPath != null) {
engineSourcePath = _fileSystem.directory(engineUriPath)
.parent
.parent
.parent
.parent
.parent
.parent
.path;
if (engineSourcePath == _fileSystem.path.dirname(engineSourcePath) || engineSourcePath.isEmpty) {
engineSourcePath = null;
throwToolExit(
_userMessages.runnerNoEngineSrcDir(
kFlutterEnginePackageName,
kFlutterEngineEnvironmentVariableName,
),
exitCode: 2,
);
}
}
return engineSourcePath;
}
EngineBuildPaths _findEngineBuildPath({
required String engineSourcePath,
String? localEngine,
String? localWebSdk,
String? localHostEngine,
}) {
if (localEngine == null && localWebSdk == null) {
throwToolExit(_userMessages.runnerLocalEngineOrWebSdkRequired, exitCode: 2);
}
String? engineBuildPath;
String? engineHostBuildPath;
if (localEngine != null) {
engineBuildPath = _fileSystem.path.normalize(_fileSystem.path.join(engineSourcePath, 'out', localEngine));
if (!_fileSystem.isDirectorySync(engineBuildPath)) {
throwToolExit(_userMessages.runnerNoEngineBuild(engineBuildPath), exitCode: 2);
}
if (localHostEngine == null) {
throwToolExit(_userMessages.runnerLocalEngineRequiresHostEngine);
}
engineHostBuildPath = _fileSystem.path.normalize(
_fileSystem.path.join(_fileSystem.path.dirname(engineBuildPath), localHostEngine),
);
if (!_fileSystem.isDirectorySync(engineHostBuildPath)) {
throwToolExit(_userMessages.runnerNoEngineBuild(engineHostBuildPath), exitCode: 2);
}
}
String? webSdkPath;
if (localWebSdk != null) {
webSdkPath = _fileSystem.path.normalize(_fileSystem.path.join(engineSourcePath, 'out', localWebSdk));
if (!_fileSystem.isDirectorySync(webSdkPath)) {
throwToolExit(_userMessages.runnerNoWebSdk(webSdkPath), exitCode: 2);
}
}
return EngineBuildPaths(targetEngine: engineBuildPath, webSdk: webSdkPath, hostEngine: engineHostBuildPath);
}
String? _tryEnginePath(String enginePath) {
if (_fileSystem.isDirectorySync(_fileSystem.path.join(enginePath, 'out'))) {
return enginePath;
}
return null;
}
}