• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2016 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5import 'dart:async';
6
7import 'package:meta/meta.dart';
8
9import '../base/common.dart';
10import '../base/file_system.dart';
11import '../base/logger.dart';
12import '../base/platform.dart';
13import '../base/process.dart';
14import '../base/utils.dart';
15import '../cache.dart';
16import '../globals.dart';
17import '../runner/flutter_command.dart';
18import 'sdk.dart';
19
20/// Represents Flutter-specific data that is added to the `PUB_ENVIRONMENT`
21/// environment variable and allows understanding the type of requests made to
22/// the package site on Flutter's behalf.
23// DO NOT update without contacting kevmoo.
24// We have server-side tooling that assumes the values are consistent.
25class PubContext {
26  PubContext._(this._values) {
27    for (String item in _values) {
28      if (!_validContext.hasMatch(item)) {
29        throw ArgumentError.value(
30            _values, 'value', 'Must match RegExp ${_validContext.pattern}');
31      }
32    }
33  }
34
35  static PubContext getVerifyContext(String commandName) =>
36      PubContext._(<String>['verify', commandName.replaceAll('-', '_')]);
37
38  static final PubContext create = PubContext._(<String>['create']);
39  static final PubContext createPackage = PubContext._(<String>['create_pkg']);
40  static final PubContext createPlugin = PubContext._(<String>['create_plugin']);
41  static final PubContext interactive = PubContext._(<String>['interactive']);
42  static final PubContext pubGet = PubContext._(<String>['get']);
43  static final PubContext pubUpgrade = PubContext._(<String>['upgrade']);
44  static final PubContext pubForward = PubContext._(<String>['forward']);
45  static final PubContext runTest = PubContext._(<String>['run_test']);
46
47  static final PubContext flutterTests = PubContext._(<String>['flutter_tests']);
48  static final PubContext updatePackages = PubContext._(<String>['update_packages']);
49
50  final List<String> _values;
51
52  static final RegExp _validContext = RegExp('[a-z][a-z_]*[a-z]');
53
54  @override
55  String toString() => 'PubContext: ${_values.join(':')}';
56}
57
58bool _shouldRunPubGet({ File pubSpecYaml, File dotPackages }) {
59  if (!dotPackages.existsSync())
60    return true;
61  final DateTime dotPackagesLastModified = dotPackages.lastModifiedSync();
62  if (pubSpecYaml.lastModifiedSync().isAfter(dotPackagesLastModified))
63    return true;
64  final File flutterToolsStamp = Cache.instance.getStampFileFor('flutter_tools');
65  if (flutterToolsStamp.existsSync() &&
66      flutterToolsStamp.lastModifiedSync().isAfter(dotPackagesLastModified))
67    return true;
68  return false;
69}
70
71/// [context] provides extra information to package server requests to
72/// understand usage.
73Future<void> pubGet({
74  @required PubContext context,
75  String directory,
76  bool skipIfAbsent = false,
77  bool upgrade = false,
78  bool offline = false,
79  bool checkLastModified = true,
80  bool skipPubspecYamlCheck = false,
81}) async {
82  directory ??= fs.currentDirectory.path;
83
84  final File pubSpecYaml = fs.file(fs.path.join(directory, 'pubspec.yaml'));
85  final File dotPackages = fs.file(fs.path.join(directory, '.packages'));
86
87  if (!skipPubspecYamlCheck && !pubSpecYaml.existsSync()) {
88    if (!skipIfAbsent)
89      throwToolExit('$directory: no pubspec.yaml found');
90    return;
91  }
92
93  if (!checkLastModified || _shouldRunPubGet(pubSpecYaml: pubSpecYaml, dotPackages: dotPackages)) {
94    final String command = upgrade ? 'upgrade' : 'get';
95    final Status status = logger.startProgress(
96      'Running "flutter pub $command" in ${fs.path.basename(directory)}...',
97      timeout: timeoutConfiguration.slowOperation,
98    );
99    final bool verbose = FlutterCommand.current != null && FlutterCommand.current.globalResults['verbose'];
100    final List<String> args = <String>[
101      if (verbose) '--verbose' else '--verbosity=warning',
102      ...<String>[command, '--no-precompile'],
103      if (offline) '--offline',
104    ];
105    try {
106      await pub(
107        args,
108        context: context,
109        directory: directory,
110        filter: _filterOverrideWarnings,
111        failureMessage: 'pub $command failed',
112        retry: true,
113      );
114      status.stop();
115    } catch (exception) {
116      status.cancel();
117      rethrow;
118    }
119  }
120
121  if (!dotPackages.existsSync())
122    throwToolExit('$directory: pub did not create .packages file.');
123
124  if (dotPackages.lastModifiedSync().isBefore(pubSpecYaml.lastModifiedSync())) {
125    throwToolExit('$directory: pub did not update .packages file (pubspec.yaml timestamp: ${pubSpecYaml.lastModifiedSync()}; .packages timestamp: ${dotPackages.lastModifiedSync()}).');
126  }
127}
128
129typedef MessageFilter = String Function(String message);
130
131/// Runs pub in 'batch' mode, forwarding complete lines written by pub to its
132/// stdout/stderr streams to the corresponding stream of this process, optionally
133/// applying filtering. The pub process will not receive anything on its stdin stream.
134///
135/// The `--trace` argument is passed to `pub` (by mutating the provided
136/// `arguments` list) when `showTraceForErrors` is true, and when `showTraceForErrors`
137/// is null/unset, and `isRunningOnBot` is true.
138///
139/// [context] provides extra information to package server requests to
140/// understand usage.
141Future<void> pub(
142  List<String> arguments, {
143  @required PubContext context,
144  String directory,
145  MessageFilter filter,
146  String failureMessage = 'pub failed',
147  @required bool retry,
148  bool showTraceForErrors,
149}) async {
150  showTraceForErrors ??= isRunningOnBot;
151
152  if (showTraceForErrors)
153    arguments.insert(0, '--trace');
154  int attempts = 0;
155  int duration = 1;
156  int code;
157  while (true) {
158    attempts += 1;
159    code = await runCommandAndStreamOutput(
160      _pubCommand(arguments),
161      workingDirectory: directory,
162      mapFunction: filter,
163      environment: _createPubEnvironment(context),
164    );
165    if (code != 69) // UNAVAILABLE in https://github.com/dart-lang/pub/blob/master/lib/src/exit_codes.dart
166      break;
167    printStatus('$failureMessage ($code) -- attempting retry $attempts in $duration second${ duration == 1 ? "" : "s"}...');
168    await Future<void>.delayed(Duration(seconds: duration));
169    if (duration < 64)
170      duration *= 2;
171  }
172  assert(code != null);
173  if (code != 0)
174    throwToolExit('$failureMessage ($code)', exitCode: code);
175}
176
177/// Runs pub in 'interactive' mode, directly piping the stdin stream of this
178/// process to that of pub, and the stdout/stderr stream of pub to the corresponding
179/// streams of this process.
180Future<void> pubInteractively(
181  List<String> arguments, {
182  String directory,
183}) async {
184  Cache.releaseLockEarly();
185  final int code = await runInteractively(
186    _pubCommand(arguments),
187    workingDirectory: directory,
188    environment: _createPubEnvironment(PubContext.interactive),
189  );
190  if (code != 0)
191    throwToolExit('pub finished with exit code $code', exitCode: code);
192}
193
194/// The command used for running pub.
195List<String> _pubCommand(List<String> arguments) {
196  return <String>[sdkBinaryName('pub'), ...arguments];
197}
198
199/// The full environment used when running pub.
200///
201/// [context] provides extra information to package server requests to
202/// understand usage.
203Map<String, String> _createPubEnvironment(PubContext context) {
204  final Map<String, String> environment = <String, String>{
205    'FLUTTER_ROOT': Cache.flutterRoot,
206    _pubEnvironmentKey: _getPubEnvironmentValue(context),
207  };
208  final String pubCache = _getRootPubCacheIfAvailable();
209  if (pubCache != null) {
210    environment[_pubCacheEnvironmentKey] = pubCache;
211  }
212  return environment;
213}
214
215final RegExp _analyzerWarning = RegExp(r'^! \w+ [^ ]+ from path \.\./\.\./bin/cache/dart-sdk/lib/\w+$');
216
217/// The console environment key used by the pub tool.
218const String _pubEnvironmentKey = 'PUB_ENVIRONMENT';
219
220/// The console environment key used by the pub tool to find the cache directory.
221const String _pubCacheEnvironmentKey = 'PUB_CACHE';
222
223/// Returns the environment value that should be used when running pub.
224///
225/// Includes any existing environment variable, if one exists.
226///
227/// [context] provides extra information to package server requests to
228/// understand usage.
229String _getPubEnvironmentValue(PubContext pubContext) {
230  // DO NOT update this function without contacting kevmoo.
231  // We have server-side tooling that assumes the values are consistent.
232  final String existing = platform.environment[_pubEnvironmentKey];
233  final List<String> values = <String>[
234    if (existing != null && existing.isNotEmpty) existing,
235    if (isRunningOnBot) 'flutter_bot',
236    'flutter_cli',
237    ...pubContext._values,
238  ];
239  return values.join(':');
240}
241
242String _getRootPubCacheIfAvailable() {
243  if (platform.environment.containsKey(_pubCacheEnvironmentKey)) {
244    return platform.environment[_pubCacheEnvironmentKey];
245  }
246
247  final String cachePath = fs.path.join(Cache.flutterRoot, '.pub-cache');
248  if (fs.directory(cachePath).existsSync()) {
249    printTrace('Using $cachePath for the pub cache.');
250    return cachePath;
251  }
252
253  // Use pub's default location by returning null.
254  return null;
255}
256
257String _filterOverrideWarnings(String message) {
258  // This function filters out these three messages:
259  //   Warning: You are using these overridden dependencies:
260  //   ! analyzer 0.29.0-alpha.0 from path ../../bin/cache/dart-sdk/lib/analyzer
261  //   ! front_end 0.1.0-alpha.0 from path ../../bin/cache/dart-sdk/lib/front_end
262  if (message == 'Warning: You are using these overridden dependencies:')
263    return null;
264  if (message.contains(_analyzerWarning))
265    return null;
266  return message;
267}
268