
* Make pub get runner respect printProgress and retry parameters * Fix typo * Add regression test * Improve test * Fix implementation and test * Test to fix flutter_drone tests * Revert test * Attempt #2 to fix flutter_drone tests * Revert attempt * Hack: Force printProgress to debug Windows tests * Use ProcessUtils.run to avoid dangling stdout and stderr * Update documentation * Clean up retry argument
836 lines
30 KiB
Dart
836 lines
30 KiB
Dart
// 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 'dart:async';
|
|
|
|
import 'package:meta/meta.dart';
|
|
import 'package:package_config/package_config.dart';
|
|
import 'package:process/process.dart';
|
|
|
|
import '../base/bot_detector.dart';
|
|
import '../base/common.dart';
|
|
import '../base/context.dart';
|
|
import '../base/file_system.dart';
|
|
import '../base/io.dart' as io;
|
|
import '../base/io.dart';
|
|
import '../base/logger.dart';
|
|
import '../base/platform.dart';
|
|
import '../base/process.dart';
|
|
import '../cache.dart';
|
|
import '../convert.dart';
|
|
import '../dart/package_map.dart';
|
|
import '../project.dart';
|
|
import '../reporting/reporting.dart';
|
|
|
|
/// The [Pub] instance.
|
|
Pub get pub => context.get<Pub>()!;
|
|
|
|
/// The console environment key used by the pub tool.
|
|
const String _kPubEnvironmentKey = 'PUB_ENVIRONMENT';
|
|
|
|
/// The console environment key used by the pub tool to find the cache directory.
|
|
const String _kPubCacheEnvironmentKey = 'PUB_CACHE';
|
|
|
|
typedef MessageFilter = String? Function(String message);
|
|
|
|
/// globalCachePath is the directory in which the content of the localCachePath will be moved in
|
|
void joinCaches({
|
|
required FileSystem fileSystem,
|
|
required Directory globalCacheDirectory,
|
|
required Directory dependencyDirectory,
|
|
}) {
|
|
for (final FileSystemEntity entity in dependencyDirectory.listSync()) {
|
|
final String newPath = fileSystem.path.join(globalCacheDirectory.path, entity.basename);
|
|
if (entity is File) {
|
|
if (!fileSystem.file(newPath).existsSync()) {
|
|
entity.copySync(newPath);
|
|
}
|
|
} else if (entity is Directory) {
|
|
if (!globalCacheDirectory.childDirectory(entity.basename).existsSync()) {
|
|
final Directory newDirectory = globalCacheDirectory.childDirectory(entity.basename);
|
|
newDirectory.createSync();
|
|
joinCaches(
|
|
fileSystem: fileSystem,
|
|
globalCacheDirectory: newDirectory,
|
|
dependencyDirectory: entity,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Directory createDependencyDirectory(Directory pubGlobalDirectory, String dependencyName) {
|
|
final Directory newDirectory = pubGlobalDirectory.childDirectory(dependencyName);
|
|
newDirectory.createSync();
|
|
return newDirectory;
|
|
}
|
|
|
|
bool tryDelete(Directory directory, Logger logger) {
|
|
try {
|
|
if (directory.existsSync()) {
|
|
directory.deleteSync(recursive: true);
|
|
}
|
|
} on FileSystemException {
|
|
logger.printWarning('Failed to delete directory at: ${directory.path}');
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/// When local cache (flutter_root/.pub-cache) and global cache (HOME/.pub-cache) are present a
|
|
/// merge needs to be done leaving only the global
|
|
///
|
|
/// Valid pubCache should look like this ./localCachePath/.pub-cache/hosted/pub.dartlang.org
|
|
bool needsToJoinCache({
|
|
required FileSystem fileSystem,
|
|
required String localCachePath,
|
|
required Directory? globalDirectory,
|
|
}) {
|
|
if (globalDirectory == null) {
|
|
return false;
|
|
}
|
|
final Directory localDirectory = fileSystem.directory(localCachePath);
|
|
|
|
return globalDirectory.childDirectory('hosted').childDirectory('pub.dartlang.org').existsSync() &&
|
|
localDirectory.childDirectory('hosted').childDirectory('pub.dartlang.org').existsSync();
|
|
}
|
|
|
|
/// Represents Flutter-specific data that is added to the `PUB_ENVIRONMENT`
|
|
/// environment variable and allows understanding the type of requests made to
|
|
/// the package site on Flutter's behalf.
|
|
// DO NOT update without contacting kevmoo.
|
|
// We have server-side tooling that assumes the values are consistent.
|
|
class PubContext {
|
|
PubContext._(this._values) {
|
|
for (final String item in _values) {
|
|
if (!_validContext.hasMatch(item)) {
|
|
throw ArgumentError.value(
|
|
_values, 'value', 'Must match RegExp ${_validContext.pattern}');
|
|
}
|
|
}
|
|
}
|
|
|
|
static PubContext getVerifyContext(String commandName) =>
|
|
PubContext._(<String>['verify', commandName.replaceAll('-', '_')]);
|
|
|
|
static final PubContext create = PubContext._(<String>['create']);
|
|
static final PubContext createPackage = PubContext._(<String>['create_pkg']);
|
|
static final PubContext createPlugin = PubContext._(<String>['create_plugin']);
|
|
static final PubContext interactive = PubContext._(<String>['interactive']);
|
|
static final PubContext pubGet = PubContext._(<String>['get']);
|
|
static final PubContext pubUpgrade = PubContext._(<String>['upgrade']);
|
|
static final PubContext pubForward = PubContext._(<String>['forward']);
|
|
static final PubContext runTest = PubContext._(<String>['run_test']);
|
|
static final PubContext flutterTests = PubContext._(<String>['flutter_tests']);
|
|
static final PubContext updatePackages = PubContext._(<String>['update_packages']);
|
|
|
|
final List<String> _values;
|
|
|
|
static final RegExp _validContext = RegExp('[a-z][a-z_]*[a-z]');
|
|
|
|
@override
|
|
String toString() => 'PubContext: ${_values.join(':')}';
|
|
|
|
String toAnalyticsString() {
|
|
return _values.map((String s) => s.replaceAll('_', '-')).toList().join('-');
|
|
}
|
|
}
|
|
|
|
/// A handle for interacting with the pub tool.
|
|
abstract class Pub {
|
|
/// Create a default [Pub] instance.
|
|
factory Pub({
|
|
required FileSystem fileSystem,
|
|
required Logger logger,
|
|
required ProcessManager processManager,
|
|
required Platform platform,
|
|
required BotDetector botDetector,
|
|
required Usage usage,
|
|
}) = _DefaultPub;
|
|
|
|
/// Create a [Pub] instance with a mocked [stdio].
|
|
@visibleForTesting
|
|
factory Pub.test({
|
|
required FileSystem fileSystem,
|
|
required Logger logger,
|
|
required ProcessManager processManager,
|
|
required Platform platform,
|
|
required BotDetector botDetector,
|
|
required Usage usage,
|
|
required Stdio stdio,
|
|
}) = _DefaultPub.test;
|
|
|
|
/// Runs `pub get` or `pub upgrade` for [project].
|
|
///
|
|
/// [context] provides extra information to package server requests to
|
|
/// understand usage.
|
|
///
|
|
/// If [shouldSkipThirdPartyGenerator] is true, the overall pub get will be
|
|
/// skipped if the package config file has a "generator" other than "pub".
|
|
/// Defaults to true.
|
|
/// Will also resolve dependencies in the example folder if present.
|
|
Future<void> get({
|
|
required PubContext context,
|
|
required FlutterProject project,
|
|
bool skipIfAbsent = false,
|
|
bool upgrade = false,
|
|
bool offline = false,
|
|
String? flutterRootOverride,
|
|
bool checkUpToDate = false,
|
|
bool shouldSkipThirdPartyGenerator = true,
|
|
bool printProgress = true,
|
|
});
|
|
|
|
/// Runs pub in 'batch' mode.
|
|
///
|
|
/// forwarding complete lines written by pub to its stdout/stderr streams to
|
|
/// the corresponding stream of this process, optionally applying filtering.
|
|
/// The pub process will not receive anything on its stdin stream.
|
|
///
|
|
/// The `--trace` argument is passed to `pub` when `showTraceForErrors`
|
|
/// `isRunningOnBot` is true.
|
|
///
|
|
/// [context] provides extra information to package server requests to
|
|
/// understand usage.
|
|
Future<void> batch(
|
|
List<String> arguments, {
|
|
required PubContext context,
|
|
String? directory,
|
|
MessageFilter? filter,
|
|
String failureMessage = 'pub failed',
|
|
});
|
|
|
|
/// Runs pub in 'interactive' mode.
|
|
///
|
|
/// directly piping the stdin stream of this process to that of pub, and the
|
|
/// stdout/stderr stream of pub to the corresponding streams of this process.
|
|
Future<void> interactively(
|
|
List<String> arguments, {
|
|
String? directory,
|
|
required io.Stdio stdio,
|
|
bool touchesPackageConfig = false,
|
|
bool generateSyntheticPackage = false,
|
|
});
|
|
}
|
|
|
|
class _DefaultPub implements Pub {
|
|
_DefaultPub({
|
|
required FileSystem fileSystem,
|
|
required Logger logger,
|
|
required ProcessManager processManager,
|
|
required Platform platform,
|
|
required BotDetector botDetector,
|
|
required Usage usage,
|
|
}) : _fileSystem = fileSystem,
|
|
_logger = logger,
|
|
_platform = platform,
|
|
_botDetector = botDetector,
|
|
_usage = usage,
|
|
_processUtils = ProcessUtils(
|
|
logger: logger,
|
|
processManager: processManager,
|
|
),
|
|
_processManager = processManager,
|
|
_stdio = null;
|
|
|
|
@visibleForTesting
|
|
_DefaultPub.test({
|
|
required FileSystem fileSystem,
|
|
required Logger logger,
|
|
required ProcessManager processManager,
|
|
required Platform platform,
|
|
required BotDetector botDetector,
|
|
required Usage usage,
|
|
required Stdio stdio,
|
|
}) : _fileSystem = fileSystem,
|
|
_logger = logger,
|
|
_platform = platform,
|
|
_botDetector = botDetector,
|
|
_usage = usage,
|
|
_processUtils = ProcessUtils(
|
|
logger: logger,
|
|
processManager: processManager,
|
|
),
|
|
_processManager = processManager,
|
|
_stdio = stdio;
|
|
|
|
final FileSystem _fileSystem;
|
|
final Logger _logger;
|
|
final ProcessUtils _processUtils;
|
|
final Platform _platform;
|
|
final BotDetector _botDetector;
|
|
final Usage _usage;
|
|
final ProcessManager _processManager;
|
|
final Stdio? _stdio;
|
|
|
|
@override
|
|
Future<void> get({
|
|
required PubContext context,
|
|
required FlutterProject project,
|
|
bool skipIfAbsent = false,
|
|
bool upgrade = false,
|
|
bool offline = false,
|
|
bool generateSyntheticPackage = false,
|
|
bool generateSyntheticPackageForExample = false,
|
|
String? flutterRootOverride,
|
|
bool checkUpToDate = false,
|
|
bool shouldSkipThirdPartyGenerator = true,
|
|
bool printProgress = true,
|
|
}) async {
|
|
final String directory = project.directory.path;
|
|
final File packageConfigFile = project.packageConfigFile;
|
|
final Directory generatedDirectory = _fileSystem.directory(
|
|
_fileSystem.path.join(directory, '.dart_tool', 'flutter_gen'));
|
|
final File lastVersion = _fileSystem.file(
|
|
_fileSystem.path.join(directory, '.dart_tool', 'version'));
|
|
final File currentVersion = _fileSystem.file(
|
|
_fileSystem.path.join(Cache.flutterRoot!, 'version'));
|
|
final File pubspecYaml = project.pubspecFile;
|
|
final File pubLockFile = _fileSystem.file(
|
|
_fileSystem.path.join(directory, 'pubspec.lock')
|
|
);
|
|
|
|
if (shouldSkipThirdPartyGenerator && project.packageConfigFile.existsSync()) {
|
|
Map<String, Object?> packageConfigMap;
|
|
try {
|
|
packageConfigMap = jsonDecode(
|
|
project.packageConfigFile.readAsStringSync(),
|
|
) as Map<String, Object?>;
|
|
} on FormatException {
|
|
packageConfigMap = <String, Object?>{};
|
|
}
|
|
|
|
final bool isPackageConfigGeneratedByThirdParty =
|
|
packageConfigMap.containsKey('generator') &&
|
|
packageConfigMap['generator'] != 'pub';
|
|
|
|
if (isPackageConfigGeneratedByThirdParty) {
|
|
_logger.printTrace('Skipping pub get: generated by third-party.');
|
|
return;
|
|
}
|
|
}
|
|
|
|
// If the pubspec.yaml is older than the package config file and the last
|
|
// flutter version used is the same as the current version skip pub get.
|
|
// This will incorrectly skip pub on the master branch if dependencies
|
|
// are being added/removed from the flutter framework packages, but this
|
|
// can be worked around by manually running pub.
|
|
if (checkUpToDate &&
|
|
packageConfigFile.existsSync() &&
|
|
pubLockFile.existsSync() &&
|
|
pubspecYaml.lastModifiedSync().isBefore(pubLockFile.lastModifiedSync()) &&
|
|
pubspecYaml.lastModifiedSync().isBefore(packageConfigFile.lastModifiedSync()) &&
|
|
lastVersion.existsSync() &&
|
|
lastVersion.readAsStringSync() == currentVersion.readAsStringSync()) {
|
|
_logger.printTrace('Skipping pub get: version match.');
|
|
return;
|
|
}
|
|
|
|
final String command = upgrade ? 'upgrade' : 'get';
|
|
final bool verbose = _logger.isVerbose;
|
|
final List<String> args = <String>[
|
|
if (_logger.supportsColor)
|
|
'--color',
|
|
if (verbose)
|
|
'--verbose',
|
|
'--directory',
|
|
_fileSystem.path.relative(directory),
|
|
...<String>[
|
|
command,
|
|
],
|
|
if (offline)
|
|
'--offline',
|
|
'--example',
|
|
];
|
|
await _runWithStdioInherited(
|
|
args,
|
|
command: command,
|
|
context: context,
|
|
directory: directory,
|
|
failureMessage: 'pub $command failed',
|
|
flutterRootOverride: flutterRootOverride,
|
|
printProgress: printProgress
|
|
);
|
|
|
|
if (!packageConfigFile.existsSync()) {
|
|
throwToolExit('$directory: pub did not create .dart_tools/package_config.json file.');
|
|
}
|
|
lastVersion.writeAsStringSync(currentVersion.readAsStringSync());
|
|
await _updatePackageConfig(
|
|
packageConfigFile,
|
|
generatedDirectory,
|
|
project.manifest.generateSyntheticPackage,
|
|
);
|
|
if (project.hasExampleApp && project.example.pubspecFile.existsSync()) {
|
|
final Directory exampleGeneratedDirectory = _fileSystem.directory(
|
|
_fileSystem.path.join(project.example.directory.path, '.dart_tool', 'flutter_gen'));
|
|
await _updatePackageConfig(
|
|
project.example.packageConfigFile,
|
|
exampleGeneratedDirectory,
|
|
project.example.manifest.generateSyntheticPackage,
|
|
);
|
|
}
|
|
}
|
|
|
|
/// Runs pub with [arguments] and [ProcessStartMode.inheritStdio] mode.
|
|
///
|
|
/// Uses [ProcessStartMode.normal] and [Pub._stdio] if [Pub.test] constructor
|
|
/// was used.
|
|
///
|
|
/// Prints the stdout and stderr of the whole run, unless silenced using
|
|
/// [printProgress].
|
|
///
|
|
/// Sends an analytics event.
|
|
Future<void> _runWithStdioInherited(
|
|
List<String> arguments, {
|
|
required String command,
|
|
required bool printProgress,
|
|
required PubContext context,
|
|
required String directory,
|
|
String failureMessage = 'pub failed',
|
|
String? flutterRootOverride,
|
|
}) async {
|
|
int exitCode;
|
|
if (printProgress) {
|
|
_logger.printStatus('Running "flutter pub $command" in ${_fileSystem.path.basename(directory)}...');
|
|
}
|
|
|
|
final List<String> pubCommand = _pubCommand(arguments);
|
|
final Map<String, String> pubEnvironment = await _createPubEnvironment(context, flutterRootOverride);
|
|
|
|
try {
|
|
if (printProgress) {
|
|
final io.Stdio? stdio = _stdio;
|
|
if (stdio == null) {
|
|
// Let pub inherit stdio and output directly to the tool's stdout and
|
|
// stderr handles.
|
|
final io.Process process = await _processUtils.start(
|
|
pubCommand,
|
|
workingDirectory: _fileSystem.path.current,
|
|
environment: pubEnvironment,
|
|
mode: ProcessStartMode.inheritStdio,
|
|
);
|
|
|
|
exitCode = await process.exitCode;
|
|
} else {
|
|
// Omit [mode] parameter to send output to [process.stdout] and
|
|
// [process.stderr].
|
|
final io.Process process = await _processUtils.start(
|
|
pubCommand,
|
|
workingDirectory: _fileSystem.path.current,
|
|
environment: pubEnvironment,
|
|
);
|
|
|
|
// Direct pub output to [Pub._stdio] for tests.
|
|
final StreamSubscription<List<int>> stdoutSubscription =
|
|
process.stdout.listen(stdio.stdout.add);
|
|
final StreamSubscription<List<int>> stderrSubscription =
|
|
process.stderr.listen(stdio.stderr.add);
|
|
|
|
await Future.wait<void>(<Future<void>>[
|
|
stdoutSubscription.asFuture<void>(),
|
|
stderrSubscription.asFuture<void>(),
|
|
]);
|
|
|
|
unawaited(stdoutSubscription.cancel());
|
|
unawaited(stderrSubscription.cancel());
|
|
|
|
exitCode = await process.exitCode;
|
|
}
|
|
} else {
|
|
// Do not try to use [ProcessUtils.start] here, because it requires you
|
|
// to read all data out of the stdout and stderr streams. If you don't
|
|
// read the streams, it may appear to work fine on your platform but
|
|
// will block the tool's process on Windows.
|
|
// See https://api.dart.dev/stable/dart-io/Process/start.html
|
|
//
|
|
// [ProcessUtils.run] will send the output to [result.stdout] and
|
|
// [result.stderr], which we will ignore.
|
|
final RunResult result = await _processUtils.run(
|
|
pubCommand,
|
|
workingDirectory: _fileSystem.path.current,
|
|
environment: pubEnvironment,
|
|
);
|
|
|
|
exitCode = result.exitCode;
|
|
}
|
|
// The exception is rethrown, so don't catch only Exceptions.
|
|
} catch (exception) { // ignore: avoid_catches_without_on_clauses
|
|
if (exception is io.ProcessException) {
|
|
final StringBuffer buffer = StringBuffer('${exception.message}\n');
|
|
final String directoryExistsMessage = _fileSystem.directory(directory).existsSync()
|
|
? 'exists'
|
|
: 'does not exist';
|
|
buffer.writeln('Working directory: "$directory" ($directoryExistsMessage)');
|
|
final Map<String, String> env = await _createPubEnvironment(context, flutterRootOverride);
|
|
buffer.write(_stringifyPubEnv(env));
|
|
throw io.ProcessException(
|
|
exception.executable,
|
|
exception.arguments,
|
|
buffer.toString(),
|
|
exception.errorCode,
|
|
);
|
|
}
|
|
rethrow;
|
|
}
|
|
|
|
final int code = exitCode;
|
|
final String result = code == 0 ? 'success' : 'failure';
|
|
PubResultEvent(
|
|
context: context.toAnalyticsString(),
|
|
result: result,
|
|
usage: _usage,
|
|
).send();
|
|
|
|
if (code != 0) {
|
|
final StringBuffer buffer = StringBuffer('$failureMessage\n');
|
|
buffer.writeln('command: "${pubCommand.join(' ')}"');
|
|
buffer.write(_stringifyPubEnv(pubEnvironment));
|
|
buffer.writeln('exit code: $code');
|
|
throwToolExit(
|
|
buffer.toString(),
|
|
exitCode: code,
|
|
);
|
|
}
|
|
}
|
|
|
|
// For surfacing pub env in crash reporting
|
|
String _stringifyPubEnv(Map<String, String> map, {String prefix = 'pub env'}) {
|
|
if (map.isEmpty) {
|
|
return '';
|
|
}
|
|
final StringBuffer buffer = StringBuffer();
|
|
buffer.writeln('$prefix: {');
|
|
for (final MapEntry<String, String> entry in map.entries) {
|
|
buffer.writeln(' "${entry.key}": "${entry.value}",');
|
|
}
|
|
buffer.writeln('}');
|
|
return buffer.toString();
|
|
}
|
|
|
|
@override
|
|
Future<void> batch(
|
|
List<String> arguments, {
|
|
required PubContext context,
|
|
String? directory,
|
|
MessageFilter? filter,
|
|
String failureMessage = 'pub failed',
|
|
String? flutterRootOverride,
|
|
}) async {
|
|
final bool showTraceForErrors = await _botDetector.isRunningOnBot;
|
|
|
|
String lastPubMessage = 'no message';
|
|
String? filterWrapper(String line) {
|
|
lastPubMessage = line;
|
|
if (filter == null) {
|
|
return line;
|
|
}
|
|
return filter(line);
|
|
}
|
|
|
|
if (showTraceForErrors) {
|
|
arguments.insert(0, '--trace');
|
|
}
|
|
final Map<String, String> pubEnvironment = await _createPubEnvironment(context, flutterRootOverride);
|
|
final List<String> pubCommand = _pubCommand(arguments);
|
|
final int code = await _processUtils.stream(
|
|
pubCommand,
|
|
workingDirectory: directory,
|
|
mapFunction: filterWrapper, // may set versionSolvingFailed, lastPubMessage
|
|
environment: pubEnvironment,
|
|
);
|
|
|
|
String result = 'success';
|
|
if (code != 0) {
|
|
result = 'failure';
|
|
}
|
|
PubResultEvent(
|
|
context: context.toAnalyticsString(),
|
|
result: result,
|
|
usage: _usage,
|
|
).send();
|
|
|
|
if (code != 0) {
|
|
final StringBuffer buffer = StringBuffer('$failureMessage\n');
|
|
buffer.writeln('command: "${pubCommand.join(' ')}"');
|
|
buffer.write(_stringifyPubEnv(pubEnvironment));
|
|
buffer.writeln('exit code: $code');
|
|
buffer.writeln('last line of pub output: "${lastPubMessage.trim()}"');
|
|
throwToolExit(
|
|
buffer.toString(),
|
|
exitCode: code,
|
|
);
|
|
}
|
|
}
|
|
|
|
@override
|
|
Future<void> interactively(
|
|
List<String> arguments, {
|
|
String? directory,
|
|
required io.Stdio stdio,
|
|
bool touchesPackageConfig = false,
|
|
bool generateSyntheticPackage = false,
|
|
}) async {
|
|
// Fully resolved pub or pub.bat is calculated based on current platform.
|
|
final io.Process process = await _processUtils.start(
|
|
_pubCommand(<String>[
|
|
if (_logger.supportsColor) '--color',
|
|
...arguments,
|
|
]),
|
|
workingDirectory: directory,
|
|
environment: await _createPubEnvironment(PubContext.interactive),
|
|
);
|
|
|
|
// Pipe the Flutter tool stdin to the pub stdin.
|
|
unawaited(process.stdin.addStream(stdio.stdin)
|
|
// If pub exits unexpectedly with an error, that will be reported below
|
|
// by the tool exit after the exit code check.
|
|
.catchError((dynamic err, StackTrace stack) {
|
|
_logger.printTrace('Echoing stdin to the pub subprocess failed:');
|
|
_logger.printTrace('$err\n$stack');
|
|
}
|
|
));
|
|
|
|
// Pipe the pub stdout and stderr to the tool stdout and stderr.
|
|
try {
|
|
await Future.wait<dynamic>(<Future<dynamic>>[
|
|
stdio.addStdoutStream(process.stdout),
|
|
stdio.addStderrStream(process.stderr),
|
|
]);
|
|
} on Exception catch (err, stack) {
|
|
_logger.printTrace('Echoing stdout or stderr from the pub subprocess failed:');
|
|
_logger.printTrace('$err\n$stack');
|
|
}
|
|
|
|
// Wait for pub to exit.
|
|
final int code = await process.exitCode;
|
|
if (code != 0) {
|
|
throwToolExit('pub finished with exit code $code', exitCode: code);
|
|
}
|
|
|
|
if (touchesPackageConfig) {
|
|
final String targetDirectory = directory ?? _fileSystem.currentDirectory.path;
|
|
final File packageConfigFile = _fileSystem.file(
|
|
_fileSystem.path.join(targetDirectory, '.dart_tool', 'package_config.json'));
|
|
final Directory generatedDirectory = _fileSystem.directory(
|
|
_fileSystem.path.join(targetDirectory, '.dart_tool', 'flutter_gen'));
|
|
final File lastVersion = _fileSystem.file(
|
|
_fileSystem.path.join(targetDirectory, '.dart_tool', 'version'));
|
|
final File currentVersion = _fileSystem.file(
|
|
_fileSystem.path.join(Cache.flutterRoot!, 'version'));
|
|
lastVersion.writeAsStringSync(currentVersion.readAsStringSync());
|
|
await _updatePackageConfig(
|
|
packageConfigFile,
|
|
generatedDirectory,
|
|
generateSyntheticPackage,
|
|
);
|
|
}
|
|
}
|
|
|
|
/// The command used for running pub.
|
|
List<String> _pubCommand(List<String> arguments) {
|
|
// TODO(zanderso): refactor to use artifacts.
|
|
final String sdkPath = _fileSystem.path.joinAll(<String>[
|
|
Cache.flutterRoot!,
|
|
'bin',
|
|
'cache',
|
|
'dart-sdk',
|
|
'bin',
|
|
'dart',
|
|
]);
|
|
if (!_processManager.canRun(sdkPath)) {
|
|
throwToolExit(
|
|
'Your Flutter SDK download may be corrupt or missing permissions to run. '
|
|
'Try re-downloading the Flutter SDK into a directory that has read/write '
|
|
'permissions for the current user.'
|
|
);
|
|
}
|
|
return <String>[sdkPath, '__deprecated_pub', ...arguments];
|
|
}
|
|
|
|
// Returns the environment value that should be used when running pub.
|
|
//
|
|
// Includes any existing environment variable, if one exists.
|
|
//
|
|
// [context] provides extra information to package server requests to
|
|
// understand usage.
|
|
Future<String> _getPubEnvironmentValue(PubContext pubContext) async {
|
|
// DO NOT update this function without contacting kevmoo.
|
|
// We have server-side tooling that assumes the values are consistent.
|
|
final String? existing = _platform.environment[_kPubEnvironmentKey];
|
|
final List<String> values = <String>[
|
|
if (existing != null && existing.isNotEmpty) existing,
|
|
if (await _botDetector.isRunningOnBot) 'flutter_bot',
|
|
'flutter_cli',
|
|
...pubContext._values,
|
|
];
|
|
return values.join(':');
|
|
}
|
|
|
|
/// There are 3 ways to get the pub cache location
|
|
///
|
|
/// 1) Provide the _kPubCacheEnvironmentKey.
|
|
/// 2) There is a local cache (in the Flutter SDK) but not a global one (in the user's home directory).
|
|
/// 3) If both local and global are available then merge the local into global and return the global.
|
|
String? _getPubCacheIfAvailable() {
|
|
if (_platform.environment.containsKey(_kPubCacheEnvironmentKey)) {
|
|
return _platform.environment[_kPubCacheEnvironmentKey];
|
|
}
|
|
|
|
final String localCachePath = _fileSystem.path.join(Cache.flutterRoot!, '.pub-cache');
|
|
final Directory? globalDirectory;
|
|
if (_platform.isWindows) {
|
|
globalDirectory = _getWindowsGlobalDirectory;
|
|
}
|
|
else {
|
|
if (_platform.environment['HOME'] == null) {
|
|
globalDirectory = null;
|
|
} else {
|
|
final String homeDirectoryPath = _platform.environment['HOME']!;
|
|
globalDirectory = _fileSystem.directory(_fileSystem.path.join(homeDirectoryPath, '.pub-cache'));
|
|
}
|
|
}
|
|
|
|
if (needsToJoinCache(
|
|
fileSystem: _fileSystem,
|
|
localCachePath: localCachePath,
|
|
globalDirectory: globalDirectory,
|
|
)) {
|
|
final Directory localDirectoryPub = _fileSystem.directory(
|
|
_fileSystem.path.join(localCachePath, 'hosted', 'pub.dartlang.org')
|
|
);
|
|
final Directory globalDirectoryPub = _fileSystem.directory(
|
|
_fileSystem.path.join(globalDirectory!.path, 'hosted', 'pub.dartlang.org')
|
|
);
|
|
for (final FileSystemEntity entity in localDirectoryPub.listSync()) {
|
|
if (entity is Directory && !globalDirectoryPub.childDirectory(entity.basename).existsSync()){
|
|
try {
|
|
final Directory newDirectory = createDependencyDirectory(globalDirectoryPub, entity.basename);
|
|
joinCaches(
|
|
fileSystem: _fileSystem,
|
|
globalCacheDirectory: newDirectory,
|
|
dependencyDirectory: entity,
|
|
);
|
|
} on FileSystemException {
|
|
if (!tryDelete(globalDirectoryPub.childDirectory(entity.basename), _logger)) {
|
|
_logger.printWarning('The join of pub-caches failed');
|
|
_logger.printStatus('Running "dart pub cache repair"');
|
|
_processManager.runSync(<String>['dart', 'pub', 'cache', 'repair']);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
tryDelete(_fileSystem.directory(localCachePath), _logger);
|
|
return globalDirectory.path;
|
|
} else if (globalDirectory != null && globalDirectory.existsSync()) {
|
|
return globalDirectory.path;
|
|
} else if (_fileSystem.directory(localCachePath).existsSync()) {
|
|
return localCachePath;
|
|
}
|
|
// Use pub's default location by returning null.
|
|
return null;
|
|
}
|
|
|
|
Directory? get _getWindowsGlobalDirectory {
|
|
// %LOCALAPPDATA% is preferred as the cache location over %APPDATA%, because the latter is synchronised between
|
|
// devices when the user roams between them, whereas the former is not.
|
|
// The default cache dir used to be in %APPDATA%, so to avoid breaking old installs,
|
|
// we use the old dir in %APPDATA% if it exists. Else, we use the new default location
|
|
// in %LOCALAPPDATA%.
|
|
for (final String envVariable in <String>['APPDATA', 'LOCALAPPDATA']) {
|
|
if (_platform.environment[envVariable] != null) {
|
|
final String homePath = _platform.environment[envVariable]!;
|
|
final Directory globalDirectory = _fileSystem.directory(_fileSystem.path.join(homePath, 'Pub', 'Cache'));
|
|
if (globalDirectory.existsSync()) {
|
|
return globalDirectory;
|
|
}
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/// The full environment used when running pub.
|
|
///
|
|
/// [context] provides extra information to package server requests to
|
|
/// understand usage.
|
|
Future<Map<String, String>> _createPubEnvironment(PubContext context, [ String? flutterRootOverride ]) async {
|
|
final Map<String, String> environment = <String, String>{
|
|
'FLUTTER_ROOT': flutterRootOverride ?? Cache.flutterRoot!,
|
|
_kPubEnvironmentKey: await _getPubEnvironmentValue(context),
|
|
};
|
|
final String? pubCache = _getPubCacheIfAvailable();
|
|
if (pubCache != null) {
|
|
environment[_kPubCacheEnvironmentKey] = pubCache;
|
|
}
|
|
return environment;
|
|
}
|
|
|
|
/// Update the package configuration file.
|
|
///
|
|
/// Creates a corresponding `package_config_subset` file that is used by the build
|
|
/// system to avoid rebuilds caused by an updated pub timestamp.
|
|
///
|
|
/// if [generateSyntheticPackage] is true then insert flutter_gen synthetic
|
|
/// package into the package configuration. This is used by the l10n localization
|
|
/// tooling to insert a new reference into the package_config file, allowing the import
|
|
/// of a package URI that is not specified in the pubspec.yaml
|
|
///
|
|
/// For more information, see:
|
|
/// * [generateLocalizations], `in lib/src/localizations/gen_l10n.dart`
|
|
Future<void> _updatePackageConfig(
|
|
File packageConfigFile,
|
|
Directory generatedDirectory,
|
|
bool generateSyntheticPackage,
|
|
) async {
|
|
final PackageConfig packageConfig = await loadPackageConfigWithLogging(packageConfigFile, logger: _logger);
|
|
|
|
packageConfigFile.parent
|
|
.childFile('package_config_subset')
|
|
.writeAsStringSync(_computePackageConfigSubset(
|
|
packageConfig,
|
|
_fileSystem,
|
|
));
|
|
|
|
if (!generateSyntheticPackage) {
|
|
return;
|
|
}
|
|
if (packageConfig.packages.any((Package package) => package.name == 'flutter_gen')) {
|
|
return;
|
|
}
|
|
|
|
// TODO(jonahwillams): Using raw json manipulation here because
|
|
// savePackageConfig always writes to local io, and it changes absolute
|
|
// paths to relative on round trip.
|
|
// See: https://github.com/dart-lang/package_config/issues/99,
|
|
// and: https://github.com/dart-lang/package_config/issues/100.
|
|
|
|
// Because [loadPackageConfigWithLogging] succeeded [packageConfigFile]
|
|
// we can rely on the file to exist and be correctly formatted.
|
|
final Map<String, dynamic> jsonContents =
|
|
json.decode(packageConfigFile.readAsStringSync()) as Map<String, dynamic>;
|
|
|
|
(jsonContents['packages'] as List<dynamic>).add(<String, dynamic>{
|
|
'name': 'flutter_gen',
|
|
'rootUri': 'flutter_gen',
|
|
'languageVersion': '2.12',
|
|
});
|
|
|
|
packageConfigFile.writeAsStringSync(json.encode(jsonContents));
|
|
}
|
|
|
|
// Subset the package config file to only the parts that are relevant for
|
|
// rerunning the dart compiler.
|
|
String _computePackageConfigSubset(PackageConfig packageConfig, FileSystem fileSystem) {
|
|
final StringBuffer buffer = StringBuffer();
|
|
for (final Package package in packageConfig.packages) {
|
|
buffer.writeln(package.name);
|
|
buffer.writeln(package.languageVersion);
|
|
buffer.writeln(package.root);
|
|
buffer.writeln(package.packageUriRoot);
|
|
}
|
|
buffer.writeln(packageConfig.version);
|
|
return buffer.toString();
|
|
}
|
|
}
|