pub.dart 9.28 KB
Newer Older
1 2 3 4 5 6
// Copyright 2016 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';

7 8
import 'package:meta/meta.dart';

9
import '../base/common.dart';
10
import '../base/file_system.dart';
11
import '../base/logger.dart';
12
import '../base/platform.dart';
13
import '../base/process.dart';
14
import '../base/utils.dart';
15
import '../cache.dart';
16
import '../globals.dart';
17
import '../runner/flutter_command.dart';
18
import 'sdk.dart';
19

20 21 22 23 24 25
/// 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 {
26 27 28 29 30 31 32 33 34 35 36
  PubContext._(this._values) {
    for (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('-', '_')]);
37

38 39 40 41 42 43 44
  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 runTest = PubContext._(<String>['run_test']);
45

46 47
  static final PubContext flutterTests = PubContext._(<String>['flutter_tests']);
  static final PubContext updatePackages = PubContext._(<String>['update_packages']);
48 49 50

  final List<String> _values;

51
  static final RegExp _validContext = RegExp('[a-z][a-z_]*[a-z]');
52 53 54 55 56

  @override
  String toString() => 'PubContext: ${_values.join(':')}';
}

57 58 59
bool _shouldRunPubGet({ File pubSpecYaml, File dotPackages }) {
  if (!dotPackages.existsSync())
    return true;
60
  final DateTime dotPackagesLastModified = dotPackages.lastModifiedSync();
61 62
  if (pubSpecYaml.lastModifiedSync().isAfter(dotPackagesLastModified))
    return true;
63
  final File flutterToolsStamp = Cache.instance.getStampFileFor('flutter_tools');
64 65
  if (flutterToolsStamp.existsSync() &&
      flutterToolsStamp.lastModifiedSync().isAfter(dotPackagesLastModified))
66 67 68 69
    return true;
  return false;
}

70
/// [context] provides extra information to package server requests to
71
/// understand usage.
72
Future<void> pubGet({
73
  @required PubContext context,
74
  String directory,
75 76 77
  bool skipIfAbsent = false,
  bool upgrade = false,
  bool offline = false,
78
  bool checkLastModified = true,
79
  bool skipPubspecYamlCheck = false,
80
}) async {
81
  directory ??= fs.currentDirectory.path;
82

83 84
  final File pubSpecYaml = fs.file(fs.path.join(directory, 'pubspec.yaml'));
  final File dotPackages = fs.file(fs.path.join(directory, '.packages'));
85

86
  if (!skipPubspecYamlCheck && !pubSpecYaml.existsSync()) {
87 88 89
    if (!skipIfAbsent)
      throwToolExit('$directory: no pubspec.yaml found');
    return;
90 91
  }

92
  if (!checkLastModified || _shouldRunPubGet(pubSpecYaml: pubSpecYaml, dotPackages: dotPackages)) {
93
    final String command = upgrade ? 'upgrade' : 'get';
94 95
    final Status status = logger.startProgress(
      'Running "flutter packages $command" in ${fs.path.basename(directory)}...',
96
      timeout: kSlowOperation,
97
    );
98 99
    final List<String> args = <String>['--verbosity=warning'];
    if (FlutterCommand.current != null && FlutterCommand.current.globalResults['verbose'])
100
      args.add('--verbose');
101
    args.addAll(<String>[command, '--no-precompile']);
102 103
    if (offline)
      args.add('--offline');
104
    try {
105 106
      await pub(
        args,
107
        context: context,
108 109 110 111 112
        directory: directory,
        filter: _filterOverrideWarnings,
        failureMessage: 'pub $command failed',
        retry: true,
      );
113
      status.stop();
114 115 116
    } catch (exception) {
      status.cancel();
      rethrow;
117
    }
118 119
  }

120 121
  if (!dotPackages.existsSync())
    throwToolExit('$directory: pub did not create .packages file');
122

123 124
  if (dotPackages.lastModifiedSync().isBefore(pubSpecYaml.lastModifiedSync()))
    throwToolExit('$directory: pub did not update .packages file (pubspec.yaml file has a newer timestamp)');
125
}
126

127
typedef MessageFilter = String Function(String message);
128

