1 | // Copyright 2014 The Flutter 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 | |
5 | /// @docImport '../localizations/gen_l10n.dart'; |
6 | library; |
7 | |
8 | import 'dart:async'; |
9 | |
10 | import 'package:meta/meta.dart' ; |
11 | import 'package:process/process.dart' ; |
12 | import '../base/bot_detector.dart'; |
13 | import '../base/common.dart'; |
14 | import '../base/context.dart'; |
15 | import '../base/file_system.dart'; |
16 | import '../base/io.dart' as io; |
17 | import '../base/io.dart'; |
18 | import '../base/logger.dart'; |
19 | import '../base/platform.dart'; |
20 | import '../base/process.dart'; |
21 | import '../cache.dart'; |
22 | import '../convert.dart'; |
23 | import '../dart/package_map.dart'; |
24 | import '../project.dart'; |
25 | import '../version.dart'; |
26 | |
27 | /// The [Pub] instance. |
28 | Pub get pub => context.get<Pub>()!; |
29 | |
30 | /// The console environment key used by the pub tool. |
31 | const _kPubEnvironmentKey = 'PUB_ENVIRONMENT' ; |
32 | |
33 | /// The console environment key used by the pub tool to find the cache directory. |
34 | const _kPubCacheEnvironmentKey = 'PUB_CACHE' ; |
35 | |
36 | typedef MessageFilter = String? Function(String message); |
37 | |
38 | bool _tryDeleteDirectory(Directory directory, Logger logger) { |
39 | try { |
40 | if (directory.existsSync()) { |
41 | directory.deleteSync(recursive: true); |
42 | } |
43 | } on FileSystemException { |
44 | logger.printWarning('Failed to delete directory at: ${directory.path}' ); |
45 | return false; |
46 | } |
47 | return true; |
48 | } |
49 | |
50 | /// Represents Flutter-specific data that is added to the `PUB_ENVIRONMENT` |
51 | /// environment variable and allows understanding the type of requests made to |
52 | /// the package site on Flutter's behalf. |
53 | // DO NOT update without contacting kevmoo. |
54 | // We have server-side tooling that assumes the values are consistent. |
55 | class PubContext { |
56 | PubContext._(this._values) { |
57 | for (final String item in _values) { |
58 | if (!_validContext.hasMatch(item)) { |
59 | throw ArgumentError.value(_values, 'value' , 'Must match RegExp ${_validContext.pattern}' ); |
60 | } |
61 | } |
62 | } |
63 | |
64 | static PubContext getVerifyContext(String commandName) => |
65 | PubContext._(<String>['verify' , commandName.replaceAll('-' , '_' )]); |
66 | |
67 | static final create = PubContext._(<String>['create' ]); |
68 | static final createPackage = PubContext._(<String>['create_pkg' ]); |
69 | static final createPlugin = PubContext._(<String>['create_plugin' ]); |
70 | static final interactive = PubContext._(<String>['interactive' ]); |
71 | static final pubGet = PubContext._(<String>['get' ]); |
72 | static final pubUpgrade = PubContext._(<String>['upgrade' ]); |
73 | static final pubAdd = PubContext._(<String>['add' ]); |
74 | static final pubRemove = PubContext._(<String>['remove' ]); |
75 | static final pubForward = PubContext._(<String>['forward' ]); |
76 | static final pubPassThrough = PubContext._(<String>['passthrough' ]); |
77 | static final runTest = PubContext._(<String>['run_test' ]); |
78 | static final flutterTests = PubContext._(<String>['flutter_tests' ]); |
79 | static final updatePackages = PubContext._(<String>['update_packages' ]); |
80 | |
81 | final List<String> _values; |
82 | |
83 | static final _validContext = RegExp('[a-z][a-z_]*[a-z]' ); |
84 | |
85 | @override |
86 | String toString() => 'PubContext: ${_values.join(':' )}' ; |
87 | |
88 | String toAnalyticsString() { |
89 | return _values.map((String s) => s.replaceAll('_' , '-' )).toList().join('-' ); |
90 | } |
91 | } |
92 | |
93 | /// Describes the amount of output that should get printed from a `pub` command. |
94 | enum PubOutputMode { |
95 | /// No normal output should be printed. |
96 | /// |
97 | /// If the command were to fail, failures are still printed. |
98 | failuresOnly, |
99 | |
100 | /// The complete output should be printed; this is typically the default. |
101 | all, |
102 | |
103 | /// Only summary information should be printed. |
104 | summaryOnly, |
105 | } |
106 | |
107 | /// A handle for interacting with the pub tool. |
108 | abstract class Pub { |
109 | /// Create a default [Pub] instance. |
110 | factory Pub({ |
111 | required FileSystem fileSystem, |
112 | required Logger logger, |
113 | required ProcessManager processManager, |
114 | required Platform platform, |
115 | required BotDetector botDetector, |
116 | }) = _DefaultPub; |
117 | |
118 | /// Create a [Pub] instance with a mocked [stdio]. |
119 | @visibleForTesting |
120 | factory Pub.test({ |
121 | required FileSystem fileSystem, |
122 | required Logger logger, |
123 | required ProcessManager processManager, |
124 | required Platform platform, |
125 | required BotDetector botDetector, |
126 | required Stdio stdio, |
127 | }) = _DefaultPub.test; |
128 | |
129 | /// Runs `pub get` for [project]. |
130 | /// |
131 | /// [context] provides extra information to package server requests to |
132 | /// understand usage. |
133 | /// |
134 | /// If [shouldSkipThirdPartyGenerator] is true, the overall pub get will be |
135 | /// skipped if the package config file has a "generator" other than "pub". |
136 | /// Defaults to true. |
137 | /// |
138 | /// [outputMode] determines how detailed the output from `pub get` will be. |
139 | /// If [PubOutputMode.all] is used, `pub get` will print its typical output |
140 | /// which includes information about all changed dependencies. If |
141 | /// [PubOutputMode.summaryOnly] is used, only summary information will be printed. |
142 | /// This is useful for cases where the user is typically not interested in |
143 | /// what dependencies were changed, such as when running `flutter create`. |
144 | /// |
145 | /// Will also resolve dependencies in the example folder if present. |
146 | Future<void> get({ |
147 | required PubContext context, |
148 | required FlutterProject project, |
149 | bool upgrade = false, |
150 | bool offline = false, |
151 | String? flutterRootOverride, |
152 | bool checkUpToDate = false, |
153 | bool shouldSkipThirdPartyGenerator = true, |
154 | bool enforceLockfile = false, |
155 | PubOutputMode outputMode = PubOutputMode.all, |
156 | }); |
157 | |
158 | /// Runs pub in 'batch' mode. |
159 | /// |
160 | /// forwarding complete lines written by pub to its stdout/stderr streams to |
161 | /// the corresponding stream of this process, optionally applying filtering. |
162 | /// The pub process will not receive anything on its stdin stream. |
163 | /// |
164 | /// The `--trace` argument is passed to `pub` when `showTraceForErrors` |
165 | /// `isRunningOnBot` is true. |
166 | /// |
167 | /// [context] provides extra information to package server requests to |
168 | /// understand usage. |
169 | Future<void> batch( |
170 | List<String> arguments, { |
171 | required PubContext context, |
172 | String? directory, |
173 | MessageFilter? filter, |
174 | String failureMessage = 'pub failed' , |
175 | }); |
176 | |
177 | /// Runs pub in 'interactive' mode. |
178 | /// |
179 | /// This will run the pub process with StdioInherited (unless `stdio` is set |
180 | /// for testing). |
181 | /// |
182 | /// The pub process will be run in current working directory, so `--directory` |
183 | /// should be passed appropriately in [arguments]. This ensures output from |
184 | /// pub will refer to relative paths correctly. |
185 | /// |
186 | /// [touchesPackageConfig] should be true if this is a command expected to |
187 | /// create a new `.dart_tool/package_config.json` file. |
188 | Future<void> interactively( |
189 | List<String> arguments, { |
190 | FlutterProject? project, |
191 | required PubContext context, |
192 | required String command, |
193 | bool touchesPackageConfig = false, |
194 | PubOutputMode outputMode = PubOutputMode.all, |
195 | }); |
196 | } |
197 | |
198 | class _DefaultPub implements Pub { |
199 | _DefaultPub({ |
200 | required FileSystem fileSystem, |
201 | required Logger logger, |
202 | required ProcessManager processManager, |
203 | required Platform platform, |
204 | required BotDetector botDetector, |
205 | }) : _fileSystem = fileSystem, |
206 | _logger = logger, |
207 | _platform = platform, |
208 | _botDetector = botDetector, |
209 | _processUtils = ProcessUtils(logger: logger, processManager: processManager), |
210 | _processManager = processManager, |
211 | _stdio = null; |
212 | |
213 | @visibleForTesting |
214 | _DefaultPub.test({ |
215 | required FileSystem fileSystem, |
216 | required Logger logger, |
217 | required ProcessManager processManager, |
218 | required Platform platform, |
219 | required BotDetector botDetector, |
220 | required Stdio stdio, |
221 | }) : _fileSystem = fileSystem, |
222 | _logger = logger, |
223 | _platform = platform, |
224 | _botDetector = botDetector, |
225 | _processUtils = ProcessUtils(logger: logger, processManager: processManager), |
226 | _processManager = processManager, |
227 | _stdio = stdio; |
228 | |
229 | final FileSystem _fileSystem; |
230 | final Logger _logger; |
231 | final ProcessUtils _processUtils; |
232 | final Platform _platform; |
233 | final BotDetector _botDetector; |
234 | final ProcessManager _processManager; |
235 | final Stdio? _stdio; |
236 | |
237 | @override |
238 | Future<void> get({ |
239 | required PubContext context, |
240 | required FlutterProject project, |
241 | bool upgrade = false, |
242 | bool offline = false, |
243 | String? flutterRootOverride, |
244 | bool checkUpToDate = false, |
245 | bool shouldSkipThirdPartyGenerator = true, |
246 | bool enforceLockfile = false, |
247 | PubOutputMode outputMode = PubOutputMode.all, |
248 | }) async { |
249 | final String directory = project.directory.path; |
250 | |
251 | // Here we use pub's private helper file to locate the package_config. |
252 | // In pub workspaces pub will generate a `.dart_tool/pub/workspace_ref.json` |
253 | // inside each workspace-package that refers to the workspace root where |
254 | // .dart_tool/package_config.json is located. |
255 | // |
256 | // By checking for this file instead of iterating parent directories until |
257 | // finding .dart_tool/package_config.json we will not mistakenly find a |
258 | // package_config.json from outside the workspace. |
259 | // |
260 | // TODO(sigurdm): avoid relying on pubs implementation details somehow? |
261 | final File workspaceRefFile = project.dartTool |
262 | .childDirectory('pub' ) |
263 | .childFile('workspace_ref.json' ); |
264 | final File packageConfigFile; |
265 | if (workspaceRefFile.existsSync()) { |
266 | switch (jsonDecode(workspaceRefFile.readAsStringSync())) { |
267 | case {'workspaceRoot' : final String workspaceRoot}: |
268 | packageConfigFile = _fileSystem.file( |
269 | _fileSystem.path.join(workspaceRefFile.parent.path, workspaceRoot), |
270 | ); |
271 | default: |
272 | // The workspace_ref.json file was malformed. Attempt to load the |
273 | // regular .dart_tool/package_config.json |
274 | // |
275 | // Most likely this doesn't exist, and we will get a new pub |
276 | // resolution. |
277 | // |
278 | // Alternatively this is a stray file somehow, and it can be ignored. |
279 | packageConfigFile = project.dartTool.childFile('package_config.json' ); |
280 | } |
281 | } else { |
282 | packageConfigFile = project.dartTool.childFile('package_config.json' ); |
283 | } |
284 | |
285 | if (packageConfigFile.existsSync()) { |
286 | final Directory workspaceRoot = packageConfigFile.parent.parent; |
287 | final File lastVersion = workspaceRoot.childDirectory('.dart_tool' ).childFile('version' ); |
288 | final File currentVersion = _fileSystem.file( |
289 | _fileSystem.path.join(Cache.flutterRoot!, 'version' ), |
290 | ); |
291 | final File pubspecYaml = project.pubspecFile; |
292 | final File pubLockFile = workspaceRoot.childFile('pubspec.lock' ); |
293 | |
294 | if (shouldSkipThirdPartyGenerator) { |
295 | Map<String, Object?> packageConfigMap; |
296 | try { |
297 | packageConfigMap = |
298 | jsonDecode(packageConfigFile.readAsStringSync()) as Map<String, Object?>; |
299 | } on FormatException { |
300 | packageConfigMap = <String, Object?>{}; |
301 | } |
302 | |
303 | final bool isPackageConfigGeneratedByThirdParty = |
304 | packageConfigMap.containsKey('generator' ) && packageConfigMap['generator' ] != 'pub' ; |
305 | |
306 | if (isPackageConfigGeneratedByThirdParty) { |
307 | _logger.printTrace('Skipping pub get: generated by third-party.' ); |
308 | return; |
309 | } |
310 | } |
311 | |
312 | // If the pubspec.yaml is older than the package config file and the last |
313 | // flutter version used is the same as the current version skip pub get. |
314 | // This will incorrectly skip pub on the master branch if dependencies |
315 | // are being added/removed from the flutter framework packages, but this |
316 | // can be worked around by manually running pub. |
317 | if (checkUpToDate && |
318 | pubLockFile.existsSync() && |
319 | pubspecYaml.lastModifiedSync().isBefore(pubLockFile.lastModifiedSync()) && |
320 | pubspecYaml.lastModifiedSync().isBefore(packageConfigFile.lastModifiedSync()) && |
321 | lastVersion.existsSync() && |
322 | lastVersion.readAsStringSync() == currentVersion.readAsStringSync()) { |
323 | _logger.printTrace('Skipping pub get: version match.' ); |
324 | return; |
325 | } |
326 | } |
327 | |
328 | final command = upgrade ? 'upgrade' : 'get' ; |
329 | final args = <String>[ |
330 | if (_logger.supportsColor) '--color' , |
331 | '--directory' , |
332 | _fileSystem.path.relative(directory), |
333 | ...<String>[command], |
334 | if (offline) '--offline' , |
335 | '--example' , |
336 | if (enforceLockfile) '--enforce-lockfile' , |
337 | ]; |
338 | await _runWithStdioInherited( |
339 | args, |
340 | command: command, |
341 | context: context, |
342 | directory: directory, |
343 | failureMessage: 'pub $command failed' , |
344 | flutterRootOverride: flutterRootOverride, |
345 | outputMode: outputMode, |
346 | ); |
347 | await _updateVersionAndPackageConfig(project); |
348 | } |
349 | |
350 | /// Runs pub with [arguments] and [ProcessStartMode.inheritStdio] mode. |
351 | /// |
352 | /// Uses [ProcessStartMode.normal] and [_stdio] if [Pub.test] constructor |
353 | /// was used. |
354 | /// |
355 | /// Prints the stdout and stderr of the whole run, unless silenced using |
356 | /// [outputMode]. |
357 | /// |
358 | /// Sends an analytics event. |
359 | Future<void> _runWithStdioInherited( |
360 | List<String> arguments, { |
361 | required String command, |
362 | required PubOutputMode outputMode, |
363 | required PubContext context, |
364 | required String directory, |
365 | String failureMessage = 'pub failed' , |
366 | String? flutterRootOverride, |
367 | }) async { |
368 | int exitCode; |
369 | |
370 | final pubCommand = <String>[..._pubCommand, ...arguments]; |
371 | final Map<String, String> pubEnvironment = await _createPubEnvironment( |
372 | context: context, |
373 | flutterRootOverride: flutterRootOverride, |
374 | summaryOnly: outputMode == PubOutputMode.summaryOnly, |
375 | ); |
376 | |
377 | String? pubStderr; |
378 | try { |
379 | if (outputMode != PubOutputMode.failuresOnly) { |
380 | final io.Stdio? stdio = _stdio; |
381 | if (stdio == null) { |
382 | // Let pub inherit stdio and output directly to the tool's stdout and |
383 | // stderr handles. |
384 | final io.Process process = await _processUtils.start( |
385 | pubCommand, |
386 | workingDirectory: _fileSystem.path.current, |
387 | environment: pubEnvironment, |
388 | mode: ProcessStartMode.inheritStdio, |
389 | ); |
390 | |
391 | exitCode = await process.exitCode; |
392 | } else { |
393 | // Omit [mode] parameter to send output to [process.stdout] and |
394 | // [process.stderr]. |
395 | final io.Process process = await _processUtils.start( |
396 | pubCommand, |
397 | workingDirectory: _fileSystem.path.current, |
398 | environment: pubEnvironment, |
399 | ); |
400 | |
401 | // Direct pub output to [Pub._stdio] for tests. |
402 | final StreamSubscription<List<int>> stdoutSubscription = process.stdout.listen( |
403 | stdio.stdout.add, |
404 | ); |
405 | final StreamSubscription<List<int>> stderrSubscription = process.stderr.listen( |
406 | stdio.stderr.add, |
407 | ); |
408 | |
409 | await Future.wait<void>(<Future<void>>[ |
410 | stdoutSubscription.asFuture<void>(), |
411 | stderrSubscription.asFuture<void>(), |
412 | ]); |
413 | |
414 | unawaited(stdoutSubscription.cancel()); |
415 | unawaited(stderrSubscription.cancel()); |
416 | |
417 | exitCode = await process.exitCode; |
418 | } |
419 | } else { |
420 | // Do not try to use [ProcessUtils.start] here, because it requires you |
421 | // to read all data out of the stdout and stderr streams. If you don't |
422 | // read the streams, it may appear to work fine on your platform but |
423 | // will block the tool's process on Windows. |
424 | // See https://api.dart.dev/stable/dart-io/Process/start.html |
425 | // |
426 | // [ProcessUtils.run] will send the output to [result.stdout] and |
427 | // [result.stderr], which we will ignore. |
428 | final RunResult result = await _processUtils.run( |
429 | pubCommand, |
430 | workingDirectory: _fileSystem.path.current, |
431 | environment: pubEnvironment, |
432 | ); |
433 | |
434 | exitCode = result.exitCode; |
435 | pubStderr = result.stderr; |
436 | } |
437 | } on io.ProcessException catch (exception) { |
438 | final buffer = StringBuffer(' ${exception.message}\n' ); |
439 | final directoryExistsMessage = _fileSystem.directory(directory).existsSync() |
440 | ? 'exists' |
441 | : 'does not exist' ; |
442 | buffer.writeln('Working directory: " $directory" ( $directoryExistsMessage)' ); |
443 | buffer.write(_stringifyPubEnv(pubEnvironment)); |
444 | throw io.ProcessException( |
445 | exception.executable, |
446 | exception.arguments, |
447 | buffer.toString(), |
448 | exception.errorCode, |
449 | ); |
450 | } |
451 | |
452 | final code = exitCode; |
453 | |
454 | if (code != 0) { |
455 | final buffer = StringBuffer(' $failureMessage\n' ); |
456 | buffer.writeln('command: " ${pubCommand.join(' ' )}"' ); |
457 | buffer.write(_stringifyPubEnv(pubEnvironment)); |
458 | buffer.writeln('exit code: $code' ); |
459 | _logger.printTrace(buffer.toString()); |
460 | |
461 | // When this is null, but a failure happened, it is assumed that stderr |
462 | // was already redirected to the process stderr. This handles the corner |
463 | // case where we otherwise would log nothing. See |
464 | // https://github.com/flutter/flutter/issues/148569 for details. |
465 | if (pubStderr != null) { |
466 | _logger.printError(pubStderr); |
467 | } |
468 | if (context == PubContext.updatePackages) { |
469 | _logger.printWarning( |
470 | 'If the current version was resolved as $kUnknownFrameworkVersion ' |
471 | 'and this is a fork of flutter/flutter, you forgot to set the remote ' |
472 | 'upstream branch to point to the canonical flutter/flutter: \n\n' |
473 | ' git remote set-url upstream https://github.com/flutter/flutter.git\n' |
474 | '\n' |
475 | 'See https://github.com/flutter/flutter/blob/main/docs/contributing/Setting-up-the-Framework-development-environment.md#set-up-your-environment.', |
476 | ); |
477 | } |
478 | throwToolExit('Failed to update packages.' , exitCode: code); |
479 | } |
480 | } |
481 | |
482 | // For surfacing pub env in crash reporting |
483 | String _stringifyPubEnv(Map<String, String> map, {String prefix = 'pub env' }) { |
484 | if (map.isEmpty) { |
485 | return '' ; |
486 | } |
487 | final buffer = StringBuffer(); |
488 | buffer.writeln(' $prefix: {' ); |
489 | for (final MapEntry<String, String> entry in map.entries) { |
490 | buffer.writeln(' " ${entry.key}": " ${entry.value}",' ); |
491 | } |
492 | buffer.writeln('}' ); |
493 | return buffer.toString(); |
494 | } |
495 | |
496 | @override |
497 | Future<void> batch( |
498 | List<String> arguments, { |
499 | required PubContext context, |
500 | String? directory, |
501 | MessageFilter? filter, |
502 | String failureMessage = 'pub failed' , |
503 | String? flutterRootOverride, |
504 | }) async { |
505 | final bool showTraceForErrors = await _botDetector.isRunningOnBot; |
506 | |
507 | var lastPubMessage = 'no message' ; |
508 | String? filterWrapper(String line) { |
509 | lastPubMessage = line; |
510 | if (filter == null) { |
511 | return line; |
512 | } |
513 | return filter(line); |
514 | } |
515 | |
516 | if (showTraceForErrors) { |
517 | arguments.insert(0, '--trace' ); |
518 | } |
519 | final Map<String, String> pubEnvironment = await _createPubEnvironment( |
520 | context: context, |
521 | flutterRootOverride: flutterRootOverride, |
522 | ); |
523 | final pubCommand = <String>[..._pubCommand, ...arguments]; |
524 | final int code = await _processUtils.stream( |
525 | pubCommand, |
526 | workingDirectory: directory, |
527 | mapFunction: filterWrapper, // may set versionSolvingFailed, lastPubMessage |
528 | environment: pubEnvironment, |
529 | ); |
530 | |
531 | if (code != 0) { |
532 | final buffer = StringBuffer(' $failureMessage\n' ); |
533 | buffer.writeln('command: " ${pubCommand.join(' ' )}"' ); |
534 | buffer.write(_stringifyPubEnv(pubEnvironment)); |
535 | buffer.writeln('exit code: $code' ); |
536 | buffer.writeln('last line of pub output: " ${lastPubMessage.trim()}"' ); |
537 | throwToolExit(buffer.toString(), exitCode: code); |
538 | } |
539 | } |
540 | |
541 | @override |
542 | Future<void> interactively( |
543 | List<String> arguments, { |
544 | FlutterProject? project, |
545 | required PubContext context, |
546 | required String command, |
547 | bool touchesPackageConfig = false, |
548 | bool generateSyntheticPackage = false, |
549 | PubOutputMode outputMode = PubOutputMode.all, |
550 | }) async { |
551 | await _runWithStdioInherited( |
552 | arguments, |
553 | command: command, |
554 | directory: _fileSystem.currentDirectory.path, |
555 | context: context, |
556 | outputMode: outputMode, |
557 | ); |
558 | if (touchesPackageConfig && project != null) { |
559 | await _updateVersionAndPackageConfig(project); |
560 | } |
561 | } |
562 | |
563 | /// The command used for running pub. |
564 | late final List<String> _pubCommand = _computePubCommand(); |
565 | |
566 | List<String> _computePubCommand() { |
567 | // TODO(zanderso): refactor to use artifacts. |
568 | final String sdkPath = _fileSystem.path.joinAll(<String>[ |
569 | Cache.flutterRoot!, |
570 | 'bin' , |
571 | 'cache' , |
572 | 'dart-sdk' , |
573 | 'bin' , |
574 | 'dart' , |
575 | ]); |
576 | if (!_processManager.canRun(sdkPath)) { |
577 | throwToolExit( |
578 | 'Your Flutter SDK download may be corrupt or missing permissions to run. ' |
579 | 'Try re-downloading the Flutter SDK into a directory that has read/write ' |
580 | 'permissions for the current user.' , |
581 | ); |
582 | } |
583 | return <String>[sdkPath, 'pub' , '--suppress-analytics' ]; |
584 | } |
585 | |
586 | // Returns the environment value that should be used when running pub. |
587 | // |
588 | // Includes any existing environment variable, if one exists. |
589 | // |
590 | // [context] provides extra information to package server requests to |
591 | // understand usage. |
592 | Future<String> _getPubEnvironmentValue(PubContext pubContext) async { |
593 | // DO NOT update this function without contacting kevmoo. |
594 | // We have server-side tooling that assumes the values are consistent. |
595 | final String? existing = _platform.environment[_kPubEnvironmentKey]; |
596 | final values = <String>[ |
597 | if (existing != null && existing.isNotEmpty) existing, |
598 | if (await _botDetector.isRunningOnBot) 'flutter_bot' , |
599 | 'flutter_cli' , |
600 | ...pubContext._values, |
601 | ]; |
602 | return values.join(':' ); |
603 | } |
604 | |
605 | /// There are 2 ways to get the pub cache location |
606 | /// |
607 | /// 1) Provide the _kPubCacheEnvironmentKey. |
608 | /// 2) The pub default user-level pub cache. |
609 | /// |
610 | /// If we are using 2, check if there are pre-packaged packages in |
611 | /// $FLUTTER_ROOT/.pub-preload-cache and install them in the user-level cache. |
612 | String? _getPubCacheIfAvailable() { |
613 | if (_platform.environment.containsKey(_kPubCacheEnvironmentKey)) { |
614 | return _platform.environment[_kPubCacheEnvironmentKey]; |
615 | } |
616 | _preloadPubCache(); |
617 | // Use pub's default location by returning null. |
618 | return null; |
619 | } |
620 | |
621 | /// Load any package-files stored in `FLUTTER_ROOT/.pub-preload-cache` into |
622 | /// the pub cache if it exists. |
623 | /// |
624 | /// Deletes the `.pub-preload-cache` directory. |
625 | void _preloadPubCache() { |
626 | final String flutterRootPath = Cache.flutterRoot!; |
627 | final Directory flutterRoot = _fileSystem.directory(flutterRootPath); |
628 | final Directory preloadCacheDir = flutterRoot.childDirectory('.pub-preload-cache' ); |
629 | if (preloadCacheDir.existsSync()) { |
630 | /// We only want to inform about existing caches on first run of a freshly |
631 | /// downloaded Flutter SDK. Therefore it is conditioned on the existence |
632 | /// of the .pub-preload-cache dir. |
633 | final Iterable<String> cacheFiles = preloadCacheDir |
634 | .listSync() |
635 | .map((FileSystemEntity f) => f.path) |
636 | .where((String path) => path.endsWith('.tar.gz' )); |
637 | _processManager.runSync(<String>[..._pubCommand, 'cache' , 'preload' , ...cacheFiles]); |
638 | _tryDeleteDirectory(preloadCacheDir, _logger); |
639 | } |
640 | } |
641 | |
642 | /// The full environment used when running pub. |
643 | /// |
644 | /// [context] provides extra information to package server requests to |
645 | /// understand usage. |
646 | Future<Map<String, String>> _createPubEnvironment({ |
647 | required PubContext context, |
648 | String? flutterRootOverride, |
649 | bool? summaryOnly = false, |
650 | }) async { |
651 | final environment = <String, String>{ |
652 | 'FLUTTER_ROOT' : flutterRootOverride ?? Cache.flutterRoot!, |
653 | _kPubEnvironmentKey: await _getPubEnvironmentValue(context), |
654 | if (summaryOnly ?? false) 'PUB_SUMMARY_ONLY' : '1' , |
655 | }; |
656 | final String? pubCache = _getPubCacheIfAvailable(); |
657 | if (pubCache != null) { |
658 | environment[_kPubCacheEnvironmentKey] = pubCache; |
659 | } |
660 | return environment; |
661 | } |
662 | |
663 | /// Updates the .dart_tool/version file to be equal to current Flutter |
664 | /// version. |
665 | /// |
666 | /// This should be called after pub invocations that are expected to update |
667 | /// the packageConfig. |
668 | Future<void> _updateVersionAndPackageConfig(FlutterProject project) async { |
669 | final File? packageConfig = findPackageConfigFile(project.directory); |
670 | if (packageConfig == null) { |
671 | throwToolExit( |
672 | ' ${project.directory}: pub did not create .dart_tools/package_config.json file.' , |
673 | ); |
674 | } |
675 | final File lastVersion = _fileSystem.file( |
676 | _fileSystem.path.join(packageConfig.parent.path, 'version' ), |
677 | ); |
678 | final File currentVersion = _fileSystem.file( |
679 | _fileSystem.path.join(Cache.flutterRoot!, 'version' ), |
680 | ); |
681 | lastVersion.writeAsStringSync(currentVersion.readAsStringSync()); |
682 | |
683 | if (project.hasExampleApp && project.example.pubspecFile.existsSync()) { |
684 | final File? examplePackageConfig = findPackageConfigFile(project.example.directory); |
685 | if (examplePackageConfig == null) { |
686 | throwToolExit( |
687 | ' ${project.directory}: pub did not create example/.dart_tools/package_config.json file.' , |
688 | ); |
689 | } |
690 | } |
691 | } |
692 | } |
693 | |