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

Provided by KDAB

Privacy Policy
Learn more about Flutter for embedded and desktop on industrialflutter.com