129 130 131
/// 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.
132 133
///
/// The `--trace` argument is passed to `pub` (by mutating the provided
134 135
/// `arguments` list) when `showTraceForErrors` is true, and when `showTraceForErrors`
/// is null/unset, and `isRunningOnBot` is true.
136 137
///
/// [context] provides extra information to package server requests to
138
/// understand usage.
139 140
Future<void> pub(
  List<String> arguments, {
141
  @required PubContext context,
142 143
  String directory,
  MessageFilter filter,
144
  String failureMessage = 'pub failed',
145
  @required bool retry,
146
  bool showTraceForErrors,
147
}) async {
148 149
  showTraceForErrors ??= isRunningOnBot;

150 151
  if (showTraceForErrors)
    arguments.insert(0, '--trace');
152 153 154 155 156 157
  int attempts = 0;
  int duration = 1;
  int code;
  while (true) {
    attempts += 1;
    code = await runCommandAndStreamOutput(
158
      _pubCommand(arguments),
159 160
      workingDirectory: directory,
      mapFunction: filter,
161
      environment: _createPubEnvironment(context),
162 163 164 165
    );
    if (code != 69) // UNAVAILABLE in https://github.com/dart-lang/pub/blob/master/lib/src/exit_codes.dart
      break;
    printStatus('$failureMessage ($code) -- attempting retry $attempts in $duration second${ duration == 1 ? "" : "s"}...');
166
    await Future<void>.delayed(Duration(seconds: duration));
167 168 169 170
    if (duration < 64)
      duration *= 2;
  }
  assert(code != null);
171 172 173 174
  if (code != 0)
    throwToolExit('$failureMessage ($code)', exitCode: code);
}

175 176 177
/// 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.
178 179
Future<void> pubInteractively(
  List<String> arguments, {
180 181
  String directory,
}) async {
JustWe's avatar
JustWe committed
182
  Cache.releaseLockEarly();
183 184 185
  final int code = await runInteractively(
    _pubCommand(arguments),
    workingDirectory: directory,
186
    environment: _createPubEnvironment(PubContext.interactive),
187 188 189 190 191 192 193 194 195 196 197
  );
  if (code != 0)
    throwToolExit('pub finished with exit code $code', exitCode: code);
}

/// The command used for running pub.
List<String> _pubCommand(List<String> arguments) {
  return <String>[ sdkBinaryName('pub') ]..addAll(arguments);
}

/// The full environment used when running pub.
198 199
///
/// [context] provides extra information to package server requests to
200 201
/// understand usage.
Map<String, String> _createPubEnvironment(PubContext context) {
202 203 204 205 206 207 208 209 210 211
  final Map<String, String> environment = <String, String>{
    'FLUTTER_ROOT': Cache.flutterRoot,
    _pubEnvironmentKey: _getPubEnvironmentValue(context),
  };
  final String pubCache = _getRootPubCacheIfAvailable();
  if (pubCache != null) {
    environment[_pubCacheEnvironmentKey] = pubCache;
  }
  return environment;
}
212

213
final RegExp _analyzerWarning = RegExp(r'^! \w+ [^ ]+ from path \.\./\.\./bin/cache/dart-sdk/lib/\w+$');
214

215 216 217
/// The console environment key used by the pub tool.
const String _pubEnvironmentKey = 'PUB_ENVIRONMENT';

218 219 220
/// The console environment key used by the pub tool to find the cache directory.
const String _pubCacheEnvironmentKey = 'PUB_CACHE';

221 222 223
/// Returns the environment value that should be used when running pub.
///
/// Includes any existing environment variable, if one exists.
224 225
///
/// [context] provides extra information to package server requests to
226 227 228 229
/// understand usage.
String _getPubEnvironmentValue(PubContext pubContext) {
  // DO NOT update this function without contacting kevmoo.
  // We have server-side tooling that assumes the values are consistent.
230 231 232 233 234 235 236 237 238 239 240 241 242
  final List<String> values = <String>[];

  final String existing = platform.environment[_pubEnvironmentKey];

  if ((existing != null) && existing.isNotEmpty) {
    values.add(existing);
  }

  if (isRunningOnBot) {
    values.add('flutter_bot');
  }

  values.add('flutter_cli');
243
  values.addAll(pubContext._values);
244

245 246 247
  return values.join(':');
}

248 249 250 251 252 253 254 255 256 257 258 259 260 261 262
String _getRootPubCacheIfAvailable() {
  if (platform.environment.containsKey(_pubCacheEnvironmentKey)) {
    return platform.environment[_pubCacheEnvironmentKey];
  }

  final String cachePath = fs.path.join(Cache.flutterRoot, '.pub-cache');
  if (fs.directory(cachePath).existsSync()) {
    printTrace('Using $cachePath for the pub cache.');
    return cachePath;
  }

  // Use pub's default location by returning null.
  return null;
}

263
String _filterOverrideWarnings(String message) {
264
  // This function filters out these three messages:
265 266
  //   Warning: You are using these overridden dependencies:
  //   ! analyzer 0.29.0-alpha.0 from path ../../bin/cache/dart-sdk/lib/analyzer
267
  //   ! front_end 0.1.0-alpha.0 from path ../../bin/cache/dart-sdk/lib/front_end
268
  if (message == 'Warning: You are using these overridden dependencies:')
269
    return null;
270
  if (message.contains(_analyzerWarning))
271
    return null;
272
  return message;
273
}