diff --git a/.github/labeler.yml b/.github/labeler.yml index 628e93490d1..04670086690 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -38,11 +38,6 @@ - any-glob-to-any-file: - packages/flutter_lints/**/* -'p: flutter_migrate': - - changed-files: - - any-glob-to-any-file: - - packages/flutter_migrate/**/* - 'p: flutter_plugin_android_lifecycle': - changed-files: - any-glob-to-any-file: diff --git a/CODEOWNERS b/CODEOWNERS index fcd6bdd9683..40837cbc59c 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -10,7 +10,6 @@ packages/cross_file/** @ditman packages/extension_google_sign_in_as_googleapis_auth/** @ditman packages/file_selector/** @stuartmorgan-g packages/flutter_lints/** @chunhtai -packages/flutter_migrate/** @stuartmorgan-g packages/flutter_template_images/** @stuartmorgan-g packages/go_router/** @chunhtai packages/go_router_builder/** @chunhtai diff --git a/packages/flutter_migrate/.gitignore b/packages/flutter_migrate/.gitignore deleted file mode 100644 index 437cb45872e..00000000000 --- a/packages/flutter_migrate/.gitignore +++ /dev/null @@ -1,36 +0,0 @@ -# Miscellaneous -*.class -*.log -*.pyc -*.swp -.DS_Store -.atom/ -.buildlog/ -.history -.svn/ - -# IntelliJ related -*.iml -*.ipr -*.iws -.idea/ - -# The .vscode folder contains launch configuration and tasks you configure in -# VS Code which you may wish to be included in version control, so this line -# is commented out by default. -#.vscode/ - -# Flutter/Dart/Pub related -**/doc/api/ -.dart_tool/ -.flutter-plugins -.packages -.pub-cache/ -.pub/ -/build/ - -# Web related -lib/generated_plugin_registrant.dart - -# Exceptions to above rules. -!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages diff --git a/packages/flutter_migrate/AUTHORS b/packages/flutter_migrate/AUTHORS deleted file mode 100644 index 557dff97933..00000000000 --- a/packages/flutter_migrate/AUTHORS +++ /dev/null @@ -1,6 +0,0 @@ -# Below is a list of people and organizations that have contributed -# to the Flutter project. Names should be added to the list like so: -# -# Name/Organization - -Google Inc. diff --git a/packages/flutter_migrate/CHANGELOG.md b/packages/flutter_migrate/CHANGELOG.md deleted file mode 100644 index 1482a62df38..00000000000 --- a/packages/flutter_migrate/CHANGELOG.md +++ /dev/null @@ -1,24 +0,0 @@ -## NEXT - -* Updates minimum supported SDK version to Flutter 3.29/Dart 3.7. - -## 0.0.1+4 - -* Adds `missing_code_block_language_in_doc_comment` lint. -* Updates minimum supported SDK version to Flutter 3.19/Dart 3.3. - -## 0.0.1+3 - -* Removes obsolete null checks on non-nullable values. - -## 0.0.1+2 - -* Removes use of `runtimeType.toString()`. - -## 0.0.1+1 - -* Updates code to fix strict-cast violations. - -## 0.0.1 - -* Initial version. diff --git a/packages/flutter_migrate/LICENSE b/packages/flutter_migrate/LICENSE deleted file mode 100644 index c6823b81eb8..00000000000 --- a/packages/flutter_migrate/LICENSE +++ /dev/null @@ -1,25 +0,0 @@ -Copyright 2013 The Flutter Authors. All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following - disclaimer in the documentation and/or other materials provided - with the distribution. - * Neither the name of Google Inc. nor the names of its - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/packages/flutter_migrate/README.md b/packages/flutter_migrate/README.md deleted file mode 100644 index 2d33273d940..00000000000 --- a/packages/flutter_migrate/README.md +++ /dev/null @@ -1,50 +0,0 @@ -# Flutter Migrate - -## Overview - -This is a tool that helps migrate legacy Flutter projects generated with old version of -Flutter to modern Flutter templates. This allows old apps to access new features, update -key dependenices and prevent slow bitrot of projects over time without domain knowledge -of individual platforms like Android and iOS. - -## Prerequisites - -This tool supports migrating apps generated with Flutter 1.0.0 and newer. However, projects -generated with older versions of Flutter (beta, alpha, etc) may still be compatible with -this tool, but results may vary and official support will not be provided. - -Projects that contain heavy modifications to the project's platform directories (eg, -`android/`, `ios/`, `linux/`) may result in many conflicts. - -Currently, only full Flutter apps are supported. This tool will not work properly with -plugins, or add-to-app Flutter apps. - -The project must be a git repository with no uncommitted changes. Git is used to revert -any migrations that are broken. - -## Usage - -To run the tool enter the root directory of your flutter project and run: - - `dart run /bin/flutter_migrate.dart [parameters]` - -The core subcommand sequence to use is `start`, `apply`. - -* `start` will generate a migration that will be staged in the `migration_staging_directory` - in your project home. This command may take some time to complete depending on network speed. - The generated migration may have conflicts that should be manually resolved or resolved with - the `resolve-conflicts` subcommand. - -* `apply` will apply staged changes to the actual project. Any merge conflicts should be resolved - in the staging directory before applying - -These additional commands help you manage and navigate the migration: - -* `status` Prints the diffs of the staged changes as well as a list of the files with changes. - Any files with conflicts will also be highlighted. - -* `abandon` Abandons the existing migration by deleting the staging directory. - -* `resolve-conflicts` Wizard that assists in resolving routine conflicts. The wizard will - routinely show each conflict where the option to keep the old code, new code, or skip and - resolve manually are presented. diff --git a/packages/flutter_migrate/analysis_options.yaml b/packages/flutter_migrate/analysis_options.yaml deleted file mode 100644 index 9d4b82234de..00000000000 --- a/packages/flutter_migrate/analysis_options.yaml +++ /dev/null @@ -1,7 +0,0 @@ -# Specify analysis options. - -include: ../../analysis_options.yaml - -linter: - rules: - public_member_api_docs: false # Standalone executable, no public API diff --git a/packages/flutter_migrate/bin/flutter_migrate.dart b/packages/flutter_migrate/bin/flutter_migrate.dart deleted file mode 100644 index 5e49696c40d..00000000000 --- a/packages/flutter_migrate/bin/flutter_migrate.dart +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright 2013 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 'package:flutter_migrate/executable.dart' as executable; - -void main(List args) { - executable.main(args); -} diff --git a/packages/flutter_migrate/dart_test.yaml b/packages/flutter_migrate/dart_test.yaml deleted file mode 100644 index 91ec220b8e2..00000000000 --- a/packages/flutter_migrate/dart_test.yaml +++ /dev/null @@ -1 +0,0 @@ -test_on: vm diff --git a/packages/flutter_migrate/lib/executable.dart b/packages/flutter_migrate/lib/executable.dart deleted file mode 100644 index e90b782ee9f..00000000000 --- a/packages/flutter_migrate/lib/executable.dart +++ /dev/null @@ -1,102 +0,0 @@ -// Copyright 2013 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 'dart:io' as io; - -import 'package:args/args.dart'; -import 'package:args/command_runner.dart'; - -import 'src/base/command.dart'; -import 'src/base/file_system.dart'; -import 'src/base_dependencies.dart'; - -import 'src/commands/abandon.dart'; -import 'src/commands/apply.dart'; -import 'src/commands/start.dart'; -import 'src/commands/status.dart'; - -Future main(List args) async { - final bool veryVerbose = args.contains('-vv'); - final bool verbose = - args.contains('-v') || args.contains('--verbose') || veryVerbose; - - final MigrateBaseDependencies baseDependencies = MigrateBaseDependencies(); - - final List commands = [ - MigrateStartCommand( - verbose: verbose, - logger: baseDependencies.logger, - fileSystem: baseDependencies.fileSystem, - processManager: baseDependencies.processManager, - ), - MigrateStatusCommand( - verbose: verbose, - logger: baseDependencies.logger, - fileSystem: baseDependencies.fileSystem, - processManager: baseDependencies.processManager, - ), - MigrateAbandonCommand( - logger: baseDependencies.logger, - fileSystem: baseDependencies.fileSystem, - terminal: baseDependencies.terminal, - processManager: baseDependencies.processManager, - ), - MigrateApplyCommand( - verbose: verbose, - logger: baseDependencies.logger, - fileSystem: baseDependencies.fileSystem, - terminal: baseDependencies.terminal, - processManager: baseDependencies.processManager, - ), - ]; - - final MigrateCommandRunner runner = MigrateCommandRunner(); - - commands.forEach(runner.addCommand); - await runner.run(args); - - await _exit( - 0, - baseDependencies, - shutdownHooks: baseDependencies.fileSystem.shutdownHooks, - ); - await baseDependencies.fileSystem.dispose(); -} - -/// Simple extension of a CommandRunner to provide migrate specific global flags. -class MigrateCommandRunner extends CommandRunner { - MigrateCommandRunner() - : super('flutter', 'Migrates legacy flutter projects to modern versions.') { - argParser.addFlag( - 'verbose', - abbr: 'v', - negatable: false, - help: 'Noisy logging, including all shell commands executed.', - ); - } - - @override - ArgParser get argParser => _argParser; - final ArgParser _argParser = ArgParser(); -} - -Future _exit( - int code, - MigrateBaseDependencies baseDependencies, { - required ShutdownHooks shutdownHooks, -}) async { - // Run shutdown hooks before flushing logs - await shutdownHooks.runShutdownHooks(baseDependencies.logger); - - final Completer completer = Completer(); - - // Give the task / timer queue one cycle through before we hard exit. - Timer.run(() { - io.exit(code); - }); - - await completer.future; - return code; -} diff --git a/packages/flutter_migrate/lib/src/base/command.dart b/packages/flutter_migrate/lib/src/base/command.dart deleted file mode 100644 index 22fca4854c9..00000000000 --- a/packages/flutter_migrate/lib/src/base/command.dart +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright 2013 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 'package:args/command_runner.dart'; - -import 'logger.dart'; -import 'project.dart'; - -enum ExitStatus { success, warning, fail, killed } - -const String flutterNoPubspecMessage = - 'Error: No pubspec.yaml file found.\n' - 'This command should be run from the root of your Flutter project.'; - -class CommandResult { - const CommandResult(this.exitStatus); - - /// A command that succeeded. It is used to log the result of a command invocation. - factory CommandResult.success() { - return const CommandResult(ExitStatus.success); - } - - /// A command that exited with a warning. It is used to log the result of a command invocation. - factory CommandResult.warning() { - return const CommandResult(ExitStatus.warning); - } - - /// A command that failed. It is used to log the result of a command invocation. - factory CommandResult.fail() { - return const CommandResult(ExitStatus.fail); - } - - final ExitStatus exitStatus; - - @override - String toString() { - switch (exitStatus) { - case ExitStatus.success: - return 'success'; - case ExitStatus.warning: - return 'warning'; - case ExitStatus.fail: - return 'fail'; - case ExitStatus.killed: - return 'killed'; - } - } -} - -abstract class MigrateCommand extends Command { - @override - Future run() async { - await runCommand(); - return; - } - - Future runCommand(); - - /// Gets the parsed command-line option named [name] as a `bool?`. - bool? boolArg(String name) { - if (!argParser.options.containsKey(name)) { - return null; - } - return argResults == null ? null : argResults![name] as bool; - } - - String? stringArg(String name) { - if (!argParser.options.containsKey(name)) { - return null; - } - return argResults == null ? null : argResults![name] as String?; - } - - /// Gets the parsed command-line option named [name] as an `int`. - int? intArg(String name) => argResults?[name] as int?; - - bool validateWorkingDirectory(FlutterProject project, Logger logger) { - if (!project.pubspecFile.existsSync()) { - logger.printError(flutterNoPubspecMessage); - return false; - } - return true; - } -} diff --git a/packages/flutter_migrate/lib/src/base/common.dart b/packages/flutter_migrate/lib/src/base/common.dart deleted file mode 100644 index 21a9230d81b..00000000000 --- a/packages/flutter_migrate/lib/src/base/common.dart +++ /dev/null @@ -1,284 +0,0 @@ -// Copyright 2013 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 'dart:io'; - -import 'file_system.dart'; - -/// Throw a specialized exception for expected situations -/// where the tool should exit with a clear message to the user -/// and no stack trace unless the --verbose option is specified. -/// For example: network errors. -Never throwToolExit(String? message, {int? exitCode}) { - throw ToolExit(message, exitCode: exitCode); -} - -/// Specialized exception for expected situations -/// where the tool should exit with a clear message to the user -/// and no stack trace unless the --verbose option is specified. -/// For example: network errors. -class ToolExit implements Exception { - ToolExit(this.message, {this.exitCode}); - - final String? message; - final int? exitCode; - - @override - String toString() => 'Error: $message'; -} - -/// Return the name of an enum item. -String getEnumName(dynamic enumItem) { - final String name = '$enumItem'; - final int index = name.indexOf('.'); - return index == -1 ? name : name.substring(index + 1); -} - -/// Runs [fn] with special handling of asynchronous errors. -/// -/// If the execution of [fn] does not throw a synchronous exception, and if the -/// [Future] returned by [fn] is completed with a value, then the [Future] -/// returned by [asyncGuard] is completed with that value if it has not already -/// been completed with an error. -/// -/// If the execution of [fn] throws a synchronous exception, and no [onError] -/// callback is provided, then the [Future] returned by [asyncGuard] is -/// completed with an error whose object and stack trace are given by the -/// synchronous exception. If an [onError] callback is provided, then the -/// [Future] returned by [asyncGuard] is completed with its result when passed -/// the error object and stack trace. -/// -/// If the execution of [fn] results in an asynchronous exception that would -/// otherwise be unhandled, and no [onError] callback is provided, then the -/// [Future] returned by [asyncGuard] is completed with an error whose object -/// and stack trace are given by the asynchronous exception. If an [onError] -/// callback is provided, then the [Future] returned by [asyncGuard] is -/// completed with its result when passed the error object and stack trace. -/// -/// After the returned [Future] is completed, whether it be with a value or an -/// error, all further errors resulting from the execution of [fn] are ignored. -/// -/// Rationale: -/// -/// Consider the following snippet: -/// ```dart -/// try { -/// await foo(); -/// ... -/// } catch (e) { -/// ... -/// } -/// ``` -/// If the [Future] returned by `foo` is completed with an error, that error is -/// handled by the catch block. However, if `foo` spawns an asynchronous -/// operation whose errors are unhandled, those errors will not be caught by -/// the catch block, and will instead propagate to the containing [Zone]. This -/// behavior is non-intuitive to programmers expecting the `catch` to catch all -/// the errors resulting from the code under the `try`. -/// -/// As such, it would be convenient if the `try {} catch {}` here could handle -/// not only errors completing the awaited [Future]s it contains, but also -/// any otherwise unhandled asynchronous errors occurring as a result of awaited -/// expressions. This is how `await` is often assumed to work, which leads to -/// unexpected unhandled exceptions. -/// -/// [asyncGuard] is intended to wrap awaited expressions occurring in a `try` -/// block. The behavior described above gives the behavior that users -/// intuitively expect from `await`. Consider the snippet: -/// ```dart -/// try { -/// await asyncGuard(() async { -/// var c = Completer(); -/// c.completeError('Error'); -/// }); -/// } catch (e) { -/// // e is 'Error'; -/// } -/// ``` -/// Without the [asyncGuard] the error 'Error' would be propagated to the -/// error handler of the containing [Zone]. With the [asyncGuard], the error -/// 'Error' is instead caught by the `catch`. -/// -/// [asyncGuard] also accepts an [onError] callback for situations in which -/// completing the returned [Future] with an error is not appropriate. -/// For example, it is not always possible to immediately await the returned -/// [Future]. In these cases, an [onError] callback is needed to prevent an -/// error from propagating to the containing [Zone]. -/// -/// [onError] must have type `FutureOr Function(Object error)` or -/// `FutureOr Function(Object error, StackTrace stackTrace)` otherwise an -/// [ArgumentError] will be thrown synchronously. -Future asyncGuard(Future Function() fn, {Function? onError}) { - if (onError != null && - onError is! _UnaryOnError && - onError is! _BinaryOnError) { - throw ArgumentError( - 'onError must be a unary function accepting an Object, ' - 'or a binary function accepting an Object and ' - 'StackTrace. onError must return a T', - ); - } - final Completer completer = Completer(); - - void handleError(Object e, StackTrace s) { - if (completer.isCompleted) { - return; - } - if (onError == null) { - completer.completeError(e, s); - return; - } - if (onError is _BinaryOnError) { - completer.complete(onError(e, s)); - } else if (onError is _UnaryOnError) { - completer.complete(onError(e)); - } - } - - runZonedGuarded( - () async { - try { - final T result = await fn(); - if (!completer.isCompleted) { - completer.complete(result); - } - // This catches all exceptions so that they can be propagated to the - // caller-supplied error handling or the completer. - } catch (e, s) { - // ignore: avoid_catches_without_on_clauses, forwards to Future - handleError(e, s); - } - }, - (Object e, StackTrace s) { - handleError(e, s); - }, - ); - - return completer.future; -} - -typedef _UnaryOnError = FutureOr Function(Object error); -typedef _BinaryOnError = - FutureOr Function(Object error, StackTrace stackTrace); - -/// Whether the test is running in a web browser compiled to JavaScript. -/// -/// See also: -/// -/// * [kIsWeb], the equivalent constant in the `foundation` library. -const bool isBrowser = identical(0, 0.0); - -/// Whether the test is running on the Windows operating system. -/// -/// This does not include tests compiled to JavaScript running in a browser on -/// the Windows operating system. -/// -/// See also: -/// -/// * [isBrowser], which reports true for tests running in browsers. -bool get isWindows { - if (isBrowser) { - return false; - } - return Platform.isWindows; -} - -/// Whether the test is running on the macOS operating system. -/// -/// This does not include tests compiled to JavaScript running in a browser on -/// the macOS operating system. -/// -/// See also: -/// -/// * [isBrowser], which reports true for tests running in browsers. -bool get isMacOS { - if (isBrowser) { - return false; - } - return Platform.isMacOS; -} - -/// Whether the test is running on the Linux operating system. -/// -/// This does not include tests compiled to JavaScript running in a browser on -/// the Linux operating system. -/// -/// See also: -/// -/// * [isBrowser], which reports true for tests running in browsers. -bool get isLinux { - if (isBrowser) { - return false; - } - return Platform.isLinux; -} - -String? flutterRoot; - -/// Determine the absolute and normalized path for the root of the current -/// Flutter checkout. -/// -/// This method has a series of fallbacks for determining the repo location. The -/// first success will immediately return the root without further checks. -/// -/// The order of these tests is: -/// 1. FLUTTER_ROOT environment variable contains the path. -/// 2. Platform script is a data URI scheme, returning `../..` to support -/// tests run from `packages/flutter_tools`. -/// 3. Platform script is package URI scheme, returning the grandparent directory -/// of the package config file location from `packages/flutter_tools/.packages`. -/// 4. Platform script file path is the snapshot path generated by `bin/flutter`, -/// returning the grandparent directory from `bin/cache`. -/// 5. Platform script file name is the entrypoint in `packages/flutter_tools/bin/flutter_tools.dart`, -/// returning the 4th parent directory. -/// 6. The current directory -/// -/// If an exception is thrown during any of these checks, an error message is -/// printed and `.` is returned by default (6). -String defaultFlutterRoot({required FileSystem fileSystem}) { - const String kFlutterRootEnvironmentVariableName = - 'FLUTTER_ROOT'; // should point to //flutter/ (root of flutter/flutter repo) - const String kSnapshotFileName = - 'flutter_tools.snapshot'; // in //flutter/bin/cache/ - const String kFlutterToolsScriptFileName = - 'flutter_tools.dart'; // in //flutter/packages/flutter_tools/bin/ - String normalize(String path) { - return fileSystem.path.normalize(fileSystem.path.absolute(path)); - } - - if (Platform.environment.containsKey(kFlutterRootEnvironmentVariableName)) { - return normalize( - Platform.environment[kFlutterRootEnvironmentVariableName]!, - ); - } - try { - if (Platform.script.scheme == 'data') { - return normalize('../..'); // The tool is running as a test. - } - final String Function(String) dirname = fileSystem.path.dirname; - - if (Platform.script.scheme == 'package') { - final String packageConfigPath = Uri.parse( - Platform.packageConfig!, - ).toFilePath(windows: isWindows); - return normalize(dirname(dirname(dirname(packageConfigPath)))); - } - - if (Platform.script.scheme == 'file') { - final String script = Platform.script.toFilePath(windows: isWindows); - if (fileSystem.path.basename(script) == kSnapshotFileName) { - return normalize(dirname(dirname(fileSystem.path.dirname(script)))); - } - if (fileSystem.path.basename(script) == kFlutterToolsScriptFileName) { - return normalize(dirname(dirname(dirname(dirname(script))))); - } - } - } on Exception catch (error) { - // There is currently no logger attached since this is computed at startup. - // ignore: avoid_print - print('$error'); - } - return normalize('.'); -} diff --git a/packages/flutter_migrate/lib/src/base/context.dart b/packages/flutter_migrate/lib/src/base/context.dart deleted file mode 100644 index 6ef5db4f22f..00000000000 --- a/packages/flutter_migrate/lib/src/base/context.dart +++ /dev/null @@ -1,200 +0,0 @@ -// Copyright 2013 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 'dart:collection'; - -import 'package:meta/meta.dart'; - -/// Generates an [AppContext] value. -/// -/// Generators are allowed to return `null`, in which case the context will -/// store the `null` value as the value for that type. -typedef Generator = dynamic Function(); - -/// An exception thrown by [AppContext] when you try to get a [Type] value from -/// the context, and the instantiation of the value results in a dependency -/// cycle. -class ContextDependencyCycleException implements Exception { - ContextDependencyCycleException._(this.cycle); - - /// The dependency cycle (last item depends on first item). - final List cycle; - - @override - String toString() => 'Dependency cycle detected: ${cycle.join(' -> ')}'; -} - -/// The Zone key used to look up the [AppContext]. -@visibleForTesting -const Object contextKey = _Key.key; - -/// The current [AppContext], as determined by the [Zone] hierarchy. -/// -/// This will be the first context found as we scan up the zone hierarchy, or -/// the "root" context if a context cannot be found in the hierarchy. The root -/// context will not have any values associated with it. -/// -/// This is guaranteed to never return `null`. -AppContext get context => - Zone.current[contextKey] as AppContext? ?? AppContext._root; - -/// A lookup table (mapping types to values) and an implied scope, in which -/// code is run. -/// -/// [AppContext] is used to define a singleton injection context for code that -/// is run within it. Each time you call [run], a child context (and a new -/// scope) is created. -/// -/// Child contexts are created and run using zones. To read more about how -/// zones work, see https://api.dart.dev/stable/dart-async/Zone-class.html. -class AppContext { - AppContext._( - this._parent, - this.name, [ - this._overrides = const {}, - this._fallbacks = const {}, - ]); - - final String? name; - final AppContext? _parent; - final Map _overrides; - final Map _fallbacks; - final Map _values = {}; - - List? _reentrantChecks; - - /// Bootstrap context. - static final AppContext _root = AppContext._(null, 'ROOT'); - - dynamic _boxNull(dynamic value) => value ?? _BoxedNull.instance; - - dynamic _unboxNull(dynamic value) => - value == _BoxedNull.instance ? null : value; - - /// Returns the generated value for [type] if such a generator exists. - /// - /// If [generators] does not contain a mapping for the specified [type], this - /// returns `null`. - /// - /// If a generator existed and generated a `null` value, this will return a - /// boxed value indicating null. - /// - /// If a value for [type] has already been generated by this context, the - /// existing value will be returned, and the generator will not be invoked. - /// - /// If the generator ends up triggering a reentrant call, it signals a - /// dependency cycle, and a [ContextDependencyCycleException] will be thrown. - dynamic _generateIfNecessary(Type type, Map generators) { - if (!generators.containsKey(type)) { - return null; - } - - return _values.putIfAbsent(type, () { - _reentrantChecks ??= []; - - final int index = _reentrantChecks!.indexOf(type); - if (index >= 0) { - // We're already in the process of trying to generate this type. - throw ContextDependencyCycleException._( - UnmodifiableListView(_reentrantChecks!.sublist(index)), - ); - } - - _reentrantChecks!.add(type); - try { - return _boxNull(generators[type]!()); - } finally { - _reentrantChecks!.removeLast(); - if (_reentrantChecks!.isEmpty) { - _reentrantChecks = null; - } - } - }); - } - - /// Gets the value associated with the specified [type], or `null` if no - /// such value has been associated. - T? get() { - dynamic value = _generateIfNecessary(T, _overrides); - if (value == null && _parent != null) { - value = _parent.get(); - } - return _unboxNull(value ?? _generateIfNecessary(T, _fallbacks)) as T?; - } - - /// Runs [body] in a child context and returns the value returned by [body]. - /// - /// If [overrides] is specified, the child context will return corresponding - /// values when consulted via [operator[]]. - /// - /// If [fallbacks] is specified, the child context will return corresponding - /// values when consulted via [operator[]] only if its parent context didn't - /// return such a value. - /// - /// If [name] is specified, the child context will be assigned the given - /// name. This is useful for debugging purposes and is analogous to naming a - /// thread in Java. - Future run({ - required FutureOr Function() body, - String? name, - Map? overrides, - Map? fallbacks, - ZoneSpecification? zoneSpecification, - }) async { - final AppContext child = AppContext._( - this, - name, - Map.unmodifiable(overrides ?? const {}), - Map.unmodifiable(fallbacks ?? const {}), - ); - return runZoned>( - () async => await body(), - zoneValues: <_Key, AppContext>{_Key.key: child}, - zoneSpecification: zoneSpecification, - ); - } - - @override - String toString() { - final StringBuffer buf = StringBuffer(); - String indent = ''; - AppContext? ctx = this; - while (ctx != null) { - buf.write('AppContext'); - if (ctx.name != null) { - buf.write('[${ctx.name}]'); - } - if (ctx._overrides.isNotEmpty) { - buf.write('\n$indent overrides: [${ctx._overrides.keys.join(', ')}]'); - } - if (ctx._fallbacks.isNotEmpty) { - buf.write('\n$indent fallbacks: [${ctx._fallbacks.keys.join(', ')}]'); - } - if (ctx._parent != null) { - buf.write('\n$indent parent: '); - } - ctx = ctx._parent; - indent += ' '; - } - return buf.toString(); - } -} - -/// Private key used to store the [AppContext] in the [Zone]. -class _Key { - const _Key(); - - static const _Key key = _Key(); - - @override - String toString() => 'context'; -} - -/// Private object that denotes a generated `null` value. -class _BoxedNull { - const _BoxedNull(); - - static const _BoxedNull instance = _BoxedNull(); -} diff --git a/packages/flutter_migrate/lib/src/base/file_system.dart b/packages/flutter_migrate/lib/src/base/file_system.dart deleted file mode 100644 index 701ee99185e..00000000000 --- a/packages/flutter_migrate/lib/src/base/file_system.dart +++ /dev/null @@ -1,174 +0,0 @@ -// Copyright 2013 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:file/file.dart'; -import 'package:file/local.dart' as local_fs; -import 'package:meta/meta.dart'; - -import 'common.dart'; -import 'io.dart'; -import 'logger.dart'; -import 'signals.dart'; - -// package:file/local.dart must not be exported. This exposes LocalFileSystem, -// which we override to ensure that temporary directories are cleaned up when -// the tool is killed by a signal. -export 'package:file/file.dart'; - -/// Exception indicating that a file that was expected to exist was not found. -class FileNotFoundException implements IOException { - const FileNotFoundException(this.path); - - final String path; - - @override - String toString() => 'File not found: $path'; -} - -/// Return a relative path if [fullPath] is contained by the cwd, else return an -/// absolute path. -String getDisplayPath(String fullPath, FileSystem fileSystem) { - final String cwd = - fileSystem.currentDirectory.path + fileSystem.path.separator; - return fullPath.startsWith(cwd) ? fullPath.substring(cwd.length) : fullPath; -} - -/// This class extends [local_fs.LocalFileSystem] in order to clean up -/// directories and files that the tool creates under the system temporary -/// directory when the tool exits either normally or when killed by a signal. -class LocalFileSystem extends local_fs.LocalFileSystem { - LocalFileSystem(this._signals, this._fatalSignals, this.shutdownHooks); - - @visibleForTesting - LocalFileSystem.test({ - required Signals signals, - List fatalSignals = Signals.defaultExitSignals, - }) : this(signals, fatalSignals, ShutdownHooks()); - - Directory? _systemTemp; - final Map _signalTokens = {}; - - final ShutdownHooks shutdownHooks; - - Future dispose() async { - _tryToDeleteTemp(); - for (final MapEntry signalToken - in _signalTokens.entries) { - await _signals.removeHandler(signalToken.key, signalToken.value); - } - _signalTokens.clear(); - } - - final Signals _signals; - final List _fatalSignals; - - void _tryToDeleteTemp() { - try { - if (_systemTemp?.existsSync() ?? false) { - _systemTemp?.deleteSync(recursive: true); - } - } on FileSystemException { - // ignore - } - _systemTemp = null; - } - - // This getter returns a fresh entry under /tmp, like - // /tmp/flutter_tools.abcxyz, then the rest of the tool creates /tmp entries - // under that, like /tmp/flutter_tools.abcxyz/flutter_build_stuff.123456. - // Right before exiting because of a signal or otherwise, we delete - // /tmp/flutter_tools.abcxyz, not the whole of /tmp. - @override - Directory get systemTempDirectory { - if (_systemTemp == null) { - if (!superSystemTempDirectory.existsSync()) { - throwToolExit( - 'Your system temp directory (${superSystemTempDirectory.path}) does not exist. ' - 'Did you set an invalid override in your environment? See issue https://github.com/flutter/flutter/issues/74042 for more context.', - ); - } - _systemTemp = superSystemTempDirectory.createTempSync('flutter_tools.') - ..createSync(recursive: true); - // Make sure that the temporary directory is cleaned up if the tool is - // killed by a signal. - for (final ProcessSignal signal in _fatalSignals) { - final Object token = _signals.addHandler(signal, (ProcessSignal _) { - _tryToDeleteTemp(); - }); - _signalTokens[signal] = token; - } - // Make sure that the temporary directory is cleaned up when the tool - // exits normally. - shutdownHooks.addShutdownHook(_tryToDeleteTemp); - } - return _systemTemp!; - } - - // This only exist because the memory file system does not support a systemTemp that does not exists #74042 - @visibleForTesting - Directory get superSystemTempDirectory => super.systemTempDirectory; -} - -/// A function that will be run before the VM exits. -typedef ShutdownHook = FutureOr Function(); - -abstract class ShutdownHooks { - factory ShutdownHooks() => _DefaultShutdownHooks(); - - /// Registers a [ShutdownHook] to be executed before the VM exits. - void addShutdownHook(ShutdownHook shutdownHook); - - @visibleForTesting - List get registeredHooks; - - /// Runs all registered shutdown hooks and returns a future that completes when - /// all such hooks have finished. - /// - /// Shutdown hooks will be run in groups by their [ShutdownStage]. All shutdown - /// hooks within a given stage will be started in parallel and will be - /// guaranteed to run to completion before shutdown hooks in the next stage are - /// started. - /// - /// This class is constructed before the [Logger], so it cannot be direct - /// injected in the constructor. - Future runShutdownHooks(Logger logger); -} - -class _DefaultShutdownHooks implements ShutdownHooks { - _DefaultShutdownHooks(); - - @override - final List registeredHooks = []; - - bool _shutdownHooksRunning = false; - - @override - void addShutdownHook(ShutdownHook shutdownHook) { - assert(!_shutdownHooksRunning); - registeredHooks.add(shutdownHook); - } - - @override - Future runShutdownHooks(Logger logger) async { - logger.printTrace( - 'Running ${registeredHooks.length} shutdown hook${registeredHooks.length == 1 ? '' : 's'}', - ); - _shutdownHooksRunning = true; - try { - final List> futures = >[]; - for (final ShutdownHook shutdownHook in registeredHooks) { - final FutureOr result = shutdownHook(); - if (result is Future) { - futures.add(result); - } - } - await Future.wait(futures); - } finally { - _shutdownHooksRunning = false; - } - logger.printTrace('Shutdown hooks complete'); - } -} diff --git a/packages/flutter_migrate/lib/src/base/io.dart b/packages/flutter_migrate/lib/src/base/io.dart deleted file mode 100644 index f8a89f5de34..00000000000 --- a/packages/flutter_migrate/lib/src/base/io.dart +++ /dev/null @@ -1,346 +0,0 @@ -// Copyright 2013 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. - -// This file serves as the single point of entry into the `dart:io` APIs -// within Flutter tools. -// -// In order to make Flutter tools more testable, we use the `FileSystem` APIs -// in `package:file` rather than using the `dart:io` file APIs directly (see -// `file_system.dart`). Doing so allows us to swap out local file system -// access with mockable (or in-memory) file systems, making our tests hermetic -// vis-a-vis file system access. -// -// We also use `package:platform` to provide an abstraction away from the -// static methods in the `dart:io` `Platform` class (see `platform.dart`). As -// such, do not export Platform from this file! -// -// To ensure that all file system and platform API access within Flutter tools -// goes through the proper APIs, we forbid direct imports of `dart:io` (via a -// test), forcing all callers to instead import this file, which exports the -// blessed subset of `dart:io` that is legal to use in Flutter tools. -// -// Because of the nature of this file, it is important that **platform and file -// APIs not be exported from `dart:io` in this file**! Moreover, be careful -// about any additional exports that you add to this file, as doing so will -// increase the API surface that we have to test in Flutter tools, and the APIs -// in `dart:io` can sometimes be hard to use in tests. - -// We allow `print()` in this file as a fallback for writing to the terminal via -// regular stdout/stderr/stdio paths. Everything else in the flutter_tools -// library should route terminal I/O through the [Stdio] class defined below. -// ignore_for_file: avoid_print - -import 'dart:async'; -import 'dart:io' - as io - show - IOSink, - Process, - ProcessSignal, - Stdin, - StdinException, - Stdout, - StdoutException, - stderr, - stdin, - stdout; - -import 'package:meta/meta.dart'; - -import 'common.dart'; - -export 'dart:io' - show - BytesBuilder, - CompressionOptions, - // Directory, NO! Use `file_system.dart` - // File, NO! Use `file_system.dart` - // FileSystemEntity, NO! Use `file_system.dart` - GZipCodec, - HandshakeException, - HttpClient, - HttpClientRequest, - HttpClientResponse, - HttpClientResponseCompressionState, - HttpException, - HttpHeaders, - HttpRequest, - HttpResponse, - HttpServer, - HttpStatus, - IOException, - IOSink, - InternetAddress, - InternetAddressType, - // Link NO! Use `file_system.dart` - // NetworkInterface NO! Use `io.dart` - OSError, - Platform, - Process, - ProcessException, - // ProcessInfo, NO! use `io.dart` - ProcessResult, - // ProcessSignal NO! Use [ProcessSignal] below. - ProcessStartMode, - // RandomAccessFile NO! Use `file_system.dart` - ServerSocket, - SignalException, - Socket, - SocketException, - Stdin, - StdinException, - Stdout, - WebSocket, - WebSocketException, - WebSocketTransformer, - ZLibEncoder, - exitCode, - gzip, - pid, - // stderr, NO! Use `io.dart` - // stdin, NO! Use `io.dart` - // stdout, NO! Use `io.dart` - systemEncoding; - -/// A class that wraps stdout, stderr, and stdin, and exposes the allowed -/// operations. -/// -/// In particular, there are three ways that writing to stdout and stderr -/// can fail. A call to stdout.write() can fail: -/// * by throwing a regular synchronous exception, -/// * by throwing an exception asynchronously, and -/// * by completing the Future stdout.done with an error. -/// -/// This class enapsulates all three so that we don't have to worry about it -/// anywhere else. -class Stdio { - Stdio(); - - /// Tests can provide overrides to use instead of the stdout and stderr from - /// dart:io. - @visibleForTesting - Stdio.test({required io.Stdout stdout, required io.IOSink stderr}) - : _stdoutOverride = stdout, - _stderrOverride = stderr; - - io.Stdout? _stdoutOverride; - io.IOSink? _stderrOverride; - - // These flags exist to remember when the done Futures on stdout and stderr - // complete to avoid trying to write to a closed stream sink, which would - // generate a [StateError]. - bool _stdoutDone = false; - bool _stderrDone = false; - - Stream> get stdin => io.stdin; - - io.Stdout get stdout { - if (_stdout != null) { - return _stdout!; - } - _stdout = _stdoutOverride ?? io.stdout; - _stdout!.done.then( - (void _) { - _stdoutDone = true; - }, - onError: (Object err, StackTrace st) { - _stdoutDone = true; - }, - ); - return _stdout!; - } - - io.Stdout? _stdout; - - @visibleForTesting - io.IOSink get stderr { - if (_stderr != null) { - return _stderr!; - } - _stderr = _stderrOverride ?? io.stderr; - _stderr!.done.then( - (void _) { - _stderrDone = true; - }, - onError: (Object err, StackTrace st) { - _stderrDone = true; - }, - ); - return _stderr!; - } - - io.IOSink? _stderr; - - bool get hasTerminal => io.stdout.hasTerminal; - - static bool? _stdinHasTerminal; - - /// Determines whether there is a terminal attached. - /// - /// [io.Stdin.hasTerminal] only covers a subset of cases. In this check the - /// echoMode is toggled on and off to catch cases where the tool running in - /// a docker container thinks there is an attached terminal. This can cause - /// runtime errors such as "inappropriate ioctl for device" if not handled. - bool get stdinHasTerminal { - if (_stdinHasTerminal != null) { - return _stdinHasTerminal!; - } - if (stdin is! io.Stdin) { - return _stdinHasTerminal = false; - } - final io.Stdin ioStdin = stdin as io.Stdin; - if (!ioStdin.hasTerminal) { - return _stdinHasTerminal = false; - } - try { - final bool currentEchoMode = ioStdin.echoMode; - ioStdin.echoMode = !currentEchoMode; - ioStdin.echoMode = currentEchoMode; - } on io.StdinException { - return _stdinHasTerminal = false; - } - return _stdinHasTerminal = true; - } - - int? get terminalColumns => hasTerminal ? stdout.terminalColumns : null; - int? get terminalLines => hasTerminal ? stdout.terminalLines : null; - bool get supportsAnsiEscapes => hasTerminal && stdout.supportsAnsiEscapes; - - /// Writes [message] to [stderr], falling back on [fallback] if the write - /// throws any exception. The default fallback calls [print] on [message]. - void stderrWrite( - String message, { - void Function(String, dynamic, StackTrace)? fallback, - }) { - if (!_stderrDone) { - _stdioWrite(stderr, message, fallback: fallback); - return; - } - fallback == null - ? print(message) - : fallback( - message, - const io.StdoutException('stderr is done'), - StackTrace.current, - ); - } - - /// Writes [message] to [stdout], falling back on [fallback] if the write - /// throws any exception. The default fallback calls [print] on [message]. - void stdoutWrite( - String message, { - void Function(String, dynamic, StackTrace)? fallback, - }) { - if (!_stdoutDone) { - _stdioWrite(stdout, message, fallback: fallback); - return; - } - fallback == null - ? print(message) - : fallback( - message, - const io.StdoutException('stdout is done'), - StackTrace.current, - ); - } - - // Helper for [stderrWrite] and [stdoutWrite]. - void _stdioWrite( - io.IOSink sink, - String message, { - void Function(String, dynamic, StackTrace)? fallback, - }) { - asyncGuard( - () async { - sink.write(message); - }, - onError: (Object error, StackTrace stackTrace) { - if (fallback == null) { - print(message); - } else { - fallback(message, error, stackTrace); - } - }, - ); - } - - /// Adds [stream] to [stdout]. - Future addStdoutStream(Stream> stream) => - stdout.addStream(stream); - - /// Adds [stream] to [stderr]. - Future addStderrStream(Stream> stream) => - stderr.addStream(stream); -} - -/// A portable version of [io.ProcessSignal]. -/// -/// Listening on signals that don't exist on the current platform is just a -/// no-op. This is in contrast to [io.ProcessSignal], where listening to -/// non-existent signals throws an exception. -/// -/// This class does NOT implement io.ProcessSignal, because that class uses -/// private fields. This means it cannot be used with, e.g., [Process.killPid]. -/// Alternative implementations of the relevant methods that take -/// [ProcessSignal] instances are available on this class (e.g. "send"). -class ProcessSignal { - @visibleForTesting - const ProcessSignal(this._delegate); - - static const ProcessSignal sigwinch = PosixProcessSignal( - io.ProcessSignal.sigwinch, - ); - static const ProcessSignal sigterm = PosixProcessSignal( - io.ProcessSignal.sigterm, - ); - static const ProcessSignal sigusr1 = PosixProcessSignal( - io.ProcessSignal.sigusr1, - ); - static const ProcessSignal sigusr2 = PosixProcessSignal( - io.ProcessSignal.sigusr2, - ); - static const ProcessSignal sigint = ProcessSignal(io.ProcessSignal.sigint); - static const ProcessSignal sigkill = ProcessSignal(io.ProcessSignal.sigkill); - - final io.ProcessSignal _delegate; - - Stream watch() { - return _delegate.watch().map( - (io.ProcessSignal signal) => this, - ); - } - - /// Sends the signal to the given process (identified by pid). - /// - /// Returns true if the signal was delivered, false otherwise. - /// - /// On Windows, this can only be used with [ProcessSignal.sigterm], which - /// terminates the process. - /// - /// This is implemented by sending the signal using [Process.killPid]. - bool send(int pid) { - assert(!isWindows || this == ProcessSignal.sigterm); - return io.Process.killPid(pid, _delegate); - } - - @override - String toString() => _delegate.toString(); -} - -/// A [ProcessSignal] that is only available on Posix platforms. -/// -/// Listening to a [_PosixProcessSignal] is a no-op on Windows. -@visibleForTesting -class PosixProcessSignal extends ProcessSignal { - const PosixProcessSignal(super.wrappedSignal); - - @override - Stream watch() { - // This uses the real platform since it invokes dart:io functionality directly. - if (isWindows) { - return const Stream.empty(); - } - return super.watch(); - } -} diff --git a/packages/flutter_migrate/lib/src/base/logger.dart b/packages/flutter_migrate/lib/src/base/logger.dart deleted file mode 100644 index a01103fa748..00000000000 --- a/packages/flutter_migrate/lib/src/base/logger.dart +++ /dev/null @@ -1,1396 +0,0 @@ -// Copyright 2013 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 'dart:io'; -import 'dart:math' as math; -import 'dart:math'; - -import 'package:intl/intl.dart'; -import 'package:meta/meta.dart'; - -import 'common.dart'; -import 'io.dart'; -import 'terminal.dart' show OutputPreferences, Terminal, TerminalColor; - -const int kDefaultStatusPadding = 59; -final NumberFormat kSecondsFormat = NumberFormat('0.0'); -final NumberFormat kMillisecondsFormat = NumberFormat.decimalPattern(); - -/// Smallest column that will be used for text wrapping. If the requested column -/// width is smaller than this, then this is what will be used. -const int kMinColumnWidth = 10; - -/// A factory for generating [Stopwatch] instances for [Status] instances. -class StopwatchFactory { - /// const constructor so that subclasses may be const. - const StopwatchFactory(); - - /// Create a new [Stopwatch] instance. - /// - /// The optional [name] parameter is useful in tests when there are multiple - /// instances being created. - Stopwatch createStopwatch([String name = '']) => Stopwatch(); -} - -typedef VoidCallback = void Function(); - -abstract class Logger { - /// Whether or not this logger should print [printTrace] messages. - bool get isVerbose => false; - - /// If true, silences the logger output. - bool quiet = false; - - /// If true, this logger supports color output. - bool get supportsColor; - - /// If true, this logger is connected to a terminal. - bool get hasTerminal; - - /// If true, then [printError] has been called at least once for this logger - /// since the last time it was set to false. - bool hadErrorOutput = false; - - /// If true, then [printWarning] has been called at least once for this logger - /// since the last time it was reset to false. - bool hadWarningOutput = false; - - /// Causes [checkForFatalLogs] to call [throwToolExit] when it is called if - /// [hadWarningOutput] is true. - bool fatalWarnings = false; - - /// Returns the terminal attached to this logger. - Terminal get terminal; - - /// Display an error `message` to the user. Commands should use this if they - /// fail in some way. Errors are typically followed shortly by a call to - /// [throwToolExit] to terminate the run. - /// - /// The `message` argument is printed to the stderr in [TerminalColor.red] by - /// default. - /// - /// The `stackTrace` argument is the stack trace that will be printed if - /// supplied. - /// - /// The `emphasis` argument will cause the output message be printed in bold text. - /// - /// The `color` argument will print the message in the supplied color instead - /// of the default of red. Colors will not be printed if the output terminal - /// doesn't support them. - /// - /// The `indent` argument specifies the number of spaces to indent the overall - /// message. If wrapping is enabled in [outputPreferences], then the wrapped - /// lines will be indented as well. - /// - /// If `hangingIndent` is specified, then any wrapped lines will be indented - /// by this much more than the first line, if wrapping is enabled in - /// [outputPreferences]. - /// - /// If `wrap` is specified, then it overrides the - /// `outputPreferences.wrapText` setting. - void printError( - String message, { - StackTrace? stackTrace, - bool? emphasis, - TerminalColor? color, - int? indent, - int? hangingIndent, - bool? wrap, - }); - - /// Display a warning `message` to the user. Commands should use this if they - /// important information to convey to the user that is not fatal. - /// - /// The `message` argument is printed to the stderr in [TerminalColor.cyan] by - /// default. - /// - /// The `emphasis` argument will cause the output message be printed in bold text. - /// - /// The `color` argument will print the message in the supplied color instead - /// of the default of cyan. Colors will not be printed if the output terminal - /// doesn't support them. - /// - /// The `indent` argument specifies the number of spaces to indent the overall - /// message. If wrapping is enabled in [outputPreferences], then the wrapped - /// lines will be indented as well. - /// - /// If `hangingIndent` is specified, then any wrapped lines will be indented - /// by this much more than the first line, if wrapping is enabled in - /// [outputPreferences]. - /// - /// If `wrap` is specified, then it overrides the - /// `outputPreferences.wrapText` setting. - void printWarning( - String message, { - bool? emphasis, - TerminalColor? color, - int? indent, - int? hangingIndent, - bool? wrap, - }); - - /// Display normal output of the command. This should be used for things like - /// progress messages, success messages, or just normal command output. - /// - /// The `message` argument is printed to the stdout. - /// - /// The `stackTrace` argument is the stack trace that will be printed if - /// supplied. - /// - /// If the `emphasis` argument is true, it will cause the output message be - /// printed in bold text. Defaults to false. - /// - /// The `color` argument will print the message in the supplied color instead - /// of the default of red. Colors will not be printed if the output terminal - /// doesn't support them. - /// - /// If `newline` is true, then a newline will be added after printing the - /// status. Defaults to true. - /// - /// The `indent` argument specifies the number of spaces to indent the overall - /// message. If wrapping is enabled in [outputPreferences], then the wrapped - /// lines will be indented as well. - /// - /// If `hangingIndent` is specified, then any wrapped lines will be indented - /// by this much more than the first line, if wrapping is enabled in - /// [outputPreferences]. - /// - /// If `wrap` is specified, then it overrides the - /// `outputPreferences.wrapText` setting. - void printStatus( - String message, { - bool? emphasis, - TerminalColor? color, - bool? newline, - int? indent, - int? hangingIndent, - bool? wrap, - }); - - /// Display the [message] inside a box. - /// - /// For example, this is the generated output: - /// - /// ┌─ [title] ─┐ - /// │ [message] │ - /// └───────────┘ - /// - /// If a terminal is attached, the lines in [message] are automatically wrapped based on - /// the available columns. - /// - /// Use this utility only to highlight a message in the logs. - /// - /// This is particularly useful when the message can be easily missed because of clutter - /// generated by other commands invoked by the tool. - /// - /// One common use case is to provide actionable steps in a Flutter app when a Gradle - /// error is printed. - /// - /// In the future, this output can be integrated with an IDE like VS Code to display a - /// notification, and allow the user to trigger an action. e.g. run a migration. - void printBox(String message, {String? title}); - - /// Use this for verbose tracing output. Users can turn this output on in order - /// to help diagnose issues with the toolchain or with their setup. - void printTrace(String message); - - /// Start an indeterminate progress display. - /// - /// The `message` argument is the message to display to the user. - /// - /// The `progressId` argument provides an ID that can be used to identify - /// this type of progress (e.g. `hot.reload`, `hot.restart`). - /// - /// The `progressIndicatorPadding` can optionally be used to specify the width - /// of the space into which the `message` is placed before the progress - /// indicator, if any. It is ignored if the message is longer. - Status startProgress( - String message, { - String? progressId, - int progressIndicatorPadding = kDefaultStatusPadding, - }); - - /// A [SilentStatus] or an [AnonymousSpinnerStatus] (depending on whether the - /// terminal is fancy enough), already started. - Status startSpinner({ - VoidCallback? onFinish, - Duration? timeout, - SlowWarningCallback? slowWarningCallback, - }); - - /// Clears all output. - void clear(); - - /// If [fatalWarnings] is set, causes the logger to check if - /// [hadWarningOutput] is true, and then to call [throwToolExit] if so. - /// - /// The [fatalWarnings] flag can be set from the command line with the - /// "--fatal-warnings" option on commands that support it. - void checkForFatalLogs() { - if (fatalWarnings && (hadWarningOutput || hadErrorOutput)) { - throwToolExit( - 'Logger received ${hadErrorOutput ? 'error' : 'warning'} output ' - 'during the run, and "--fatal-warnings" is enabled.', - ); - } - } -} - -class StdoutLogger extends Logger { - StdoutLogger({ - required this.terminal, - required Stdio stdio, - required OutputPreferences outputPreferences, - StopwatchFactory stopwatchFactory = const StopwatchFactory(), - }) : _stdio = stdio, - _outputPreferences = outputPreferences, - _stopwatchFactory = stopwatchFactory; - - @override - final Terminal terminal; - final OutputPreferences _outputPreferences; - final Stdio _stdio; - final StopwatchFactory _stopwatchFactory; - - Status? _status; - - @override - bool get isVerbose => false; - - @override - bool get supportsColor => terminal.supportsColor; - - @override - bool get hasTerminal => _stdio.stdinHasTerminal; - - @override - void printError( - String message, { - StackTrace? stackTrace, - bool? emphasis, - TerminalColor? color, - int? indent, - int? hangingIndent, - bool? wrap, - }) { - hadErrorOutput = true; - _status?.pause(); - message = wrapText( - message, - indent: indent, - hangingIndent: hangingIndent, - shouldWrap: wrap ?? _outputPreferences.wrapText, - columnWidth: _outputPreferences.wrapColumn, - ); - if (emphasis ?? false) { - message = terminal.bolden(message); - } - message = terminal.color(message, color ?? TerminalColor.red); - writeToStdErr('$message\n'); - if (stackTrace != null) { - writeToStdErr('$stackTrace\n'); - } - _status?.resume(); - } - - @override - void printWarning( - String message, { - bool? emphasis, - TerminalColor? color, - int? indent, - int? hangingIndent, - bool? wrap, - }) { - hadWarningOutput = true; - _status?.pause(); - message = wrapText( - message, - indent: indent, - hangingIndent: hangingIndent, - shouldWrap: wrap ?? _outputPreferences.wrapText, - columnWidth: _outputPreferences.wrapColumn, - ); - if (emphasis ?? false) { - message = terminal.bolden(message); - } - message = terminal.color(message, color ?? TerminalColor.cyan); - writeToStdErr('$message\n'); - _status?.resume(); - } - - @override - void printStatus( - String message, { - bool? emphasis, - TerminalColor? color, - bool? newline, - int? indent, - int? hangingIndent, - bool? wrap, - }) { - _status?.pause(); - message = wrapText( - message, - indent: indent, - hangingIndent: hangingIndent, - shouldWrap: wrap ?? _outputPreferences.wrapText, - columnWidth: _outputPreferences.wrapColumn, - ); - if (emphasis ?? false) { - message = terminal.bolden(message); - } - if (color != null) { - message = terminal.color(message, color); - } - if (newline ?? true) { - message = '$message\n'; - } - writeToStdOut(message); - _status?.resume(); - } - - @override - void printBox(String message, {String? title}) { - _status?.pause(); - _generateBox( - title: title, - message: message, - wrapColumn: _outputPreferences.wrapColumn, - terminal: terminal, - write: writeToStdOut, - ); - _status?.resume(); - } - - @protected - void writeToStdOut(String message) => _stdio.stdoutWrite(message); - - @protected - void writeToStdErr(String message) => _stdio.stderrWrite(message); - - @override - void printTrace(String message) {} - - @override - Status startProgress( - String message, { - String? progressId, - int progressIndicatorPadding = kDefaultStatusPadding, - }) { - if (_status != null) { - // Ignore nested progresses; return a no-op status object. - return SilentStatus(stopwatch: _stopwatchFactory.createStopwatch()) - ..start(); - } - if (supportsColor) { - _status = SpinnerStatus( - message: message, - padding: progressIndicatorPadding, - onFinish: _clearStatus, - stdio: _stdio, - stopwatch: _stopwatchFactory.createStopwatch(), - terminal: terminal, - )..start(); - } else { - _status = SummaryStatus( - message: message, - padding: progressIndicatorPadding, - onFinish: _clearStatus, - stdio: _stdio, - stopwatch: _stopwatchFactory.createStopwatch(), - )..start(); - } - return _status!; - } - - @override - Status startSpinner({ - VoidCallback? onFinish, - Duration? timeout, - SlowWarningCallback? slowWarningCallback, - }) { - if (_status != null || !supportsColor) { - return SilentStatus( - onFinish: onFinish, - stopwatch: _stopwatchFactory.createStopwatch(), - )..start(); - } - _status = AnonymousSpinnerStatus( - onFinish: () { - if (onFinish != null) { - onFinish(); - } - _clearStatus(); - }, - stdio: _stdio, - stopwatch: _stopwatchFactory.createStopwatch(), - terminal: terminal, - timeout: timeout, - slowWarningCallback: slowWarningCallback, - )..start(); - return _status!; - } - - void _clearStatus() { - _status = null; - } - - @override - void clear() { - _status?.pause(); - writeToStdOut('${terminal.clearScreen()}\n'); - _status?.resume(); - } -} - -/// A [StdoutLogger] which replaces Unicode characters that cannot be printed to -/// the Windows console with alternative symbols. -/// -/// By default, Windows uses either "Consolas" or "Lucida Console" as fonts to -/// render text in the console. Both fonts only have a limited character set. -/// Unicode characters, that are not available in either of the two default -/// fonts, should be replaced by this class with printable symbols. Otherwise, -/// they will show up as the unrepresentable character symbol '�'. -class WindowsStdoutLogger extends StdoutLogger { - WindowsStdoutLogger({ - required super.terminal, - required super.stdio, - required super.outputPreferences, - super.stopwatchFactory, - }); - - @override - void writeToStdOut(String message) { - final String windowsMessage = - terminal.supportsEmoji - ? message - : message - .replaceAll('🔥', '') - .replaceAll('🖼️', '') - .replaceAll('✗', 'X') - .replaceAll('✓', '√') - .replaceAll('🔨', '') - .replaceAll('💪', '') - .replaceAll('⚠️', '!') - .replaceAll('✏️', ''); - _stdio.stdoutWrite(windowsMessage); - } -} - -typedef _Writter = void Function(String message); - -/// Wraps the message in a box, and writes the bytes by calling [write]. -/// -/// Example output: -/// -/// ┌─ [title] ─┐ -/// │ [message] │ -/// └───────────┘ -/// -/// When [title] is provided, the box will have a title above it. -/// -/// The box width never exceeds [wrapColumn]. -/// -/// If [wrapColumn] is not provided, the default value is 100. -void _generateBox({ - required String message, - required int wrapColumn, - required _Writter write, - required Terminal terminal, - String? title, -}) { - const int kPaddingLeftRight = 1; - const int kEdges = 2; - - final int maxTextWidthPerLine = wrapColumn - kEdges - kPaddingLeftRight * 2; - final List lines = wrapText( - message, - shouldWrap: true, - columnWidth: maxTextWidthPerLine, - ).split('\n'); - final List lineWidth = - lines.map((String line) => _getColumnSize(line)).toList(); - final int maxColumnSize = lineWidth.reduce( - (int currLen, int maxLen) => max(currLen, maxLen), - ); - final int textWidth = min(maxColumnSize, maxTextWidthPerLine); - final int textWithPaddingWidth = textWidth + kPaddingLeftRight * 2; - - write('\n'); - - // Write `┌─ [title] ─┐`. - write('┌'); - write('─'); - if (title == null) { - write('─' * (textWithPaddingWidth - 1)); - } else { - write(' ${terminal.bolden(title)} '); - write('─' * (textWithPaddingWidth - title.length - 3)); - } - write('┐'); - write('\n'); - - // Write `│ [message] │`. - for (int lineIdx = 0; lineIdx < lines.length; lineIdx++) { - write('│'); - write(' ' * kPaddingLeftRight); - write(lines[lineIdx]); - final int remainingSpacesToEnd = textWidth - lineWidth[lineIdx]; - write(' ' * (remainingSpacesToEnd + kPaddingLeftRight)); - write('│'); - write('\n'); - } - - // Write `└───────────┘`. - write('└'); - write('─' * textWithPaddingWidth); - write('┘'); - write('\n'); -} - -final RegExp _ansiEscapePattern = RegExp( - '\x1B\\[[\x30-\x3F]*[\x20-\x2F]*[\x40-\x7E]', -); - -int _getColumnSize(String line) { - // Remove ANSI escape characters from the string. - return line.replaceAll(_ansiEscapePattern, '').length; -} - -class BufferLogger extends Logger { - BufferLogger({ - required this.terminal, - required OutputPreferences outputPreferences, - StopwatchFactory stopwatchFactory = const StopwatchFactory(), - bool verbose = false, - }) : _outputPreferences = outputPreferences, - _stopwatchFactory = stopwatchFactory, - _verbose = verbose; - - /// Create a [BufferLogger] with test preferences. - BufferLogger.test({ - Terminal? terminal, - OutputPreferences? outputPreferences, - bool verbose = false, - }) : terminal = terminal ?? Terminal.test(), - _outputPreferences = outputPreferences ?? OutputPreferences.test(), - _stopwatchFactory = const StopwatchFactory(), - _verbose = verbose; - - final OutputPreferences _outputPreferences; - - @override - final Terminal terminal; - - final StopwatchFactory _stopwatchFactory; - - final bool _verbose; - - @override - bool get isVerbose => _verbose; - - @override - bool get supportsColor => terminal.supportsColor; - - final StringBuffer _error = StringBuffer(); - final StringBuffer _warning = StringBuffer(); - final StringBuffer _status = StringBuffer(); - final StringBuffer _trace = StringBuffer(); - final StringBuffer _events = StringBuffer(); - - String get errorText => _error.toString(); - String get warningText => _warning.toString(); - String get statusText => _status.toString(); - String get traceText => _trace.toString(); - String get eventText => _events.toString(); - - @override - bool get hasTerminal => false; - - @override - void printError( - String message, { - StackTrace? stackTrace, - bool? emphasis, - TerminalColor? color, - int? indent, - int? hangingIndent, - bool? wrap, - }) { - hadErrorOutput = true; - _error.writeln( - terminal.color( - wrapText( - message, - indent: indent, - hangingIndent: hangingIndent, - shouldWrap: wrap ?? _outputPreferences.wrapText, - columnWidth: _outputPreferences.wrapColumn, - ), - color ?? TerminalColor.red, - ), - ); - } - - @override - void printWarning( - String message, { - bool? emphasis, - TerminalColor? color, - int? indent, - int? hangingIndent, - bool? wrap, - }) { - hadWarningOutput = true; - _warning.writeln( - terminal.color( - wrapText( - message, - indent: indent, - hangingIndent: hangingIndent, - shouldWrap: wrap ?? _outputPreferences.wrapText, - columnWidth: _outputPreferences.wrapColumn, - ), - color ?? TerminalColor.cyan, - ), - ); - } - - @override - void printStatus( - String message, { - bool? emphasis, - TerminalColor? color, - bool? newline, - int? indent, - int? hangingIndent, - bool? wrap, - }) { - if (newline ?? true) { - _status.writeln( - wrapText( - message, - indent: indent, - hangingIndent: hangingIndent, - shouldWrap: wrap ?? _outputPreferences.wrapText, - columnWidth: _outputPreferences.wrapColumn, - ), - ); - } else { - _status.write( - wrapText( - message, - indent: indent, - hangingIndent: hangingIndent, - shouldWrap: wrap ?? _outputPreferences.wrapText, - columnWidth: _outputPreferences.wrapColumn, - ), - ); - } - } - - @override - void printBox(String message, {String? title}) { - _generateBox( - title: title, - message: message, - wrapColumn: _outputPreferences.wrapColumn, - terminal: terminal, - write: _status.write, - ); - } - - @override - void printTrace(String message) => _trace.writeln(message); - - @override - Status startProgress( - String message, { - String? progressId, - int progressIndicatorPadding = kDefaultStatusPadding, - }) { - printStatus(message); - return SilentStatus(stopwatch: _stopwatchFactory.createStopwatch()) - ..start(); - } - - @override - Status startSpinner({ - VoidCallback? onFinish, - Duration? timeout, - SlowWarningCallback? slowWarningCallback, - }) { - return SilentStatus( - stopwatch: _stopwatchFactory.createStopwatch(), - onFinish: onFinish, - )..start(); - } - - @override - void clear() { - _error.clear(); - _status.clear(); - _trace.clear(); - _events.clear(); - } -} - -typedef SlowWarningCallback = String Function(); - -/// A [Status] class begins when start is called, and may produce progress -/// information asynchronously. -/// -/// The [SilentStatus] class never has any output. -/// -/// The [SpinnerStatus] subclass shows a message with a spinner, and replaces it -/// with timing information when stopped. When canceled, the information isn't -/// shown. In either case, a newline is printed. -/// -/// The [AnonymousSpinnerStatus] subclass just shows a spinner. -/// -/// The [SummaryStatus] subclass shows only a static message (without an -/// indicator), then updates it when the operation ends. -/// -/// Generally, consider `logger.startProgress` instead of directly creating -/// a [Status] or one of its subclasses. -abstract class Status { - Status({this.onFinish, required Stopwatch stopwatch, this.timeout}) - : _stopwatch = stopwatch; - - final VoidCallback? onFinish; - final Duration? timeout; - - @protected - final Stopwatch _stopwatch; - - @protected - String get elapsedTime { - if (_stopwatch.elapsed.inSeconds > 2) { - return _getElapsedAsSeconds(_stopwatch.elapsed); - } - return _getElapsedAsMilliseconds(_stopwatch.elapsed); - } - - String _getElapsedAsSeconds(Duration duration) { - final double seconds = - duration.inMilliseconds / Duration.millisecondsPerSecond; - return '${kSecondsFormat.format(seconds)}s'; - } - - String _getElapsedAsMilliseconds(Duration duration) { - return '${kMillisecondsFormat.format(duration.inMilliseconds)}ms'; - } - - @visibleForTesting - bool get seemsSlow => timeout != null && _stopwatch.elapsed > timeout!; - - /// Call to start spinning. - void start() { - assert(!_stopwatch.isRunning); - _stopwatch.start(); - } - - /// Call to stop spinning after success. - void stop() { - finish(); - } - - /// Call to cancel the spinner after failure or cancellation. - void cancel() { - finish(); - } - - /// Call to clear the current line but not end the progress. - void pause() {} - - /// Call to resume after a pause. - void resume() {} - - @protected - void finish() { - assert(_stopwatch.isRunning); - _stopwatch.stop(); - onFinish?.call(); - } -} - -/// A [Status] that shows nothing. -class SilentStatus extends Status { - SilentStatus({required super.stopwatch, super.onFinish}); - - @override - void finish() { - onFinish?.call(); - } -} - -/// Constructor writes [message] to [stdout]. On [cancel] or [stop], will call -/// [onFinish]. On [stop], will additionally print out summary information. -class SummaryStatus extends Status { - SummaryStatus({ - this.message = '', - required super.stopwatch, - this.padding = kDefaultStatusPadding, - super.onFinish, - required Stdio stdio, - }) : _stdio = stdio; - - final String message; - final int padding; - final Stdio _stdio; - - bool _messageShowingOnCurrentLine = false; - - @override - void start() { - _printMessage(); - super.start(); - } - - void _writeToStdOut(String message) => _stdio.stdoutWrite(message); - - void _printMessage() { - assert(!_messageShowingOnCurrentLine); - _writeToStdOut('${message.padRight(padding)} '); - _messageShowingOnCurrentLine = true; - } - - @override - void stop() { - if (!_messageShowingOnCurrentLine) { - _printMessage(); - } - super.stop(); - assert(_messageShowingOnCurrentLine); - _writeToStdOut(elapsedTime.padLeft(_kTimePadding)); - _writeToStdOut('\n'); - } - - @override - void cancel() { - super.cancel(); - if (_messageShowingOnCurrentLine) { - _writeToStdOut('\n'); - } - } - - @override - void pause() { - super.pause(); - if (_messageShowingOnCurrentLine) { - _writeToStdOut('\n'); - _messageShowingOnCurrentLine = false; - } - } -} - -const int _kTimePadding = 8; // should fit "99,999ms" - -/// A kind of animated [Status] that has no message. -/// -/// Call [pause] before outputting any text while this is running. -class AnonymousSpinnerStatus extends Status { - AnonymousSpinnerStatus({ - super.onFinish, - required super.stopwatch, - required Stdio stdio, - required Terminal terminal, - this.slowWarningCallback, - super.timeout, - }) : _stdio = stdio, - _terminal = terminal, - _animation = _selectAnimation(terminal); - - final Stdio _stdio; - final Terminal _terminal; - String _slowWarning = ''; - final SlowWarningCallback? slowWarningCallback; - - static const String _backspaceChar = '\b'; - static const String _clearChar = ' '; - - static const List _emojiAnimations = [ - '⣾⣽⣻⢿⡿⣟⣯⣷', // counter-clockwise - '⣾⣷⣯⣟⡿⢿⣻⣽', // clockwise - '⣾⣷⣯⣟⡿⢿⣻⣽⣷⣾⣽⣻⢿⡿⣟⣯⣷', // bouncing clockwise and counter-clockwise - '⣾⣷⣯⣽⣻⣟⡿⢿⣻⣟⣯⣽', // snaking - '⣾⣽⣻⢿⣿⣷⣯⣟⡿⣿', // alternating rain - '⣀⣠⣤⣦⣶⣾⣿⡿⠿⠻⠛⠋⠉⠙⠛⠟⠿⢿⣿⣷⣶⣴⣤⣄', // crawl up and down, large - '⠙⠚⠖⠦⢤⣠⣄⡤⠴⠲⠓⠋', // crawl up and down, small - '⣀⡠⠤⠔⠒⠊⠉⠑⠒⠢⠤⢄', // crawl up and down, tiny - '⡀⣄⣦⢷⠻⠙⠈⠀⠁⠋⠟⡾⣴⣠⢀⠀', // slide up and down - '⠙⠸⢰⣠⣄⡆⠇⠋', // clockwise line - '⠁⠈⠐⠠⢀⡀⠄⠂', // clockwise dot - '⢇⢣⢱⡸⡜⡎', // vertical wobble up - '⡇⡎⡜⡸⢸⢱⢣⢇', // vertical wobble down - '⡀⣀⣐⣒⣖⣶⣾⣿⢿⠿⠯⠭⠩⠉⠁⠀', // swirl - '⠁⠐⠄⢀⢈⢂⢠⣀⣁⣐⣄⣌⣆⣤⣥⣴⣼⣶⣷⣿⣾⣶⣦⣤⣠⣀⡀⠀⠀', // snowing and melting - '⠁⠋⠞⡴⣠⢀⠀⠈⠙⠻⢷⣦⣄⡀⠀⠉⠛⠲⢤⢀⠀', // falling water - '⠄⡢⢑⠈⠀⢀⣠⣤⡶⠞⠋⠁⠀⠈⠙⠳⣆⡀⠀⠆⡷⣹⢈⠀⠐⠪⢅⡀⠀', // fireworks - '⠐⢐⢒⣒⣲⣶⣷⣿⡿⡷⡧⠧⠇⠃⠁⠀⡀⡠⡡⡱⣱⣳⣷⣿⢿⢯⢧⠧⠣⠃⠂⠀⠈⠨⠸⠺⡺⡾⡿⣿⡿⡷⡗⡇⡅⡄⠄⠀⡀⡐⣐⣒⣓⣳⣻⣿⣾⣼⡼⡸⡘⡈⠈⠀', // fade - '⢸⡯⠭⠅⢸⣇⣀⡀⢸⣇⣸⡇⠈⢹⡏⠁⠈⢹⡏⠁⢸⣯⣭⡅⢸⡯⢕⡂⠀⠀', // text crawl - ]; - - static const List _asciiAnimations = [r'-\|/']; - - static List _selectAnimation(Terminal terminal) { - final List animations = - terminal.supportsEmoji ? _emojiAnimations : _asciiAnimations; - return animations[terminal.preferredStyle % animations.length].runes - .map((int scalar) => String.fromCharCode(scalar)) - .toList(); - } - - final List _animation; - - Timer? timer; - int ticks = 0; - int _lastAnimationFrameLength = 0; - bool timedOut = false; - - String get _currentAnimationFrame => _animation[ticks % _animation.length]; - int get _currentLineLength => _lastAnimationFrameLength + _slowWarning.length; - - void _writeToStdOut(String message) => _stdio.stdoutWrite(message); - - void _clear(int length) { - _writeToStdOut( - '${_backspaceChar * length}' - '${_clearChar * length}' - '${_backspaceChar * length}', - ); - } - - @override - void start() { - super.start(); - assert(timer == null); - _startSpinner(); - } - - void _startSpinner() { - timer = Timer.periodic(const Duration(milliseconds: 100), _callback); - _callback(timer!); - } - - void _callback(Timer timer) { - assert(this.timer == timer); - assert(timer.isActive); - _writeToStdOut(_backspaceChar * _lastAnimationFrameLength); - ticks += 1; - if (seemsSlow) { - if (!timedOut) { - timedOut = true; - _clear(_currentLineLength); - } - if (_slowWarning == '' && slowWarningCallback != null) { - _slowWarning = slowWarningCallback!(); - _writeToStdOut(_slowWarning); - } - } - final String newFrame = _currentAnimationFrame; - _lastAnimationFrameLength = newFrame.runes.length; - _writeToStdOut(newFrame); - } - - @override - void pause() { - assert(timer != null); - assert(timer!.isActive); - if (_terminal.supportsColor) { - _writeToStdOut('\r\x1B[K'); // go to start of line and clear line - } else { - _clear(_currentLineLength); - } - _lastAnimationFrameLength = 0; - timer?.cancel(); - } - - @override - void resume() { - assert(timer != null); - assert(!timer!.isActive); - _startSpinner(); - } - - @override - void finish() { - assert(timer != null); - assert(timer!.isActive); - timer?.cancel(); - timer = null; - _clear(_lastAnimationFrameLength); - _lastAnimationFrameLength = 0; - super.finish(); - } -} - -/// An animated version of [Status]. -/// -/// The constructor writes [message] to [stdout] with padding, then starts an -/// indeterminate progress indicator animation. -/// -/// On [cancel] or [stop], will call [onFinish]. On [stop], will -/// additionally print out summary information. -/// -/// Call [pause] before outputting any text while this is running. -class SpinnerStatus extends AnonymousSpinnerStatus { - SpinnerStatus({ - required this.message, - this.padding = kDefaultStatusPadding, - super.onFinish, - required super.stopwatch, - required super.stdio, - required super.terminal, - }); - - final String message; - final int padding; - - static final String _margin = - AnonymousSpinnerStatus._clearChar * (5 + _kTimePadding - 1); - - int _totalMessageLength = 0; - - @override - int get _currentLineLength => _totalMessageLength + super._currentLineLength; - - @override - void start() { - _printStatus(); - super.start(); - } - - void _printStatus() { - final String line = '${message.padRight(padding)}$_margin'; - _totalMessageLength = line.length; - _writeToStdOut(line); - } - - @override - void pause() { - super.pause(); - _totalMessageLength = 0; - } - - @override - void resume() { - _printStatus(); - super.resume(); - } - - @override - void stop() { - super.stop(); // calls finish, which clears the spinner - assert(_totalMessageLength > _kTimePadding); - _writeToStdOut(AnonymousSpinnerStatus._backspaceChar * (_kTimePadding - 1)); - _writeToStdOut(elapsedTime.padLeft(_kTimePadding)); - _writeToStdOut('\n'); - } - - @override - void cancel() { - super.cancel(); // calls finish, which clears the spinner - assert(_totalMessageLength > 0); - _writeToStdOut('\n'); - } -} - -/// Wraps a block of text into lines no longer than [columnWidth]. -/// -/// Tries to split at whitespace, but if that's not good enough to keep it under -/// the limit, then it splits in the middle of a word. If [columnWidth] (minus -/// any indent) is smaller than [kMinColumnWidth], the text is wrapped at that -/// [kMinColumnWidth] instead. -/// -/// Preserves indentation (leading whitespace) for each line (delimited by '\n') -/// in the input, and will indent wrapped lines that same amount, adding -/// [indent] spaces in addition to any existing indent. -/// -/// If [hangingIndent] is supplied, then that many additional spaces will be -/// added to each line, except for the first line. The [hangingIndent] is added -/// to the specified [indent], if any. This is useful for wrapping -/// text with a heading prefix (e.g. "Usage: "): -/// -/// ```dart -/// String prefix = "Usage: "; -/// print(prefix + wrapText(invocation, indent: 2, hangingIndent: prefix.length, columnWidth: 40)); -/// ``` -/// -/// yields: -/// ```none -/// Usage: app main_command -/// [arguments] -/// ``` -/// -/// If [outputPreferences.wrapText] is false, then the text will be returned -/// unchanged. If [shouldWrap] is specified, then it overrides the -/// [outputPreferences.wrapText] setting. -/// -/// If the amount of indentation (from the text, [indent], and [hangingIndent]) -/// is such that less than [kMinColumnWidth] characters can fit in the -/// [columnWidth], then the indent is truncated to allow the text to fit. -String wrapText( - String text, { - required int columnWidth, - required bool shouldWrap, - int? hangingIndent, - int? indent, -}) { - assert(columnWidth >= 0); - if (text.isEmpty) { - return ''; - } - indent ??= 0; - hangingIndent ??= 0; - final List splitText = text.split('\n'); - final List result = []; - for (final String line in splitText) { - String trimmedText = line.trimLeft(); - final String leadingWhitespace = line.substring( - 0, - line.length - trimmedText.length, - ); - List notIndented; - if (hangingIndent != 0) { - // When we have a hanging indent, we want to wrap the first line at one - // width, and the rest at another (offset by hangingIndent), so we wrap - // them twice and recombine. - final List firstLineWrap = _wrapTextAsLines( - trimmedText, - columnWidth: columnWidth - leadingWhitespace.length - indent, - shouldWrap: shouldWrap, - ); - notIndented = [firstLineWrap.removeAt(0)]; - trimmedText = trimmedText.substring(notIndented[0].length).trimLeft(); - if (trimmedText.isNotEmpty) { - notIndented.addAll( - _wrapTextAsLines( - trimmedText, - columnWidth: - columnWidth - leadingWhitespace.length - indent - hangingIndent, - shouldWrap: shouldWrap, - ), - ); - } - } else { - notIndented = _wrapTextAsLines( - trimmedText, - columnWidth: columnWidth - leadingWhitespace.length - indent, - shouldWrap: shouldWrap, - ); - } - String? hangingIndentString; - final String indentString = ' ' * indent; - result.addAll( - notIndented.map((String line) { - // Don't return any lines with just whitespace on them. - if (line.isEmpty) { - return ''; - } - String truncatedIndent = - '$indentString${hangingIndentString ?? ''}$leadingWhitespace'; - if (truncatedIndent.length > columnWidth - kMinColumnWidth) { - truncatedIndent = truncatedIndent.substring( - 0, - math.max(columnWidth - kMinColumnWidth, 0), - ); - } - final String result = '$truncatedIndent$line'; - hangingIndentString ??= ' ' * hangingIndent!; - return result; - }), - ); - } - return result.join('\n'); -} - -/// Wraps a block of text into lines no longer than [columnWidth], starting at the -/// [start] column, and returning the result as a list of strings. -/// -/// Tries to split at whitespace, but if that's not good enough to keep it -/// under the limit, then splits in the middle of a word. Preserves embedded -/// newlines, but not indentation (it trims whitespace from each line). -/// -/// If [columnWidth] is not specified, then the column width will be the width of the -/// terminal window by default. If the stdout is not a terminal window, then the -/// default will be [outputPreferences.wrapColumn]. -/// -/// The [columnWidth] is clamped to [kMinColumnWidth] at minimum (so passing negative -/// widths is fine, for instance). -/// -/// If [outputPreferences.wrapText] is false, then the text will be returned -/// simply split at the newlines, but not wrapped. If [shouldWrap] is specified, -/// then it overrides the [outputPreferences.wrapText] setting. -List _wrapTextAsLines( - String text, { - int start = 0, - required int columnWidth, - required bool shouldWrap, -}) { - if (text.isEmpty) { - return ['']; - } - assert(start >= 0); - - // Splits a string so that the resulting list has the same number of elements - // as there are visible characters in the string, but elements may include one - // or more adjacent ANSI sequences. Joining the list elements again will - // reconstitute the original string. This is useful for manipulating "visible" - // characters in the presence of ANSI control codes. - List<_AnsiRun> splitWithCodes(String input) { - final RegExp characterOrCode = RegExp( - '(\u001b\\[[0-9;]*m|.)', - multiLine: true, - ); - List<_AnsiRun> result = <_AnsiRun>[]; - final StringBuffer current = StringBuffer(); - for (final Match match in characterOrCode.allMatches(input)) { - current.write(match[0]); - if (match[0]!.length < 4) { - // This is a regular character, write it out. - result.add(_AnsiRun(current.toString(), match[0]!)); - current.clear(); - } - } - // If there's something accumulated, then it must be an ANSI sequence, so - // add it to the end of the last entry so that we don't lose it. - if (current.isNotEmpty) { - if (result.isNotEmpty) { - result.last.original += current.toString(); - } else { - // If there is nothing in the string besides control codes, then just - // return them as the only entry. - result = <_AnsiRun>[_AnsiRun(current.toString(), '')]; - } - } - return result; - } - - String joinRun(List<_AnsiRun> list, int start, [int? end]) { - return list - .sublist(start, end) - .map((_AnsiRun run) => run.original) - .join() - .trim(); - } - - final List result = []; - final int effectiveLength = math.max(columnWidth - start, kMinColumnWidth); - for (final String line in text.split('\n')) { - // If the line is short enough, even with ANSI codes, then we can just add - // add it and move on. - if (line.length <= effectiveLength || !shouldWrap) { - result.add(line); - continue; - } - final List<_AnsiRun> splitLine = splitWithCodes(line); - if (splitLine.length <= effectiveLength) { - result.add(line); - continue; - } - - int currentLineStart = 0; - int? lastWhitespace; - // Find the start of the current line. - for (int index = 0; index < splitLine.length; ++index) { - if (splitLine[index].character.isNotEmpty && - _isWhitespace(splitLine[index])) { - lastWhitespace = index; - } - - if (index - currentLineStart >= effectiveLength) { - // Back up to the last whitespace, unless there wasn't any, in which - // case we just split where we are. - if (lastWhitespace != null) { - index = lastWhitespace; - } - - result.add(joinRun(splitLine, currentLineStart, index)); - - // Skip any intervening whitespace. - while (index < splitLine.length && _isWhitespace(splitLine[index])) { - index++; - } - - currentLineStart = index; - lastWhitespace = null; - } - } - result.add(joinRun(splitLine, currentLineStart)); - } - return result; -} - -// Used to represent a run of ANSI control sequences next to a visible -// character. -class _AnsiRun { - _AnsiRun(this.original, this.character); - - String original; - String character; -} - -/// Returns true if the code unit at [index] in [text] is a whitespace -/// character. -/// -/// Based on: https://en.wikipedia.org/wiki/Whitespace_character#Unicode -bool _isWhitespace(_AnsiRun run) { - final int rune = run.character.isNotEmpty ? run.character.codeUnitAt(0) : 0x0; - return rune >= 0x0009 && rune <= 0x000D || - rune == 0x0020 || - rune == 0x0085 || - rune == 0x1680 || - rune == 0x180E || - rune >= 0x2000 && rune <= 0x200A || - rune == 0x2028 || - rune == 0x2029 || - rune == 0x202F || - rune == 0x205F || - rune == 0x3000 || - rune == 0xFEFF; -} - -/// An abstraction for instantiation of the correct logger type. -/// -/// Our logger class hierarchy and runtime requirements are overly complicated. -class LoggerFactory { - LoggerFactory({ - required Terminal terminal, - required Stdio stdio, - required OutputPreferences outputPreferences, - StopwatchFactory stopwatchFactory = const StopwatchFactory(), - }) : _terminal = terminal, - _stdio = stdio, - _stopwatchFactory = stopwatchFactory, - _outputPreferences = outputPreferences; - - final Terminal _terminal; - final Stdio _stdio; - final StopwatchFactory _stopwatchFactory; - final OutputPreferences _outputPreferences; - - /// Create the appropriate logger for the current platform and configuration. - Logger createLogger({required bool windows}) { - Logger logger; - if (windows) { - logger = WindowsStdoutLogger( - terminal: _terminal, - stdio: _stdio, - outputPreferences: _outputPreferences, - stopwatchFactory: _stopwatchFactory, - ); - } else { - logger = StdoutLogger( - terminal: _terminal, - stdio: _stdio, - outputPreferences: _outputPreferences, - stopwatchFactory: _stopwatchFactory, - ); - } - return logger; - } -} diff --git a/packages/flutter_migrate/lib/src/base/project.dart b/packages/flutter_migrate/lib/src/base/project.dart deleted file mode 100644 index 4b9385acb4b..00000000000 --- a/packages/flutter_migrate/lib/src/base/project.dart +++ /dev/null @@ -1,81 +0,0 @@ -// Copyright 2013 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 'package:meta/meta.dart'; - -import 'file_system.dart'; -import 'logger.dart'; - -/// Emum for each officially supported platform. -enum SupportedPlatform { android, ios, linux, macos, web, windows, fuchsia } - -class FlutterProjectFactory { - FlutterProjectFactory(); - - @visibleForTesting - final Map projects = {}; - - /// Returns a [FlutterProject] view of the given directory or a ToolExit error, - /// if `pubspec.yaml` or `example/pubspec.yaml` is invalid. - FlutterProject fromDirectory(Directory directory) { - return projects.putIfAbsent(directory.path, () { - return FlutterProject(directory); - }); - } -} - -/// Represents the contents of a Flutter project at the specified [directory]. -class FlutterProject { - FlutterProject(this.directory); - - /// Returns a [FlutterProject] view of the current directory or a ToolExit error, - /// if `pubspec.yaml` or `example/pubspec.yaml` is invalid. - static FlutterProject current(FileSystem fs) => - FlutterProject(fs.currentDirectory); - - /// Create a [FlutterProject] and bypass the project caching. - @visibleForTesting - static FlutterProject fromDirectoryTest( - Directory directory, [ - Logger? logger, - ]) { - logger ??= BufferLogger.test(); - return FlutterProject(directory); - } - - Directory directory; - - /// The `pubspec.yaml` file of this project. - File get pubspecFile => directory.childFile('pubspec.yaml'); - - /// The `.metadata` file of this project. - File get metadataFile => directory.childFile('.metadata'); - - /// Returns a list of platform names that are supported by the project. - List getSupportedPlatforms() { - final List platforms = []; - if (directory.childDirectory('android').existsSync()) { - platforms.add(SupportedPlatform.android); - } - if (directory.childDirectory('ios').existsSync()) { - platforms.add(SupportedPlatform.ios); - } - if (directory.childDirectory('web').existsSync()) { - platforms.add(SupportedPlatform.web); - } - if (directory.childDirectory('macos').existsSync()) { - platforms.add(SupportedPlatform.macos); - } - if (directory.childDirectory('linux').existsSync()) { - platforms.add(SupportedPlatform.linux); - } - if (directory.childDirectory('windows').existsSync()) { - platforms.add(SupportedPlatform.windows); - } - if (directory.childDirectory('fuchsia').existsSync()) { - platforms.add(SupportedPlatform.fuchsia); - } - return platforms; - } -} diff --git a/packages/flutter_migrate/lib/src/base/signals.dart b/packages/flutter_migrate/lib/src/base/signals.dart deleted file mode 100644 index 968838433f1..00000000000 --- a/packages/flutter_migrate/lib/src/base/signals.dart +++ /dev/null @@ -1,150 +0,0 @@ -// Copyright 2013 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 'dart:io'; - -import 'package:meta/meta.dart'; - -import 'common.dart'; -import 'io.dart'; - -typedef SignalHandler = FutureOr Function(ProcessSignal signal); - -/// A class that manages signal handlers. -/// -/// Signal handlers are run in the order that they were added. -abstract class Signals { - @visibleForTesting - factory Signals.test({ - List exitSignals = defaultExitSignals, - }) => LocalSignals._(exitSignals); - - // The default list of signals that should cause the process to exit. - static const List defaultExitSignals = [ - ProcessSignal.sigterm, - ProcessSignal.sigint, - ]; - - /// Adds a signal handler to run on receipt of signal. - /// - /// The handler will run after all handlers that were previously added for the - /// signal. The function returns an abstract token that should be provided to - /// removeHandler to remove the handler. - Object addHandler(ProcessSignal signal, SignalHandler handler); - - /// Removes a signal handler. - /// - /// Removes the signal handler for the signal identified by the abstract - /// token parameter. Returns true if the handler was removed and false - /// otherwise. - Future removeHandler(ProcessSignal signal, Object token); - - /// If a [SignalHandler] throws an error, either synchronously or - /// asynchronously, it will be added to this stream instead of propagated. - Stream get errors; -} - -/// A class that manages the real dart:io signal handlers. -/// -/// We use a singleton instance of this class to ensure that all handlers for -/// fatal signals run before this class calls exit(). -class LocalSignals implements Signals { - LocalSignals._(this.exitSignals); - - static LocalSignals instance = LocalSignals._(Signals.defaultExitSignals); - - final List exitSignals; - - // A table mapping (signal, token) -> signal handler. - final Map> _handlersTable = - >{}; - - // A table mapping (signal) -> signal handler list. The list is in the order - // that the signal handlers should be run. - final Map> _handlersList = - >{}; - - // A table mapping (signal) -> low-level signal event stream. - final Map> - _streamSubscriptions = >{}; - - // The stream controller for errors coming from signal handlers. - final StreamController _errorStreamController = - StreamController.broadcast(); - - @override - Stream get errors => _errorStreamController.stream; - - @override - Object addHandler(ProcessSignal signal, SignalHandler handler) { - final Object token = Object(); - _handlersTable.putIfAbsent(signal, () => {}); - _handlersTable[signal]![token] = handler; - - _handlersList.putIfAbsent(signal, () => []); - _handlersList[signal]!.add(handler); - - // If we added the first one, then call signal.watch(), listen, and cache - // the stream controller. - if (_handlersList[signal]!.length == 1) { - _streamSubscriptions[signal] = signal.watch().listen( - _handleSignal, - onError: (Object e) { - _handlersTable[signal]?.remove(token); - _handlersList[signal]?.remove(handler); - }, - ); - } - return token; - } - - @override - Future removeHandler(ProcessSignal signal, Object token) async { - // We don't know about this signal. - if (!_handlersTable.containsKey(signal)) { - return false; - } - // We don't know about this token. - if (!_handlersTable[signal]!.containsKey(token)) { - return false; - } - final SignalHandler? handler = _handlersTable[signal]!.remove(token); - if (handler == null) { - return false; - } - final bool removed = _handlersList[signal]!.remove(handler); - if (!removed) { - return false; - } - - // If _handlersList[signal] is empty, then lookup the cached stream - // controller and unsubscribe from the stream. - if (_handlersList.isEmpty) { - await _streamSubscriptions[signal]?.cancel(); - } - return true; - } - - Future _handleSignal(ProcessSignal s) async { - final List? handlers = _handlersList[s]; - if (handlers != null) { - final List handlersCopy = handlers.toList(); - for (final SignalHandler handler in handlersCopy) { - try { - await asyncGuard(() async => handler(s)); - } on Exception catch (e) { - if (_errorStreamController.hasListener) { - _errorStreamController.add(e); - } - } - } - } - // If this was a signal that should cause the process to go down, then - // call exit(); - if (exitSignals.contains(s)) { - exit(0); - } - } -} diff --git a/packages/flutter_migrate/lib/src/base/terminal.dart b/packages/flutter_migrate/lib/src/base/terminal.dart deleted file mode 100644 index 442771609db..00000000000 --- a/packages/flutter_migrate/lib/src/base/terminal.dart +++ /dev/null @@ -1,415 +0,0 @@ -// Copyright 2013 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:convert'; -import 'dart:io'; - -import 'common.dart'; -import 'io.dart' as io; -import 'logger.dart'; - -enum TerminalColor { red, green, blue, cyan, yellow, magenta, grey } - -/// A class that contains the context settings for command text output to the -/// console. -class OutputPreferences { - OutputPreferences({ - bool? wrapText, - int? wrapColumn, - bool? showColor, - io.Stdio? stdio, - }) : _stdio = stdio, - wrapText = wrapText ?? stdio?.hasTerminal ?? false, - _overrideWrapColumn = wrapColumn, - showColor = showColor ?? false; - - /// A version of this class for use in tests. - OutputPreferences.test({ - this.wrapText = false, - int wrapColumn = kDefaultTerminalColumns, - this.showColor = false, - }) : _overrideWrapColumn = wrapColumn, - _stdio = null; - - final io.Stdio? _stdio; - - /// If [wrapText] is true, then any text sent to the context's [Logger] - /// instance (e.g. from the [printError] or [printStatus] functions) will be - /// wrapped (newlines added between words) to be no longer than the - /// [wrapColumn] specifies. Defaults to true if there is a terminal. To - /// determine if there's a terminal, [OutputPreferences] asks the context's - /// stdio. - final bool wrapText; - - /// The terminal width used by the [wrapText] function if there is no terminal - /// attached to [io.Stdio], --wrap is on, and --wrap-columns was not specified. - static const int kDefaultTerminalColumns = 100; - - /// The column at which output sent to the context's [Logger] instance - /// (e.g. from the [printError] or [printStatus] functions) will be wrapped. - /// Ignored if [wrapText] is false. Defaults to the width of the output - /// terminal, or to [kDefaultTerminalColumns] if not writing to a terminal. - final int? _overrideWrapColumn; - int get wrapColumn { - return _overrideWrapColumn ?? - _stdio?.terminalColumns ?? - kDefaultTerminalColumns; - } - - /// Whether or not to output ANSI color codes when writing to the output - /// terminal. Defaults to whatever [platform.stdoutSupportsAnsi] says if - /// writing to a terminal, and false otherwise. - final bool showColor; - - @override - String toString() { - return 'OutputPreferences[wrapText: $wrapText, wrapColumn: $wrapColumn, showColor: $showColor]'; - } -} - -/// The command line terminal, if available. -abstract class Terminal { - /// Create a new test [Terminal]. - /// - /// If not specified, [supportsColor] defaults to `false`. - factory Terminal.test({bool supportsColor, bool supportsEmoji}) = - _TestTerminal; - - /// Whether the current terminal supports color escape codes. - bool get supportsColor; - - /// Whether the current terminal can display emoji. - bool get supportsEmoji; - - /// When we have a choice of styles (e.g. animated spinners), this selects the - /// style to use. - int get preferredStyle; - - /// Whether we are interacting with the flutter tool via the terminal. - /// - /// If not set, defaults to false. - bool get usesTerminalUi; - set usesTerminalUi(bool value); - - /// Whether there is a terminal attached to stdin. - /// - /// If true, this usually indicates that a user is using the CLI as - /// opposed to using an IDE. This can be used to determine - /// whether it is appropriate to show a terminal prompt, - /// or whether an automatic selection should be made instead. - bool get stdinHasTerminal; - - /// Warning mark to use in stdout or stderr. - String get warningMark; - - /// Success mark to use in stdout. - String get successMark; - - String bolden(String message); - - String color(String message, TerminalColor color); - - String clearScreen(); - - bool get singleCharMode; - set singleCharMode(bool value); - - /// Return keystrokes from the console. - /// - /// This is a single-subscription stream. This stream may be closed before - /// the application exits. - /// - /// Useful when the console is in [singleCharMode]. - Stream get keystrokes; - - /// Prompts the user to input a character within a given list. Re-prompts if - /// entered character is not in the list. - /// - /// The `prompt`, if non-null, is the text displayed prior to waiting for user - /// input each time. If `prompt` is non-null and `displayAcceptedCharacters` - /// is true, the accepted keys are printed next to the `prompt`. - /// - /// The returned value is the user's input; if `defaultChoiceIndex` is not - /// null, and the user presses enter without any other input, the return value - /// will be the character in `acceptedCharacters` at the index given by - /// `defaultChoiceIndex`. - /// - /// The accepted characters must be a String with a length of 1, excluding any - /// whitespace characters such as `\t`, `\n`, or ` `. - /// - /// If [usesTerminalUi] is false, throws a [StateError]. - Future promptForCharInput( - List acceptedCharacters, { - required Logger logger, - String? prompt, - int? defaultChoiceIndex, - bool displayAcceptedCharacters = true, - }); -} - -class AnsiTerminal implements Terminal { - AnsiTerminal({ - required io.Stdio stdio, - DateTime? - now, // Time used to determine preferredStyle. Defaults to 0001-01-01 00:00. - bool? supportsColor, - }) : _stdio = stdio, - _now = now ?? DateTime(1), - _supportsColor = supportsColor; - - final io.Stdio _stdio; - final DateTime _now; - - static const String bold = '\u001B[1m'; - static const String resetAll = '\u001B[0m'; - static const String resetColor = '\u001B[39m'; - static const String resetBold = '\u001B[22m'; - static const String clear = '\u001B[2J\u001B[H'; - - static const String red = '\u001b[31m'; - static const String green = '\u001b[32m'; - static const String blue = '\u001b[34m'; - static const String cyan = '\u001b[36m'; - static const String magenta = '\u001b[35m'; - static const String yellow = '\u001b[33m'; - static const String grey = '\u001b[90m'; - - static const Map _colorMap = { - TerminalColor.red: red, - TerminalColor.green: green, - TerminalColor.blue: blue, - TerminalColor.cyan: cyan, - TerminalColor.magenta: magenta, - TerminalColor.yellow: yellow, - TerminalColor.grey: grey, - }; - - static String colorCode(TerminalColor color) => _colorMap[color]!; - - @override - bool get supportsColor => _supportsColor ?? stdout.supportsAnsiEscapes; - final bool? _supportsColor; - - // Assume unicode emojis are supported when not on Windows. - // If we are on Windows, unicode emojis are supported in Windows Terminal, - // which sets the WT_SESSION environment variable. See: - // https://github.com/microsoft/terminal/blob/master/doc/user-docs/index.md#tips-and-tricks - @override - bool get supportsEmoji => - !isWindows || Platform.environment.containsKey('WT_SESSION'); - - @override - int get preferredStyle { - const int workdays = DateTime.friday; - if (_now.weekday <= workdays) { - return _now.weekday - 1; - } - return _now.hour + workdays; - } - - final RegExp _boldControls = RegExp( - '(${RegExp.escape(resetBold)}|${RegExp.escape(bold)})', - ); - - @override - bool usesTerminalUi = false; - - @override - String get warningMark { - return bolden(color('[!]', TerminalColor.red)); - } - - @override - String get successMark { - return bolden(color('✓', TerminalColor.green)); - } - - @override - String bolden(String message) { - if (!supportsColor || message.isEmpty) { - return message; - } - final StringBuffer buffer = StringBuffer(); - for (String line in message.split('\n')) { - // If there were bolds or resetBolds in the string before, then nuke them: - // they're redundant. This prevents previously embedded resets from - // stopping the boldness. - line = line.replaceAll(_boldControls, ''); - buffer.writeln('$bold$line$resetBold'); - } - final String result = buffer.toString(); - // avoid introducing a new newline to the emboldened text - return (!message.endsWith('\n') && result.endsWith('\n')) - ? result.substring(0, result.length - 1) - : result; - } - - @override - String color(String message, TerminalColor color) { - if (!supportsColor || message.isEmpty) { - return message; - } - final StringBuffer buffer = StringBuffer(); - final String colorCodes = _colorMap[color]!; - for (String line in message.split('\n')) { - // If there were resets in the string before, then keep them, but - // restart the color right after. This prevents embedded resets from - // stopping the colors, and allows nesting of colors. - line = line.replaceAll(resetColor, '$resetColor$colorCodes'); - buffer.writeln('$colorCodes$line$resetColor'); - } - final String result = buffer.toString(); - // avoid introducing a new newline to the colored text - return (!message.endsWith('\n') && result.endsWith('\n')) - ? result.substring(0, result.length - 1) - : result; - } - - @override - String clearScreen() => supportsColor ? clear : '\n\n'; - - @override - bool get singleCharMode { - if (!_stdio.stdinHasTerminal) { - return false; - } - final io.Stdin stdin = _stdio.stdin as io.Stdin; - return stdin.lineMode && stdin.echoMode; - } - - @override - set singleCharMode(bool value) { - if (!_stdio.stdinHasTerminal) { - return; - } - final io.Stdin stdin = _stdio.stdin as io.Stdin; - // The order of setting lineMode and echoMode is important on Windows. - if (value) { - stdin.echoMode = false; - stdin.lineMode = false; - } else { - stdin.lineMode = true; - stdin.echoMode = true; - } - } - - @override - bool get stdinHasTerminal => _stdio.stdinHasTerminal; - - Stream? _broadcastStdInString; - - @override - Stream get keystrokes { - return _broadcastStdInString ??= - _stdio.stdin - .transform(const AsciiDecoder(allowInvalid: true)) - .asBroadcastStream(); - } - - @override - Future promptForCharInput( - List acceptedCharacters, { - required Logger logger, - String? prompt, - int? defaultChoiceIndex, - bool displayAcceptedCharacters = true, - }) async { - assert(acceptedCharacters.isNotEmpty); - assert(prompt == null || prompt.isNotEmpty); - if (!usesTerminalUi) { - throw StateError('cannot prompt without a terminal ui'); - } - List charactersToDisplay = acceptedCharacters; - if (defaultChoiceIndex != null) { - assert( - defaultChoiceIndex >= 0 && - defaultChoiceIndex < acceptedCharacters.length, - ); - charactersToDisplay = List.of(charactersToDisplay); - charactersToDisplay[defaultChoiceIndex] = bolden( - charactersToDisplay[defaultChoiceIndex], - ); - acceptedCharacters.add(''); - } - String? choice; - singleCharMode = true; - while (choice == null || - choice.length > 1 || - !acceptedCharacters.contains(choice)) { - if (prompt != null) { - logger.printStatus(prompt, emphasis: true, newline: false); - if (displayAcceptedCharacters) { - logger.printStatus( - ' [${charactersToDisplay.join("|")}]', - newline: false, - ); - } - // prompt ends with ': ' - logger.printStatus(': ', emphasis: true, newline: false); - } - choice = (await keystrokes.first).trim(); - logger.printStatus(choice); - } - singleCharMode = false; - if (defaultChoiceIndex != null && choice == '') { - choice = acceptedCharacters[defaultChoiceIndex]; - } - return choice; - } -} - -class _TestTerminal implements Terminal { - _TestTerminal({this.supportsColor = false, this.supportsEmoji = false}); - - @override - bool usesTerminalUi = false; - - @override - String bolden(String message) => message; - - @override - String clearScreen() => '\n\n'; - - @override - String color(String message, TerminalColor color) => message; - - @override - Stream get keystrokes => const Stream.empty(); - - @override - Future promptForCharInput( - List acceptedCharacters, { - required Logger logger, - String? prompt, - int? defaultChoiceIndex, - bool displayAcceptedCharacters = true, - }) { - throw UnsupportedError( - 'promptForCharInput not supported in the test terminal.', - ); - } - - @override - bool get singleCharMode => false; - @override - set singleCharMode(bool value) {} - - @override - final bool supportsColor; - - @override - final bool supportsEmoji; - - @override - int get preferredStyle => 0; - - @override - bool get stdinHasTerminal => false; - - @override - String get successMark => '✓'; - - @override - String get warningMark => '[!]'; -} diff --git a/packages/flutter_migrate/lib/src/base_dependencies.dart b/packages/flutter_migrate/lib/src/base_dependencies.dart deleted file mode 100644 index 745084a57fd..00000000000 --- a/packages/flutter_migrate/lib/src/base_dependencies.dart +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright 2013 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:io'; - -import 'package:process/process.dart'; - -import 'base/common.dart'; -import 'base/file_system.dart'; -import 'base/io.dart'; -import 'base/logger.dart'; -import 'base/signals.dart'; -import 'base/terminal.dart'; - -/// Initializes the boilerplate dependencies needed by the migrate tool. -class MigrateBaseDependencies { - MigrateBaseDependencies() { - processManager = const LocalProcessManager(); - fileSystem = LocalFileSystem( - LocalSignals.instance, - Signals.defaultExitSignals, - ShutdownHooks(), - ); - - stdio = Stdio(); - terminal = AnsiTerminal(stdio: stdio); - - final LoggerFactory loggerFactory = LoggerFactory( - outputPreferences: OutputPreferences( - wrapText: stdio.hasTerminal, - showColor: stdout.supportsAnsiEscapes, - stdio: stdio, - ), - terminal: terminal, - stdio: stdio, - ); - logger = loggerFactory.createLogger(windows: isWindows); - } - - late final ProcessManager processManager; - late final LocalFileSystem fileSystem; - late final Stdio stdio; - late final Terminal terminal; - late final Logger logger; -} - -/// An abstraction for instantiation of the correct logger type. -/// -/// Our logger class hierarchy and runtime requirements are overly complicated. -class LoggerFactory { - LoggerFactory({ - required Terminal terminal, - required Stdio stdio, - required OutputPreferences outputPreferences, - StopwatchFactory stopwatchFactory = const StopwatchFactory(), - }) : _terminal = terminal, - _stdio = stdio, - _stopwatchFactory = stopwatchFactory, - _outputPreferences = outputPreferences; - - final Terminal _terminal; - final Stdio _stdio; - final StopwatchFactory _stopwatchFactory; - final OutputPreferences _outputPreferences; - - /// Create the appropriate logger for the current platform and configuration. - Logger createLogger({required bool windows}) { - Logger logger; - if (windows) { - logger = WindowsStdoutLogger( - terminal: _terminal, - stdio: _stdio, - outputPreferences: _outputPreferences, - stopwatchFactory: _stopwatchFactory, - ); - } else { - logger = StdoutLogger( - terminal: _terminal, - stdio: _stdio, - outputPreferences: _outputPreferences, - stopwatchFactory: _stopwatchFactory, - ); - } - return logger; - } -} diff --git a/packages/flutter_migrate/lib/src/commands/abandon.dart b/packages/flutter_migrate/lib/src/commands/abandon.dart deleted file mode 100644 index 2e266da8f2d..00000000000 --- a/packages/flutter_migrate/lib/src/commands/abandon.dart +++ /dev/null @@ -1,156 +0,0 @@ -// Copyright 2013 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 'package:process/process.dart'; - -import '../base/command.dart'; -import '../base/file_system.dart'; -import '../base/logger.dart'; -import '../base/project.dart'; -import '../base/terminal.dart'; - -import '../utils.dart'; - -/// Abandons the existing migration by deleting the migrate working directory. -class MigrateAbandonCommand extends MigrateCommand { - MigrateAbandonCommand({ - required this.logger, - required this.fileSystem, - required this.terminal, - required ProcessManager processManager, - this.standalone = false, - }) : migrateUtils = MigrateUtils( - logger: logger, - fileSystem: fileSystem, - processManager: processManager, - ) { - argParser.addOption( - 'staging-directory', - help: - 'Specifies the custom migration working directory used to stage ' - 'and edit proposed changes. This path can be absolute or relative ' - 'to the flutter project root. This defaults to ' - '`$kDefaultMigrateStagingDirectoryName`', - valueHelp: 'path', - ); - argParser.addOption( - 'project-directory', - help: - 'The root directory of the flutter project. This defaults to the ' - 'current working directory if omitted.', - valueHelp: 'path', - ); - argParser.addFlag( - 'force', - abbr: 'f', - help: - 'Delete the migrate working directory without asking for confirmation.', - ); - argParser.addFlag( - 'flutter-subcommand', - help: - 'Enable when using the flutter tool as a subcommand. This changes the ' - 'wording of log messages to indicate the correct suggested commands to use.', - ); - } - - final Logger logger; - - final FileSystem fileSystem; - - final Terminal terminal; - - final MigrateUtils migrateUtils; - - final bool standalone; - - @override - final String name = 'abandon'; - - @override - final String description = - 'Deletes the current active migration working directory.'; - - @override - Future runCommand() async { - final String? projectDirectory = stringArg('project-directory'); - final FlutterProjectFactory flutterProjectFactory = FlutterProjectFactory(); - final FlutterProject project = - projectDirectory == null - ? FlutterProject.current(fileSystem) - : flutterProjectFactory.fromDirectory( - fileSystem.directory(projectDirectory), - ); - final bool isSubcommand = boolArg('flutter-subcommand') ?? !standalone; - - if (!validateWorkingDirectory(project, logger)) { - return const CommandResult(ExitStatus.fail); - } - - Directory stagingDirectory = project.directory.childDirectory( - kDefaultMigrateStagingDirectoryName, - ); - final String? customStagingDirectoryPath = stringArg('staging-directory'); - if (customStagingDirectoryPath != null) { - if (fileSystem.path.isAbsolute(customStagingDirectoryPath)) { - stagingDirectory = fileSystem.directory(customStagingDirectoryPath); - } else { - stagingDirectory = project.directory.childDirectory( - customStagingDirectoryPath, - ); - } - if (!stagingDirectory.existsSync()) { - logger.printError( - 'Provided staging directory `$customStagingDirectoryPath` ' - 'does not exist or is not valid.', - ); - return const CommandResult(ExitStatus.fail); - } - } - if (!stagingDirectory.existsSync()) { - logger.printStatus( - 'No migration in progress. Start a new migration with:', - ); - printCommandText('start', logger, standalone: !isSubcommand); - return const CommandResult(ExitStatus.fail); - } - - logger.printStatus( - '\nAbandoning the existing migration will delete the ' - 'migration staging directory at ${stagingDirectory.path}', - ); - final bool force = boolArg('force') ?? false; - if (!force) { - String selection = 'y'; - terminal.usesTerminalUi = true; - try { - selection = await terminal.promptForCharInput( - ['y', 'n'], - logger: logger, - prompt: - 'Are you sure you wish to continue with abandoning? (y)es, (N)o', - defaultChoiceIndex: 1, - ); - } on StateError catch (e) { - logger.printError(e.message, indent: 0); - } - if (selection != 'y') { - return const CommandResult(ExitStatus.success); - } - } - - try { - stagingDirectory.deleteSync(recursive: true); - } on FileSystemException catch (e) { - logger.printError('Deletion failed with: $e'); - logger.printError( - 'Please manually delete the staging directory at `${stagingDirectory.path}`', - ); - } - - logger.printStatus('\nAbandon complete. Start a new migration with:'); - printCommandText('start', logger, standalone: !isSubcommand); - return const CommandResult(ExitStatus.success); - } -} diff --git a/packages/flutter_migrate/lib/src/commands/apply.dart b/packages/flutter_migrate/lib/src/commands/apply.dart deleted file mode 100644 index 43ce7b6aefa..00000000000 --- a/packages/flutter_migrate/lib/src/commands/apply.dart +++ /dev/null @@ -1,268 +0,0 @@ -// Copyright 2013 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 'package:process/process.dart'; - -import '../base/command.dart'; -import '../base/file_system.dart'; -import '../base/logger.dart'; -import '../base/project.dart'; -import '../base/terminal.dart'; -import '../environment.dart'; -import '../flutter_project_metadata.dart'; - -import '../manifest.dart'; -import '../update_locks.dart'; -import '../utils.dart'; - -/// Migrate subcommand that checks the migrate working directory for unresolved conflicts and -/// applies the staged changes to the project. -class MigrateApplyCommand extends MigrateCommand { - MigrateApplyCommand({ - bool verbose = false, - required this.logger, - required this.fileSystem, - required this.terminal, - required ProcessManager processManager, - this.standalone = false, - }) : _verbose = verbose, - _processManager = processManager, - migrateUtils = MigrateUtils( - logger: logger, - fileSystem: fileSystem, - processManager: processManager, - ) { - argParser.addOption( - 'staging-directory', - help: - 'Specifies the custom migration working directory used to stage ' - 'and edit proposed changes. This path can be absolute or relative ' - 'to the flutter project root. This defaults to ' - '`$kDefaultMigrateStagingDirectoryName`', - valueHelp: 'path', - ); - argParser.addOption( - 'project-directory', - help: - 'The root directory of the flutter project. This defaults to the ' - 'current working directory if omitted.', - valueHelp: 'path', - ); - argParser.addFlag( - 'force', - abbr: 'f', - help: - 'Ignore unresolved merge conflicts and uncommitted changes and ' - 'apply staged changes by force.', - ); - argParser.addFlag( - 'keep-working-directory', - help: 'Do not delete the working directory.', - ); - argParser.addFlag( - 'flutter-subcommand', - help: - 'Enable when using the flutter tool as a subcommand. This changes the ' - 'wording of log messages to indicate the correct suggested commands to use.', - ); - } - - final bool _verbose; - - final ProcessManager _processManager; - - final Logger logger; - - final FileSystem fileSystem; - - final Terminal terminal; - - final MigrateUtils migrateUtils; - - final bool standalone; - - @override - final String name = 'apply'; - - @override - final String description = - r'Accepts the changes produced by `$ flutter ' - 'migrate start` and copies the changed files into ' - 'your project files. All merge conflicts should ' - 'be resolved before apply will complete ' - 'successfully. If conflicts still exist, this ' - 'command will print the remaining conflicted files.'; - - @override - Future runCommand() async { - final String? projectDirectory = stringArg('project-directory'); - final FlutterProjectFactory flutterProjectFactory = FlutterProjectFactory(); - final FlutterProject project = - projectDirectory == null - ? FlutterProject.current(fileSystem) - : flutterProjectFactory.fromDirectory( - fileSystem.directory(projectDirectory), - ); - final FlutterToolsEnvironment environment = - await FlutterToolsEnvironment.initializeFlutterToolsEnvironment( - _processManager, - logger, - ); - final bool isSubcommand = boolArg('flutter-subcommand') ?? !standalone; - - if (!validateWorkingDirectory(project, logger)) { - return CommandResult.fail(); - } - - if (!await gitRepoExists(project.directory.path, logger, migrateUtils)) { - logger.printStatus( - 'No git repo found. Please run in a project with an ' - 'initialized git repo or initialize one with:', - ); - printCommand('git init', logger); - return const CommandResult(ExitStatus.fail); - } - - final bool force = boolArg('force') ?? false; - - Directory stagingDirectory = project.directory.childDirectory( - kDefaultMigrateStagingDirectoryName, - ); - final String? customStagingDirectoryPath = stringArg('staging-directory'); - if (customStagingDirectoryPath != null) { - if (fileSystem.path.isAbsolute(customStagingDirectoryPath)) { - stagingDirectory = fileSystem.directory(customStagingDirectoryPath); - } else { - stagingDirectory = project.directory.childDirectory( - customStagingDirectoryPath, - ); - } - } - if (!stagingDirectory.existsSync()) { - logger.printStatus( - 'No migration in progress at $stagingDirectory. Please run:', - ); - printCommandText('start', logger, standalone: !isSubcommand); - return const CommandResult(ExitStatus.fail); - } - - final File manifestFile = MigrateManifest.getManifestFileFromDirectory( - stagingDirectory, - ); - final MigrateManifest manifest = MigrateManifest.fromFile(manifestFile); - if (!checkAndPrintMigrateStatus( - manifest, - stagingDirectory, - warnConflict: true, - logger: logger, - ) && - !force) { - logger.printStatus( - 'Conflicting files found. Resolve these conflicts and try again.', - ); - logger.printStatus('Guided conflict resolution wizard:'); - printCommandText('resolve-conflicts', logger, standalone: !isSubcommand); - return const CommandResult(ExitStatus.fail); - } - - if (await hasUncommittedChanges( - project.directory.path, - logger, - migrateUtils, - ) && - !force) { - return const CommandResult(ExitStatus.fail); - } - - logger.printStatus('Applying migration.'); - // Copy files from working directory to project root - final List allFilesToCopy = []; - allFilesToCopy.addAll(manifest.mergedFiles); - allFilesToCopy.addAll(manifest.conflictFiles); - allFilesToCopy.addAll(manifest.addedFiles); - if (allFilesToCopy.isNotEmpty && _verbose) { - logger.printStatus( - 'Modifying ${allFilesToCopy.length} files.', - indent: 2, - ); - } - for (final String localPath in allFilesToCopy) { - if (_verbose) { - logger.printStatus('Writing $localPath'); - } - final File workingFile = stagingDirectory.childFile(localPath); - final File targetFile = project.directory.childFile(localPath); - if (!workingFile.existsSync()) { - continue; - } - - if (!targetFile.existsSync()) { - targetFile.createSync(recursive: true); - } - try { - targetFile.writeAsStringSync( - workingFile.readAsStringSync(), - flush: true, - ); - } on FileSystemException { - targetFile.writeAsBytesSync(workingFile.readAsBytesSync(), flush: true); - } - } - // Delete files slated for deletion. - if (manifest.deletedFiles.isNotEmpty) { - logger.printStatus( - 'Deleting ${manifest.deletedFiles.length} files.', - indent: 2, - ); - } - for (final String localPath in manifest.deletedFiles) { - final File targetFile = project.directory.childFile(localPath); - targetFile.deleteSync(); - } - - // Update the migrate config files to reflect latest migration. - if (_verbose) { - logger.printStatus('Updating .migrate_configs'); - } - final FlutterProjectMetadata metadata = FlutterProjectMetadata( - project.directory.childFile('.metadata'), - logger, - ); - - final String currentGitHash = - environment.getString('FlutterVersion.frameworkRevision') ?? ''; - metadata.migrateConfig.populate( - projectDirectory: project.directory, - currentRevision: currentGitHash, - logger: logger, - ); - - // Clean up the working directory - final bool keepWorkingDirectory = - boolArg('keep-working-directory') ?? false; - if (!keepWorkingDirectory) { - stagingDirectory.deleteSync(recursive: true); - } - - // Detect pub dependency locking. Run flutter pub upgrade --major-versions - await updatePubspecDependencies(project, migrateUtils, logger, terminal); - - // Detect gradle lockfiles in android directory. Delete lockfiles and regenerate with ./gradlew tasks (any gradle task that requires a build). - await updateGradleDependencyLocking( - project, - migrateUtils, - logger, - terminal, - _verbose, - fileSystem, - ); - - logger.printStatus( - 'Migration complete. You may use commands like `git ' - 'status`, `git diff` and `git restore ` to continue ' - 'working with the migrated files.', - ); - return const CommandResult(ExitStatus.success); - } -} diff --git a/packages/flutter_migrate/lib/src/commands/start.dart b/packages/flutter_migrate/lib/src/commands/start.dart deleted file mode 100644 index 637b2d5e240..00000000000 --- a/packages/flutter_migrate/lib/src/commands/start.dart +++ /dev/null @@ -1,382 +0,0 @@ -// Copyright 2013 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 'package:process/process.dart'; - -import '../base/command.dart'; -import '../base/file_system.dart'; -import '../base/logger.dart'; -import '../base/project.dart'; - -import '../compute.dart'; -import '../environment.dart'; -import '../manifest.dart'; -import '../result.dart'; -import '../utils.dart'; - -class MigrateStartCommand extends MigrateCommand { - MigrateStartCommand({ - bool verbose = false, - required this.logger, - required this.fileSystem, - required this.processManager, - this.standalone = false, - }) : _verbose = verbose, - migrateUtils = MigrateUtils( - logger: logger, - fileSystem: fileSystem, - processManager: processManager, - ) { - argParser.addOption( - 'staging-directory', - help: - 'Specifies the custom migration staging directory used to stage and edit proposed changes. ' - 'This path can be absolute or relative to the flutter project root.', - valueHelp: 'path', - ); - argParser.addOption( - 'project-directory', - help: 'The root directory of the flutter project.', - valueHelp: 'path', - ); - argParser.addOption( - 'platforms', - help: - 'Restrict the tool to only migrate the listed platforms. By default all platforms generated by ' - 'flutter create will be migrated. To indicate the project root, use the `root` platform', - valueHelp: 'root,android,ios,windows...', - ); - argParser.addFlag( - 'delete-temp-directories', - help: - 'Indicates if the temporary directories created by the migrate tool will be deleted.', - ); - argParser.addOption( - 'base-app-directory', - help: - 'The directory containing the base reference app. This is used as the common ancestor in a 3 way merge. ' - 'Providing this directory will prevent the tool from generating its own. This is primarily used ' - 'in testing and CI.', - valueHelp: 'path', - hide: !verbose, - ); - argParser.addOption( - 'target-app-directory', - help: - 'The directory containing the target reference app. This is used as the target app in 3 way merge. ' - 'Providing this directory will prevent the tool from generating its own. This is primarily used ' - 'in testing and CI.', - valueHelp: 'path', - hide: !verbose, - ); - argParser.addFlag( - 'allow-fallback-base-revision', - help: - 'If a base revision cannot be determined, this flag enables using flutter 1.0.0 as a fallback base revision. ' - 'Using this fallback will typically produce worse quality migrations and possibly more conflicts.', - ); - argParser.addOption( - 'base-revision', - help: - 'Manually sets the base revision to generate the base ancestor reference app with. This may be used ' - 'if the tool is unable to determine an appropriate base revision.', - valueHelp: 'git revision hash', - ); - argParser.addOption( - 'target-revision', - help: - 'Manually sets the target revision to generate the target reference app with. Passing this indicates ' - 'that the current flutter sdk version is not the version that should be migrated to.', - valueHelp: 'git revision hash', - ); - argParser.addFlag( - 'prefer-two-way-merge', - negatable: false, - help: - 'Avoid three way merges when possible. Enabling this effectively ignores the base ancestor reference ' - 'files when a merge is required, opting for a simpler two way merge instead. In some edge cases typically ' - 'involving using a fallback or incorrect base revision, the default three way merge algorithm may produce ' - 'incorrect merges. Two way merges are more conflict prone, but less likely to produce incorrect results ' - 'silently.', - ); - argParser.addFlag( - 'flutter-subcommand', - help: - 'Enable when using the flutter tool as a subcommand. This changes the ' - 'wording of log messages to indicate the correct suggested commands to use.', - ); - } - - final bool _verbose; - - final Logger logger; - - final FileSystem fileSystem; - - final MigrateUtils migrateUtils; - - final ProcessManager processManager; - - final bool standalone; - - @override - final String name = 'start'; - - @override - final String description = - r'Begins a new migration. Computes the changes needed to migrate the project from the base revision of Flutter to the current revision of Flutter and outputs the results in a working directory. Use `$ flutter migrate apply` accept and apply the changes.'; - - @override - Future runCommand() async { - final FlutterToolsEnvironment environment = - await FlutterToolsEnvironment.initializeFlutterToolsEnvironment( - processManager, - logger, - ); - if (!_validateEnvironment(environment)) { - return const CommandResult(ExitStatus.fail); - } - final String? projectRootDirPath = - stringArg('project-directory') ?? - environment.getString('FlutterProject.directory'); - final Directory projectRootDir = fileSystem.directory(projectRootDirPath); - final FlutterProjectFactory flutterProjectFactory = FlutterProjectFactory(); - final FlutterProject project = - projectRootDirPath == null - ? FlutterProject.current(fileSystem) - : flutterProjectFactory.fromDirectory( - fileSystem.directory(projectRootDirPath), - ); - - if (!validateWorkingDirectory(project, logger)) { - return CommandResult.fail(); - } - - final bool isModule = - environment.getBool('FlutterProject.isModule') ?? false; - final bool isPlugin = - environment.getBool('FlutterProject.isPlugin') ?? false; - if (isModule || isPlugin) { - logger.printError( - 'Migrate tool only supports app projects. This project is a ${isModule ? 'module' : 'plugin'}', - ); - return const CommandResult(ExitStatus.fail); - } - final bool isSubcommand = boolArg('flutter-subcommand') ?? !standalone; - - if (!await gitRepoExists(project.directory.path, logger, migrateUtils)) { - return const CommandResult(ExitStatus.fail); - } - - Directory stagingDirectory = project.directory.childDirectory( - kDefaultMigrateStagingDirectoryName, - ); - final String? customStagingDirectoryPath = stringArg('staging-directory'); - if (customStagingDirectoryPath != null) { - if (fileSystem.path.isAbsolute(customStagingDirectoryPath)) { - stagingDirectory = fileSystem.directory(customStagingDirectoryPath); - } else { - stagingDirectory = project.directory.childDirectory( - customStagingDirectoryPath, - ); - } - } - if (stagingDirectory.existsSync()) { - logger.printStatus('Old migration already in progress.', emphasis: true); - logger.printStatus( - 'Pending migration files exist in `${stagingDirectory.path}/$kDefaultMigrateStagingDirectoryName`', - ); - logger.printStatus( - 'Resolve merge conflicts and accept changes with by running:', - ); - printCommandText('apply', logger, standalone: !isSubcommand); - logger.printStatus( - 'You may also abandon the existing migration and start a new one with:', - ); - printCommandText('abandon', logger, standalone: !isSubcommand); - return const CommandResult(ExitStatus.fail); - } - - if (await hasUncommittedChanges( - project.directory.path, - logger, - migrateUtils, - )) { - return const CommandResult(ExitStatus.fail); - } - - List? platforms; - if (stringArg('platforms') != null) { - platforms = []; - for (String platformString in stringArg('platforms')!.split(',')) { - platformString = platformString.trim(); - platforms.add( - SupportedPlatform.values.firstWhere( - (SupportedPlatform val) => - val.toString() == 'SupportedPlatform.$platformString', - ), - ); - } - } - - final MigrateCommandParameters commandParameters = MigrateCommandParameters( - verbose: _verbose, - baseAppPath: stringArg('base-app-directory'), - targetAppPath: stringArg('target-app-directory'), - baseRevision: stringArg('base-revision'), - targetRevision: stringArg('target-revision'), - deleteTempDirectories: boolArg('delete-temp-directories') ?? true, - platforms: platforms, - preferTwoWayMerge: boolArg('prefer-two-way-merge') ?? false, - allowFallbackBaseRevision: - boolArg('allow-fallback-base-revision') ?? false, - ); - - final MigrateResult? migrateResult = await computeMigration( - flutterProject: project, - commandParameters: commandParameters, - fileSystem: fileSystem, - logger: logger, - migrateUtils: migrateUtils, - environment: environment, - ); - if (migrateResult == null) { - return const CommandResult(ExitStatus.fail); - } - - await writeStagingDir( - migrateResult, - logger, - verbose: _verbose, - projectRootDir: projectRootDir, - ); - - _deleteTempDirectories( - paths: [], - directories: migrateResult.tempDirectories, - ); - - logger.printStatus( - 'The migrate tool has staged proposed changes in the migrate staging directory.\n', - ); - logger.printStatus('Guided conflict resolution wizard:'); - printCommandText('resolve-conflicts', logger, standalone: !isSubcommand); - logger.printStatus('Check the status and diffs of the migration with:'); - printCommandText('status', logger, standalone: !isSubcommand); - logger.printStatus('Abandon the proposed migration with:'); - printCommandText('abandon', logger, standalone: !isSubcommand); - logger.printStatus( - 'Accept staged changes after resolving any merge conflicts with:', - ); - printCommandText('apply', logger, standalone: !isSubcommand); - - return const CommandResult(ExitStatus.success); - } - - /// Deletes the files or directories at the provided paths. - void _deleteTempDirectories({ - List paths = const [], - List directories = const [], - }) { - for (final Directory d in directories) { - try { - d.deleteSync(recursive: true); - } on FileSystemException catch (e) { - logger.printError( - 'Unabled to delete ${d.path} due to ${e.message}, please clean up manually.', - ); - } - } - for (final String p in paths) { - try { - fileSystem.directory(p).deleteSync(recursive: true); - } on FileSystemException catch (e) { - logger.printError( - 'Unabled to delete $p due to ${e.message}, please clean up manually.', - ); - } - } - } - - bool _validateEnvironment(FlutterToolsEnvironment environment) { - if (environment.getString('FlutterProject.directory') == null) { - logger.printError( - 'No valid flutter project found. This command must be run from a flutter project directory', - ); - return false; - } - if (environment.getString('FlutterProject.manifest.appname') == null) { - logger.printError('No app name found in project pubspec.yaml'); - return false; - } - if (!(environment.getBool('FlutterProject.android.exists') ?? false) && - environment['FlutterProject.android.isKotlin'] == null) { - logger.printError( - 'Could not detect if android project uses kotlin or java', - ); - return false; - } - if (!(environment.getBool('FlutterProject.ios.exists') ?? false) && - environment['FlutterProject.ios.isSwift'] == null) { - logger.printError( - 'Could not detect if iosProject uses swift or objective-c', - ); - return false; - } - return true; - } - - /// Writes the files into the working directory for the developer to review and resolve any conflicts. - Future writeStagingDir( - MigrateResult migrateResult, - Logger logger, { - bool verbose = false, - required Directory projectRootDir, - }) async { - final Directory stagingDir = projectRootDir.childDirectory( - kDefaultMigrateStagingDirectoryName, - ); - if (verbose) { - logger.printStatus( - 'Writing migrate staging directory at `${stagingDir.path}`', - ); - } - // Write files in working dir - for (final MergeResult result in migrateResult.mergeResults) { - final File file = stagingDir.childFile(result.localPath); - file.createSync(recursive: true); - if (result is StringMergeResult) { - file.writeAsStringSync(result.mergedString, flush: true); - } else { - file.writeAsBytesSync( - (result as BinaryMergeResult).mergedBytes, - flush: true, - ); - } - } - - // Write all files that are newly added in target - for (final FilePendingMigration addedFile in migrateResult.addedFiles) { - final File file = stagingDir.childFile(addedFile.localPath); - file.createSync(recursive: true); - try { - file.writeAsStringSync(addedFile.file.readAsStringSync(), flush: true); - } on FileSystemException { - file.writeAsBytesSync(addedFile.file.readAsBytesSync(), flush: true); - } - } - - // Write the MigrateManifest. - final MigrateManifest manifest = MigrateManifest( - migrateRootDir: stagingDir, - migrateResult: migrateResult, - ); - manifest.writeFile(); - - // output the manifest contents. - checkAndPrintMigrateStatus(manifest, stagingDir, logger: logger); - - logger.printBox('Staging directory created at `${stagingDir.path}`'); - } -} diff --git a/packages/flutter_migrate/lib/src/commands/status.dart b/packages/flutter_migrate/lib/src/commands/status.dart deleted file mode 100644 index 84dd99b17d9..00000000000 --- a/packages/flutter_migrate/lib/src/commands/status.dart +++ /dev/null @@ -1,212 +0,0 @@ -// Copyright 2013 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 'package:process/process.dart'; - -import '../base/command.dart'; -import '../base/file_system.dart'; -import '../base/logger.dart'; -import '../base/project.dart'; -import '../base/terminal.dart'; - -import '../manifest.dart'; -import '../utils.dart'; - -/// Flutter migrate subcommand to check the migration status of the project. -class MigrateStatusCommand extends MigrateCommand { - MigrateStatusCommand({ - bool verbose = false, - required this.logger, - required this.fileSystem, - required ProcessManager processManager, - this.standalone = false, - }) : _verbose = verbose, - migrateUtils = MigrateUtils( - logger: logger, - fileSystem: fileSystem, - processManager: processManager, - ) { - argParser.addOption( - 'staging-directory', - help: - 'Specifies the custom migration working directory used to stage ' - 'and edit proposed changes. This path can be absolute or relative ' - 'to the flutter project root. This defaults to ' - '`$kDefaultMigrateStagingDirectoryName`', - valueHelp: 'path', - ); - argParser.addOption( - 'project-directory', - help: - 'The root directory of the flutter project. This defaults to the ' - 'current working directory if omitted.', - valueHelp: 'path', - ); - argParser.addFlag( - 'diff', - defaultsTo: true, - help: 'Shows the diff output when enabled. Enabled by default.', - ); - argParser.addFlag( - 'show-added-files', - help: 'Shows the contents of added files. Disabled by default.', - ); - argParser.addFlag( - 'flutter-subcommand', - help: - 'Enable when using the flutter tool as a subcommand. This changes the ' - 'wording of log messages to indicate the correct suggested commands to use.', - ); - } - - final bool _verbose; - - final Logger logger; - - final FileSystem fileSystem; - - final MigrateUtils migrateUtils; - - final bool standalone; - - @override - final String name = 'status'; - - @override - final String description = - 'Prints the current status of the in progress migration.'; - - /// Manually marks the lines in a diff that should be printed unformatted for visbility. - /// - /// This is used to ensure the initial lines that display the files being diffed and the - /// git revisions are printed and never skipped. - final Set _initialDiffLines = {0, 1}; - - @override - Future runCommand() async { - final String? projectDirectory = stringArg('project-directory'); - final FlutterProjectFactory flutterProjectFactory = FlutterProjectFactory(); - final FlutterProject project = - projectDirectory == null - ? FlutterProject.current(fileSystem) - : flutterProjectFactory.fromDirectory( - fileSystem.directory(projectDirectory), - ); - final bool isSubcommand = boolArg('flutter-subcommand') ?? !standalone; - - if (!validateWorkingDirectory(project, logger)) { - return CommandResult.fail(); - } - - Directory stagingDirectory = project.directory.childDirectory( - kDefaultMigrateStagingDirectoryName, - ); - final String? customStagingDirectoryPath = stringArg('staging-directory'); - if (customStagingDirectoryPath != null) { - if (fileSystem.path.isAbsolute(customStagingDirectoryPath)) { - stagingDirectory = fileSystem.directory(customStagingDirectoryPath); - } else { - stagingDirectory = project.directory.childDirectory( - customStagingDirectoryPath, - ); - } - } - if (!stagingDirectory.existsSync()) { - logger.printStatus( - 'No migration in progress in $stagingDirectory. Start a new migration with:', - ); - printCommandText('start', logger, standalone: !isSubcommand); - return const CommandResult(ExitStatus.fail); - } - - final File manifestFile = MigrateManifest.getManifestFileFromDirectory( - stagingDirectory, - ); - if (!manifestFile.existsSync()) { - logger.printError( - 'No migrate manifest in the migrate working directory ' - 'at ${stagingDirectory.path}. Fix the working directory ' - 'or abandon and restart the migration.', - ); - return const CommandResult(ExitStatus.fail); - } - final MigrateManifest manifest = MigrateManifest.fromFile(manifestFile); - - final bool showDiff = boolArg('diff') ?? true; - final bool showAddedFiles = boolArg('show-added-files') ?? true; - if (showDiff || _verbose) { - if (showAddedFiles || _verbose) { - for (final String localPath in manifest.addedFiles) { - logger.printStatus('Newly added file at $localPath:\n'); - try { - logger.printStatus( - stagingDirectory.childFile(localPath).readAsStringSync(), - color: TerminalColor.green, - ); - } on FileSystemException { - logger.printStatus( - 'Contents are byte data\n', - color: TerminalColor.grey, - ); - } - } - } - final List files = []; - files.addAll(manifest.mergedFiles); - files.addAll(manifest.resolvedConflictFiles(stagingDirectory)); - files.addAll(manifest.remainingConflictFiles(stagingDirectory)); - for (final String localPath in files) { - final DiffResult result = await migrateUtils.diffFiles( - project.directory.childFile(localPath), - stagingDirectory.childFile(localPath), - ); - if (result.diff != '' && result.diff != null) { - // Print with different colors for better visibility. - int lineNumber = -1; - for (final String line in result.diff!.split('\n')) { - lineNumber++; - if (line.startsWith('---') || - line.startsWith('+++') || - line.startsWith('&&') || - _initialDiffLines.contains(lineNumber)) { - logger.printStatus(line); - continue; - } - if (line.startsWith('-')) { - logger.printStatus(line, color: TerminalColor.red); - continue; - } - if (line.startsWith('+')) { - logger.printStatus(line, color: TerminalColor.green); - continue; - } - logger.printStatus(line, color: TerminalColor.grey); - } - } - } - } - - logger.printBox('Staging directory at `${stagingDirectory.path}`'); - - checkAndPrintMigrateStatus(manifest, stagingDirectory, logger: logger); - - final bool readyToApply = - manifest.remainingConflictFiles(stagingDirectory).isEmpty; - - if (!readyToApply) { - logger.printStatus('Guided conflict resolution wizard:'); - printCommandText('resolve-conflicts', logger, standalone: !isSubcommand); - logger.printStatus('Resolve conflicts and accept changes with:'); - } else { - logger.printStatus( - 'All conflicts resolved. Review changes above and ' - 'apply the migration with:', - color: TerminalColor.green, - ); - } - printCommandText('apply', logger, standalone: !isSubcommand); - - return const CommandResult(ExitStatus.success); - } -} diff --git a/packages/flutter_migrate/lib/src/compute.dart b/packages/flutter_migrate/lib/src/compute.dart deleted file mode 100644 index abf96e6e3e3..00000000000 --- a/packages/flutter_migrate/lib/src/compute.dart +++ /dev/null @@ -1,1157 +0,0 @@ -// Copyright 2013 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 'package:path/path.dart'; - -import 'base/common.dart'; -import 'base/file_system.dart'; -import 'base/logger.dart'; -import 'base/project.dart'; -import 'custom_merge.dart'; -import 'environment.dart'; -import 'flutter_project_metadata.dart'; -import 'migrate_logger.dart'; -import 'result.dart'; -import 'utils.dart'; - -// This defines paths of files and directories relative to the project root -// that should be ignored by the migrate tool regardless of .gitignore and -// config settings. -// Paths use `/` as a stand-in for path separator. -const List _skippedFiles = [ - 'ios/Runner.xcodeproj/project.pbxproj', // Xcode managed configs that may not merge cleanly. - 'README.md', // changes to this shouldn't be overwritten since is is user owned. -]; - -const List _skippedDirectories = [ - '.dart_tool', // The .dart_tool generated dir. - '.git', // Git metadata. - 'assets', // Common directory for user assets. - 'build', // Build artifacts. - 'lib', // Always user owned and we don't want to overwrite their apps. - 'test', // Typically user owned and flutter-side changes are not relevant. -]; - -final Iterable canonicalizedSkippedFiles = _skippedFiles.map( - (String path) => canonicalize(path), -); - -// Returns true for paths relative to the project root that should be skipped -// completely by the migrate tool. -bool _skipped( - String localPath, - FileSystem fileSystem, { - Set? skippedPrefixes, -}) { - final String canonicalizedLocalPath = canonicalize(localPath); - final Iterable canonicalizedSkippedFiles = _skippedFiles.map( - (String path) => canonicalize(path), - ); - if (canonicalizedSkippedFiles.contains(canonicalizedLocalPath)) { - return true; - } - final Iterable canonicalizedSkippedDirectories = _skippedDirectories - .map((String path) => canonicalize(path)); - for (final String dir in canonicalizedSkippedDirectories) { - if (canonicalizedLocalPath.startsWith('$dir${fileSystem.path.separator}')) { - return true; - } - } - if (skippedPrefixes != null) { - return skippedPrefixes.any( - (String prefix) => localPath.startsWith( - '${normalize(prefix.replaceAll(r'\', fileSystem.path.separator))}${fileSystem.path.separator}', - ), - ); - } - return false; -} - -// File extensions that the tool will not attempt to merge. Changes -// in files with these extensions will be accepted wholesale. -// -// The executables and binaries in this list are not meant to be -// comprehensive and need only cover the files that are generated -// in `flutter create` as only files generated by the template -// will be attempted to be merged. -const List _doNotMergeFileExtensions = [ - // Don't merge image files - '.bmp', - '.gif', - '.jpg', - '.jpeg', - '.png', - '.svg', - // Don't merge compiled artifacts and executables - '.dll', - '.exe', - '.jar', - '.so', -]; - -// These files should always go through the migrate process as -// they are either integral to the migrate process or we expect -// new versions of this file to always be desired. -const Set _alwaysMigrateFiles = { - '.metadata', // .metadata tracks key migration information. - 'android/gradle/wrapper/gradle-wrapper.jar', - // Always add .gitignore back in even if user-deleted as it makes it - // difficult to migrate in the future and the migrate tool enforces git - // usage. - '.gitignore', -}; - -/// False for files that should not be merged. Typically, images and binary files. -bool _mergable(String localPath) { - return _alwaysMigrateFiles.contains(localPath) || - !_doNotMergeFileExtensions.any((String ext) => localPath.endsWith(ext)); -} - -// Compile the set of path prefixes that should be ignored as configured -// in the command arguments. -Set _getSkippedPrefixes(List platforms) { - final Set skippedPrefixes = {}; - for (final SupportedPlatform platform in SupportedPlatform.values) { - skippedPrefixes.add(platformToSubdirectoryPrefix(platform)); - } - for (final SupportedPlatform platform in platforms) { - skippedPrefixes.remove(platformToSubdirectoryPrefix(platform)); - } - return skippedPrefixes; -} - -/// Data class holds the common context that is used throughout the steps of a migrate computation. -class MigrateContext { - MigrateContext({ - required this.flutterProject, - required this.skippedPrefixes, - required this.fileSystem, - required this.migrateLogger, - required this.migrateUtils, - required this.environment, - this.baseProject, - this.targetProject, - }); - - final FlutterProject flutterProject; - final Set skippedPrefixes; - final FileSystem fileSystem; - final MigrateLogger migrateLogger; - final MigrateUtils migrateUtils; - final FlutterToolsEnvironment environment; - - MigrateBaseFlutterProject? baseProject; - MigrateTargetFlutterProject? targetProject; -} - -/// Returns the path relative to the flutter project's root. -String getLocalPath(String path, String basePath, FileSystem fileSystem) { - return path.replaceFirst(basePath + fileSystem.path.separator, ''); -} - -String platformToSubdirectoryPrefix(SupportedPlatform platform) { - switch (platform) { - case SupportedPlatform.android: - return 'android'; - case SupportedPlatform.ios: - return 'ios'; - case SupportedPlatform.linux: - return 'linux'; - case SupportedPlatform.macos: - return 'macos'; - case SupportedPlatform.web: - return 'web'; - case SupportedPlatform.windows: - return 'windows'; - case SupportedPlatform.fuchsia: - return 'fuchsia'; - } -} - -/// Data class that contains the command line arguments passed by the user. -class MigrateCommandParameters { - MigrateCommandParameters({ - this.baseAppPath, - this.targetAppPath, - this.baseRevision, - this.targetRevision, - this.preferTwoWayMerge = false, - this.verbose = false, - this.allowFallbackBaseRevision = false, - this.deleteTempDirectories = true, - this.platforms, - }); - final String? baseAppPath; - final String? targetAppPath; - final String? baseRevision; - final String? targetRevision; - final bool preferTwoWayMerge; - final bool verbose; - final bool allowFallbackBaseRevision; - final bool deleteTempDirectories; - final List? platforms; -} - -/// Computes the changes that migrates the current flutter project to the target revision. -/// -/// This is the entry point to the core migration computations and drives the migration process. -/// -/// This method attempts to find a base revision, which is the revision of the Flutter SDK -/// the app was generated with or the last revision the app was migrated to. The base revision -/// typically comes from the .metadata, but for legacy apps, the config may not exist. In -/// this case, we fallback to using the revision in .metadata, and if that does not exist, we -/// use the target revision as the base revision. In the final fallback case, the migration should -/// still work, but will likely generate slightly less accurate merges. -/// -/// Operations the computation performs: -/// -/// - Parse .metadata file -/// - Collect revisions to use for each platform -/// - Download each flutter revision and call `flutter create` for each. -/// - Call `flutter create` with target revision (target is typically current flutter version) -/// - Diff base revision generated app with target revision generated app -/// - Compute all newly added files between base and target revisions -/// - Compute merge of all files that are modified by user and flutter -/// - Track temp dirs to be deleted -/// -/// Structure: This method builds upon a MigrateResult instance -Future computeMigration({ - FlutterProject? flutterProject, - required MigrateCommandParameters commandParameters, - required FileSystem fileSystem, - required Logger logger, - required MigrateUtils migrateUtils, - required FlutterToolsEnvironment environment, -}) async { - flutterProject ??= FlutterProject.current(fileSystem); - - final MigrateLogger migrateLogger = MigrateLogger( - logger: logger, - verbose: commandParameters.verbose, - ); - migrateLogger.logStep('start'); - // Find the path prefixes to ignore. This allows subdirectories of platforms - // not part of the migration to be skipped. - final List platforms = - commandParameters.platforms ?? flutterProject.getSupportedPlatforms(); - final Set skippedPrefixes = _getSkippedPrefixes(platforms); - - final MigrateResult result = MigrateResult.empty(); - final MigrateContext context = MigrateContext( - flutterProject: flutterProject, - skippedPrefixes: skippedPrefixes, - migrateLogger: migrateLogger, - fileSystem: fileSystem, - migrateUtils: migrateUtils, - environment: environment, - ); - - migrateLogger.logStep('revisions'); - final MigrateRevisions revisionConfig = MigrateRevisions( - context: context, - baseRevision: commandParameters.baseRevision, - allowFallbackBaseRevision: commandParameters.allowFallbackBaseRevision, - platforms: platforms, - environment: environment, - ); - - // Extract the unamanged files/paths that should be ignored by the migrate tool. - // These paths are absolute paths. - migrateLogger.logStep('unmanaged'); - final List unmanagedFiles = []; - final List unmanagedDirectories = []; - final String basePath = flutterProject.directory.path; - for (final String localPath in revisionConfig.config.unmanagedFiles) { - if (localPath.endsWith(fileSystem.path.separator)) { - unmanagedDirectories.add(fileSystem.path.join(basePath, localPath)); - } else { - unmanagedFiles.add(fileSystem.path.join(basePath, localPath)); - } - } - - migrateLogger.logStep('generating_base'); - // Generate the base templates - final ReferenceProjects referenceProjects = - await _generateBaseAndTargetReferenceProjects( - context: context, - result: result, - revisionConfig: revisionConfig, - platforms: platforms, - commandParameters: commandParameters, - ); - - // Generate diffs. These diffs are used to determine if a file is newly added, needs merging, - // or deleted (rare). Only files with diffs between the base and target revisions need to be - // migrated. If files are unchanged between base and target, then there are no changes to merge. - migrateLogger.logStep('diff'); - result.diffMap.addAll( - await referenceProjects.baseProject.diff( - context, - referenceProjects.targetProject, - ), - ); - - // Check for any new files that were added in the target reference app that did not - // exist in the base reference app. - migrateLogger.logStep('new_files'); - result.addedFiles.addAll( - await referenceProjects.baseProject.computeNewlyAddedFiles( - context, - result, - referenceProjects.targetProject, - ), - ); - - // Merge any base->target changed files with the version in the developer's project. - // Files that the developer left unchanged are fully updated to match the target reference. - // Files that the developer changed and were changed from base->target are merged. - migrateLogger.logStep('merging'); - await MigrateFlutterProject.merge( - context, - result, - referenceProjects.baseProject, - referenceProjects.targetProject, - unmanagedFiles, - unmanagedDirectories, - commandParameters.preferTwoWayMerge, - ); - - // Clean up any temp directories generated by this tool. - migrateLogger.logStep('cleaning'); - _registerTempDirectoriesForCleaning( - commandParameters: commandParameters, - result: result, - referenceProjects: referenceProjects, - ); - migrateLogger.stop(); - return result; -} - -/// Returns a base revision to fallback to in case a true base revision is unknown. -String _getFallbackBaseRevision( - bool allowFallbackBaseRevision, - MigrateLogger migrateLogger, -) { - if (!allowFallbackBaseRevision) { - migrateLogger.stop(); - migrateLogger.printError( - 'Could not determine base revision this app was created with:', - ); - migrateLogger.printError( - '.metadata file did not exist or did not contain a valid revision.', - indent: 2, - ); - migrateLogger.printError( - 'Run this command again with the `--allow-fallback-base-revision` flag to use Flutter v1.0.0 as the base revision or manually pass a revision with `--base-revision=`', - indent: 2, - ); - throwToolExit('Failed to resolve base revision'); - } - // Earliest version of flutter with .metadata: c17099f474675d8066fec6984c242d8b409ae985 (2017) - // Flutter 2.0.0: 60bd88df915880d23877bfc1602e8ddcf4c4dd2a - // Flutter v1.0.0: 5391447fae6209bb21a89e6a5a6583cac1af9b4b - // - // TODO(garyq): Use things like dart sdk version and other hints to better fine-tune this fallback. - // - // We fall back on flutter v1.0.0 if .metadata doesn't exist. - migrateLogger.printIfVerbose( - 'Could not determine base revision, falling back on `v1.0.0`, revision 5391447fae6209bb21a89e6a5a6583cac1af9b4b', - ); - return '5391447fae6209bb21a89e6a5a6583cac1af9b4b'; -} - -/// Simple data class that holds the base and target reference -/// projects. -class ReferenceProjects { - ReferenceProjects({ - required this.baseProject, - required this.targetProject, - required this.customBaseProjectDir, - required this.customTargetProjectDir, - }); - - MigrateBaseFlutterProject baseProject; - MigrateTargetFlutterProject targetProject; - - // Whether a user provided base and target projects were provided. - bool customBaseProjectDir; - bool customTargetProjectDir; -} - -// Generate reference base and target flutter projects. -// -// This function generates reference vaniilla projects by using `flutter create` with -// the base revision Flutter SDK as well as the target revision SDK. -Future _generateBaseAndTargetReferenceProjects({ - required MigrateContext context, - required MigrateResult result, - required MigrateRevisions revisionConfig, - required List platforms, - required MigrateCommandParameters commandParameters, -}) async { - // Use user-provided projects if provided, if not, generate them internally. - final bool customBaseProjectDir = commandParameters.baseAppPath != null; - final bool customTargetProjectDir = commandParameters.targetAppPath != null; - Directory baseProjectDir = context.fileSystem.systemTempDirectory - .createTempSync('baseProject'); - Directory targetProjectDir = context.fileSystem.systemTempDirectory - .createTempSync('targetProject'); - if (customBaseProjectDir) { - baseProjectDir = context.fileSystem.directory( - commandParameters.baseAppPath, - ); - } else { - baseProjectDir = context.fileSystem.systemTempDirectory.createTempSync( - 'baseProject', - ); - context.migrateLogger.printIfVerbose( - 'Created temporary directory: ${baseProjectDir.path}', - ); - } - if (customTargetProjectDir) { - targetProjectDir = context.fileSystem.directory( - commandParameters.targetAppPath, - ); - } else { - targetProjectDir = context.fileSystem.systemTempDirectory.createTempSync( - 'targetProject', - ); - context.migrateLogger.printIfVerbose( - 'Created temporary directory: ${targetProjectDir.path}', - ); - } - - // Git init to enable running further git commands on the reference projects. - await context.migrateUtils.gitInit(baseProjectDir.absolute.path); - await context.migrateUtils.gitInit(targetProjectDir.absolute.path); - - result.generatedBaseTemplateDirectory = baseProjectDir; - result.generatedTargetTemplateDirectory = targetProjectDir; - - final String name = - context.environment['FlutterProject.manifest.appname']! as String; - final String androidLanguage = - context.environment['FlutterProject.android.isKotlin']! as bool - ? 'kotlin' - : 'java'; - final String iosLanguage = - context.environment['FlutterProject.ios.isSwift']! as bool - ? 'swift' - : 'objc'; - - final Directory targetFlutterDirectory = context.fileSystem.directory( - context.environment.getString('Cache.flutterRoot'), - ); - - // Create the base reference vanilla app. - // - // This step clones the base flutter sdk, and uses it to create a new vanilla app. - // The vanilla base app is used as part of a 3 way merge between the base app, target - // app, and the current user-owned app. - final MigrateBaseFlutterProject baseProject = MigrateBaseFlutterProject( - path: commandParameters.baseAppPath, - directory: baseProjectDir, - name: name, - androidLanguage: androidLanguage, - iosLanguage: iosLanguage, - platformWhitelist: platforms, - ); - context.baseProject = baseProject; - await baseProject.createProject( - context, - result, - revisionConfig.revisionsList, - revisionConfig.revisionToConfigs, - commandParameters.baseRevision ?? - revisionConfig.metadataRevision ?? - _getFallbackBaseRevision( - commandParameters.allowFallbackBaseRevision, - context.migrateLogger, - ), - revisionConfig.targetRevision, - targetFlutterDirectory, - ); - - // Create target reference app when not provided. - // - // This step directly calls flutter create with the target (the current installed revision) - // flutter sdk. - final MigrateTargetFlutterProject targetProject = MigrateTargetFlutterProject( - path: commandParameters.targetAppPath, - directory: targetProjectDir, - name: name, - androidLanguage: androidLanguage, - iosLanguage: iosLanguage, - platformWhitelist: platforms, - ); - context.targetProject = targetProject; - await targetProject.createProject( - context, - result, - revisionConfig.targetRevision, - targetFlutterDirectory, - ); - - return ReferenceProjects( - baseProject: baseProject, - targetProject: targetProject, - customBaseProjectDir: customBaseProjectDir, - customTargetProjectDir: customTargetProjectDir, - ); -} - -// Registers any generated temporary directories for optional deletion upon tool exit. -void _registerTempDirectoriesForCleaning({ - required MigrateCommandParameters commandParameters, - required MigrateResult result, - required ReferenceProjects referenceProjects, -}) { - if (commandParameters.deleteTempDirectories) { - // Don't delete user-provided directories - if (!referenceProjects.customBaseProjectDir) { - result.tempDirectories.add(result.generatedBaseTemplateDirectory!); - } - if (!referenceProjects.customTargetProjectDir) { - result.tempDirectories.add(result.generatedTargetTemplateDirectory!); - } - result.tempDirectories.addAll(result.sdkDirs.values); - } -} - -/// A reference flutter project. -/// -/// A MigrateFlutterProject is a project that is generated internally within the tool -/// to see what changes need to be made to the user's project. This class -/// provides methods to merge, diff, and otherwise compare multiple MigrateFlutterProject -/// instances. -abstract class MigrateFlutterProject { - MigrateFlutterProject({ - required this.path, - required this.directory, - required this.name, - required this.androidLanguage, - required this.iosLanguage, - this.platformWhitelist, - }); - - final String? path; - final Directory directory; - final String name; - final String androidLanguage; - final String iosLanguage; - final List? platformWhitelist; - - /// Run git diff over each matching pair of files in the this project and the provided target project. - Future> diff( - MigrateContext context, - MigrateFlutterProject other, - ) async { - final Map diffMap = {}; - final List thisFiles = directory.listSync( - recursive: true, - ); - int modifiedFilesCount = 0; - for (final FileSystemEntity entity in thisFiles) { - if (entity is! File) { - continue; - } - final File thisFile = entity.absolute; - final String localPath = getLocalPath( - thisFile.path, - directory.absolute.path, - context.fileSystem, - ); - if (_skipped( - localPath, - context.fileSystem, - skippedPrefixes: context.skippedPrefixes, - )) { - continue; - } - if (await context.migrateUtils.isGitIgnored( - thisFile.absolute.path, - directory.absolute.path, - )) { - diffMap[localPath] = DiffResult(diffType: DiffType.ignored); - } - final File otherFile = other.directory.childFile(localPath); - if (otherFile.existsSync()) { - final DiffResult diff = await context.migrateUtils.diffFiles( - thisFile, - otherFile, - ); - diffMap[localPath] = diff; - if (diff.diff != '') { - context.migrateLogger.printIfVerbose( - 'Found ${diff.exitCode} changes in $localPath', - indent: 4, - ); - modifiedFilesCount++; - } - } else { - // Current file has no new template counterpart, which is equivalent to a deletion. - // This could also indicate a renaming if there is an addition with equivalent contents. - diffMap[localPath] = DiffResult(diffType: DiffType.deletion); - } - } - context.migrateLogger.printIfVerbose( - '$modifiedFilesCount files were modified between base and target apps.', - ); - return diffMap; - } - - /// Find all files that exist in the target reference app but not in the base reference app. - Future> computeNewlyAddedFiles( - MigrateContext context, - MigrateResult result, - MigrateFlutterProject other, - ) async { - final List addedFiles = []; - final List otherFiles = other.directory.listSync( - recursive: true, - ); - for (final FileSystemEntity entity in otherFiles) { - if (entity is! File) { - continue; - } - final File otherFile = entity.absolute; - final String localPath = getLocalPath( - otherFile.path, - other.directory.absolute.path, - context.fileSystem, - ); - if (directory.childFile(localPath).existsSync() || - _skipped( - localPath, - context.fileSystem, - skippedPrefixes: context.skippedPrefixes, - )) { - continue; - } - if (await context.migrateUtils.isGitIgnored( - otherFile.absolute.path, - other.directory.absolute.path, - )) { - result.diffMap[localPath] = DiffResult(diffType: DiffType.ignored); - } - result.diffMap[localPath] = DiffResult(diffType: DiffType.addition); - if (context.flutterProject.directory.childFile(localPath).existsSync()) { - // Don't store as added file if file already exists in the project. - continue; - } - addedFiles.add(FilePendingMigration(localPath, otherFile)); - } - context.migrateLogger.printIfVerbose( - '${addedFiles.length} files were newly added in the target app.', - ); - return addedFiles; - } - - /// Loops through each existing file and intelligently merges it with the base->target changes. - static Future merge( - MigrateContext context, - MigrateResult result, - MigrateFlutterProject baseProject, - MigrateFlutterProject targetProject, - List unmanagedFiles, - List unmanagedDirectories, - bool preferTwoWayMerge, - ) async { - final List customMerges = [ - MetadataCustomMerge(logger: context.migrateLogger.logger), - ]; - // For each existing file in the project, we attempt to 3 way merge if it is changed by the user. - final List currentFiles = context.flutterProject.directory - .listSync(recursive: true); - final String projectRootPath = - context.flutterProject.directory.absolute.path; - final Set missingAlwaysMigrateFiles = Set.of( - _alwaysMigrateFiles, - ); - for (final FileSystemEntity entity in currentFiles) { - if (entity is! File) { - continue; - } - // check if the file is unmanaged/ignored by the migration tool. - bool ignored = false; - ignored = unmanagedFiles.contains(entity.absolute.path); - for (final String path in unmanagedDirectories) { - if (entity.absolute.path.startsWith(path)) { - ignored = true; - break; - } - } - if (ignored) { - continue; // Skip if marked as unmanaged - } - - final File currentFile = entity.absolute; - // Diff the current file against the old generated template - final String localPath = getLocalPath( - currentFile.path, - projectRootPath, - context.fileSystem, - ); - missingAlwaysMigrateFiles.remove(localPath); - if (result.diffMap.containsKey(localPath) && - result.diffMap[localPath]!.diffType == DiffType.ignored || - await context.migrateUtils.isGitIgnored( - currentFile.path, - context.flutterProject.directory.absolute.path, - ) || - _skipped( - localPath, - context.fileSystem, - skippedPrefixes: context.skippedPrefixes, - ) || - !_mergable(localPath)) { - continue; - } - final File baseTemplateFile = baseProject.directory.childFile(localPath); - final File targetTemplateFile = targetProject.directory.childFile( - localPath, - ); - final DiffResult userDiff = await context.migrateUtils.diffFiles( - currentFile, - baseTemplateFile, - ); - final DiffResult targetDiff = await context.migrateUtils.diffFiles( - currentFile, - targetTemplateFile, - ); - if (targetDiff.exitCode == 0) { - // current file is already the same as the target file. - continue; - } - - final bool alwaysMigrate = _alwaysMigrateFiles.contains(localPath); - - // Current file unchanged by user, thus we consider it owned by the tool. - if (userDiff.exitCode == 0 || alwaysMigrate) { - if ((result.diffMap.containsKey(localPath) || alwaysMigrate) && - result.diffMap[localPath] != null) { - // File changed between base and target - if (result.diffMap[localPath]!.diffType == DiffType.deletion) { - // File is deleted in new template - result.deletedFiles.add( - FilePendingMigration(localPath, currentFile), - ); - continue; - } - if (result.diffMap[localPath]!.exitCode != 0 || alwaysMigrate) { - // Accept the target version wholesale - MergeResult mergeResult; - try { - mergeResult = StringMergeResult.explicit( - mergedString: targetTemplateFile.readAsStringSync(), - hasConflict: false, - exitCode: 0, - localPath: localPath, - ); - } on FileSystemException { - mergeResult = BinaryMergeResult.explicit( - mergedBytes: targetTemplateFile.readAsBytesSync(), - hasConflict: false, - exitCode: 0, - localPath: localPath, - ); - } - result.mergeResults.add(mergeResult); - continue; - } - } - continue; - } - - // File changed by user - if (result.diffMap.containsKey(localPath)) { - MergeResult? mergeResult; - // Default to two way merge as it does not require the base file to exist. - MergeType mergeType = - result.mergeTypeMap[localPath] ?? MergeType.twoWay; - for (final CustomMerge customMerge in customMerges) { - if (customMerge.localPath == localPath) { - mergeResult = customMerge.merge( - currentFile, - baseTemplateFile, - targetTemplateFile, - ); - mergeType = MergeType.custom; - break; - } - } - if (mergeResult == null) { - late String basePath; - late String currentPath; - late String targetPath; - - // Use two way merge if diff between base and target are the same. - // This prevents the three way merge re-deleting the base->target changes. - if (preferTwoWayMerge) { - mergeType = MergeType.twoWay; - } - switch (mergeType) { - case MergeType.twoWay: - { - basePath = currentFile.path; - currentPath = currentFile.path; - targetPath = context.fileSystem.path.join( - result.generatedTargetTemplateDirectory!.path, - localPath, - ); - break; - } - case MergeType.threeWay: - { - basePath = context.fileSystem.path.join( - result.generatedBaseTemplateDirectory!.path, - localPath, - ); - currentPath = currentFile.path; - targetPath = context.fileSystem.path.join( - result.generatedTargetTemplateDirectory!.path, - localPath, - ); - break; - } - case MergeType.custom: - { - break; // handled above - } - } - if (mergeType != MergeType.custom) { - mergeResult = await context.migrateUtils.gitMergeFile( - base: basePath, - current: currentPath, - target: targetPath, - localPath: localPath, - ); - } - } - if (mergeResult != null) { - // Don't include if result is identical to the current file. - if (mergeResult is StringMergeResult) { - if (mergeResult.mergedString == currentFile.readAsStringSync()) { - context.migrateLogger.printIfVerbose( - '$localPath was merged with a $mergeType.', - ); - continue; - } - } else { - if ((mergeResult as BinaryMergeResult).mergedBytes == - currentFile.readAsBytesSync()) { - continue; - } - } - result.mergeResults.add(mergeResult); - } - context.migrateLogger.printStatus( - '$localPath was merged with a $mergeType.', - ); - continue; - } - } - - // Add files that are in the target, marked as always migrate, and missing in the current project. - for (final String localPath in missingAlwaysMigrateFiles) { - final File targetTemplateFile = result.generatedTargetTemplateDirectory! - .childFile(localPath); - if (targetTemplateFile.existsSync() && - !_skipped( - localPath, - context.fileSystem, - skippedPrefixes: context.skippedPrefixes, - )) { - result.addedFiles.add( - FilePendingMigration(localPath, targetTemplateFile), - ); - } - } - } -} - -/// The base reference project used in a migration computation. -/// -/// This project is a clean re-generation of the version the user's project -/// was 1. originally generated with, or 2. the last successful migrated to. -class MigrateBaseFlutterProject extends MigrateFlutterProject { - MigrateBaseFlutterProject({ - required super.path, - required super.directory, - required super.name, - required super.androidLanguage, - required super.iosLanguage, - super.platformWhitelist, - }); - - /// Creates the base reference app based off of the migrate config in the .metadata file. - Future createProject( - MigrateContext context, - MigrateResult result, - List revisionsList, - Map> revisionToConfigs, - String fallbackRevision, - String targetRevision, - Directory targetFlutterDirectory, - ) async { - // Create base - // Clone base flutter - if (path == null) { - final Map revisionToFlutterSdkDir = - {}; - for (final String revision in revisionsList) { - final List platforms = []; - for (final MigratePlatformConfig config - in revisionToConfigs[revision]!) { - if (config.component == FlutterProjectComponent.root) { - continue; - } - platforms.add(config.component.toString().split('.').last); - } - - // In the case of the revision being invalid or not a hash of the master branch, - // we want to fallback in the following order: - // - parsed revision - // - fallback revision - // - target revision (currently installed flutter) - late Directory sdkDir; - final List revisionsToTry = [revision]; - if (revision != fallbackRevision) { - revisionsToTry.add(fallbackRevision); - } - bool sdkAvailable = false; - int index = 0; - do { - if (index < revisionsToTry.length) { - final String activeRevision = revisionsToTry[index++]; - if (activeRevision != revision && - revisionToFlutterSdkDir.containsKey(activeRevision)) { - sdkDir = revisionToFlutterSdkDir[activeRevision]!; - revisionToFlutterSdkDir[revision] = sdkDir; - sdkAvailable = true; - } else { - sdkDir = context.fileSystem.systemTempDirectory.createTempSync( - 'flutter_$activeRevision', - ); - result.sdkDirs[activeRevision] = sdkDir; - context.migrateLogger.printStatus('Cloning SDK $activeRevision'); - sdkAvailable = await context.migrateUtils.cloneFlutter( - activeRevision, - sdkDir.absolute.path, - ); - revisionToFlutterSdkDir[revision] = sdkDir; - } - } else { - // fallback to just using the modern target version of flutter. - sdkDir = targetFlutterDirectory; - revisionToFlutterSdkDir[revision] = sdkDir; - sdkAvailable = true; - } - } while (!sdkAvailable); - context.migrateLogger.printStatus( - 'Creating base app for $platforms with revision $revision.', - ); - final String newDirectoryPath = await context.migrateUtils - .createFromTemplates( - sdkDir.childDirectory('bin').absolute.path, - name: name, - androidLanguage: androidLanguage, - iosLanguage: iosLanguage, - outputDirectory: - result.generatedBaseTemplateDirectory!.absolute.path, - platforms: platforms, - ); - if (newDirectoryPath != result.generatedBaseTemplateDirectory?.path) { - result.generatedBaseTemplateDirectory = context.fileSystem.directory( - newDirectoryPath, - ); - } - // Determine merge type for each newly generated file. - final List generatedBaseFiles = result - .generatedBaseTemplateDirectory! - .listSync(recursive: true); - for (final FileSystemEntity entity in generatedBaseFiles) { - if (entity is! File) { - continue; - } - final File baseTemplateFile = entity.absolute; - final String localPath = getLocalPath( - baseTemplateFile.path, - result.generatedBaseTemplateDirectory!.absolute.path, - context.fileSystem, - ); - if (!result.mergeTypeMap.containsKey(localPath)) { - // Use two way merge when the base revision is the same as the target revision. - result.mergeTypeMap[localPath] = - revision == targetRevision - ? MergeType.twoWay - : MergeType.threeWay; - } - } - if (newDirectoryPath != result.generatedBaseTemplateDirectory?.path) { - result.generatedBaseTemplateDirectory = context.fileSystem.directory( - newDirectoryPath, - ); - break; // The create command is old and does not distinguish between platforms so it only needs to be called once. - } - } - } - } -} - -/// Represents a manifested flutter project that is the migration target. -/// -/// The files in this project are the version the migrate tool will try -/// to transform the existing files into. -class MigrateTargetFlutterProject extends MigrateFlutterProject { - MigrateTargetFlutterProject({ - required super.path, - required super.directory, - required super.name, - required super.androidLanguage, - required super.iosLanguage, - super.platformWhitelist, - }); - - /// Creates the base reference app based off of the migrate config in the .metadata file. - Future createProject( - MigrateContext context, - MigrateResult result, - String targetRevision, - Directory targetFlutterDirectory, - ) async { - if (path == null) { - // Create target - context.migrateLogger.printStatus( - 'Creating target app with revision $targetRevision.', - ); - context.migrateLogger.printIfVerbose('Creating target app.'); - await context.migrateUtils.createFromTemplates( - targetFlutterDirectory.childDirectory('bin').absolute.path, - name: name, - androidLanguage: androidLanguage, - iosLanguage: iosLanguage, - outputDirectory: result.generatedTargetTemplateDirectory!.absolute.path, - ); - } - } -} - -/// Parses the metadata of the flutter project, extracts, computes, and stores the -/// revisions that the migration should use to migrate between. -class MigrateRevisions { - MigrateRevisions({ - required MigrateContext context, - required String? baseRevision, - required bool allowFallbackBaseRevision, - required List platforms, - required FlutterToolsEnvironment environment, - }) { - _computeRevisions( - context, - baseRevision, - allowFallbackBaseRevision, - platforms, - environment, - ); - } - - late List revisionsList; - late Map> revisionToConfigs; - late String fallbackRevision; - late String targetRevision; - late String? metadataRevision; - late MigrateConfig config; - - void _computeRevisions( - MigrateContext context, - String? baseRevision, - bool allowFallbackBaseRevision, - List platforms, - FlutterToolsEnvironment environment, - ) { - final List components = - []; - for (final SupportedPlatform platform in platforms) { - components.add(platform.toFlutterProjectComponent()); - } - components.add(FlutterProjectComponent.root); - final FlutterProjectMetadata metadata = FlutterProjectMetadata( - context.flutterProject.directory.childFile('.metadata'), - context.migrateLogger.logger, - ); - config = metadata.migrateConfig; - - // We call populate in case MigrateConfig is empty. If it is filled, populate should not do anything. - config.populate( - projectDirectory: context.flutterProject.directory, - update: false, - logger: context.migrateLogger.logger, - ); - - metadataRevision = metadata.versionRevision; - if (environment.getString('FlutterVersion.frameworkRevision') == null) { - throwToolExit('Flutter framework revision was null'); - } - targetRevision = environment.getString('FlutterVersion.frameworkRevision')!; - String rootBaseRevision = ''; - revisionToConfigs = >{}; - final Set revisions = {}; - if (baseRevision == null) { - for (final MigratePlatformConfig platform - in config.platformConfigs.values) { - final String effectiveRevision = - platform.baseRevision == null - ? metadataRevision ?? - _getFallbackBaseRevision( - allowFallbackBaseRevision, - context.migrateLogger, - ) - : platform.baseRevision!; - if (!components.contains(platform.component)) { - continue; - } - if (platform.component == FlutterProjectComponent.root) { - rootBaseRevision = effectiveRevision; - } - revisions.add(effectiveRevision); - if (revisionToConfigs[effectiveRevision] == null) { - revisionToConfigs[effectiveRevision] = []; - } - revisionToConfigs[effectiveRevision]!.add(platform); - } - } else { - rootBaseRevision = baseRevision; - revisionToConfigs[baseRevision] = []; - for (final FlutterProjectComponent component in components) { - revisionToConfigs[baseRevision]!.add( - MigratePlatformConfig( - component: component, - baseRevision: baseRevision, - ), - ); - } - // revisionToConfigs[baseRevision]!.add( - // MigratePlatformConfig(platform: null, baseRevision: baseRevision)); - } - // Reorder such that the root revision is created first. - revisions.remove(rootBaseRevision); - revisionsList = List.from(revisions); - if (rootBaseRevision != '') { - revisionsList.insert(0, rootBaseRevision); - } - context.migrateLogger.printIfVerbose( - 'Potential base revisions: $revisionsList', - ); - fallbackRevision = _getFallbackBaseRevision(true, context.migrateLogger); - if (revisionsList.contains(fallbackRevision) && - baseRevision != fallbackRevision && - metadataRevision != fallbackRevision) { - context.migrateLogger.printStatus( - 'Using Flutter v1.0.0 ($fallbackRevision) as the base revision since a valid base revision could not be found in the .metadata file. This may result in more merge conflicts than normally expected.', - indent: 4, - ); - } - } -} diff --git a/packages/flutter_migrate/lib/src/custom_merge.dart b/packages/flutter_migrate/lib/src/custom_merge.dart deleted file mode 100644 index e0cb1175c6c..00000000000 --- a/packages/flutter_migrate/lib/src/custom_merge.dart +++ /dev/null @@ -1,120 +0,0 @@ -// Copyright 2013 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 'base/file_system.dart'; -import 'base/logger.dart'; -import 'flutter_project_metadata.dart'; -import 'utils.dart'; - -/// Handles the custom/manual merging of one file at `localPath`. -/// -/// The `merge` method should be overridden to implement custom merging. -abstract class CustomMerge { - CustomMerge({required this.logger, required this.localPath}); - - /// The local path (with the project root as the root directory) of the file to merge. - final String localPath; - final Logger logger; - - /// Called to perform a custom three way merge between the current, - /// base, and target files. - MergeResult merge(File current, File base, File target); -} - -/// Manually merges a flutter .metadata file. -/// -/// See `FlutterProjectMetadata`. -class MetadataCustomMerge extends CustomMerge { - MetadataCustomMerge({required super.logger}) : super(localPath: '.metadata'); - - @override - MergeResult merge(File current, File base, File target) { - final FlutterProjectMetadata result = computeMerge( - FlutterProjectMetadata(current, logger), - FlutterProjectMetadata(base, logger), - FlutterProjectMetadata(target, logger), - logger, - ); - return StringMergeResult.explicit( - mergedString: result.toString(), - hasConflict: false, - exitCode: 0, - localPath: localPath, - ); - } - - FlutterProjectMetadata computeMerge( - FlutterProjectMetadata current, - FlutterProjectMetadata base, - FlutterProjectMetadata target, - Logger logger, - ) { - // Prefer to update the version revision and channel to latest version. - final String? versionRevision = - target.versionRevision ?? - current.versionRevision ?? - base.versionRevision; - final String? versionChannel = - target.versionChannel ?? current.versionChannel ?? base.versionChannel; - // Prefer to leave the project type untouched as it is non-trivial to change project type. - final FlutterProjectType? projectType = - current.projectType ?? base.projectType ?? target.projectType; - final MigrateConfig migrateConfig = mergeMigrateConfig( - current.migrateConfig, - target.migrateConfig, - ); - final FlutterProjectMetadata output = FlutterProjectMetadata.explicit( - file: current.file, - versionRevision: versionRevision, - versionChannel: versionChannel, - projectType: projectType, - migrateConfig: migrateConfig, - logger: logger, - ); - return output; - } - - MigrateConfig mergeMigrateConfig( - MigrateConfig current, - MigrateConfig target, - ) { - // Create the superset of current and target platforms with baseRevision updated to be that of target. - final Map - projectComponentConfigs = - {}; - for (final MapEntry entry - in current.platformConfigs.entries) { - if (target.platformConfigs.containsKey(entry.key)) { - projectComponentConfigs[entry.key] = MigratePlatformConfig( - component: entry.value.component, - createRevision: entry.value.createRevision, - baseRevision: target.platformConfigs[entry.key]?.baseRevision, - ); - } else { - projectComponentConfigs[entry.key] = entry.value; - } - } - for (final MapEntry entry - in target.platformConfigs.entries) { - if (!projectComponentConfigs.containsKey(entry.key)) { - projectComponentConfigs[entry.key] = entry.value; - } - } - - // Ignore the base file list. - final List unmanagedFiles = List.from( - current.unmanagedFiles, - ); - for (final String path in target.unmanagedFiles) { - if (!unmanagedFiles.contains(path) && - !MigrateConfig.kDefaultUnmanagedFiles.contains(path)) { - unmanagedFiles.add(path); - } - } - return MigrateConfig( - platformConfigs: projectComponentConfigs, - unmanagedFiles: unmanagedFiles, - ); - } -} diff --git a/packages/flutter_migrate/lib/src/environment.dart b/packages/flutter_migrate/lib/src/environment.dart deleted file mode 100644 index 38801d50450..00000000000 --- a/packages/flutter_migrate/lib/src/environment.dart +++ /dev/null @@ -1,90 +0,0 @@ -// Copyright 2013 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 'dart:convert'; -import 'dart:io'; - -import 'package:process/process.dart'; - -import 'base/common.dart'; -import 'base/logger.dart'; - -/// Polls the flutter tool for details about the environment and project and exposes it as -/// a mapping of String keys to values. -/// -/// This class is based on the `flutter analyze --suggestions --machine` flutter_tools command -/// which dumps various variables as JSON. -class FlutterToolsEnvironment { - /// Constructs a tools environment out of a mapping of Strings to Object values. - /// - /// Each key is the String URI-style description of a value in the Flutter tool - /// and is mapped to a String or boolean value. The mapping should align with the - /// JSON output of `flutter analyze --suggestions --machine`. - FlutterToolsEnvironment({required Map mapping}) - : _mapping = mapping; - - /// Creates a FlutterToolsEnvironment instance by calling `flutter analyze --suggestions --machine` - /// and parsing its output. - static Future initializeFlutterToolsEnvironment( - ProcessManager processManager, - Logger logger, - ) async { - final ProcessResult result = await processManager.run([ - 'flutter', - 'analyze', - '--suggestions', - '--machine', - ]); - if (result.exitCode != 0) { - if ((result.stderr as String).contains( - 'The "--machine" flag is only valid with the "--version" flag.', - )) { - logger.printError( - 'The migrate tool is only compatible with flutter tools 3.4.0 or newer (git hash: 21861423f25ad03c2fdb33854b53f195bc117cb3).', - ); - } - throwToolExit( - 'Flutter tool exited while running `flutter analyze --suggestions --machine` with: ${result.stderr}', - ); - } - String commandOutput = (result.stdout as String).trim(); - Map mapping = {}; - // minimally validate basic JSON format and trim away any accidental logging before. - if (commandOutput.contains(RegExp(r'[\s\S]*{[\s\S]+}[\s\S]*'))) { - commandOutput = commandOutput.substring(commandOutput.indexOf('{')); - mapping = - jsonDecode(commandOutput.replaceAll(r'\', r'\\')) - as Map; - } - return FlutterToolsEnvironment(mapping: mapping); - } - - final Map _mapping; - - Object? operator [](String key) { - if (_mapping.containsKey(key)) { - return _mapping[key]; - } - return null; - } - - /// Returns the String stored at the key and null if - /// the key does not exist or is not a String. - String? getString(String key) { - final Object? value = _mapping[key]; - return value is String? ? value : null; - } - - /// Returns the bool stored at the key and null if - /// the key does not exist or is not a bool. - bool? getBool(String key) { - final Object? value = _mapping[key]; - return value is bool? ? value : null; - } - - bool containsKey(String key) { - return _mapping.containsKey(key); - } -} diff --git a/packages/flutter_migrate/lib/src/flutter_project_metadata.dart b/packages/flutter_migrate/lib/src/flutter_project_metadata.dart deleted file mode 100644 index 3287a59cf94..00000000000 --- a/packages/flutter_migrate/lib/src/flutter_project_metadata.dart +++ /dev/null @@ -1,429 +0,0 @@ -// Copyright 2013 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 'package:yaml/yaml.dart'; - -import 'base/common.dart'; -import 'base/file_system.dart'; -import 'base/logger.dart'; -import 'base/project.dart'; - -/// Represents subdirectories of the flutter project that can be independently created. -/// -/// This includes each supported platform as well as a component that represents the -/// root directory of the project. -enum FlutterProjectComponent { - root, - android, - ios, - linux, - macos, - web, - windows, - fuchsia, -} - -extension SupportedPlatformExtension on SupportedPlatform { - FlutterProjectComponent toFlutterProjectComponent() { - final String platformName = toString().split('.').last; - return FlutterProjectComponent.values.firstWhere( - (FlutterProjectComponent e) => - e.toString() == 'FlutterProjectComponent.$platformName', - ); - } -} - -extension FlutterProjectComponentExtension on FlutterProjectComponent { - SupportedPlatform? toSupportedPlatform() { - final String platformName = toString().split('.').last; - if (platformName == 'root') { - return null; - } - return SupportedPlatform.values.firstWhere( - (SupportedPlatform e) => - e.toString() == 'SupportedPlatform.$platformName', - ); - } -} - -enum FlutterProjectType { - /// This is the default project with the user-managed host code. - /// It is different than the "module" template in that it exposes and doesn't - /// manage the platform code. - app, - - /// A List/Detail app template that follows community best practices. - skeleton, - - /// The is a project that has managed platform host code. It is an application with - /// ephemeral .ios and .android directories that can be updated automatically. - module, - - /// This is a Flutter Dart package project. It doesn't have any native - /// components, only Dart. - package, - - /// This is a native plugin project. - plugin, - - /// This is an FFI native plugin project. - ffiPlugin, -} - -String flutterProjectTypeToString(FlutterProjectType? type) { - if (type == null) { - return ''; - } - if (type == FlutterProjectType.ffiPlugin) { - return 'plugin_ffi'; - } - return getEnumName(type); -} - -FlutterProjectType? stringToProjectType(String value) { - FlutterProjectType? result; - for (final FlutterProjectType type in FlutterProjectType.values) { - if (value == flutterProjectTypeToString(type)) { - result = type; - break; - } - } - return result; -} - -/// Verifies the expected yaml keys are present in the file. -bool _validateMetadataMap( - YamlMap map, - Map validations, - Logger logger, -) { - bool isValid = true; - for (final MapEntry entry in validations.entries) { - if (!map.keys.contains(entry.key)) { - isValid = false; - logger.printTrace('The key `${entry.key}` was not found'); - break; - } - final Object? metadataValue = map[entry.key]; - if (metadataValue.runtimeType != entry.value) { - isValid = false; - logger.printTrace( - 'The value of key `${entry.key}` in .metadata was expected to be ${entry.value} but was ${metadataValue.runtimeType}', - ); - break; - } - } - return isValid; -} - -/// A wrapper around the `.metadata` file. -class FlutterProjectMetadata { - /// Creates a MigrateConfig by parsing an existing .migrate_config yaml file. - FlutterProjectMetadata(this.file, Logger logger) - : _logger = logger, - migrateConfig = MigrateConfig() { - if (!file.existsSync()) { - _logger.printTrace('No .metadata file found at ${file.path}.'); - // Create a default empty metadata. - return; - } - Object? yamlRoot; - try { - yamlRoot = loadYaml(file.readAsStringSync()); - } on YamlException { - // Handled in _validate below. - } - if (yamlRoot is! YamlMap) { - _logger.printTrace( - '.metadata file at ${file.path} was empty or malformed.', - ); - return; - } - if (_validateMetadataMap(yamlRoot, { - 'version': YamlMap, - }, _logger)) { - final Object? versionYamlMap = yamlRoot['version']; - if (versionYamlMap is YamlMap && - _validateMetadataMap(versionYamlMap, { - 'revision': String, - 'channel': String, - }, _logger)) { - _versionRevision = versionYamlMap['revision'] as String?; - _versionChannel = versionYamlMap['channel'] as String?; - } - } - if (_validateMetadataMap(yamlRoot, { - 'project_type': String, - }, _logger)) { - _projectType = stringToProjectType(yamlRoot['project_type'] as String); - } - final Object? migrationYaml = yamlRoot['migration']; - if (migrationYaml is YamlMap) { - migrateConfig.parseYaml(migrationYaml, _logger); - } - } - - /// Creates a FlutterProjectMetadata by explicitly providing all values. - FlutterProjectMetadata.explicit({ - required this.file, - required String? versionRevision, - required String? versionChannel, - required FlutterProjectType? projectType, - required this.migrateConfig, - required Logger logger, - }) : _logger = logger, - _versionChannel = versionChannel, - _versionRevision = versionRevision, - _projectType = projectType; - - /// The name of the config file. - static const String kFileName = '.metadata'; - - String? _versionRevision; - String? get versionRevision => _versionRevision; - - String? _versionChannel; - String? get versionChannel => _versionChannel; - - FlutterProjectType? _projectType; - FlutterProjectType? get projectType => _projectType; - - /// Metadata and configuration for the migrate command. - MigrateConfig migrateConfig; - - final Logger _logger; - - final File file; - - /// Writes the .migrate_config file in the provided project directory's platform subdirectory. - /// - /// We write the file manually instead of with a template because this - /// needs to be able to write the .migrate_config file into legacy apps. - void writeFile({File? outputFile}) { - outputFile = outputFile ?? file; - outputFile - ..createSync(recursive: true) - ..writeAsStringSync(toString(), flush: true); - } - - @override - String toString() { - return ''' -# This file tracks properties of this Flutter project. -# Used by Flutter tool to assess capabilities and perform upgrades etc. -# -# This file should be version controlled. - -version: - revision: $_versionRevision - channel: $_versionChannel - -project_type: ${flutterProjectTypeToString(projectType)} -${migrateConfig.getOutputFileString()}'''; - } - - void populate({ - List? platforms, - required Directory projectDirectory, - String? currentRevision, - String? createRevision, - bool create = true, - bool update = true, - required Logger logger, - }) { - migrateConfig.populate( - platforms: platforms, - projectDirectory: projectDirectory, - currentRevision: currentRevision, - createRevision: createRevision, - create: create, - update: update, - logger: logger, - ); - } - - /// Finds the fallback revision to use when no base revision is found in the migrate config. - String getFallbackBaseRevision(Logger logger, String frameworkRevision) { - // Use the .metadata file if it exists. - if (versionRevision != null) { - return versionRevision!; - } - return frameworkRevision; - } -} - -/// Represents the migrate command metadata section of a .metadata file. -/// -/// This file tracks the flutter sdk git hashes of the last successful migration ('base') and -/// the version the project was created with. -/// -/// Each platform tracks a different set of revisions because flutter create can be -/// used to add support for new platforms, so the base and create revision may not always be the same. -class MigrateConfig { - MigrateConfig({ - Map? platformConfigs, - this.unmanagedFiles = kDefaultUnmanagedFiles, - }) : platformConfigs = - platformConfigs ?? - {}; - - /// A mapping of the files that are unmanaged by defult for each platform. - static const List kDefaultUnmanagedFiles = [ - 'lib/main.dart', - 'ios/Runner.xcodeproj/project.pbxproj', - ]; - - /// The metadata for each platform supported by the project. - final Map platformConfigs; - - /// A list of paths relative to this file the migrate tool should ignore. - /// - /// These files are typically user-owned files that should not be changed. - List unmanagedFiles; - - bool get isEmpty => - platformConfigs.isEmpty && - (unmanagedFiles.isEmpty || unmanagedFiles == kDefaultUnmanagedFiles); - - /// Parses the project for all supported platforms and populates the [MigrateConfig] - /// to reflect the project. - void populate({ - List? platforms, - required Directory projectDirectory, - String? currentRevision, - String? createRevision, - bool create = true, - bool update = true, - required Logger logger, - }) { - final FlutterProject flutterProject = FlutterProject(projectDirectory); - platforms ??= flutterProject.getSupportedPlatforms(); - - final List components = - []; - for (final SupportedPlatform platform in platforms) { - components.add(platform.toFlutterProjectComponent()); - } - components.add(FlutterProjectComponent.root); - for (final FlutterProjectComponent component in components) { - if (platformConfigs.containsKey(component)) { - if (update) { - platformConfigs[component]!.baseRevision = currentRevision; - } - } else { - if (create) { - platformConfigs[component] = MigratePlatformConfig( - component: component, - createRevision: createRevision, - baseRevision: currentRevision, - ); - } - } - } - } - - /// Returns the string that should be written to the .metadata file. - String getOutputFileString() { - String unmanagedFilesString = ''; - for (final String path in unmanagedFiles) { - unmanagedFilesString += "\n - '$path'"; - } - - String platformsString = ''; - for (final MapEntry entry - in platformConfigs.entries) { - platformsString += - '\n - platform: ${entry.key.toString().split('.').last}\n create_revision: ${entry.value.createRevision == null ? 'null' : "${entry.value.createRevision}"}\n base_revision: ${entry.value.baseRevision == null ? 'null' : "${entry.value.baseRevision}"}'; - } - - return isEmpty - ? '' - : ''' - -# Tracks metadata for the flutter migrate command -migration: - platforms:$platformsString - - # User provided section - - # List of Local paths (relative to this file) that should be - # ignored by the migrate tool. - # - # Files that are not part of the templates will be ignored by default. - unmanaged_files:$unmanagedFilesString -'''; - } - - /// Parses and validates the `migration` section of the .metadata file. - void parseYaml(YamlMap map, Logger logger) { - final Object? platformsYaml = map['platforms']; - if (_validateMetadataMap(map, { - 'platforms': YamlList, - }, logger)) { - if (platformsYaml is YamlList && platformsYaml.isNotEmpty) { - for (final YamlMap platformYamlMap - in platformsYaml.whereType()) { - if (_validateMetadataMap(platformYamlMap, { - 'platform': String, - 'create_revision': String, - 'base_revision': String, - }, logger)) { - final FlutterProjectComponent - component = FlutterProjectComponent.values.firstWhere( - (FlutterProjectComponent val) => - val.toString() == - 'FlutterProjectComponent.${platformYamlMap['platform'] as String}', - ); - platformConfigs[component] = MigratePlatformConfig( - component: component, - createRevision: platformYamlMap['create_revision'] as String?, - baseRevision: platformYamlMap['base_revision'] as String?, - ); - } else { - // malformed platform entry - continue; - } - } - } - } - if (_validateMetadataMap(map, { - 'unmanaged_files': YamlList, - }, logger)) { - final Object? unmanagedFilesYaml = map['unmanaged_files']; - if (unmanagedFilesYaml is YamlList && unmanagedFilesYaml.isNotEmpty) { - unmanagedFiles = List.from( - unmanagedFilesYaml.value.cast(), - ); - } - } - } -} - -/// Holds the revisions for a single platform for use by the flutter migrate command. -class MigratePlatformConfig { - MigratePlatformConfig({ - required this.component, - this.createRevision, - this.baseRevision, - }); - - /// The platform this config describes. - FlutterProjectComponent component; - - /// The Flutter SDK revision this platform was created by. - /// - /// Null if the initial create git revision is unknown. - final String? createRevision; - - /// The Flutter SDK revision this platform was last migrated by. - /// - /// Null if the project was never migrated or the revision is unknown. - String? baseRevision; - - bool equals(MigratePlatformConfig other) { - return component == other.component && - createRevision == other.createRevision && - baseRevision == other.baseRevision; - } -} diff --git a/packages/flutter_migrate/lib/src/manifest.dart b/packages/flutter_migrate/lib/src/manifest.dart deleted file mode 100644 index 4b36cd0a5a5..00000000000 --- a/packages/flutter_migrate/lib/src/manifest.dart +++ /dev/null @@ -1,296 +0,0 @@ -// Copyright 2013 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 'package:yaml/yaml.dart'; - -import 'base/file_system.dart'; -import 'base/logger.dart'; -import 'base/terminal.dart'; - -import 'result.dart'; -import 'utils.dart'; - -const String _kMergedFilesKey = 'merged_files'; -const String _kConflictFilesKey = 'conflict_files'; -const String _kAddedFilesKey = 'added_files'; -const String _kDeletedFilesKey = 'deleted_files'; - -/// Represents the manifest file that tracks the contents of the current -/// migration working directory. -/// -/// This manifest file is created with the MigrateResult of a computeMigration run. -class MigrateManifest { - /// Creates a new manifest from a MigrateResult. - MigrateManifest({required this.migrateRootDir, required this.migrateResult}); - - /// Parses an existing migrate manifest. - MigrateManifest.fromFile(File manifestFile) - : migrateResult = MigrateResult.empty(), - migrateRootDir = manifestFile.parent { - final Object? yamlContents = loadYaml(manifestFile.readAsStringSync()); - if (yamlContents is! YamlMap) { - throw Exception( - 'Invalid .migrate_manifest file in the migrate working directory. File is not a Yaml map.', - ); - } - final YamlMap map = yamlContents; - bool valid = - map.containsKey(_kMergedFilesKey) && - map.containsKey(_kConflictFilesKey) && - map.containsKey(_kAddedFilesKey) && - map.containsKey(_kDeletedFilesKey); - if (!valid) { - throw Exception( - 'Invalid .migrate_manifest file in the migrate working directory. File is missing an entry.', - ); - } - final Object? mergedFilesYaml = map[_kMergedFilesKey]; - final Object? conflictFilesYaml = map[_kConflictFilesKey]; - final Object? addedFilesYaml = map[_kAddedFilesKey]; - final Object? deletedFilesYaml = map[_kDeletedFilesKey]; - valid = valid && (mergedFilesYaml is YamlList || mergedFilesYaml == null); - valid = - valid && (conflictFilesYaml is YamlList || conflictFilesYaml == null); - valid = valid && (addedFilesYaml is YamlList || addedFilesYaml == null); - valid = valid && (deletedFilesYaml is YamlList || deletedFilesYaml == null); - if (!valid) { - throw Exception( - 'Invalid .migrate_manifest file in the migrate working directory. Entry is not a Yaml list.', - ); - } - if (mergedFilesYaml != null) { - for (final Object? localPath in mergedFilesYaml as YamlList) { - if (localPath is String) { - // We can fill the maps with partially dummy data as not all properties are used by the manifest. - migrateResult.mergeResults.add( - StringMergeResult.explicit( - mergedString: '', - hasConflict: false, - exitCode: 0, - localPath: localPath, - ), - ); - } - } - } - if (conflictFilesYaml != null) { - for (final Object? localPath in conflictFilesYaml as YamlList) { - if (localPath is String) { - migrateResult.mergeResults.add( - StringMergeResult.explicit( - mergedString: '', - hasConflict: true, - exitCode: 1, - localPath: localPath, - ), - ); - } - } - } - if (addedFilesYaml != null) { - for (final Object? localPath in addedFilesYaml as YamlList) { - if (localPath is String) { - migrateResult.addedFiles.add( - FilePendingMigration( - localPath, - migrateRootDir.childFile(localPath), - ), - ); - } - } - } - if (deletedFilesYaml != null) { - for (final Object? localPath in deletedFilesYaml as YamlList) { - if (localPath is String) { - migrateResult.deletedFiles.add( - FilePendingMigration( - localPath, - migrateRootDir.childFile(localPath), - ), - ); - } - } - } - } - - final Directory migrateRootDir; - final MigrateResult migrateResult; - - /// A list of local paths of files that require conflict resolution. - List get conflictFiles { - final List output = []; - for (final MergeResult result in migrateResult.mergeResults) { - if (result.hasConflict) { - output.add(result.localPath); - } - } - return output; - } - - /// A list of local paths of files that require conflict resolution. - List remainingConflictFiles(Directory workingDir) { - final List output = []; - for (final String localPath in conflictFiles) { - if (!_conflictsResolved( - workingDir.childFile(localPath).readAsStringSync(), - )) { - output.add(localPath); - } - } - return output; - } - - // A list of local paths of files that had conflicts and are now fully resolved. - List resolvedConflictFiles(Directory workingDir) { - final List output = []; - for (final String localPath in conflictFiles) { - if (_conflictsResolved( - workingDir.childFile(localPath).readAsStringSync(), - )) { - output.add(localPath); - } - } - return output; - } - - /// A list of local paths of files that were automatically merged. - List get mergedFiles { - final List output = []; - for (final MergeResult result in migrateResult.mergeResults) { - if (!result.hasConflict) { - output.add(result.localPath); - } - } - return output; - } - - /// A list of local paths of files that were newly added. - List get addedFiles { - final List output = []; - for (final FilePendingMigration file in migrateResult.addedFiles) { - output.add(file.localPath); - } - return output; - } - - /// A list of local paths of files that are marked for deletion. - List get deletedFiles { - final List output = []; - for (final FilePendingMigration file in migrateResult.deletedFiles) { - output.add(file.localPath); - } - return output; - } - - /// Returns the manifest file given a migration workind directory. - static File getManifestFileFromDirectory(Directory workingDir) { - return workingDir.childFile('.migrate_manifest'); - } - - /// Writes the manifest yaml file in the working directory. - void writeFile() { - final StringBuffer mergedFileManifestContents = StringBuffer(); - final StringBuffer conflictFilesManifestContents = StringBuffer(); - for (final MergeResult result in migrateResult.mergeResults) { - if (result.hasConflict) { - conflictFilesManifestContents.write(' - ${result.localPath}\n'); - } else { - mergedFileManifestContents.write(' - ${result.localPath}\n'); - } - } - - final StringBuffer newFileManifestContents = StringBuffer(); - for (final String localPath in addedFiles) { - newFileManifestContents.write(' - $localPath\n'); - } - - final StringBuffer deletedFileManifestContents = StringBuffer(); - for (final String localPath in deletedFiles) { - deletedFileManifestContents.write(' - $localPath\n'); - } - - final String migrateManifestContents = - 'merged_files:\n${mergedFileManifestContents}conflict_files:\n${conflictFilesManifestContents}added_files:\n${newFileManifestContents}deleted_files:\n$deletedFileManifestContents'; - final File migrateManifest = getManifestFileFromDirectory(migrateRootDir); - migrateManifest.createSync(recursive: true); - migrateManifest.writeAsStringSync(migrateManifestContents, flush: true); - } -} - -/// Returns true if the file does not contain any git conflict markers. -bool _conflictsResolved(String contents) { - if (contents.contains('>>>>>>>') && - contents.contains('=======') && - contents.contains('<<<<<<<')) { - return false; - } - return true; -} - -/// Returns true if the migration working directory has all conflicts resolved and prints the migration status. -/// -/// The migration status printout lists all added, deleted, merged, and conflicted files. -bool checkAndPrintMigrateStatus( - MigrateManifest manifest, - Directory workingDir, { - bool warnConflict = false, - Logger? logger, -}) { - final StringBuffer printout = StringBuffer(); - final StringBuffer redPrintout = StringBuffer(); - bool result = true; - final List remainingConflicts = []; - final List mergedFiles = []; - for (final String localPath in manifest.conflictFiles) { - if (!_conflictsResolved( - workingDir.childFile(localPath).readAsStringSync(), - )) { - remainingConflicts.add(localPath); - } else { - mergedFiles.add(localPath); - } - } - - mergedFiles.addAll(manifest.mergedFiles); - if (manifest.addedFiles.isNotEmpty) { - printout.write('Added files:\n'); - for (final String localPath in manifest.addedFiles) { - printout.write(' - $localPath\n'); - } - } - if (manifest.deletedFiles.isNotEmpty) { - printout.write('Deleted files:\n'); - for (final String localPath in manifest.deletedFiles) { - printout.write(' - $localPath\n'); - } - } - if (mergedFiles.isNotEmpty) { - printout.write('Modified files:\n'); - for (final String localPath in mergedFiles) { - printout.write(' - $localPath\n'); - } - } - if (remainingConflicts.isNotEmpty) { - if (warnConflict) { - printout.write( - 'Unable to apply migration. The following files in the migration working directory still have unresolved conflicts:', - ); - } else { - printout.write('Merge conflicted files:'); - } - for (final String localPath in remainingConflicts) { - redPrintout.write(' - $localPath\n'); - } - result = false; - } - if (logger != null) { - logger.printStatus(printout.toString()); - logger.printStatus( - redPrintout.toString(), - color: TerminalColor.red, - newline: false, - ); - } - return result; -} diff --git a/packages/flutter_migrate/lib/src/migrate_logger.dart b/packages/flutter_migrate/lib/src/migrate_logger.dart deleted file mode 100644 index 9b520dbdf13..00000000000 --- a/packages/flutter_migrate/lib/src/migrate_logger.dart +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright 2013 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 'base/logger.dart'; -import 'base/terminal.dart'; - -const int kDefaultStatusIndent = 2; - -class MigrateLogger { - MigrateLogger({ - required this.logger, - this.verbose = false, - this.silent = false, - }) : status = logger.startSpinner(); - - final Logger logger; - // We keep a spinner going and print periodic progress messages - // to assure the developer that the command is still working due to - // the long expected runtime. - Status status; - final bool verbose; - final bool silent; - - void start() { - status = logger.startSpinner(); - } - - void stop() { - status.stop(); - } - - static final Map _stepStringsMap = { - 'start': 'Computing migration - this command may take a while to complete.', - 'revisions': 'Obtaining revisions.', - 'unmanaged': 'Parsing unmanagedFiles.', - 'generating_base': 'Generating base reference app.', - 'diff': 'Diffing base and target reference app.', - 'new_files': 'Finding newly added files', - 'merging': 'Merging changes with existing project.', - 'cleaning': 'Cleaning up temp directories.', - 'modified_count': - 'Could not determine base revision, falling back on `v1.0.0`, revision 5391447fae6209bb21a89e6a5a6583cac1af9b4b', - }; - - void printStatus(String message, {int indent = kDefaultStatusIndent}) { - if (silent) { - return; - } - status.pause(); - logger.printStatus(message, indent: indent, color: TerminalColor.grey); - status.resume(); - } - - void printError(String message, {int indent = 0}) { - status.pause(); - logger.printError(message, indent: indent); - status.resume(); - } - - void logStep(String key) { - if (!_stepStringsMap.containsKey(key)) { - return; - } - printStatus(_stepStringsMap[key]!); - } - - void printIfVerbose(String message, {int indent = kDefaultStatusIndent}) { - if (verbose) { - printStatus(message, indent: indent); - } - } -} diff --git a/packages/flutter_migrate/lib/src/result.dart b/packages/flutter_migrate/lib/src/result.dart deleted file mode 100644 index a552a746a87..00000000000 --- a/packages/flutter_migrate/lib/src/result.dart +++ /dev/null @@ -1,86 +0,0 @@ -// Copyright 2013 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 'base/file_system.dart'; - -import 'utils.dart'; - -/// Data class that holds all results and generated directories from a computeMigration run. -/// -/// mergeResults, addedFiles, and deletedFiles includes the sets of files to be migrated while -/// the other members track the temporary sdk and generated app directories created by the tool. -/// -/// The compute function does not clean up the temp directories, as the directories may be reused, -/// so this must be done manually afterwards. -class MigrateResult { - /// Explicitly initialize the MigrateResult. - MigrateResult({ - required this.mergeResults, - required this.addedFiles, - required this.deletedFiles, - required this.tempDirectories, - required this.sdkDirs, - required this.mergeTypeMap, - required this.diffMap, - this.generatedBaseTemplateDirectory, - this.generatedTargetTemplateDirectory, - }); - - /// Creates a MigrateResult with all empty members. - MigrateResult.empty() - : mergeResults = [], - addedFiles = [], - deletedFiles = [], - tempDirectories = [], - mergeTypeMap = {}, - diffMap = {}, - sdkDirs = {}; - - /// The results of merging existing files with the target files. - final List mergeResults; - - /// Tracks the files that are to be newly added to the project. - final List addedFiles; - - /// Tracks the files that are to be deleted from the project. - final List deletedFiles; - - /// Tracks the temporary directories created during the migrate compute process. - final List tempDirectories; - - /// Mapping between the local path of a file and the type of merge that should be used. - final Map mergeTypeMap; - - /// Mapping between the local path of a file and the diff between the base and target - /// versions of the file. - final Map diffMap; - - /// The root directory of the base app. - Directory? generatedBaseTemplateDirectory; - - /// The root directory of the target app. - Directory? generatedTargetTemplateDirectory; - - /// The root directories of the Flutter SDK for each revision. - Map sdkDirs; -} - -/// Defines available merge techniques. -enum MergeType { - /// A standard three-way merge. - threeWay, - - /// A two way merge that ignores the base version of the file. - twoWay, - - /// A `CustomMerge` manually handles the merge. - custom, -} - -/// Stores a file that has been marked for migration and metadata about the file. -class FilePendingMigration { - FilePendingMigration(this.localPath, this.file); - String localPath; - File file; -} diff --git a/packages/flutter_migrate/lib/src/update_locks.dart b/packages/flutter_migrate/lib/src/update_locks.dart deleted file mode 100644 index 5c6022bca81..00000000000 --- a/packages/flutter_migrate/lib/src/update_locks.dart +++ /dev/null @@ -1,133 +0,0 @@ -// Copyright 2013 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 'base/file_system.dart'; -import 'base/logger.dart'; -import 'base/project.dart'; -import 'base/terminal.dart'; - -import 'utils.dart'; - -/// Checks if the project uses pubspec dependency locking and prompts if -/// the pub upgrade should be run. -Future updatePubspecDependencies( - FlutterProject flutterProject, - MigrateUtils migrateUtils, - Logger logger, - Terminal terminal, { - bool force = false, -}) async { - final File pubspecFile = flutterProject.directory.childFile('pubspec.yaml'); - if (!pubspecFile.existsSync()) { - return; - } - if (!pubspecFile.readAsStringSync().contains( - '# THIS LINE IS AUTOGENERATED', - )) { - return; - } - logger.printStatus('\nDart dependency locking detected in pubspec.yaml.'); - terminal.usesTerminalUi = true; - String selection = 'y'; - if (!force) { - selection = await terminal.promptForCharInput( - ['y', 'n'], - logger: logger, - prompt: - 'Do you want the tool to run `flutter pub upgrade --major-versions`? (y)es, (n)o', - defaultChoiceIndex: 1, - ); - } - if (selection == 'y') { - // Runs `flutter pub upgrade --major-versions` - await migrateUtils.flutterPubUpgrade(flutterProject.directory.path); - } -} - -/// Checks if gradle dependency locking is used and prompts the developer to -/// remove and back up the gradle dependency lockfile. -Future updateGradleDependencyLocking( - FlutterProject flutterProject, - MigrateUtils migrateUtils, - Logger logger, - Terminal terminal, - bool verbose, - FileSystem fileSystem, { - bool force = false, -}) async { - final Directory androidDir = flutterProject.directory.childDirectory( - 'android', - ); - if (!androidDir.existsSync()) { - return; - } - final List androidFiles = androidDir.listSync(); - final List lockfiles = []; - final List backedUpFilePaths = []; - for (final FileSystemEntity entity in androidFiles) { - if (entity is! File) { - continue; - } - final File file = entity.absolute; - // Don't re-handle backed up lockfiles. - if (file.path.contains('_backup_')) { - continue; - } - try { - // lockfiles generated by gradle start with this prefix. - if (file.readAsStringSync().startsWith( - '# This is a Gradle generated file for dependency locking.\n# ' - 'Manual edits can break the build and are not advised.\n# This ' - 'file is expected to be part of source control.', - )) { - lockfiles.add(file); - } - } on FileSystemException { - if (verbose) { - logger.printStatus('Unable to check ${file.path}'); - } - } - } - if (lockfiles.isNotEmpty) { - logger.printStatus('\nGradle dependency locking detected.'); - logger.printStatus( - 'Flutter can backup the lockfiles and regenerate updated ' - 'lockfiles.', - ); - terminal.usesTerminalUi = true; - String selection = 'y'; - if (!force) { - selection = await terminal.promptForCharInput( - ['y', 'n'], - logger: logger, - prompt: - 'Do you want the tool to update locked dependencies? (y)es, (n)o', - defaultChoiceIndex: 1, - ); - } - if (selection == 'y') { - for (final File file in lockfiles) { - int counter = 0; - while (true) { - final String newPath = '${file.absolute.path}_backup_$counter'; - if (!fileSystem.file(newPath).existsSync()) { - file.renameSync(newPath); - backedUpFilePaths.add(newPath); - break; - } else { - counter++; - } - } - } - // Runs `./gradlew tasks`in the project's android directory. - await migrateUtils.gradlewTasks( - flutterProject.directory.childDirectory('android').path, - ); - logger.printStatus('Old lockfiles renamed to:'); - for (final String path in backedUpFilePaths) { - logger.printStatus(path, color: TerminalColor.grey, indent: 2); - } - } - } -} diff --git a/packages/flutter_migrate/lib/src/utils.dart b/packages/flutter_migrate/lib/src/utils.dart deleted file mode 100644 index 0e6dfed8ce7..00000000000 --- a/packages/flutter_migrate/lib/src/utils.dart +++ /dev/null @@ -1,514 +0,0 @@ -// Copyright 2013 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 'dart:io'; -import 'dart:typed_data'; - -import 'package:process/process.dart'; - -import 'base/common.dart'; -import 'base/file_system.dart'; -import 'base/logger.dart'; -import 'base/terminal.dart'; - -/// The default name of the migrate working directory used to stage proposed changes. -const String kDefaultMigrateStagingDirectoryName = 'migrate_staging_dir'; - -/// Utility class that contains methods that wrap git and other shell commands. -class MigrateUtils { - MigrateUtils({ - required Logger logger, - required FileSystem fileSystem, - required ProcessManager processManager, - }) : _processManager = processManager, - _logger = logger, - _fileSystem = fileSystem; - - final Logger _logger; - final FileSystem _fileSystem; - final ProcessManager _processManager; - - Future _runCommand( - List command, { - String? workingDirectory, - bool runInShell = false, - }) { - return _processManager.run( - command, - workingDirectory: workingDirectory, - runInShell: runInShell, - ); - } - - /// Calls `git diff` on two files and returns the diff as a DiffResult. - Future diffFiles(File one, File two) async { - if (one.existsSync() && !two.existsSync()) { - return DiffResult(diffType: DiffType.deletion); - } - if (!one.existsSync() && two.existsSync()) { - return DiffResult(diffType: DiffType.addition); - } - final List cmdArgs = [ - 'git', - 'diff', - '--no-index', - one.absolute.path, - two.absolute.path, - ]; - final ProcessResult result = await _runCommand(cmdArgs); - - // diff exits with 1 if diffs are found. - checkForErrors( - result, - allowedExitCodes: [0, 1], - commandDescription: 'git ${cmdArgs.join(' ')}', - ); - return DiffResult( - diffType: DiffType.command, - diff: result.stdout as String, - exitCode: result.exitCode, - ); - } - - /// Clones a copy of the flutter repo into the destination directory. Returns false if unsuccessful. - Future cloneFlutter(String revision, String destination) async { - // Use https url instead of ssh to avoid need to setup ssh on git. - List cmdArgs = [ - 'git', - 'clone', - '--filter=blob:none', - 'https://github.com/flutter/flutter.git', - destination, - ]; - ProcessResult result = await _runCommand(cmdArgs); - checkForErrors(result, commandDescription: cmdArgs.join(' ')); - - cmdArgs.clear(); - cmdArgs = ['git', 'reset', '--hard', revision]; - result = await _runCommand(cmdArgs, workingDirectory: destination); - if (!checkForErrors( - result, - commandDescription: cmdArgs.join(' '), - exit: false, - )) { - return false; - } - return true; - } - - /// Calls `flutter create` as a re-entrant command. - Future createFromTemplates( - String flutterBinPath, { - required String name, - bool legacyNameParameter = false, - required String androidLanguage, - required String iosLanguage, - required String outputDirectory, - String? createVersion, - List platforms = const [], - int iterationsAllowed = 5, - }) async { - // Limit the number of iterations this command is allowed to attempt to prevent infinite looping. - if (iterationsAllowed <= 0) { - _logger.printError( - 'Unable to `flutter create` with the version of flutter at $flutterBinPath', - ); - return outputDirectory; - } - - final List cmdArgs = ['$flutterBinPath/flutter', 'create']; - if (!legacyNameParameter) { - cmdArgs.add('--project-name=$name'); - } - cmdArgs.add('--android-language=$androidLanguage'); - cmdArgs.add('--ios-language=$iosLanguage'); - if (platforms.isNotEmpty) { - String platformsArg = '--platforms='; - for (int i = 0; i < platforms.length; i++) { - if (i > 0) { - platformsArg += ','; - } - platformsArg += platforms[i]; - } - cmdArgs.add(platformsArg); - } - cmdArgs.add('--no-pub'); - if (legacyNameParameter) { - cmdArgs.add(name); - } else { - cmdArgs.add(outputDirectory); - } - final ProcessResult result = await _runCommand( - cmdArgs, - workingDirectory: outputDirectory, - ); - final String error = result.stderr as String; - - // Catch errors due to parameters not existing. - - // Old versions of the tool does not include the platforms option. - if (error.contains('Could not find an option named "platforms".')) { - return createFromTemplates( - flutterBinPath, - name: name, - legacyNameParameter: legacyNameParameter, - androidLanguage: androidLanguage, - iosLanguage: iosLanguage, - outputDirectory: outputDirectory, - iterationsAllowed: iterationsAllowed--, - ); - } - // Old versions of the tool does not include the project-name option. - if ((result.stderr as String).contains( - 'Could not find an option named "project-name".', - )) { - return createFromTemplates( - flutterBinPath, - name: name, - legacyNameParameter: true, - androidLanguage: androidLanguage, - iosLanguage: iosLanguage, - outputDirectory: outputDirectory, - platforms: platforms, - iterationsAllowed: iterationsAllowed--, - ); - } - if (error.contains('Multiple output directories specified.')) { - if (error.contains('Try moving --platforms')) { - return createFromTemplates( - flutterBinPath, - name: name, - legacyNameParameter: legacyNameParameter, - androidLanguage: androidLanguage, - iosLanguage: iosLanguage, - outputDirectory: outputDirectory, - iterationsAllowed: iterationsAllowed--, - ); - } - } - checkForErrors(result, commandDescription: cmdArgs.join(' '), silent: true); - - if (legacyNameParameter) { - return _fileSystem.path.join(outputDirectory, name); - } - return outputDirectory; - } - - /// Runs the git 3-way merge on three files and returns the results as a MergeResult. - /// - /// Passing the same path for base and current will perform a two-way fast forward merge. - Future gitMergeFile({ - required String base, - required String current, - required String target, - required String localPath, - }) async { - final List cmdArgs = [ - 'git', - 'merge-file', - '-p', - current, - base, - target, - ]; - final ProcessResult result = await _runCommand(cmdArgs); - checkForErrors( - result, - allowedExitCodes: [-1], - commandDescription: cmdArgs.join(' '), - ); - return StringMergeResult(result, localPath); - } - - /// Calls `git init` on the workingDirectory. - Future gitInit(String workingDirectory) async { - final List cmdArgs = ['git', 'init']; - final ProcessResult result = await _runCommand( - cmdArgs, - workingDirectory: workingDirectory, - ); - checkForErrors(result, commandDescription: cmdArgs.join(' ')); - } - - /// Returns true if the workingDirectory git repo has any uncommited changes. - Future hasUncommittedChanges( - String workingDirectory, { - String? migrateStagingDir, - }) async { - final List cmdArgs = [ - 'git', - 'ls-files', - '--deleted', - '--modified', - '--others', - '--exclude-standard', - '--exclude=${migrateStagingDir ?? kDefaultMigrateStagingDirectoryName}', - ]; - final ProcessResult result = await _runCommand( - cmdArgs, - workingDirectory: workingDirectory, - ); - checkForErrors( - result, - allowedExitCodes: [-1], - commandDescription: cmdArgs.join(' '), - ); - if ((result.stdout as String).isEmpty) { - return false; - } - return true; - } - - /// Returns true if the workingDirectory is a git repo. - Future isGitRepo(String workingDirectory) async { - final List cmdArgs = [ - 'git', - 'rev-parse', - '--is-inside-work-tree', - ]; - final ProcessResult result = await _runCommand( - cmdArgs, - workingDirectory: workingDirectory, - ); - checkForErrors( - result, - allowedExitCodes: [-1], - commandDescription: cmdArgs.join(' '), - ); - if (result.exitCode == 0) { - return true; - } - return false; - } - - /// Returns true if the file at `filePath` is covered by the `.gitignore` - Future isGitIgnored(String filePath, String workingDirectory) async { - final List cmdArgs = ['git', 'check-ignore', filePath]; - final ProcessResult result = await _runCommand( - cmdArgs, - workingDirectory: workingDirectory, - ); - checkForErrors( - result, - allowedExitCodes: [0, 1, 128], - commandDescription: cmdArgs.join(' '), - ); - return result.exitCode == 0; - } - - /// Runs `flutter pub upgrade --major-revisions`. - Future flutterPubUpgrade(String workingDirectory) async { - final List cmdArgs = [ - 'flutter', - 'pub', - 'upgrade', - '--major-versions', - ]; - final ProcessResult result = await _runCommand( - cmdArgs, - workingDirectory: workingDirectory, - ); - checkForErrors(result, commandDescription: cmdArgs.join(' ')); - } - - /// Runs `./gradlew tasks` in the android directory of a flutter project. - Future gradlewTasks(String workingDirectory) async { - final String baseCommand = isWindows ? 'gradlew.bat' : './gradlew'; - final List cmdArgs = [baseCommand, 'tasks']; - final ProcessResult result = await _runCommand( - cmdArgs, - workingDirectory: workingDirectory, - runInShell: isWindows, - ); - checkForErrors(result, commandDescription: cmdArgs.join(' ')); - } - - /// Verifies that the ProcessResult does not contain an error. - /// - /// If an error is detected, the error can be optionally logged or exit the tool. - /// - /// Passing -1 in allowedExitCodes means all exit codes are valid. - bool checkForErrors( - ProcessResult result, { - List allowedExitCodes = const [0], - String? commandDescription, - bool exit = true, - bool silent = false, - }) { - if (allowedExitCodes.contains(result.exitCode) || - allowedExitCodes.contains(-1)) { - return true; - } - if (!silent) { - _logger.printError( - 'Command encountered an error with exit code ${result.exitCode}.', - ); - if (commandDescription != null) { - _logger.printError('Command:'); - _logger.printError(commandDescription, indent: 2); - } - _logger.printError('Stdout:'); - _logger.printError(result.stdout as String, indent: 2); - _logger.printError('Stderr:'); - _logger.printError(result.stderr as String, indent: 2); - } - if (exit) { - throwToolExit( - 'Command failed with exit code ${result.exitCode}: ${result.stderr}\n${result.stdout}', - exitCode: result.exitCode, - ); - } - return false; - } - - /// Returns true if the file does not contain any git conflit markers. - bool conflictsResolved(String contents) { - final bool hasMarker = - contents.contains('>>>>>>>') || - contents.contains('=======') || - contents.contains('<<<<<<<'); - return !hasMarker; - } -} - -Future gitRepoExists( - String projectDirectory, - Logger logger, - MigrateUtils migrateUtils, -) async { - if (await migrateUtils.isGitRepo(projectDirectory)) { - return true; - } - logger.printStatus( - 'Project is not a git repo. Please initialize a git repo and try again.', - ); - printCommand('git init', logger); - return false; -} - -Future hasUncommittedChanges( - String projectDirectory, - Logger logger, - MigrateUtils migrateUtils, -) async { - if (await migrateUtils.hasUncommittedChanges(projectDirectory)) { - logger.printStatus( - 'There are uncommitted changes in your project. Please git commit, abandon, or stash your changes before trying again.', - ); - logger.printStatus('You may commit your changes using'); - printCommand('git add .', logger, newlineAfter: false); - printCommand('git commit -m ""', logger); - return true; - } - return false; -} - -void printCommand(String command, Logger logger, {bool newlineAfter = true}) { - logger.printStatus( - '\n\$ $command${newlineAfter ? '\n' : ''}', - color: TerminalColor.grey, - indent: 4, - newline: false, - ); -} - -/// Prints a command to logger with appropriate formatting. -void printCommandText( - String command, - Logger logger, { - bool? standalone = true, - bool newlineAfter = true, -}) { - final String prefix = - standalone == null - ? '' - : (standalone - ? 'dart run ${Platform.pathSeparator}bin${Platform.pathSeparator}flutter_migrate.dart ' - : 'flutter migrate '); - printCommand('$prefix$command', logger, newlineAfter: newlineAfter); -} - -/// Defines the classification of difference between files. -enum DiffType { command, addition, deletion, ignored, none } - -/// Tracks the output of a git diff command or any special cases such as addition of a new -/// file or deletion of an existing file. -class DiffResult { - DiffResult({required this.diffType, this.diff, this.exitCode}) - : assert( - diffType == DiffType.command && exitCode != null || - diffType != DiffType.command && exitCode == null, - ); - - /// The diff string output by git. - final String? diff; - - final DiffType diffType; - - /// The exit code of the command. This is zero when no diffs are found. - /// - /// The exitCode is null when the diffType is not `command`. - final int? exitCode; -} - -/// Data class to hold the results of a merge. -abstract class MergeResult { - /// Initializes a MergeResult based off of a ProcessResult. - MergeResult(ProcessResult result, this.localPath) - : hasConflict = result.exitCode != 0, - exitCode = result.exitCode; - - /// Manually initializes a MergeResult with explicit values. - MergeResult.explicit({ - required this.hasConflict, - required this.exitCode, - required this.localPath, - }); - - /// True when there is a merge conflict. - bool hasConflict; - - /// The exitcode of the merge command. - int exitCode; - - /// The local path relative to the project root of the file. - String localPath; -} - -/// The results of a string merge. -class StringMergeResult extends MergeResult { - /// Initializes a BinaryMergeResult based off of a ProcessResult. - StringMergeResult(super.result, super.localPath) - : mergedString = result.stdout as String; - - /// Manually initializes a StringMergeResult with explicit values. - StringMergeResult.explicit({ - required this.mergedString, - required super.hasConflict, - required super.exitCode, - required super.localPath, - }) : super.explicit(); - - /// The final merged string. - String mergedString; -} - -/// The results of a binary merge. -class BinaryMergeResult extends MergeResult { - /// Initializes a BinaryMergeResult based off of a ProcessResult. - BinaryMergeResult(super.result, super.localPath) - : mergedBytes = result.stdout as Uint8List; - - /// Manually initializes a BinaryMergeResult with explicit values. - BinaryMergeResult.explicit({ - required this.mergedBytes, - required super.hasConflict, - required super.exitCode, - required super.localPath, - }) : super.explicit(); - - /// The final merged bytes. - Uint8List mergedBytes; -} diff --git a/packages/flutter_migrate/pubspec.yaml b/packages/flutter_migrate/pubspec.yaml deleted file mode 100644 index b6bbdf5690c..00000000000 --- a/packages/flutter_migrate/pubspec.yaml +++ /dev/null @@ -1,26 +0,0 @@ -name: flutter_migrate -description: A tool to migrate legacy flutter projects to modern versions. -version: 0.0.1+4 -repository: https://github.com/flutter/packages/tree/main/packages/flutter_migrate -issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3Ap%3A%20flutter_migrate -publish_to: none - -environment: - sdk: ^3.7.0 - -dependencies: - args: ^2.3.1 - convert: ^3.0.2 - file: ">=6.0.0 <8.0.0" - intl: ">=0.17.0 <0.20.0" - meta: ^1.8.0 - path: ^1.8.0 - process: ^4.2.4 - vm_service: ^9.3.0 - yaml: ^3.1.1 - -dev_dependencies: - collection: ^1.16.0 - file_testing: ^3.0.0 - lints: ^2.0.0 - test: ^1.16.0 diff --git a/packages/flutter_migrate/test/abandon_test.dart b/packages/flutter_migrate/test/abandon_test.dart deleted file mode 100644 index 58d95903c35..00000000000 --- a/packages/flutter_migrate/test/abandon_test.dart +++ /dev/null @@ -1,145 +0,0 @@ -// Copyright 2013 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 'package:flutter_migrate/src/base/context.dart'; -import 'package:flutter_migrate/src/base/file_system.dart'; -import 'package:flutter_migrate/src/base/logger.dart'; -import 'package:flutter_migrate/src/base/signals.dart'; -import 'package:flutter_migrate/src/base/terminal.dart'; -import 'package:flutter_migrate/src/commands/abandon.dart'; -import 'package:flutter_migrate/src/utils.dart'; -import 'package:process/process.dart'; - -import 'src/common.dart'; -import 'src/context.dart'; -import 'src/test_flutter_command_runner.dart'; - -void main() { - late FileSystem fileSystem; - late BufferLogger logger; - late Terminal terminal; - late ProcessManager processManager; - late Directory appDir; - - setUp(() { - fileSystem = LocalFileSystem.test(signals: LocalSignals.instance); - appDir = fileSystem.systemTempDirectory.createTempSync('apptestdir'); - logger = BufferLogger.test(); - terminal = Terminal.test(); - processManager = const LocalProcessManager(); - }); - - tearDown(() async { - tryToDelete(appDir); - }); - - testUsingContext( - 'abandon deletes staging directory', - () async { - final MigrateAbandonCommand command = MigrateAbandonCommand( - logger: logger, - fileSystem: fileSystem, - terminal: terminal, - processManager: processManager, - ); - final Directory stagingDir = appDir.childDirectory( - kDefaultMigrateStagingDirectoryName, - ); - appDir.childFile('lib/main.dart').createSync(recursive: true); - final File pubspecOriginal = appDir.childFile('pubspec.yaml'); - pubspecOriginal.createSync(); - pubspecOriginal.writeAsStringSync(''' -name: originalname -description: A new Flutter project. -version: 1.0.0+1 -environment: - sdk: '>=2.18.0-58.0.dev <3.0.0' -dependencies: - flutter: - sdk: flutter -dev_dependencies: - flutter_test: - sdk: flutter -flutter: - uses-material-design: true''', flush: true); - expect(stagingDir.existsSync(), false); - await createTestCommandRunner(command).run([ - 'abandon', - '--staging-directory=${stagingDir.path}', - '--project-directory=${appDir.path}', - '--flutter-subcommand', - ]); - expect(logger.errorText, contains('Provided staging directory')); - expect( - logger.errorText, - contains('migrate_staging_dir` does not exist or is not valid.'), - ); - - logger.clear(); - await createTestCommandRunner(command).run([ - 'abandon', - '--project-directory=${appDir.path}', - '--flutter-subcommand', - ]); - expect( - logger.statusText, - contains('No migration in progress. Start a new migration with:'), - ); - - final File pubspecModified = stagingDir.childFile('pubspec.yaml'); - pubspecModified.createSync(recursive: true); - pubspecModified.writeAsStringSync(''' -name: newname -description: new description of the test project -version: 1.0.0+1 -environment: - sdk: '>=2.18.0-58.0.dev <3.0.0' -dependencies: - flutter: - sdk: flutter -dev_dependencies: - flutter_test: - sdk: flutter -flutter: - uses-material-design: false - EXTRALINE''', flush: true); - - final File addedFile = stagingDir.childFile('added.file'); - addedFile.createSync(recursive: true); - addedFile.writeAsStringSync('new file contents'); - - final File manifestFile = stagingDir.childFile('.migrate_manifest'); - manifestFile.createSync(recursive: true); - manifestFile.writeAsStringSync(''' -merged_files: - - pubspec.yaml -conflict_files: -added_files: - - added.file -deleted_files: -'''); - - expect(appDir.childFile('lib/main.dart').existsSync(), true); - - expect(stagingDir.existsSync(), true); - logger.clear(); - await createTestCommandRunner(command).run([ - 'abandon', - '--staging-directory=${stagingDir.path}', - '--project-directory=${appDir.path}', - '--force', - '--flutter-subcommand', - ]); - expect( - logger.statusText, - contains('Abandon complete. Start a new migration with:'), - ); - expect(stagingDir.existsSync(), false); - }, - overrides: { - FileSystem: () => fileSystem, - ProcessManager: () => processManager, - }, - ); -} diff --git a/packages/flutter_migrate/test/apply_test.dart b/packages/flutter_migrate/test/apply_test.dart deleted file mode 100644 index 5aa86982e5d..00000000000 --- a/packages/flutter_migrate/test/apply_test.dart +++ /dev/null @@ -1,272 +0,0 @@ -// Copyright 2013 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 'package:flutter_migrate/src/base/context.dart'; -import 'package:flutter_migrate/src/base/file_system.dart'; -import 'package:flutter_migrate/src/base/io.dart'; -import 'package:flutter_migrate/src/base/logger.dart'; -import 'package:flutter_migrate/src/base/signals.dart'; -import 'package:flutter_migrate/src/base/terminal.dart'; -import 'package:flutter_migrate/src/commands/apply.dart'; -import 'package:flutter_migrate/src/utils.dart'; -import 'package:process/process.dart'; - -import 'src/common.dart'; -import 'src/context.dart'; -import 'src/test_flutter_command_runner.dart'; - -void main() { - late FileSystem fileSystem; - late BufferLogger logger; - late Terminal terminal; - late ProcessManager processManager; - late Directory appDir; - - setUp(() { - fileSystem = LocalFileSystem.test(signals: LocalSignals.instance); - appDir = fileSystem.systemTempDirectory.createTempSync('apptestdir'); - logger = BufferLogger.test(); - terminal = Terminal.test(); - processManager = const LocalProcessManager(); - }); - - tearDown(() async { - tryToDelete(appDir); - }); - - testUsingContext( - 'Apply produces all outputs', - () async { - final ProcessResult result = await processManager.run([ - 'flutter', - '--version', - ], workingDirectory: appDir.path); - final String versionOutput = result.stdout as String; - final List versionSplit = versionOutput - .substring(8, 14) - .split('.'); - expect(versionSplit.length >= 2, true); - if (!(int.parse(versionSplit[0]) > 3 || - int.parse(versionSplit[0]) == 3 && int.parse(versionSplit[1]) > 3)) { - // Apply not supported on stable version 3.3 and below - return; - } - - final MigrateApplyCommand command = MigrateApplyCommand( - verbose: true, - logger: logger, - fileSystem: fileSystem, - terminal: terminal, - processManager: processManager, - ); - final Directory workingDir = appDir.childDirectory( - kDefaultMigrateStagingDirectoryName, - ); - appDir.childFile('lib/main.dart').createSync(recursive: true); - final File pubspecOriginal = appDir.childFile('pubspec.yaml'); - pubspecOriginal.createSync(); - pubspecOriginal.writeAsStringSync(''' -name: originalname -description: A new Flutter project. -version: 1.0.0+1 -environment: - sdk: '>=2.18.0-58.0.dev <3.0.0' -dependencies: - flutter: - sdk: flutter -dev_dependencies: - flutter_test: - sdk: flutter -flutter: - uses-material-design: true''', flush: true); - - final File gitignore = appDir.childFile('.gitignore'); - gitignore.createSync(); - gitignore.writeAsStringSync( - kDefaultMigrateStagingDirectoryName, - flush: true, - ); - - logger.clear(); - await createTestCommandRunner(command).run([ - 'apply', - '--staging-directory=${workingDir.path}', - '--project-directory=${appDir.path}', - '--flutter-subcommand', - ]); - expect( - logger.statusText, - contains( - 'Project is not a git repo. Please initialize a git repo and try again.', - ), - ); - - await processManager.run([ - 'git', - 'init', - ], workingDirectory: appDir.path); - - logger.clear(); - await createTestCommandRunner(command).run([ - 'apply', - '--staging-directory=${workingDir.path}', - '--project-directory=${appDir.path}', - '--flutter-subcommand', - ]); - expect(logger.statusText, contains('No migration in progress')); - - final File pubspecModified = workingDir.childFile('pubspec.yaml'); - pubspecModified.createSync(recursive: true); - pubspecModified.writeAsStringSync(''' -name: newname -description: new description of the test project -version: 1.0.0+1 -environment: - sdk: '>=2.18.0-58.0.dev <3.0.0' -dependencies: - flutter: - sdk: flutter -dev_dependencies: - flutter_test: - sdk: flutter -flutter: - uses-material-design: false - # EXTRALINE:''', flush: true); - - final File addedFile = workingDir.childFile('added.file'); - addedFile.createSync(recursive: true); - addedFile.writeAsStringSync('new file contents'); - - final File manifestFile = workingDir.childFile('.migrate_manifest'); - manifestFile.createSync(recursive: true); - manifestFile.writeAsStringSync(''' -merged_files: - - pubspec.yaml -conflict_files: - - conflict/conflict.file -added_files: - - added.file -deleted_files: -'''); - - // Add conflict file - final File conflictFile = workingDir - .childDirectory('conflict') - .childFile('conflict.file'); - conflictFile.createSync(recursive: true); - conflictFile.writeAsStringSync(''' -line1 -<<<<<<< /conflcit/conflict.file -line2 -======= -linetwo ->>>>>>> /var/folders/md/gm0zgfcj07vcsj6jkh_mp_wh00ff02/T/flutter_tools.4Xdep8/generatedTargetTemplatetlN44S/conflict/conflict.file -line3 -''', flush: true); - - final File conflictFileOriginal = appDir - .childDirectory('conflict') - .childFile('conflict.file'); - conflictFileOriginal.createSync(recursive: true); - conflictFileOriginal.writeAsStringSync(''' -line1 -line2 -line3 -''', flush: true); - - logger.clear(); - await createTestCommandRunner(command).run([ - 'apply', - '--staging-directory=${workingDir.path}', - '--project-directory=${appDir.path}', - '--flutter-subcommand', - ]); - expect( - logger.statusText, - contains(r''' -Added files: - - added.file -Modified files: - - pubspec.yaml -Unable to apply migration. The following files in the migration working directory still have unresolved conflicts: - - conflict/conflict.file -Conflicting files found. Resolve these conflicts and try again. -Guided conflict resolution wizard: - - $ flutter migrate resolve-conflicts'''), - ); - - conflictFile.writeAsStringSync(''' -line1 -linetwo -line3 -''', flush: true); - - logger.clear(); - await createTestCommandRunner(command).run([ - 'apply', - '--staging-directory=${workingDir.path}', - '--project-directory=${appDir.path}', - '--flutter-subcommand', - ]); - expect( - logger.statusText, - contains( - 'There are uncommitted changes in your project. Please git commit, abandon, or stash your changes before trying again.', - ), - ); - - await processManager.run([ - 'git', - 'add', - '.', - ], workingDirectory: appDir.path); - await processManager.run([ - 'git', - 'commit', - '-m', - 'Initial commit', - ], workingDirectory: appDir.path); - - logger.clear(); - await createTestCommandRunner(command).run([ - 'apply', - '--staging-directory=${workingDir.path}', - '--project-directory=${appDir.path}', - '--flutter-subcommand', - ]); - expect( - logger.statusText, - contains( - r''' -Added files: - - added.file -Modified files: - - conflict/conflict.file - - pubspec.yaml - -Applying migration. - Modifying 3 files. -Writing pubspec.yaml -Writing conflict/conflict.file -Writing added.file -Updating .migrate_configs -Migration complete. You may use commands like `git status`, `git diff` and `git restore ` to continue working with the migrated files.''', - ), - ); - - expect(pubspecOriginal.readAsStringSync(), contains('# EXTRALINE')); - expect(conflictFileOriginal.readAsStringSync(), contains('linetwo')); - expect(appDir.childFile('added.file').existsSync(), true); - expect( - appDir.childFile('added.file').readAsStringSync(), - contains('new file contents'), - ); - }, - overrides: { - FileSystem: () => fileSystem, - ProcessManager: () => processManager, - }, - ); -} diff --git a/packages/flutter_migrate/test/base/context_test.dart b/packages/flutter_migrate/test/base/context_test.dart deleted file mode 100644 index 551838e701d..00000000000 --- a/packages/flutter_migrate/test/base/context_test.dart +++ /dev/null @@ -1,301 +0,0 @@ -// Copyright 2013 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:flutter_migrate/src/base/context.dart'; - -import '../src/common.dart'; - -void main() { - group('AppContext', () { - group('global getter', () { - late bool called; - - setUp(() { - called = false; - }); - - test('returns non-null context in the root zone', () { - expect(context, isNotNull); - }); - - test( - 'returns root context in child of root zone if zone was manually created', - () { - final Zone rootZone = Zone.current; - final AppContext rootContext = context; - runZoned(() { - expect(Zone.current, isNot(rootZone)); - expect(Zone.current.parent, rootZone); - expect(context, rootContext); - called = true; - }); - expect(called, isTrue); - }, - ); - - test('returns child context after run', () async { - final AppContext rootContext = context; - await rootContext.run( - name: 'child', - body: () { - expect(context, isNot(rootContext)); - expect(context.name, 'child'); - called = true; - }, - ); - expect(called, isTrue); - }); - - test('returns grandchild context after nested run', () async { - final AppContext rootContext = context; - await rootContext.run( - name: 'child', - body: () async { - final AppContext childContext = context; - await childContext.run( - name: 'grandchild', - body: () { - expect(context, isNot(rootContext)); - expect(context, isNot(childContext)); - expect(context.name, 'grandchild'); - called = true; - }, - ); - }, - ); - expect(called, isTrue); - }); - - test('scans up zone hierarchy for first context', () async { - final AppContext rootContext = context; - await rootContext.run( - name: 'child', - body: () { - final AppContext childContext = context; - runZoned(() { - expect(context, isNot(rootContext)); - expect(context, same(childContext)); - expect(context.name, 'child'); - called = true; - }); - }, - ); - expect(called, isTrue); - }); - }); - - group('operator[]', () { - test( - 'still finds values if async code runs after body has finished', - () async { - final Completer outer = Completer(); - final Completer inner = Completer(); - String? value; - await context.run( - body: () { - outer.future.then((_) { - value = context.get(); - inner.complete(); - }); - }, - fallbacks: {String: () => 'value'}, - ); - expect(value, isNull); - outer.complete(); - await inner.future; - expect(value, 'value'); - }, - ); - - test('caches generated override values', () async { - int consultationCount = 0; - String? value; - await context.run( - body: () async { - final StringBuffer buf = StringBuffer(context.get()!); - buf.write(context.get()); - await context.run( - body: () { - buf.write(context.get()); - }, - ); - value = buf.toString(); - }, - overrides: { - String: () { - consultationCount++; - return 'v'; - }, - }, - ); - expect(value, 'vvv'); - expect(consultationCount, 1); - }); - - test('caches generated fallback values', () async { - int consultationCount = 0; - String? value; - await context.run( - body: () async { - final StringBuffer buf = StringBuffer(context.get()!); - buf.write(context.get()); - await context.run( - body: () { - buf.write(context.get()); - }, - ); - value = buf.toString(); - }, - fallbacks: { - String: () { - consultationCount++; - return 'v'; - }, - }, - ); - expect(value, 'vvv'); - expect(consultationCount, 1); - }); - - test('returns null if generated value is null', () async { - final String? value = await context.run( - body: () => context.get(), - overrides: {String: () => null}, - ); - expect(value, isNull); - }); - - test('throws if generator has dependency cycle', () async { - final Future value = context.run( - body: () async { - return context.get(); - }, - fallbacks: { - int: () => int.parse(context.get() ?? ''), - String: () => '${context.get()}', - double: () => context.get()! * 1.0, - }, - ); - expect( - () => value, - throwsA( - isA() - .having( - (ContextDependencyCycleException error) => error.cycle, - 'cycle', - [String, double, int], - ) - .having( - (ContextDependencyCycleException error) => error.toString(), - 'toString()', - 'Dependency cycle detected: String -> double -> int', - ), - ), - ); - }); - }); - - group('run', () { - test('returns the value returned by body', () async { - expect(await context.run(body: () => 123), 123); - expect(await context.run(body: () => 'value'), 'value'); - expect(await context.run(body: () async => 456), 456); - }); - - test('passes name to child context', () async { - await context.run( - name: 'child', - body: () { - expect(context.name, 'child'); - }, - ); - }); - - group('fallbacks', () { - late bool called; - - setUp(() { - called = false; - }); - - test('are applied after parent context is consulted', () async { - final String? value = await context.run( - body: () { - return context.run( - body: () { - called = true; - return context.get(); - }, - fallbacks: {String: () => 'child'}, - ); - }, - ); - expect(called, isTrue); - expect(value, 'child'); - }); - - test('are not applied if parent context supplies value', () async { - bool childConsulted = false; - final String? value = await context.run( - body: () { - return context.run( - body: () { - called = true; - return context.get(); - }, - fallbacks: { - String: () { - childConsulted = true; - return 'child'; - }, - }, - ); - }, - fallbacks: {String: () => 'parent'}, - ); - expect(called, isTrue); - expect(value, 'parent'); - expect(childConsulted, isFalse); - }); - - test('may depend on one another', () async { - final String? value = await context.run( - body: () { - return context.get(); - }, - fallbacks: { - int: () => 123, - String: () => '-${context.get()}-', - }, - ); - expect(value, '-123-'); - }); - }); - - group('overrides', () { - test('intercept consultation of parent context', () async { - bool parentConsulted = false; - final String? value = await context.run( - body: () { - return context.run( - body: () => context.get(), - overrides: {String: () => 'child'}, - ); - }, - fallbacks: { - String: () { - parentConsulted = true; - return 'parent'; - }, - }, - ); - expect(value, 'child'); - expect(parentConsulted, isFalse); - }); - }); - }); - }); -} diff --git a/packages/flutter_migrate/test/base/file_system_test.dart b/packages/flutter_migrate/test/base/file_system_test.dart deleted file mode 100644 index a1538e19c6e..00000000000 --- a/packages/flutter_migrate/test/base/file_system_test.dart +++ /dev/null @@ -1,363 +0,0 @@ -// Copyright 2013 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 'dart:io' as io; - -import 'package:file/memory.dart'; -import 'package:file_testing/file_testing.dart'; -import 'package:flutter_migrate/src/base/common.dart'; -import 'package:flutter_migrate/src/base/file_system.dart'; -import 'package:flutter_migrate/src/base/io.dart'; -import 'package:flutter_migrate/src/base/logger.dart'; -import 'package:flutter_migrate/src/base/signals.dart'; -import 'package:test/fake.dart'; - -import '../src/common.dart'; - -class LocalFileSystemFake extends LocalFileSystem { - LocalFileSystemFake.test({required super.signals}) : super.test(); - - @override - Directory get superSystemTempDirectory => directory('/does_not_exist'); -} - -void main() { - group('fsUtils', () { - late MemoryFileSystem fs; - late FileSystemUtils fsUtils; - - setUp(() { - fs = MemoryFileSystem.test(); - fsUtils = FileSystemUtils(fileSystem: fs); - }); - - testWithoutContext('getUniqueFile creates a unique file name', () async { - final File fileA = fsUtils.getUniqueFile( - fs.currentDirectory, - 'foo', - 'json', - )..createSync(); - final File fileB = fsUtils.getUniqueFile( - fs.currentDirectory, - 'foo', - 'json', - ); - - expect(fileA.path, '/foo_01.json'); - expect(fileB.path, '/foo_02.json'); - }); - - testWithoutContext( - 'getUniqueDirectory creates a unique directory name', - () async { - final Directory directoryA = fsUtils.getUniqueDirectory( - fs.currentDirectory, - 'foo', - )..createSync(); - final Directory directoryB = fsUtils.getUniqueDirectory( - fs.currentDirectory, - 'foo', - ); - - expect(directoryA.path, '/foo_01'); - expect(directoryB.path, '/foo_02'); - }, - ); - }); - - group('copyDirectorySync', () { - /// Test file_systems.copyDirectorySync() using MemoryFileSystem. - /// Copies between 2 instances of file systems which is also supported by copyDirectorySync(). - testWithoutContext('test directory copy', () async { - final MemoryFileSystem sourceMemoryFs = MemoryFileSystem.test(); - const String sourcePath = '/some/origin'; - final Directory sourceDirectory = await sourceMemoryFs - .directory(sourcePath) - .create(recursive: true); - sourceMemoryFs.currentDirectory = sourcePath; - final File sourceFile1 = sourceMemoryFs.file('some_file.txt') - ..writeAsStringSync('bleh'); - final DateTime writeTime = sourceFile1.lastModifiedSync(); - sourceMemoryFs - .file('sub_dir/another_file.txt') - .createSync(recursive: true); - sourceMemoryFs.directory('empty_directory').createSync(); - - // Copy to another memory file system instance. - final MemoryFileSystem targetMemoryFs = MemoryFileSystem.test(); - const String targetPath = '/some/non-existent/target'; - final Directory targetDirectory = targetMemoryFs.directory(targetPath); - - copyDirectory(sourceDirectory, targetDirectory); - - expect(targetDirectory.existsSync(), true); - targetMemoryFs.currentDirectory = targetPath; - expect(targetMemoryFs.directory('empty_directory').existsSync(), true); - expect( - targetMemoryFs.file('sub_dir/another_file.txt').existsSync(), - true, - ); - expect(targetMemoryFs.file('some_file.txt').readAsStringSync(), 'bleh'); - - // Assert that the copy operation hasn't modified the original file in some way. - expect( - sourceMemoryFs.file('some_file.txt').lastModifiedSync(), - writeTime, - ); - // There's still 3 things in the original directory as there were initially. - expect(sourceMemoryFs.directory(sourcePath).listSync().length, 3); - }); - - testWithoutContext('Skip files if shouldCopyFile returns false', () { - final MemoryFileSystem fileSystem = MemoryFileSystem.test(); - final Directory origin = fileSystem.directory('/origin'); - origin.createSync(); - fileSystem - .file(fileSystem.path.join('origin', 'a.txt')) - .writeAsStringSync('irrelevant'); - fileSystem.directory('/origin/nested').createSync(); - fileSystem - .file(fileSystem.path.join('origin', 'nested', 'a.txt')) - .writeAsStringSync('irrelevant'); - fileSystem - .file(fileSystem.path.join('origin', 'nested', 'b.txt')) - .writeAsStringSync('irrelevant'); - - final Directory destination = fileSystem.directory('/destination'); - copyDirectory( - origin, - destination, - shouldCopyFile: (File origin, File dest) { - return origin.basename == 'b.txt'; - }, - ); - - expect(destination.existsSync(), isTrue); - expect(destination.childDirectory('nested').existsSync(), isTrue); - expect( - destination.childDirectory('nested').childFile('b.txt').existsSync(), - isTrue, - ); - - expect(destination.childFile('a.txt').existsSync(), isFalse); - expect( - destination.childDirectory('nested').childFile('a.txt').existsSync(), - isFalse, - ); - }); - - testWithoutContext( - 'Skip directories if shouldCopyDirectory returns false', - () { - final MemoryFileSystem fileSystem = MemoryFileSystem.test(); - final Directory origin = fileSystem.directory('/origin'); - origin.createSync(); - fileSystem - .file(fileSystem.path.join('origin', 'a.txt')) - .writeAsStringSync('irrelevant'); - fileSystem.directory('/origin/nested').createSync(); - fileSystem - .file(fileSystem.path.join('origin', 'nested', 'a.txt')) - .writeAsStringSync('irrelevant'); - fileSystem - .file(fileSystem.path.join('origin', 'nested', 'b.txt')) - .writeAsStringSync('irrelevant'); - - final Directory destination = fileSystem.directory('/destination'); - copyDirectory( - origin, - destination, - shouldCopyDirectory: (Directory directory) { - return !directory.path.endsWith('nested'); - }, - ); - - expect(destination, exists); - expect(destination.childDirectory('nested'), isNot(exists)); - expect( - destination.childDirectory('nested').childFile('b.txt'), - isNot(exists), - ); - }, - ); - }); - - group('LocalFileSystem', () { - late FakeProcessSignal fakeSignal; - late ProcessSignal signalUnderTest; - - setUp(() { - fakeSignal = FakeProcessSignal(); - signalUnderTest = ProcessSignal(fakeSignal); - }); - - testWithoutContext('runs shutdown hooks', () async { - final Signals signals = Signals.test(); - final LocalFileSystem localFileSystem = LocalFileSystem.test( - signals: signals, - ); - final Directory temp = localFileSystem.systemTempDirectory; - - expect(temp.existsSync(), isTrue); - expect(localFileSystem.shutdownHooks.registeredHooks, hasLength(1)); - final BufferLogger logger = BufferLogger.test(); - await localFileSystem.shutdownHooks.runShutdownHooks(logger); - expect(temp.existsSync(), isFalse); - expect(logger.traceText, contains('Running 1 shutdown hook')); - }); - - testWithoutContext('deletes system temp entry on a fatal signal', () async { - final Completer completer = Completer(); - final Signals signals = Signals.test(); - final LocalFileSystem localFileSystem = LocalFileSystem.test( - signals: signals, - fatalSignals: [signalUnderTest], - ); - final Directory temp = localFileSystem.systemTempDirectory; - - signals.addHandler(signalUnderTest, (ProcessSignal s) { - completer.complete(); - }); - - expect(temp.existsSync(), isTrue); - - fakeSignal.controller.add(fakeSignal); - await completer.future; - - expect(temp.existsSync(), isFalse); - }); - - testWithoutContext('throwToolExit when temp not found', () async { - final Signals signals = Signals.test(); - final LocalFileSystemFake localFileSystem = LocalFileSystemFake.test( - signals: signals, - ); - - try { - localFileSystem.systemTempDirectory; - fail('expected tool exit'); - } on ToolExit catch (e) { - expect( - e.message, - 'Your system temp directory (/does_not_exist) does not exist. ' - 'Did you set an invalid override in your environment? ' - 'See issue https://github.com/flutter/flutter/issues/74042 for more context.', - ); - } - }); - }); -} - -class FakeProcessSignal extends Fake implements io.ProcessSignal { - final StreamController controller = - StreamController(); - - @override - Stream watch() => controller.stream; -} - -/// Various convenience file system methods. -class FileSystemUtils { - FileSystemUtils({required FileSystem fileSystem}) : _fileSystem = fileSystem; - - final FileSystem _fileSystem; - - /// Appends a number to a filename in order to make it unique under a - /// directory. - File getUniqueFile(Directory dir, String baseName, String ext) { - final FileSystem fs = dir.fileSystem; - int i = 1; - - while (true) { - final String name = '${baseName}_${i.toString().padLeft(2, '0')}.$ext'; - final File file = fs.file(dir.fileSystem.path.join(dir.path, name)); - if (!file.existsSync()) { - file.createSync(recursive: true); - return file; - } - i += 1; - } - } - - // /// Appends a number to a filename in order to make it unique under a - // /// directory. - // File getUniqueFile(Directory dir, String baseName, String ext) { - // return _getUniqueFile(dir, baseName, ext); - // } - - /// Appends a number to a directory name in order to make it unique under a - /// directory. - Directory getUniqueDirectory(Directory dir, String baseName) { - final FileSystem fs = dir.fileSystem; - int i = 1; - - while (true) { - final String name = '${baseName}_${i.toString().padLeft(2, '0')}'; - final Directory directory = fs.directory( - _fileSystem.path.join(dir.path, name), - ); - if (!directory.existsSync()) { - return directory; - } - i += 1; - } - } -} - -/// Creates `destDir` if needed, then recursively copies `srcDir` to -/// `destDir`, invoking [onFileCopied], if specified, for each -/// source/destination file pair. -/// -/// Skips files if [shouldCopyFile] returns `false`. -/// Does not recurse over directories if [shouldCopyDirectory] returns `false`. -void copyDirectory( - Directory srcDir, - Directory destDir, { - bool Function(File srcFile, File destFile)? shouldCopyFile, - bool Function(Directory)? shouldCopyDirectory, - void Function(File srcFile, File destFile)? onFileCopied, -}) { - if (!srcDir.existsSync()) { - throw Exception( - 'Source directory "${srcDir.path}" does not exist, nothing to copy', - ); - } - - if (!destDir.existsSync()) { - destDir.createSync(recursive: true); - } - - for (final FileSystemEntity entity in srcDir.listSync()) { - final String newPath = destDir.fileSystem.path.join( - destDir.path, - entity.basename, - ); - if (entity is Link) { - final Link newLink = destDir.fileSystem.link(newPath); - newLink.createSync(entity.targetSync()); - } else if (entity is File) { - final File newFile = destDir.fileSystem.file(newPath); - if (shouldCopyFile != null && !shouldCopyFile(entity, newFile)) { - continue; - } - newFile.writeAsBytesSync(entity.readAsBytesSync()); - onFileCopied?.call(entity, newFile); - } else if (entity is Directory) { - if (shouldCopyDirectory != null && !shouldCopyDirectory(entity)) { - continue; - } - copyDirectory( - entity, - destDir.fileSystem.directory(newPath), - shouldCopyFile: shouldCopyFile, - onFileCopied: onFileCopied, - ); - } else { - throw Exception( - '${entity.path} is neither File nor Directory, was ${entity.runtimeType}', - ); - } - } -} diff --git a/packages/flutter_migrate/test/base/io_test.dart b/packages/flutter_migrate/test/base/io_test.dart deleted file mode 100644 index 28b954ce3d0..00000000000 --- a/packages/flutter_migrate/test/base/io_test.dart +++ /dev/null @@ -1,95 +0,0 @@ -// Copyright 2013 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 'dart:io' as io; - -import 'package:file/memory.dart'; -import 'package:flutter_migrate/src/base/io.dart'; -import 'package:test/fake.dart'; - -import '../src/common.dart'; -import '../src/io.dart'; - -void main() { - testWithoutContext('IOOverrides can inject a memory file system', () async { - final MemoryFileSystem memoryFileSystem = MemoryFileSystem.test(); - final FlutterIOOverrides flutterIOOverrides = FlutterIOOverrides( - fileSystem: memoryFileSystem, - ); - await io.IOOverrides.runWithIOOverrides(() async { - // statics delegate correctly. - expect( - io.FileSystemEntity.isWatchSupported, - memoryFileSystem.isWatchSupported, - ); - expect( - io.Directory.systemTemp.path, - memoryFileSystem.systemTempDirectory.path, - ); - - // can create and write to files/directories sync. - final io.File file = io.File('abc'); - file.writeAsStringSync('def'); - final io.Directory directory = io.Directory('foobar'); - directory.createSync(); - - expect(memoryFileSystem.file('abc').existsSync(), true); - expect(memoryFileSystem.file('abc').readAsStringSync(), 'def'); - expect(memoryFileSystem.directory('foobar').existsSync(), true); - - // can create and write to files/directories async. - final io.File fileB = io.File('xyz'); - await fileB.writeAsString('def'); - final io.Directory directoryB = io.Directory('barfoo'); - await directoryB.create(); - - expect(memoryFileSystem.file('xyz').existsSync(), true); - expect(memoryFileSystem.file('xyz').readAsStringSync(), 'def'); - expect(memoryFileSystem.directory('barfoo').existsSync(), true); - - // Links - final io.Link linkA = io.Link('hhh'); - final io.Link linkB = io.Link('ggg'); - io.File('jjj').createSync(); - io.File('lll').createSync(); - await linkA.create('jjj'); - linkB.createSync('lll'); - - expect( - await memoryFileSystem.link('hhh').resolveSymbolicLinks(), - await linkA.resolveSymbolicLinks(), - ); - expect( - memoryFileSystem.link('ggg').resolveSymbolicLinksSync(), - linkB.resolveSymbolicLinksSync(), - ); - }, flutterIOOverrides); - }); - - testWithoutContext('ProcessSignal signals are properly delegated', () async { - final FakeProcessSignal signal = FakeProcessSignal(); - final ProcessSignal signalUnderTest = ProcessSignal(signal); - - signal.controller.add(signal); - - expect(signalUnderTest, await signalUnderTest.watch().first); - }); - - testWithoutContext('ProcessSignal toString() works', () async { - expect(io.ProcessSignal.sigint.toString(), ProcessSignal.sigint.toString()); - }); - - testWithoutContext('test_api defines the Declarer in a known place', () { - expect(Zone.current[#test.declarer], isNotNull); - }); -} - -class FakeProcessSignal extends Fake implements io.ProcessSignal { - final StreamController controller = - StreamController(); - - @override - Stream watch() => controller.stream; -} diff --git a/packages/flutter_migrate/test/base/logger_test.dart b/packages/flutter_migrate/test/base/logger_test.dart deleted file mode 100644 index b0cd9636129..00000000000 --- a/packages/flutter_migrate/test/base/logger_test.dart +++ /dev/null @@ -1,680 +0,0 @@ -// Copyright 2013 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:flutter_migrate/src/base/io.dart'; -import 'package:flutter_migrate/src/base/logger.dart'; -import 'package:flutter_migrate/src/base/terminal.dart'; -import 'package:test/fake.dart'; - -import '../src/common.dart'; -import '../src/fakes.dart'; - -void main() { - testWithoutContext('correct logger instance is created', () { - final LoggerFactory loggerFactory = LoggerFactory( - terminal: Terminal.test(), - stdio: FakeStdio(), - outputPreferences: OutputPreferences.test(), - ); - - expect(loggerFactory.createLogger(windows: false), isA()); - expect( - loggerFactory.createLogger(windows: true), - isA(), - ); - }); - - testWithoutContext( - 'WindowsStdoutLogger rewrites emojis when terminal does not support emoji', - () { - final FakeStdio stdio = FakeStdio(); - final WindowsStdoutLogger logger = WindowsStdoutLogger( - outputPreferences: OutputPreferences.test(), - stdio: stdio, - terminal: Terminal.test(), - ); - - logger.printStatus('🔥🖼️✗✓🔨💪✏️'); - - expect(stdio.writtenToStdout, ['X√\n']); - }, - ); - - testWithoutContext( - 'WindowsStdoutLogger does not rewrite emojis when terminal does support emoji', - () { - final FakeStdio stdio = FakeStdio(); - final WindowsStdoutLogger logger = WindowsStdoutLogger( - outputPreferences: OutputPreferences.test(), - stdio: stdio, - terminal: Terminal.test(supportsColor: true, supportsEmoji: true), - ); - - logger.printStatus('🔥🖼️✗✓🔨💪✏️'); - - expect(stdio.writtenToStdout, ['🔥🖼️✗✓🔨💪✏️\n']); - }, - ); - testWithoutContext( - 'Logger does not throw when stdio write throws synchronously', - () async { - final FakeStdout stdout = FakeStdout(syncError: true); - final FakeStdout stderr = FakeStdout(syncError: true); - final Stdio stdio = Stdio.test(stdout: stdout, stderr: stderr); - final Logger logger = StdoutLogger( - terminal: AnsiTerminal(stdio: stdio), - stdio: stdio, - outputPreferences: OutputPreferences.test(), - ); - - logger.printStatus('message'); - logger.printError('error message'); - }, - ); - - testWithoutContext( - 'Logger does not throw when stdio write throws asynchronously', - () async { - final FakeStdout stdout = FakeStdout(syncError: false); - final FakeStdout stderr = FakeStdout(syncError: false); - final Stdio stdio = Stdio.test(stdout: stdout, stderr: stderr); - final Logger logger = StdoutLogger( - terminal: AnsiTerminal(stdio: stdio), - stdio: stdio, - outputPreferences: OutputPreferences.test(), - ); - logger.printStatus('message'); - logger.printError('error message'); - - await stdout.done; - await stderr.done; - }, - ); - - testWithoutContext( - 'Logger does not throw when stdio completes done with an error', - () async { - final FakeStdout stdout = FakeStdout( - syncError: false, - completeWithError: true, - ); - final FakeStdout stderr = FakeStdout( - syncError: false, - completeWithError: true, - ); - final Stdio stdio = Stdio.test(stdout: stdout, stderr: stderr); - final Logger logger = StdoutLogger( - terminal: AnsiTerminal(stdio: stdio), - stdio: stdio, - outputPreferences: OutputPreferences.test(), - ); - logger.printStatus('message'); - logger.printError('error message'); - - expect(() async => stdout.done, throwsException); - expect(() async => stderr.done, throwsException); - }, - ); - - group('Output format', () { - late FakeStdio fakeStdio; - late SummaryStatus summaryStatus; - late int called; - - setUp(() { - fakeStdio = FakeStdio(); - called = 0; - summaryStatus = SummaryStatus( - message: 'Hello world', - padding: 20, - onFinish: () => called++, - stdio: fakeStdio, - stopwatch: FakeStopwatch(), - ); - }); - - List outputStdout() => fakeStdio.writtenToStdout.join().split('\n'); - List outputStderr() => fakeStdio.writtenToStderr.join().split('\n'); - - testWithoutContext('Error logs are wrapped', () async { - final Logger logger = StdoutLogger( - terminal: AnsiTerminal(stdio: fakeStdio), - stdio: fakeStdio, - outputPreferences: OutputPreferences.test( - wrapText: true, - wrapColumn: 40, - ), - ); - logger.printError('0123456789' * 15); - final List lines = outputStderr(); - - expect(outputStdout().length, equals(1)); - expect(outputStdout().first, isEmpty); - expect(lines[0], equals('0123456789' * 4)); - expect(lines[1], equals('0123456789' * 4)); - expect(lines[2], equals('0123456789' * 4)); - expect(lines[3], equals('0123456789' * 3)); - }); - - testWithoutContext('Error logs are wrapped and can be indented.', () async { - final Logger logger = StdoutLogger( - terminal: AnsiTerminal(stdio: fakeStdio), - stdio: fakeStdio, - outputPreferences: OutputPreferences.test( - wrapText: true, - wrapColumn: 40, - ), - ); - logger.printError('0123456789' * 15, indent: 5); - final List lines = outputStderr(); - - expect(outputStdout().length, equals(1)); - expect(outputStdout().first, isEmpty); - expect(lines.length, equals(6)); - expect(lines[0], equals(' 01234567890123456789012345678901234')); - expect(lines[1], equals(' 56789012345678901234567890123456789')); - expect(lines[2], equals(' 01234567890123456789012345678901234')); - expect(lines[3], equals(' 56789012345678901234567890123456789')); - expect(lines[4], equals(' 0123456789')); - expect(lines[5], isEmpty); - }); - - testWithoutContext( - 'Error logs are wrapped and can have hanging indent.', - () async { - final Logger logger = StdoutLogger( - terminal: AnsiTerminal(stdio: fakeStdio), - stdio: fakeStdio, - outputPreferences: OutputPreferences.test( - wrapText: true, - wrapColumn: 40, - ), - ); - logger.printError('0123456789' * 15, hangingIndent: 5); - final List lines = outputStderr(); - - expect(outputStdout().length, equals(1)); - expect(outputStdout().first, isEmpty); - expect(lines.length, equals(6)); - expect(lines[0], equals('0123456789012345678901234567890123456789')); - expect(lines[1], equals(' 01234567890123456789012345678901234')); - expect(lines[2], equals(' 56789012345678901234567890123456789')); - expect(lines[3], equals(' 01234567890123456789012345678901234')); - expect(lines[4], equals(' 56789')); - expect(lines[5], isEmpty); - }, - ); - - testWithoutContext( - 'Error logs are wrapped, indented, and can have hanging indent.', - () async { - final Logger logger = StdoutLogger( - terminal: AnsiTerminal(stdio: fakeStdio), - stdio: fakeStdio, - outputPreferences: OutputPreferences.test( - wrapText: true, - wrapColumn: 40, - ), - ); - logger.printError('0123456789' * 15, indent: 4, hangingIndent: 5); - final List lines = outputStderr(); - - expect(outputStdout().length, equals(1)); - expect(outputStdout().first, isEmpty); - expect(lines.length, equals(6)); - expect(lines[0], equals(' 012345678901234567890123456789012345')); - expect(lines[1], equals(' 6789012345678901234567890123456')); - expect(lines[2], equals(' 7890123456789012345678901234567')); - expect(lines[3], equals(' 8901234567890123456789012345678')); - expect(lines[4], equals(' 901234567890123456789')); - expect(lines[5], isEmpty); - }, - ); - - testWithoutContext('Stdout logs are wrapped', () async { - final Logger logger = StdoutLogger( - terminal: AnsiTerminal(stdio: fakeStdio), - stdio: fakeStdio, - outputPreferences: OutputPreferences.test( - wrapText: true, - wrapColumn: 40, - ), - ); - logger.printStatus('0123456789' * 15); - final List lines = outputStdout(); - - expect(outputStderr().length, equals(1)); - expect(outputStderr().first, isEmpty); - expect(lines[0], equals('0123456789' * 4)); - expect(lines[1], equals('0123456789' * 4)); - expect(lines[2], equals('0123456789' * 4)); - expect(lines[3], equals('0123456789' * 3)); - }); - - testWithoutContext( - 'Stdout logs are wrapped and can be indented.', - () async { - final Logger logger = StdoutLogger( - terminal: AnsiTerminal(stdio: fakeStdio), - stdio: fakeStdio, - outputPreferences: OutputPreferences.test( - wrapText: true, - wrapColumn: 40, - ), - ); - logger.printStatus('0123456789' * 15, indent: 5); - final List lines = outputStdout(); - - expect(outputStderr().length, equals(1)); - expect(outputStderr().first, isEmpty); - expect(lines.length, equals(6)); - expect(lines[0], equals(' 01234567890123456789012345678901234')); - expect(lines[1], equals(' 56789012345678901234567890123456789')); - expect(lines[2], equals(' 01234567890123456789012345678901234')); - expect(lines[3], equals(' 56789012345678901234567890123456789')); - expect(lines[4], equals(' 0123456789')); - expect(lines[5], isEmpty); - }, - ); - - testWithoutContext( - 'Stdout logs are wrapped and can have hanging indent.', - () async { - final Logger logger = StdoutLogger( - terminal: AnsiTerminal(stdio: fakeStdio), - stdio: fakeStdio, - outputPreferences: OutputPreferences.test( - wrapText: true, - wrapColumn: 40, - ), - ); - logger.printStatus('0123456789' * 15, hangingIndent: 5); - final List lines = outputStdout(); - - expect(outputStderr().length, equals(1)); - expect(outputStderr().first, isEmpty); - expect(lines.length, equals(6)); - expect(lines[0], equals('0123456789012345678901234567890123456789')); - expect(lines[1], equals(' 01234567890123456789012345678901234')); - expect(lines[2], equals(' 56789012345678901234567890123456789')); - expect(lines[3], equals(' 01234567890123456789012345678901234')); - expect(lines[4], equals(' 56789')); - expect(lines[5], isEmpty); - }, - ); - - testWithoutContext( - 'Stdout logs are wrapped, indented, and can have hanging indent.', - () async { - final Logger logger = StdoutLogger( - terminal: AnsiTerminal(stdio: fakeStdio), - stdio: fakeStdio, - outputPreferences: OutputPreferences.test( - wrapText: true, - wrapColumn: 40, - ), - ); - logger.printStatus('0123456789' * 15, indent: 4, hangingIndent: 5); - final List lines = outputStdout(); - - expect(outputStderr().length, equals(1)); - expect(outputStderr().first, isEmpty); - expect(lines.length, equals(6)); - expect(lines[0], equals(' 012345678901234567890123456789012345')); - expect(lines[1], equals(' 6789012345678901234567890123456')); - expect(lines[2], equals(' 7890123456789012345678901234567')); - expect(lines[3], equals(' 8901234567890123456789012345678')); - expect(lines[4], equals(' 901234567890123456789')); - expect(lines[5], isEmpty); - }, - ); - - testWithoutContext('Error logs are red', () async { - final Logger logger = StdoutLogger( - terminal: AnsiTerminal(stdio: fakeStdio, supportsColor: true), - stdio: fakeStdio, - outputPreferences: OutputPreferences.test(showColor: true), - ); - logger.printError('Pants on fire!'); - final List lines = outputStderr(); - - expect(outputStdout().length, equals(1)); - expect(outputStdout().first, isEmpty); - expect( - lines[0], - equals('${AnsiTerminal.red}Pants on fire!${AnsiTerminal.resetColor}'), - ); - }); - - testWithoutContext('Stdout logs are not colored', () async { - final Logger logger = StdoutLogger( - terminal: AnsiTerminal(stdio: fakeStdio), - stdio: fakeStdio, - outputPreferences: OutputPreferences.test(showColor: true), - ); - logger.printStatus('All good.'); - - final List lines = outputStdout(); - expect(outputStderr().length, equals(1)); - expect(outputStderr().first, isEmpty); - expect(lines[0], equals('All good.')); - }); - - testWithoutContext('Stdout printBox puts content inside a box', () { - final Logger logger = StdoutLogger( - terminal: AnsiTerminal(stdio: fakeStdio), - stdio: fakeStdio, - outputPreferences: OutputPreferences.test(showColor: true), - ); - logger.printBox('Hello world', title: 'Test title'); - final String stdout = fakeStdio.writtenToStdout.join(); - expect( - stdout, - contains( - '\n' - '┌─ Test title ┐\n' - '│ Hello world │\n' - '└─────────────┘\n', - ), - ); - }); - - testWithoutContext('Stdout printBox does not require title', () { - final Logger logger = StdoutLogger( - terminal: AnsiTerminal(stdio: fakeStdio), - stdio: fakeStdio, - outputPreferences: OutputPreferences.test(showColor: true), - ); - logger.printBox('Hello world'); - final String stdout = fakeStdio.writtenToStdout.join(); - expect( - stdout, - contains( - '\n' - '┌─────────────┐\n' - '│ Hello world │\n' - '└─────────────┘\n', - ), - ); - }); - - testWithoutContext('Stdout printBox handles new lines', () { - final Logger logger = StdoutLogger( - terminal: AnsiTerminal(stdio: fakeStdio), - stdio: fakeStdio, - outputPreferences: OutputPreferences.test(showColor: true), - ); - logger.printBox('Hello world\nThis is a new line', title: 'Test title'); - final String stdout = fakeStdio.writtenToStdout.join(); - expect( - stdout, - contains( - '\n' - '┌─ Test title ───────┐\n' - '│ Hello world │\n' - '│ This is a new line │\n' - '└────────────────────┘\n', - ), - ); - }); - - testWithoutContext( - 'Stdout printBox handles content with ANSI escape characters', - () { - final Logger logger = StdoutLogger( - terminal: AnsiTerminal(stdio: fakeStdio), - stdio: fakeStdio, - outputPreferences: OutputPreferences.test(showColor: true), - ); - const String bold = '\u001B[1m'; - const String clear = '\u001B[2J\u001B[H'; - logger.printBox('${bold}Hello world$clear', title: 'Test title'); - final String stdout = fakeStdio.writtenToStdout.join(); - expect( - stdout, - contains( - '\n' - '┌─ Test title ┐\n' - '│ ${bold}Hello world$clear │\n' - '└─────────────┘\n', - ), - ); - }, - ); - - testWithoutContext('Stdout printBox handles column limit', () { - const int columnLimit = 14; - final Logger logger = StdoutLogger( - terminal: AnsiTerminal(stdio: fakeStdio), - stdio: fakeStdio, - outputPreferences: OutputPreferences.test( - showColor: true, - wrapColumn: columnLimit, - ), - ); - logger.printBox( - 'This line is longer than $columnLimit characters', - title: 'Test', - ); - final String stdout = fakeStdio.writtenToStdout.join(); - final List stdoutLines = stdout.split('\n'); - - expect(stdoutLines.length, greaterThan(1)); - expect(stdoutLines[1].length, equals(columnLimit)); - expect( - stdout, - contains( - '\n' - '┌─ Test ─────┐\n' - '│ This line │\n' - '│ is longer │\n' - '│ than 14 │\n' - '│ characters │\n' - '└────────────┘\n', - ), - ); - }); - - testWithoutContext( - 'Stdout printBox handles column limit and respects new lines', - () { - const int columnLimit = 14; - final Logger logger = StdoutLogger( - terminal: AnsiTerminal(stdio: fakeStdio), - stdio: fakeStdio, - outputPreferences: OutputPreferences.test( - showColor: true, - wrapColumn: columnLimit, - ), - ); - logger.printBox( - 'This\nline is longer than\n\n$columnLimit characters', - title: 'Test', - ); - final String stdout = fakeStdio.writtenToStdout.join(); - final List stdoutLines = stdout.split('\n'); - - expect(stdoutLines.length, greaterThan(1)); - expect(stdoutLines[1].length, equals(columnLimit)); - expect( - stdout, - contains( - '\n' - '┌─ Test ─────┐\n' - '│ This │\n' - '│ line is │\n' - '│ longer │\n' - '│ than │\n' - '│ │\n' - '│ 14 │\n' - '│ characters │\n' - '└────────────┘\n', - ), - ); - }, - ); - - testWithoutContext( - 'Stdout printBox breaks long words that exceed the column limit', - () { - const int columnLimit = 14; - final Logger logger = StdoutLogger( - terminal: AnsiTerminal(stdio: fakeStdio), - stdio: fakeStdio, - outputPreferences: OutputPreferences.test( - showColor: true, - wrapColumn: columnLimit, - ), - ); - logger.printBox( - 'Thiswordislongerthan${columnLimit}characters', - title: 'Test', - ); - final String stdout = fakeStdio.writtenToStdout.join(); - final List stdoutLines = stdout.split('\n'); - - expect(stdoutLines.length, greaterThan(1)); - expect(stdoutLines[1].length, equals(columnLimit)); - expect( - stdout, - contains( - '\n' - '┌─ Test ─────┐\n' - '│ Thiswordis │\n' - '│ longerthan │\n' - '│ 14characte │\n' - '│ rs │\n' - '└────────────┘\n', - ), - ); - }, - ); - - testWithoutContext('Stdout startProgress on non-color terminal', () async { - final FakeStopwatch fakeStopwatch = FakeStopwatch(); - final Logger logger = StdoutLogger( - terminal: AnsiTerminal(stdio: fakeStdio), - stdio: fakeStdio, - outputPreferences: OutputPreferences.test(), - stopwatchFactory: FakeStopwatchFactory(stopwatch: fakeStopwatch), - ); - final Status status = logger.startProgress( - 'Hello', - progressIndicatorPadding: - 20, // this minus the "Hello" equals the 15 below. - ); - expect(outputStderr().length, equals(1)); - expect(outputStderr().first, isEmpty); - // the 5 below is the margin that is always included between the message and the time. - expect(outputStdout().join('\n'), matches(r'^Hello {15} {5}$')); - - fakeStopwatch.elapsed = const Duration(seconds: 4, milliseconds: 123); - status.stop(); - - expect(outputStdout(), ['Hello 4.1s', '']); - }); - - testWithoutContext('SummaryStatus works when canceled', () async { - final SummaryStatus summaryStatus = SummaryStatus( - message: 'Hello world', - padding: 20, - onFinish: () => called++, - stdio: fakeStdio, - stopwatch: FakeStopwatch(), - ); - summaryStatus.start(); - final List lines = outputStdout(); - expect(lines[0], startsWith('Hello world ')); - expect(lines.length, equals(1)); - expect(lines[0].endsWith('\n'), isFalse); - - // Verify a cancel does _not_ print the time and prints a newline. - summaryStatus.cancel(); - expect(outputStdout(), ['Hello world ', '']); - - // Verify that stopping or canceling multiple times throws. - expect(summaryStatus.cancel, throwsAssertionError); - expect(summaryStatus.stop, throwsAssertionError); - }); - - testWithoutContext('SummaryStatus works when stopped', () async { - summaryStatus.start(); - final List lines = outputStdout(); - expect(lines[0], startsWith('Hello world ')); - expect(lines.length, equals(1)); - - // Verify a stop prints the time. - summaryStatus.stop(); - expect(outputStdout(), ['Hello world 0ms', '']); - - // Verify that stopping or canceling multiple times throws. - expect(summaryStatus.stop, throwsAssertionError); - expect(summaryStatus.cancel, throwsAssertionError); - }); - - testWithoutContext( - 'sequential startProgress calls with StdoutLogger', - () async { - final Logger logger = StdoutLogger( - terminal: AnsiTerminal(stdio: fakeStdio), - stdio: fakeStdio, - outputPreferences: OutputPreferences.test(), - ); - logger.startProgress('AAA').stop(); - logger.startProgress('BBB').stop(); - final List output = outputStdout(); - - expect(output.length, equals(3)); - - // There's 61 spaces at the start: 59 (padding default) - 3 (length of AAA) + 5 (margin). - // Then there's a left-padded "0ms" 8 characters wide, so 5 spaces then "0ms" - // (except sometimes it's randomly slow so we handle up to "99,999ms"). - expect(output[0], matches(RegExp(r'AAA[ ]{61}[\d, ]{5}[\d]ms'))); - expect(output[1], matches(RegExp(r'BBB[ ]{61}[\d, ]{5}[\d]ms'))); - }, - ); - - testWithoutContext( - 'sequential startProgress calls with BufferLogger', - () async { - final BufferLogger logger = BufferLogger( - terminal: AnsiTerminal(stdio: fakeStdio), - outputPreferences: OutputPreferences.test(), - ); - logger.startProgress('AAA').stop(); - logger.startProgress('BBB').stop(); - - expect(logger.statusText, 'AAA\nBBB\n'); - }, - ); - }); -} - -class FakeStdout extends Fake implements Stdout { - FakeStdout({required this.syncError, this.completeWithError = false}); - - final bool syncError; - final bool completeWithError; - final Completer _completer = Completer(); - - @override - void write(Object? object) { - if (syncError) { - throw Exception('Error!'); - } - Zone.current.runUnaryGuarded((_) { - if (completeWithError) { - _completer.completeError(Exception('Some pipe error')); - } else { - _completer.complete(); - throw Exception('Error!'); - } - }, null); - } - - @override - Future get done => _completer.future; -} diff --git a/packages/flutter_migrate/test/base/signals_test.dart b/packages/flutter_migrate/test/base/signals_test.dart deleted file mode 100644 index 0283fc3a9b8..00000000000 --- a/packages/flutter_migrate/test/base/signals_test.dart +++ /dev/null @@ -1,180 +0,0 @@ -// Copyright 2013 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 'dart:io' as io; - -import 'package:flutter_migrate/src/base/io.dart'; -import 'package:flutter_migrate/src/base/signals.dart'; -import 'package:test/fake.dart'; - -import '../src/common.dart'; - -void main() { - group('Signals', () { - late Signals signals; - late FakeProcessSignal fakeSignal; - late ProcessSignal signalUnderTest; - - setUp(() { - signals = Signals.test(); - fakeSignal = FakeProcessSignal(); - signalUnderTest = ProcessSignal(fakeSignal); - }); - - testWithoutContext('signal handler runs', () async { - final Completer completer = Completer(); - signals.addHandler(signalUnderTest, (ProcessSignal s) { - expect(s, signalUnderTest); - completer.complete(); - }); - - fakeSignal.controller.add(fakeSignal); - await completer.future; - }); - - testWithoutContext('signal handlers run in order', () async { - final Completer completer = Completer(); - - bool first = false; - - signals.addHandler(signalUnderTest, (ProcessSignal s) { - expect(s, signalUnderTest); - first = true; - }); - - signals.addHandler(signalUnderTest, (ProcessSignal s) { - expect(s, signalUnderTest); - expect(first, isTrue); - completer.complete(); - }); - - fakeSignal.controller.add(fakeSignal); - await completer.future; - }); - - testWithoutContext( - 'signal handlers do not cause concurrent modification errors when removing handlers in a signal callback', - () async { - final Completer completer = Completer(); - late Object token; - Future handle(ProcessSignal s) async { - expect(s, signalUnderTest); - expect(await signals.removeHandler(signalUnderTest, token), true); - completer.complete(); - } - - token = signals.addHandler(signalUnderTest, handle); - - fakeSignal.controller.add(fakeSignal); - await completer.future; - }, - ); - - testWithoutContext('signal handler error goes on error stream', () async { - final Exception exn = Exception('Error'); - signals.addHandler(signalUnderTest, (ProcessSignal s) async { - throw exn; - }); - - final Completer completer = Completer(); - final List errList = []; - final StreamSubscription errSub = signals.errors.listen(( - Object err, - ) { - errList.add(err); - completer.complete(); - }); - - fakeSignal.controller.add(fakeSignal); - await completer.future; - await errSub.cancel(); - expect(errList, contains(exn)); - }); - - testWithoutContext('removed signal handler does not run', () async { - final Object token = signals.addHandler(signalUnderTest, ( - ProcessSignal s, - ) async { - fail('Signal handler should have been removed.'); - }); - - await signals.removeHandler(signalUnderTest, token); - - final List errList = []; - final StreamSubscription errSub = signals.errors.listen(( - Object err, - ) { - errList.add(err); - }); - - fakeSignal.controller.add(fakeSignal); - - await errSub.cancel(); - expect(errList, isEmpty); - }); - - testWithoutContext('non-removed signal handler still runs', () async { - final Completer completer = Completer(); - signals.addHandler(signalUnderTest, (ProcessSignal s) { - expect(s, signalUnderTest); - completer.complete(); - }); - - final Object token = signals.addHandler(signalUnderTest, ( - ProcessSignal s, - ) async { - fail('Signal handler should have been removed.'); - }); - await signals.removeHandler(signalUnderTest, token); - - final List errList = []; - final StreamSubscription errSub = signals.errors.listen(( - Object err, - ) { - errList.add(err); - }); - - fakeSignal.controller.add(fakeSignal); - await completer.future; - await errSub.cancel(); - expect(errList, isEmpty); - }); - - testWithoutContext('only handlers for the correct signal run', () async { - final FakeProcessSignal mockSignal2 = FakeProcessSignal(); - final ProcessSignal otherSignal = ProcessSignal(mockSignal2); - - final Completer completer = Completer(); - signals.addHandler(signalUnderTest, (ProcessSignal s) { - expect(s, signalUnderTest); - completer.complete(); - }); - - signals.addHandler(otherSignal, (ProcessSignal s) async { - fail('Wrong signal!.'); - }); - - final List errList = []; - final StreamSubscription errSub = signals.errors.listen(( - Object err, - ) { - errList.add(err); - }); - - fakeSignal.controller.add(fakeSignal); - await completer.future; - await errSub.cancel(); - expect(errList, isEmpty); - }); - }); -} - -class FakeProcessSignal extends Fake implements io.ProcessSignal { - final StreamController controller = - StreamController(); - - @override - Stream watch() => controller.stream; -} diff --git a/packages/flutter_migrate/test/base/terminal_test.dart b/packages/flutter_migrate/test/base/terminal_test.dart deleted file mode 100644 index a61913c907d..00000000000 --- a/packages/flutter_migrate/test/base/terminal_test.dart +++ /dev/null @@ -1,382 +0,0 @@ -// Copyright 2013 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 'package:flutter_migrate/src/base/io.dart'; -import 'package:flutter_migrate/src/base/logger.dart'; -import 'package:flutter_migrate/src/base/terminal.dart'; -import 'package:test/fake.dart'; - -import '../src/common.dart'; - -void main() { - group('output preferences', () { - testWithoutContext('can wrap output', () async { - final BufferLogger bufferLogger = BufferLogger( - outputPreferences: OutputPreferences.test( - wrapText: true, - wrapColumn: 40, - ), - terminal: TestTerminal(), - ); - bufferLogger.printStatus('0123456789' * 8); - - expect(bufferLogger.statusText, equals('${'0123456789' * 4}\n' * 2)); - }); - - testWithoutContext('can turn off wrapping', () async { - final BufferLogger bufferLogger = BufferLogger( - outputPreferences: OutputPreferences.test(), - terminal: TestTerminal(), - ); - final String testString = '0123456789' * 20; - bufferLogger.printStatus(testString); - - expect(bufferLogger.statusText, equals('$testString\n')); - }); - }); - - group('ANSI coloring and bold', () { - late AnsiTerminal terminal; - - setUp(() { - terminal = AnsiTerminal( - stdio: Stdio(), // Danger, using real stdio. - supportsColor: true, - ); - }); - - testWithoutContext('adding colors works', () { - for (final TerminalColor color in TerminalColor.values) { - expect( - terminal.color('output', color), - equals( - '${AnsiTerminal.colorCode(color)}output${AnsiTerminal.resetColor}', - ), - ); - } - }); - - testWithoutContext('adding bold works', () { - expect( - terminal.bolden('output'), - equals('${AnsiTerminal.bold}output${AnsiTerminal.resetBold}'), - ); - }); - - testWithoutContext('nesting bold within color works', () { - expect( - terminal.color(terminal.bolden('output'), TerminalColor.blue), - equals( - '${AnsiTerminal.blue}${AnsiTerminal.bold}output${AnsiTerminal.resetBold}${AnsiTerminal.resetColor}', - ), - ); - expect( - terminal.color( - 'non-bold ${terminal.bolden('output')} also non-bold', - TerminalColor.blue, - ), - equals( - '${AnsiTerminal.blue}non-bold ${AnsiTerminal.bold}output${AnsiTerminal.resetBold} also non-bold${AnsiTerminal.resetColor}', - ), - ); - }); - - testWithoutContext('nesting color within bold works', () { - expect( - terminal.bolden(terminal.color('output', TerminalColor.blue)), - equals( - '${AnsiTerminal.bold}${AnsiTerminal.blue}output${AnsiTerminal.resetColor}${AnsiTerminal.resetBold}', - ), - ); - expect( - terminal.bolden( - 'non-color ${terminal.color('output', TerminalColor.blue)} also non-color', - ), - equals( - '${AnsiTerminal.bold}non-color ${AnsiTerminal.blue}output${AnsiTerminal.resetColor} also non-color${AnsiTerminal.resetBold}', - ), - ); - }); - - testWithoutContext('nesting color within color works', () { - expect( - terminal.color( - terminal.color('output', TerminalColor.blue), - TerminalColor.magenta, - ), - equals( - '${AnsiTerminal.magenta}${AnsiTerminal.blue}output${AnsiTerminal.resetColor}${AnsiTerminal.magenta}${AnsiTerminal.resetColor}', - ), - ); - expect( - terminal.color( - 'magenta ${terminal.color('output', TerminalColor.blue)} also magenta', - TerminalColor.magenta, - ), - equals( - '${AnsiTerminal.magenta}magenta ${AnsiTerminal.blue}output${AnsiTerminal.resetColor}${AnsiTerminal.magenta} also magenta${AnsiTerminal.resetColor}', - ), - ); - }); - - testWithoutContext('nesting bold within bold works', () { - expect( - terminal.bolden(terminal.bolden('output')), - equals('${AnsiTerminal.bold}output${AnsiTerminal.resetBold}'), - ); - expect( - terminal.bolden('bold ${terminal.bolden('output')} still bold'), - equals( - '${AnsiTerminal.bold}bold output still bold${AnsiTerminal.resetBold}', - ), - ); - }); - }); - - group('character input prompt', () { - late AnsiTerminal terminalUnderTest; - - setUp(() { - terminalUnderTest = TestTerminal(stdio: FakeStdio()); - }); - - testWithoutContext( - 'character prompt throws if usesTerminalUi is false', - () async { - expect( - terminalUnderTest.promptForCharInput( - ['a', 'b', 'c'], - prompt: 'Please choose something', - logger: BufferLogger.test(), - ), - throwsStateError, - ); - }, - ); - - testWithoutContext('character prompt', () async { - final BufferLogger bufferLogger = BufferLogger( - terminal: terminalUnderTest, - outputPreferences: OutputPreferences.test(), - ); - terminalUnderTest.usesTerminalUi = true; - mockStdInStream = - Stream.fromFutures(>[ - Future.value('d'), // Not in accepted list. - Future.value('\n'), // Not in accepted list - Future.value('b'), - ]).asBroadcastStream(); - final String choice = await terminalUnderTest.promptForCharInput( - ['a', 'b', 'c'], - prompt: 'Please choose something', - logger: bufferLogger, - ); - expect(choice, 'b'); - expect( - bufferLogger.statusText, - 'Please choose something [a|b|c]: d\n' - 'Please choose something [a|b|c]: \n' - 'Please choose something [a|b|c]: b\n', - ); - }); - - testWithoutContext( - 'default character choice without displayAcceptedCharacters', - () async { - final BufferLogger bufferLogger = BufferLogger( - terminal: terminalUnderTest, - outputPreferences: OutputPreferences.test(), - ); - terminalUnderTest.usesTerminalUi = true; - mockStdInStream = - Stream.fromFutures(>[ - Future.value('\n'), // Not in accepted list - ]).asBroadcastStream(); - final String choice = await terminalUnderTest.promptForCharInput( - ['a', 'b', 'c'], - prompt: 'Please choose something', - displayAcceptedCharacters: false, - defaultChoiceIndex: 1, // which is b. - logger: bufferLogger, - ); - - expect(choice, 'b'); - expect(bufferLogger.statusText, 'Please choose something: \n'); - }, - ); - - testWithoutContext( - 'Does not set single char mode when a terminal is not attached', - () { - final Stdio stdio = FakeStdio()..stdinHasTerminal = false; - final AnsiTerminal ansiTerminal = AnsiTerminal(stdio: stdio); - - expect(() => ansiTerminal.singleCharMode = true, returnsNormally); - }, - ); - }); - - testWithoutContext('AnsiTerminal.preferredStyle', () { - final Stdio stdio = FakeStdio(); - expect( - AnsiTerminal(stdio: stdio).preferredStyle, - 0, - ); // Defaults to 0 for backwards compatibility. - - expect(AnsiTerminal(stdio: stdio, now: DateTime(2018)).preferredStyle, 0); - expect( - AnsiTerminal(stdio: stdio, now: DateTime(2018, 1, 2)).preferredStyle, - 1, - ); - expect( - AnsiTerminal(stdio: stdio, now: DateTime(2018, 1, 3)).preferredStyle, - 2, - ); - expect( - AnsiTerminal(stdio: stdio, now: DateTime(2018, 1, 4)).preferredStyle, - 3, - ); - expect( - AnsiTerminal(stdio: stdio, now: DateTime(2018, 1, 5)).preferredStyle, - 4, - ); - expect( - AnsiTerminal(stdio: stdio, now: DateTime(2018, 1, 6)).preferredStyle, - 5, - ); - expect( - AnsiTerminal(stdio: stdio, now: DateTime(2018, 1, 7)).preferredStyle, - 5, - ); - expect( - AnsiTerminal(stdio: stdio, now: DateTime(2018, 1, 8)).preferredStyle, - 0, - ); - expect( - AnsiTerminal(stdio: stdio, now: DateTime(2018, 1, 9)).preferredStyle, - 1, - ); - expect( - AnsiTerminal(stdio: stdio, now: DateTime(2018, 1, 10)).preferredStyle, - 2, - ); - expect( - AnsiTerminal(stdio: stdio, now: DateTime(2018, 1, 11)).preferredStyle, - 3, - ); - - expect( - AnsiTerminal(stdio: stdio, now: DateTime(2018, 1, 1, 1)).preferredStyle, - 0, - ); - expect( - AnsiTerminal(stdio: stdio, now: DateTime(2018, 1, 2, 1)).preferredStyle, - 1, - ); - expect( - AnsiTerminal(stdio: stdio, now: DateTime(2018, 1, 3, 1)).preferredStyle, - 2, - ); - expect( - AnsiTerminal(stdio: stdio, now: DateTime(2018, 1, 4, 1)).preferredStyle, - 3, - ); - expect( - AnsiTerminal(stdio: stdio, now: DateTime(2018, 1, 5, 1)).preferredStyle, - 4, - ); - expect( - AnsiTerminal(stdio: stdio, now: DateTime(2018, 1, 6, 1)).preferredStyle, - 6, - ); - expect( - AnsiTerminal(stdio: stdio, now: DateTime(2018, 1, 7, 1)).preferredStyle, - 6, - ); - expect( - AnsiTerminal(stdio: stdio, now: DateTime(2018, 1, 8, 1)).preferredStyle, - 0, - ); - expect( - AnsiTerminal(stdio: stdio, now: DateTime(2018, 1, 9, 1)).preferredStyle, - 1, - ); - expect( - AnsiTerminal(stdio: stdio, now: DateTime(2018, 1, 10, 1)).preferredStyle, - 2, - ); - expect( - AnsiTerminal(stdio: stdio, now: DateTime(2018, 1, 11, 1)).preferredStyle, - 3, - ); - - expect( - AnsiTerminal(stdio: stdio, now: DateTime(2018, 1, 1, 23)).preferredStyle, - 0, - ); - expect( - AnsiTerminal(stdio: stdio, now: DateTime(2018, 1, 2, 23)).preferredStyle, - 1, - ); - expect( - AnsiTerminal(stdio: stdio, now: DateTime(2018, 1, 3, 23)).preferredStyle, - 2, - ); - expect( - AnsiTerminal(stdio: stdio, now: DateTime(2018, 1, 4, 23)).preferredStyle, - 3, - ); - expect( - AnsiTerminal(stdio: stdio, now: DateTime(2018, 1, 5, 23)).preferredStyle, - 4, - ); - expect( - AnsiTerminal(stdio: stdio, now: DateTime(2018, 1, 6, 23)).preferredStyle, - 28, - ); - expect( - AnsiTerminal(stdio: stdio, now: DateTime(2018, 1, 7, 23)).preferredStyle, - 28, - ); - expect( - AnsiTerminal(stdio: stdio, now: DateTime(2018, 1, 8, 23)).preferredStyle, - 0, - ); - expect( - AnsiTerminal(stdio: stdio, now: DateTime(2018, 1, 9, 23)).preferredStyle, - 1, - ); - expect( - AnsiTerminal(stdio: stdio, now: DateTime(2018, 1, 10, 23)).preferredStyle, - 2, - ); - expect( - AnsiTerminal(stdio: stdio, now: DateTime(2018, 1, 11, 23)).preferredStyle, - 3, - ); - }); -} - -late Stream mockStdInStream; - -class TestTerminal extends AnsiTerminal { - TestTerminal({Stdio? stdio, DateTime? now}) - : super(stdio: stdio ?? Stdio(), now: now ?? DateTime(2018)); - - @override - Stream get keystrokes { - return mockStdInStream; - } - - @override - bool singleCharMode = false; - - @override - int get preferredStyle => 0; -} - -class FakeStdio extends Fake implements Stdio { - @override - bool stdinHasTerminal = false; -} diff --git a/packages/flutter_migrate/test/compute_test.dart b/packages/flutter_migrate/test/compute_test.dart deleted file mode 100644 index 925881ab204..00000000000 --- a/packages/flutter_migrate/test/compute_test.dart +++ /dev/null @@ -1,1021 +0,0 @@ -// Copyright 2013 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 'package:flutter_migrate/src/base/common.dart'; -import 'package:flutter_migrate/src/base/file_system.dart'; -import 'package:flutter_migrate/src/base/logger.dart'; -import 'package:flutter_migrate/src/base/project.dart'; -import 'package:flutter_migrate/src/base/signals.dart'; -import 'package:flutter_migrate/src/compute.dart'; -import 'package:flutter_migrate/src/environment.dart'; -import 'package:flutter_migrate/src/flutter_project_metadata.dart'; -import 'package:flutter_migrate/src/migrate_logger.dart'; -import 'package:flutter_migrate/src/result.dart'; -import 'package:flutter_migrate/src/utils.dart'; -import 'package:path/path.dart'; -import 'package:process/process.dart'; - -import 'environment_test.dart'; -import 'src/common.dart'; -import 'src/context.dart'; -import 'src/test_utils.dart'; -import 'test_data/migrate_project.dart'; - -void main() { - late FileSystem fileSystem; - late BufferLogger logger; - late MigrateUtils utils; - late MigrateContext context; - late MigrateResult result; - late Directory targetFlutterDirectory; - late Directory newerTargetFlutterDirectory; - late Directory currentDir; - late FlutterToolsEnvironment environment; - late ProcessManager processManager; - late FakeProcessManager envProcessManager; - late String separator; - - const String oldSdkRevision = '5391447fae6209bb21a89e6a5a6583cac1af9b4b'; - const String newSdkRevision = '85684f9300908116a78138ea4c6036c35c9a1236'; - - Future setUpFullEnv() async { - fileSystem = LocalFileSystem.test(signals: LocalSignals.instance); - currentDir = createResolvedTempDirectorySync('current_app.'); - logger = BufferLogger.test(); - processManager = const LocalProcessManager(); - utils = MigrateUtils( - logger: logger, - fileSystem: fileSystem, - processManager: processManager, - ); - await MigrateProject.installProject('version:1.22.6_stable', currentDir); - final FlutterProjectFactory flutterFactory = FlutterProjectFactory(); - final FlutterProject flutterProject = flutterFactory.fromDirectory( - currentDir, - ); - result = MigrateResult.empty(); - final MigrateLogger migrateLogger = MigrateLogger( - logger: logger, - verbose: true, - ); - migrateLogger.start(); - separator = isWindows ? r'\\' : '/'; - envProcessManager = FakeProcessManager(''' -{ - "FlutterProject.directory": "/Users/test/flutter", - "FlutterProject.metadataFile": "/Users/test/flutter/.metadata", - "FlutterProject.android.exists": false, - "FlutterProject.ios.exists": false, - "FlutterProject.web.exists": false, - "FlutterProject.macos.exists": false, - "FlutterProject.linux.exists": false, - "FlutterProject.windows.exists": false, - "FlutterProject.fuchsia.exists": false, - "FlutterProject.android.isKotlin": false, - "FlutterProject.ios.isSwift": false, - "FlutterProject.isModule": false, - "FlutterProject.isPlugin": false, - "FlutterProject.manifest.appname": "test_app_name", - "FlutterVersion.frameworkRevision": "4e181f012c717777681862e4771af5a941774bb9", - "Platform.operatingSystem": "macos", - "Platform.isAndroid": true, - "Platform.isIOS": false, - "Platform.isWindows": ${isWindows ? 'true' : 'false'}, - "Platform.isMacOS": ${isMacOS ? 'true' : 'false'}, - "Platform.isFuchsia": false, - "Platform.pathSeparator": "$separator", - "Cache.flutterRoot": "/Users/test/flutter" -} -'''); - environment = - await FlutterToolsEnvironment.initializeFlutterToolsEnvironment( - envProcessManager, - logger, - ); - context = MigrateContext( - flutterProject: flutterProject, - skippedPrefixes: {}, - fileSystem: fileSystem, - migrateLogger: migrateLogger, - migrateUtils: utils, - environment: environment, - ); - targetFlutterDirectory = createResolvedTempDirectorySync( - 'targetFlutterDir.', - ); - newerTargetFlutterDirectory = createResolvedTempDirectorySync( - 'newerTargetFlutterDir.', - ); - await context.migrateUtils.cloneFlutter( - oldSdkRevision, - targetFlutterDirectory.absolute.path, - ); - await context.migrateUtils.cloneFlutter( - newSdkRevision, - newerTargetFlutterDirectory.absolute.path, - ); - } - - group( - 'MigrateFlutterProject', - () { - setUp(() async { - await setUpFullEnv(); - }); - - tearDown(() async { - tryToDelete(targetFlutterDirectory); - tryToDelete(newerTargetFlutterDirectory); - }); - - testUsingContext( - 'MigrateTargetFlutterProject creates', - () async { - final Directory workingDir = createResolvedTempDirectorySync( - 'migrate_working_dir.', - ); - final Directory targetDir = createResolvedTempDirectorySync( - 'target_dir.', - ); - result.generatedTargetTemplateDirectory = targetDir; - workingDir.createSync(recursive: true); - final MigrateTargetFlutterProject targetProject = - MigrateTargetFlutterProject( - path: null, - directory: targetDir, - name: 'base', - androidLanguage: 'java', - iosLanguage: 'objc', - ); - - await targetProject.createProject( - context, - result, - oldSdkRevision, //targetRevision - targetFlutterDirectory, //targetFlutterDirectory - ); - - expect(targetDir.childFile('pubspec.yaml').existsSync(), true); - expect( - targetDir - .childDirectory('android') - .childFile('build.gradle') - .existsSync(), - true, - ); - }, - timeout: const Timeout(Duration(seconds: 500)), - ); - - testUsingContext( - 'MigrateBaseFlutterProject creates', - () async { - final Directory workingDir = createResolvedTempDirectorySync( - 'migrate_working_dir.', - ); - final Directory baseDir = createResolvedTempDirectorySync( - 'base_dir.', - ); - result.generatedBaseTemplateDirectory = baseDir; - workingDir.createSync(recursive: true); - final MigrateBaseFlutterProject baseProject = - MigrateBaseFlutterProject( - path: null, - directory: baseDir, - name: 'base', - androidLanguage: 'java', - iosLanguage: 'objc', - ); - - await baseProject.createProject( - context, - result, - [oldSdkRevision], //revisionsList - >{ - oldSdkRevision: [ - MigratePlatformConfig( - component: FlutterProjectComponent.android, - ), - MigratePlatformConfig(component: FlutterProjectComponent.ios), - ], - }, //revisionToConfigs - oldSdkRevision, //fallbackRevision - oldSdkRevision, //targetRevision - targetFlutterDirectory, //targetFlutterDirectory - ); - - expect(baseDir.childFile('pubspec.yaml').existsSync(), true); - expect( - baseDir - .childDirectory('android') - .childFile('build.gradle') - .existsSync(), - true, - ); - }, - timeout: const Timeout(Duration(seconds: 500)), - ); - - testUsingContext( - 'Migrate___FlutterProject skips when path exists', - () async { - final Directory workingDir = createResolvedTempDirectorySync( - 'migrate_working_dir.', - ); - final Directory targetDir = createResolvedTempDirectorySync( - 'target_dir.', - ); - final Directory baseDir = createResolvedTempDirectorySync( - 'base_dir.', - ); - result.generatedTargetTemplateDirectory = targetDir; - result.generatedBaseTemplateDirectory = baseDir; - workingDir.createSync(recursive: true); - - final MigrateBaseFlutterProject baseProject = - MigrateBaseFlutterProject( - path: 'some_existing_base_path', - directory: baseDir, - name: 'base', - androidLanguage: 'java', - iosLanguage: 'objc', - ); - final MigrateTargetFlutterProject targetProject = - MigrateTargetFlutterProject( - path: 'some_existing_target_path', - directory: targetDir, - name: 'base', - androidLanguage: 'java', - iosLanguage: 'objc', - ); - - await baseProject.createProject( - context, - result, - [oldSdkRevision], //revisionsList - >{ - oldSdkRevision: [ - MigratePlatformConfig( - component: FlutterProjectComponent.android, - ), - MigratePlatformConfig(component: FlutterProjectComponent.ios), - ], - }, //revisionToConfigs - oldSdkRevision, //fallbackRevision - oldSdkRevision, //targetRevision - targetFlutterDirectory, //targetFlutterDirectory - ); - - expect(baseDir.childFile('pubspec.yaml').existsSync(), false); - expect( - baseDir - .childDirectory('android') - .childFile('build.gradle') - .existsSync(), - false, - ); - - await targetProject.createProject( - context, - result, - oldSdkRevision, //revisionsList - targetFlutterDirectory, //targetFlutterDirectory - ); - - expect(targetDir.childFile('pubspec.yaml').existsSync(), false); - expect( - targetDir - .childDirectory('android') - .childFile('build.gradle') - .existsSync(), - false, - ); - }, - timeout: const Timeout(Duration(seconds: 500)), - ); - }, - // TODO(stuartmorgan): These should not be unit tests, see - // https://github.com/flutter/flutter/issues/121257. - skip: 'TODO: Speed up, or move to another type of test', - ); - - group( - 'MigrateRevisions', - () { - setUp(() async { - fileSystem = LocalFileSystem.test(signals: LocalSignals.instance); - currentDir = createResolvedTempDirectorySync('current_app.'); - logger = BufferLogger.test(); - utils = MigrateUtils( - logger: logger, - fileSystem: fileSystem, - processManager: const LocalProcessManager(), - ); - await MigrateProject.installProject( - 'version:1.22.6_stable', - currentDir, - ); - final FlutterProjectFactory flutterFactory = FlutterProjectFactory(); - final FlutterProject flutterProject = flutterFactory.fromDirectory( - currentDir, - ); - result = MigrateResult.empty(); - final MigrateLogger migrateLogger = MigrateLogger( - logger: logger, - verbose: true, - ); - migrateLogger.start(); - context = MigrateContext( - flutterProject: flutterProject, - skippedPrefixes: {}, - fileSystem: fileSystem, - migrateLogger: migrateLogger, - migrateUtils: utils, - environment: environment, - ); - }); - - testUsingContext('extracts revisions underpopulated metadata', () async { - final MigrateRevisions revisions = MigrateRevisions( - context: context, - baseRevision: oldSdkRevision, - allowFallbackBaseRevision: true, - platforms: [ - SupportedPlatform.android, - SupportedPlatform.ios, - ], - environment: environment, - ); - - expect(revisions.revisionsList, [oldSdkRevision]); - expect(revisions.fallbackRevision, oldSdkRevision); - expect( - revisions.metadataRevision, - '9b2d32b605630f28625709ebd9d78ab3016b2bf6', - ); - expect(revisions.config.unmanagedFiles.isEmpty, false); - expect(revisions.config.platformConfigs.isEmpty, false); - expect(revisions.config.platformConfigs.length, 3); - expect( - revisions.config.platformConfigs.containsKey( - FlutterProjectComponent.root, - ), - true, - ); - expect( - revisions.config.platformConfigs.containsKey( - FlutterProjectComponent.android, - ), - true, - ); - expect( - revisions.config.platformConfigs.containsKey( - FlutterProjectComponent.ios, - ), - true, - ); - }); - - testUsingContext('extracts revisions full metadata', () async { - final File metadataFile = context.flutterProject.directory.childFile( - '.metadata', - ); - if (metadataFile.existsSync()) { - if (!tryToDelete(metadataFile)) { - // TODO(garyq): Inaccessible .metadata on windows. - // Skip test for now. We should reneable. - return; - } - } - metadataFile.createSync(recursive: true); - metadataFile.writeAsStringSync(''' -# This file tracks properties of this Flutter project. -# Used by Flutter tool to assess capabilities and perform upgrades etc. -# -# This file should be version controlled and should not be manually edited. - -version: - revision: 9b2d32b605630f28625709ebd9d78ab3016b2bf6 - channel: unknown - -project_type: app - -# Tracks metadata for the flutter migrate command -migration: - platforms: - - platform: root - create_revision: 9b2d32b605630f28625709ebd9d78ab3016b2bf6 - base_revision: 9b2d32b605630f28625709ebd9d78ab3016b2bf6 - - platform: android - create_revision: 9b2d32b605630f28625709ebd9d78ab3016b2bf6 - base_revision: 9b2d32b605630f28625709ebd9d78ab3016b2bf6 - - platform: ios - create_revision: 9b2d32b605630f28625709ebd9d78ab3016b2bf6 - base_revision: 9b2d32b605630f28625709ebd9d78ab3016b2bf6 - - platform: linux - create_revision: 9b2d32b605630f28625709ebd9d78ab3016b2bf6 - base_revision: 9b2d32b605630f28625709ebd9d78ab3016b2bf6 - - platform: macos - create_revision: 9b2d32b605630f28625709ebd9d78ab3016b2bf6 - base_revision: 9b2d32b605630f28625709ebd9d78ab3016b2bf6 - - platform: web - create_revision: 9b2d32b605630f28625709ebd9d78ab3016b2bf6 - base_revision: 9b2d32b605630f28625709ebd9d78ab3016b2bf6 - - platform: windows - create_revision: 36427af29421f406ac95ff55ea31d1dc49a45b5f - base_revision: 36427af29421f406ac95ff55ea31d1dc49a45b5f - - # User provided section - - # List of Local paths (relative to this file) that should be - # ignored by the migrate tool. - # - # Files that are not part of the templates will be ignored by default. - unmanaged_files: - - 'lib/main.dart' - - 'blah.dart' - - 'ios/Runner.xcodeproj/project.pbxproj' -''', flush: true); - - final MigrateRevisions revisions = MigrateRevisions( - context: context, - baseRevision: oldSdkRevision, - allowFallbackBaseRevision: true, - platforms: [ - SupportedPlatform.android, - SupportedPlatform.ios, - ], - environment: environment, - ); - - expect(revisions.revisionsList, [oldSdkRevision]); - expect(revisions.fallbackRevision, oldSdkRevision); - expect( - revisions.metadataRevision, - '9b2d32b605630f28625709ebd9d78ab3016b2bf6', - ); - expect(revisions.config.unmanagedFiles.isEmpty, false); - expect(revisions.config.unmanagedFiles.length, 3); - expect(revisions.config.unmanagedFiles.contains('lib/main.dart'), true); - expect(revisions.config.unmanagedFiles.contains('blah.dart'), true); - expect( - revisions.config.unmanagedFiles.contains( - 'ios/Runner.xcodeproj/project.pbxproj', - ), - true, - ); - - expect(revisions.config.platformConfigs.length, 7); - expect( - revisions.config.platformConfigs.containsKey( - FlutterProjectComponent.root, - ), - true, - ); - expect( - revisions.config.platformConfigs.containsKey( - FlutterProjectComponent.android, - ), - true, - ); - expect( - revisions.config.platformConfigs.containsKey( - FlutterProjectComponent.ios, - ), - true, - ); - expect( - revisions.config.platformConfigs.containsKey( - FlutterProjectComponent.linux, - ), - true, - ); - expect( - revisions.config.platformConfigs.containsKey( - FlutterProjectComponent.macos, - ), - true, - ); - expect( - revisions.config.platformConfigs.containsKey( - FlutterProjectComponent.web, - ), - true, - ); - expect( - revisions.config.platformConfigs.containsKey( - FlutterProjectComponent.windows, - ), - true, - ); - - expect( - revisions - .config - .platformConfigs[FlutterProjectComponent.root]! - .createRevision, - '9b2d32b605630f28625709ebd9d78ab3016b2bf6', - ); - expect( - revisions - .config - .platformConfigs[FlutterProjectComponent.android]! - .createRevision, - '9b2d32b605630f28625709ebd9d78ab3016b2bf6', - ); - expect( - revisions - .config - .platformConfigs[FlutterProjectComponent.ios]! - .createRevision, - '9b2d32b605630f28625709ebd9d78ab3016b2bf6', - ); - expect( - revisions - .config - .platformConfigs[FlutterProjectComponent.linux]! - .createRevision, - '9b2d32b605630f28625709ebd9d78ab3016b2bf6', - ); - expect( - revisions - .config - .platformConfigs[FlutterProjectComponent.macos]! - .createRevision, - '9b2d32b605630f28625709ebd9d78ab3016b2bf6', - ); - expect( - revisions - .config - .platformConfigs[FlutterProjectComponent.web]! - .createRevision, - '9b2d32b605630f28625709ebd9d78ab3016b2bf6', - ); - expect( - revisions - .config - .platformConfigs[FlutterProjectComponent.windows]! - .createRevision, - '36427af29421f406ac95ff55ea31d1dc49a45b5f', - ); - - expect( - revisions - .config - .platformConfigs[FlutterProjectComponent.root]! - .baseRevision, - '9b2d32b605630f28625709ebd9d78ab3016b2bf6', - ); - expect( - revisions - .config - .platformConfigs[FlutterProjectComponent.android]! - .baseRevision, - '9b2d32b605630f28625709ebd9d78ab3016b2bf6', - ); - expect( - revisions - .config - .platformConfigs[FlutterProjectComponent.ios]! - .baseRevision, - '9b2d32b605630f28625709ebd9d78ab3016b2bf6', - ); - expect( - revisions - .config - .platformConfigs[FlutterProjectComponent.linux]! - .baseRevision, - '9b2d32b605630f28625709ebd9d78ab3016b2bf6', - ); - expect( - revisions - .config - .platformConfigs[FlutterProjectComponent.macos]! - .baseRevision, - '9b2d32b605630f28625709ebd9d78ab3016b2bf6', - ); - expect( - revisions - .config - .platformConfigs[FlutterProjectComponent.web]! - .baseRevision, - '9b2d32b605630f28625709ebd9d78ab3016b2bf6', - ); - expect( - revisions - .config - .platformConfigs[FlutterProjectComponent.windows]! - .baseRevision, - '36427af29421f406ac95ff55ea31d1dc49a45b5f', - ); - }); - }, - // TODO(stuartmorgan): These should not be unit tests, see - // https://github.com/flutter/flutter/issues/121257. - skip: 'TODO: Speed up, or move to another type of test', - ); - - group( - 'project operations', - () { - setUp(() async { - await setUpFullEnv(); - }); - - tearDown(() async { - tryToDelete(targetFlutterDirectory); - tryToDelete(newerTargetFlutterDirectory); - }); - - testUsingContext('diff base and target', () async { - final Directory workingDir = createResolvedTempDirectorySync( - 'migrate_working_dir.', - ); - final Directory targetDir = createResolvedTempDirectorySync( - 'target_dir.', - ); - final Directory baseDir = createResolvedTempDirectorySync('base_dir.'); - result.generatedTargetTemplateDirectory = targetDir; - result.generatedBaseTemplateDirectory = baseDir; - workingDir.createSync(recursive: true); - - final MigrateBaseFlutterProject baseProject = MigrateBaseFlutterProject( - path: null, - directory: baseDir, - name: 'base', - androidLanguage: 'java', - iosLanguage: 'objc', - ); - final MigrateTargetFlutterProject targetProject = - MigrateTargetFlutterProject( - path: null, - directory: targetDir, - name: 'base', - androidLanguage: 'java', - iosLanguage: 'objc', - ); - - await baseProject.createProject( - context, - result, - [oldSdkRevision], //revisionsList - >{ - oldSdkRevision: [ - MigratePlatformConfig(component: FlutterProjectComponent.android), - MigratePlatformConfig(component: FlutterProjectComponent.ios), - ], - }, //revisionToConfigs - oldSdkRevision, //fallbackRevision - oldSdkRevision, //targetRevision - targetFlutterDirectory, //targetFlutterDirectory - ); - - expect(baseDir.childFile('pubspec.yaml').existsSync(), true); - expect( - baseDir - .childDirectory('android') - .childFile('build.gradle') - .existsSync(), - true, - ); - - await targetProject.createProject( - context, - result, - newSdkRevision, //revisionsList - newerTargetFlutterDirectory, //targetFlutterDirectory - ); - - expect(targetDir.childFile('pubspec.yaml').existsSync(), true); - expect( - targetDir - .childDirectory('android') - .childFile('build.gradle') - .existsSync(), - true, - ); - - final Map diffResults = await baseProject.diff( - context, - targetProject, - ); - final Map canonicalizedDiffResults = - {}; - for (final MapEntry entry in diffResults.entries) { - canonicalizedDiffResults[canonicalize(entry.key)] = entry.value; - } - result.diffMap.addAll(diffResults); - - List expectedFiles = [ - '.metadata', - 'ios/Runner.xcworkspace/contents.xcworkspacedata', - 'ios/Runner/AppDelegate.h', - 'ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png', - 'ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png', - 'ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md', - 'ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json', - 'ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png', - 'ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png', - 'ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png', - 'ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png', - 'ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png', - 'ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png', - 'ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png', - 'ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png', - 'ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json', - 'ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png', - 'ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png', - 'ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png', - 'ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png', - 'ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png', - 'ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png', - 'ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png', - 'ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png', - 'ios/Runner/Base.lproj/LaunchScreen.storyboard', - 'ios/Runner/Base.lproj/Main.storyboard', - 'ios/Runner/main.m', - 'ios/Runner/AppDelegate.m', - 'ios/Runner/Info.plist', - 'ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata', - 'ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme', - 'ios/Flutter/Debug.xcconfig', - 'ios/Flutter/Release.xcconfig', - 'ios/Flutter/AppFrameworkInfo.plist', - 'pubspec.yaml', - '.gitignore', - 'android/base_android.iml', - 'android/app/build.gradle', - 'android/app/src/main/res/mipmap-mdpi/ic_launcher.png', - 'android/app/src/main/res/mipmap-hdpi/ic_launcher.png', - 'android/app/src/main/res/drawable/launch_background.xml', - 'android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png', - 'android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png', - 'android/app/src/main/res/values/styles.xml', - 'android/app/src/main/res/mipmap-xhdpi/ic_launcher.png', - 'android/app/src/main/AndroidManifest.xml', - 'android/app/src/main/java/com/example/base/MainActivity.java', - 'android/local.properties', - 'android/gradle/wrapper/gradle-wrapper.jar', - 'android/gradle/wrapper/gradle-wrapper.properties', - 'android/gradlew', - 'android/build.gradle', - 'android/gradle.properties', - 'android/gradlew.bat', - 'android/settings.gradle', - 'base.iml', - '.idea/runConfigurations/main_dart.xml', - '.idea/libraries/Dart_SDK.xml', - '.idea/libraries/KotlinJavaRuntime.xml', - '.idea/libraries/Flutter_for_Android.xml', - '.idea/workspace.xml', - '.idea/modules.xml', - ]; - expectedFiles = List.from( - expectedFiles.map((String e) => canonicalize(e)), - ); - expect(diffResults.length, 62); - expect(expectedFiles.length, 62); - for (final String diffResultPath in canonicalizedDiffResults.keys) { - expect(expectedFiles.contains(diffResultPath), true); - } - // Spot check diffs on key files: - expect( - canonicalizedDiffResults[canonicalize('android/build.gradle')]!.diff, - contains(r''' -@@ -1,18 +1,20 @@ - buildscript { -+ ext.kotlin_version = '1.6.10' - repositories { - google() -- jcenter() -+ mavenCentral() - }'''), - ); - expect( - canonicalizedDiffResults[canonicalize('android/build.gradle')]!.diff, - contains(r''' - dependencies { -- classpath 'com.android.tools.build:gradle:3.2.1' -+ classpath 'com.android.tools.build:gradle:7.1.2' -+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - } - }'''), - ); - expect( - canonicalizedDiffResults[canonicalize('android/build.gradle')]!.diff, - contains(r''' - allprojects { - repositories { - google() -- jcenter() -+ mavenCentral() - } - }'''), - ); - expect( - canonicalizedDiffResults[canonicalize( - 'android/app/src/main/AndroidManifest.xml', - )]! - .diff, - contains(r''' -@@ -1,39 +1,34 @@ - -- -- -- -- -- -- - -- -+ - -+ android:name="io.flutter.embedding.android.NormalTheme" -+ android:resource="@style/NormalTheme" -+ /> - - - - - -+ -+ - - '''), - ); - }, timeout: const Timeout(Duration(seconds: 500))); - - testUsingContext('Merge succeeds', () async { - final Directory workingDir = createResolvedTempDirectorySync( - 'migrate_working_dir.', - ); - final Directory targetDir = createResolvedTempDirectorySync( - 'target_dir.', - ); - final Directory baseDir = createResolvedTempDirectorySync('base_dir.'); - result.generatedTargetTemplateDirectory = targetDir; - result.generatedBaseTemplateDirectory = baseDir; - workingDir.createSync(recursive: true); - - final MigrateBaseFlutterProject baseProject = MigrateBaseFlutterProject( - path: null, - directory: baseDir, - name: 'base', - androidLanguage: 'java', - iosLanguage: 'objc', - ); - final MigrateTargetFlutterProject targetProject = - MigrateTargetFlutterProject( - path: null, - directory: targetDir, - name: 'base', - androidLanguage: 'java', - iosLanguage: 'objc', - ); - - await baseProject.createProject( - context, - result, - [oldSdkRevision], //revisionsList - >{ - oldSdkRevision: [ - MigratePlatformConfig(component: FlutterProjectComponent.android), - MigratePlatformConfig(component: FlutterProjectComponent.ios), - ], - }, //revisionToConfigs - oldSdkRevision, //fallbackRevision - oldSdkRevision, //targetRevision - targetFlutterDirectory, //targetFlutterDirectory - ); - - expect(baseDir.childFile('pubspec.yaml').existsSync(), true); - expect(baseDir.childFile('.metadata').existsSync(), true); - expect( - baseDir - .childDirectory('android') - .childFile('build.gradle') - .existsSync(), - true, - ); - - await targetProject.createProject( - context, - result, - newSdkRevision, //revisionsList - newerTargetFlutterDirectory, //targetFlutterDirectory - ); - - expect(targetDir.childFile('pubspec.yaml').existsSync(), true); - expect(targetDir.childFile('.metadata').existsSync(), true); - expect( - targetDir - .childDirectory('android') - .childFile('build.gradle') - .existsSync(), - true, - ); - - result.diffMap.addAll(await baseProject.diff(context, targetProject)); - - await MigrateFlutterProject.merge( - context, - result, - baseProject, - targetProject, - [], // unmanagedFiles - [], // unmanagedDirectories - false, // preferTwoWayMerge - ); - - List expectedMergedPaths = [ - '.metadata', - 'ios/Runner/Info.plist', - 'ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata', - 'ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme', - 'ios/Flutter/AppFrameworkInfo.plist', - 'pubspec.yaml', - '.gitignore', - 'android/app/build.gradle', - 'android/app/src/main/res/values/styles.xml', - 'android/app/src/main/AndroidManifest.xml', - 'android/gradle/wrapper/gradle-wrapper.properties', - 'android/build.gradle', - ]; - expectedMergedPaths = List.from( - expectedMergedPaths.map((String e) => canonicalize(e)), - ); - expect(result.mergeResults.length, 12); - expect(expectedMergedPaths.length, 12); - - for (final MergeResult mergeResult in result.mergeResults) { - expect( - expectedMergedPaths.contains(canonicalize(mergeResult.localPath)), - true, - ); - } - - expect(result.mergeResults[0].exitCode, 0); - expect(result.mergeResults[1].exitCode, 0); - expect(result.mergeResults[2].exitCode, 0); - expect(result.mergeResults[3].exitCode, 0); - expect(result.mergeResults[4].exitCode, 0); - expect(result.mergeResults[5].exitCode, 0); - expect(result.mergeResults[6].exitCode, 0); - expect(result.mergeResults[7].exitCode, 0); - expect(result.mergeResults[8].exitCode, 0); - expect(result.mergeResults[9].exitCode, 0); - expect(result.mergeResults[10].exitCode, 0); - expect(result.mergeResults[11].exitCode, 0); - - expect(result.mergeResults[0].hasConflict, false); - expect(result.mergeResults[1].hasConflict, false); - expect(result.mergeResults[2].hasConflict, false); - expect(result.mergeResults[3].hasConflict, false); - expect(result.mergeResults[4].hasConflict, false); - expect(result.mergeResults[5].hasConflict, false); - expect(result.mergeResults[6].hasConflict, false); - expect(result.mergeResults[7].hasConflict, false); - expect(result.mergeResults[8].hasConflict, false); - expect(result.mergeResults[9].hasConflict, false); - expect(result.mergeResults[10].hasConflict, false); - expect(result.mergeResults[11].hasConflict, false); - }, timeout: const Timeout(Duration(seconds: 500))); - }, - // TODO(stuartmorgan): These should not be unit tests, see - // https://github.com/flutter/flutter/issues/121257. - skip: 'TODO: Speed up, or move to another type of test', - ); -} diff --git a/packages/flutter_migrate/test/custom_merge_test.dart b/packages/flutter_migrate/test/custom_merge_test.dart deleted file mode 100644 index 4dd683afad4..00000000000 --- a/packages/flutter_migrate/test/custom_merge_test.dart +++ /dev/null @@ -1,280 +0,0 @@ -// Copyright 2013 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 'package:file/memory.dart'; - -import 'package:flutter_migrate/src/base/file_system.dart'; -import 'package:flutter_migrate/src/base/logger.dart'; -import 'package:flutter_migrate/src/custom_merge.dart'; -import 'package:flutter_migrate/src/utils.dart'; - -import 'src/common.dart'; - -void main() { - late FileSystem fileSystem; - late BufferLogger logger; - - setUpAll(() { - fileSystem = MemoryFileSystem.test(); - logger = BufferLogger.test(); - }); - - group('.metadata merge', () { - late MetadataCustomMerge merger; - - setUp(() { - merger = MetadataCustomMerge(logger: logger); - }); - - testWithoutContext('merges empty', () async { - const String current = ''; - const String base = ''; - const String target = ''; - final File currentFile = fileSystem.file('.metadata_current'); - final File baseFile = fileSystem.file('.metadata_base'); - final File targetFile = fileSystem.file('.metadata_target'); - - currentFile - ..createSync(recursive: true) - ..writeAsStringSync(current, flush: true); - baseFile - ..createSync(recursive: true) - ..writeAsStringSync(base, flush: true); - targetFile - ..createSync(recursive: true) - ..writeAsStringSync(target, flush: true); - - final StringMergeResult result = - merger.merge(currentFile, baseFile, targetFile) as StringMergeResult; - expect( - result.mergedString, - ''' -# This file tracks properties of this Flutter project. -# Used by Flutter tool to assess capabilities and perform upgrades etc. -# -# This file should be version controlled. - -version: - revision: null - channel: null - -project_type: ''' - ''' - - -# Tracks metadata for the flutter migrate command -migration: - platforms: - - # User provided section - - # List of Local paths (relative to this file) that should be - # ignored by the migrate tool. - # - # Files that are not part of the templates will be ignored by default. - unmanaged_files: - - 'lib/main.dart' - - 'ios/Runner.xcodeproj/project.pbxproj' -''', - ); - }); - - testWithoutContext('merge adds migration section', () async { - const String current = ''' -# my own comment -version: - revision: abcdefg12345 - channel: stable -project_type: app - '''; - const String base = ''' -version: - revision: abcdefg12345base - channel: stable -project_type: app -migration: - platforms: - - platform: root - create_revision: somecreaterevision - base_revision: somebaserevision - - platform: android - create_revision: somecreaterevision - base_revision: somebaserevision - unmanaged_files: - - 'lib/main.dart' - - 'ios/Runner.xcodeproj/project.pbxproj' -'''; - const String target = ''' -version: - revision: abcdefg12345target - channel: stable -project_type: app -migration: - platforms: - - platform: root - create_revision: somecreaterevision - base_revision: somebaserevision - - platform: android - create_revision: somecreaterevision - base_revision: somebaserevision - unmanaged_files: - - 'lib/main.dart' - - 'ios/Runner.xcodeproj/project.pbxproj' -'''; - final File currentFile = fileSystem.file('.metadata_current'); - final File baseFile = fileSystem.file('.metadata_base'); - final File targetFile = fileSystem.file('.metadata_target'); - - currentFile - ..createSync(recursive: true) - ..writeAsStringSync(current, flush: true); - baseFile - ..createSync(recursive: true) - ..writeAsStringSync(base, flush: true); - targetFile - ..createSync(recursive: true) - ..writeAsStringSync(target, flush: true); - - final StringMergeResult result = - merger.merge(currentFile, baseFile, targetFile) as StringMergeResult; - expect(result.mergedString, ''' -# This file tracks properties of this Flutter project. -# Used by Flutter tool to assess capabilities and perform upgrades etc. -# -# This file should be version controlled. - -version: - revision: abcdefg12345target - channel: stable - -project_type: app - -# Tracks metadata for the flutter migrate command -migration: - platforms: - - platform: root - create_revision: somecreaterevision - base_revision: somebaserevision - - platform: android - create_revision: somecreaterevision - base_revision: somebaserevision - - # User provided section - - # List of Local paths (relative to this file) that should be - # ignored by the migrate tool. - # - # Files that are not part of the templates will be ignored by default. - unmanaged_files: - - 'lib/main.dart' - - 'ios/Runner.xcodeproj/project.pbxproj' -'''); - }); - - testWithoutContext('merge handles standard migration flow', () async { - const String current = ''' -# my own comment -version: - revision: abcdefg12345current - channel: stable -project_type: app -migration: - platforms: - - platform: root - create_revision: somecreaterevisioncurrent - base_revision: somebaserevisioncurrent - - platform: android - create_revision: somecreaterevisioncurrent - base_revision: somebaserevisioncurrent - unmanaged_files: - - 'lib/main.dart' - - 'new/file.dart' - '''; - const String base = ''' -version: - revision: abcdefg12345base - channel: stable -project_type: app -migration: - platforms: - - platform: root - create_revision: somecreaterevisionbase - base_revision: somebaserevisionbase - - platform: android - create_revision: somecreaterevisionbase - base_revision: somebaserevisionbase - unmanaged_files: - - 'lib/main.dart' - - 'ios/Runner.xcodeproj/project.pbxproj' - '''; - const String target = ''' -version: - revision: abcdefg12345target - channel: stable -project_type: app -migration: - platforms: - - platform: root - create_revision: somecreaterevisiontarget - base_revision: somebaserevisiontarget - - platform: android - create_revision: somecreaterevisiontarget - base_revision: somebaserevisiontarget - unmanaged_files: - - 'lib/main.dart' - - 'ios/Runner.xcodeproj/project.pbxproj' - - 'extra/file' -'''; - final File currentFile = fileSystem.file('.metadata_current'); - final File baseFile = fileSystem.file('.metadata_base'); - final File targetFile = fileSystem.file('.metadata_target'); - - currentFile - ..createSync(recursive: true) - ..writeAsStringSync(current, flush: true); - baseFile - ..createSync(recursive: true) - ..writeAsStringSync(base, flush: true); - targetFile - ..createSync(recursive: true) - ..writeAsStringSync(target, flush: true); - - final StringMergeResult result = - merger.merge(currentFile, baseFile, targetFile) as StringMergeResult; - expect(result.mergedString, ''' -# This file tracks properties of this Flutter project. -# Used by Flutter tool to assess capabilities and perform upgrades etc. -# -# This file should be version controlled. - -version: - revision: abcdefg12345target - channel: stable - -project_type: app - -# Tracks metadata for the flutter migrate command -migration: - platforms: - - platform: root - create_revision: somecreaterevisioncurrent - base_revision: somebaserevisiontarget - - platform: android - create_revision: somecreaterevisioncurrent - base_revision: somebaserevisiontarget - - # User provided section - - # List of Local paths (relative to this file) that should be - # ignored by the migrate tool. - # - # Files that are not part of the templates will be ignored by default. - unmanaged_files: - - 'lib/main.dart' - - 'new/file.dart' - - 'extra/file' -'''); - }); - }); -} diff --git a/packages/flutter_migrate/test/environment_test.dart b/packages/flutter_migrate/test/environment_test.dart deleted file mode 100644 index e38351eb832..00000000000 --- a/packages/flutter_migrate/test/environment_test.dart +++ /dev/null @@ -1,129 +0,0 @@ -// Copyright 2013 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:convert'; - -import 'package:flutter_migrate/src/base/common.dart'; -import 'package:flutter_migrate/src/base/context.dart'; -import 'package:flutter_migrate/src/base/file_system.dart'; -import 'package:flutter_migrate/src/base/io.dart'; -import 'package:flutter_migrate/src/base/logger.dart'; -import 'package:flutter_migrate/src/base/signals.dart'; -import 'package:flutter_migrate/src/environment.dart'; -import 'package:process/process.dart'; - -import 'src/common.dart'; -import 'src/context.dart'; - -void main() { - late FileSystem fileSystem; - late BufferLogger logger; - late ProcessManager processManager; - late Directory appDir; - late String separator; - - setUp(() { - fileSystem = LocalFileSystem.test(signals: LocalSignals.instance); - appDir = fileSystem.systemTempDirectory.createTempSync('apptestdir'); - logger = BufferLogger.test(); - separator = isWindows ? r'\\' : '/'; - processManager = FakeProcessManager(''' -{ - "FlutterProject.directory": "/Users/test/flutter", - "FlutterProject.metadataFile": "/Users/test/flutter/.metadata", - "FlutterProject.android.exists": false, - "FlutterProject.ios.exists": false, - "FlutterProject.web.exists": false, - "FlutterProject.macos.exists": false, - "FlutterProject.linux.exists": false, - "FlutterProject.windows.exists": false, - "FlutterProject.fuchsia.exists": false, - "FlutterProject.android.isKotlin": false, - "FlutterProject.ios.isSwift": false, - "FlutterProject.isModule": false, - "FlutterProject.isPlugin": false, - "FlutterProject.manifest.appname": "test_app_name", - "FlutterVersion.frameworkRevision": "4e181f012c717777681862e4771af5a941774bb9", - "Platform.operatingSystem": "macos", - "Platform.isAndroid": true, - "Platform.isIOS": false, - "Platform.isWindows": ${isWindows ? 'true' : 'false'}, - "Platform.isMacOS": ${isMacOS ? 'true' : 'false'}, - "Platform.isFuchsia": false, - "Platform.pathSeparator": "$separator", - "Cache.flutterRoot": "/Users/test/flutter" -} -'''); - }); - - tearDown(() async { - tryToDelete(appDir); - }); - - testUsingContext( - 'Environment initialization', - () async { - final FlutterToolsEnvironment env = - await FlutterToolsEnvironment.initializeFlutterToolsEnvironment( - processManager, - logger, - ); - expect(env.getString('invalid key') == null, true); - expect(env.getBool('invalid key') == null, true); - - expect(env.getString('FlutterProject.directory'), '/Users/test/flutter'); - expect( - env.getString('FlutterProject.metadataFile'), - '/Users/test/flutter/.metadata', - ); - expect(env.getBool('FlutterProject.android.exists'), false); - expect(env.getBool('FlutterProject.ios.exists'), false); - expect(env.getBool('FlutterProject.web.exists'), false); - expect(env.getBool('FlutterProject.macos.exists'), false); - expect(env.getBool('FlutterProject.linux.exists'), false); - expect(env.getBool('FlutterProject.windows.exists'), false); - expect(env.getBool('FlutterProject.fuchsia.exists'), false); - expect(env.getBool('FlutterProject.android.isKotlin'), false); - expect(env.getBool('FlutterProject.ios.isSwift'), false); - expect(env.getBool('FlutterProject.isModule'), false); - expect(env.getBool('FlutterProject.isPlugin'), false); - expect(env.getString('FlutterProject.manifest.appname'), 'test_app_name'); - expect( - env.getString('FlutterVersion.frameworkRevision'), - '4e181f012c717777681862e4771af5a941774bb9', - ); - expect(env.getString('Platform.operatingSystem'), 'macos'); - expect(env.getBool('Platform.isAndroid'), true); - expect(env.getBool('Platform.isIOS'), false); - expect(env.getBool('Platform.isWindows'), isWindows); - expect(env.getBool('Platform.isMacOS'), isMacOS); - expect(env.getBool('Platform.isFuchsia'), false); - expect(env.getString('Platform.pathSeparator'), separator); - expect(env.getString('Cache.flutterRoot'), '/Users/test/flutter'); - }, - overrides: { - FileSystem: () => fileSystem, - ProcessManager: () => processManager, - }, - ); -} - -class FakeProcessManager extends LocalProcessManager { - FakeProcessManager(this.runResult); - - final String runResult; - - @override - Future run( - List command, { - String? workingDirectory, - Map? environment, - bool includeParentEnvironment = true, - bool runInShell = false, - covariant Encoding? stdoutEncoding = systemEncoding, - covariant Encoding? stderrEncoding = systemEncoding, - }) async { - return ProcessResult(0, 0, runResult, ''); - } -} diff --git a/packages/flutter_migrate/test/flutter_project_metadata_test.dart b/packages/flutter_migrate/test/flutter_project_metadata_test.dart deleted file mode 100644 index 6b3bef6c38e..00000000000 --- a/packages/flutter_migrate/test/flutter_project_metadata_test.dart +++ /dev/null @@ -1,323 +0,0 @@ -// Copyright 2013 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 'package:file/memory.dart'; -import 'package:flutter_migrate/src/base/file_system.dart'; -import 'package:flutter_migrate/src/base/logger.dart'; -import 'package:flutter_migrate/src/flutter_project_metadata.dart'; - -import 'src/common.dart'; - -void main() { - late FileSystem fileSystem; - late BufferLogger logger; - late File metadataFile; - - setUp(() { - fileSystem = MemoryFileSystem.test(); - logger = BufferLogger.test(); - metadataFile = fileSystem.file('.metadata'); - }); - - testWithoutContext( - 'project metadata fields are empty when file does not exist', - () { - final FlutterProjectMetadata projectMetadata = FlutterProjectMetadata( - metadataFile, - logger, - ); - expect(projectMetadata.projectType, isNull); - expect(projectMetadata.versionChannel, isNull); - expect(projectMetadata.versionRevision, isNull); - - expect( - logger.traceText, - contains('No .metadata file found at .metadata'), - ); - }, - ); - - testWithoutContext( - 'project metadata fields are empty when file is empty', - () { - metadataFile.createSync(); - final FlutterProjectMetadata projectMetadata = FlutterProjectMetadata( - metadataFile, - logger, - ); - expect(projectMetadata.projectType, isNull); - expect(projectMetadata.versionChannel, isNull); - expect(projectMetadata.versionRevision, isNull); - - expect( - logger.traceText, - contains('.metadata file at .metadata was empty or malformed.'), - ); - }, - ); - - testWithoutContext( - 'project metadata fields are empty when file is not valid yaml', - () { - metadataFile.writeAsStringSync(' channel: @something'); - final FlutterProjectMetadata projectMetadata = FlutterProjectMetadata( - metadataFile, - logger, - ); - expect(projectMetadata.projectType, isNull); - expect(projectMetadata.versionChannel, isNull); - expect(projectMetadata.versionRevision, isNull); - - expect( - logger.traceText, - contains('.metadata file at .metadata was empty or malformed.'), - ); - }, - ); - - testWithoutContext('projectType is populated when version is null', () { - metadataFile - ..createSync() - ..writeAsStringSync(''' -version: -project_type: plugin - '''); - final FlutterProjectMetadata projectMetadata = FlutterProjectMetadata( - metadataFile, - logger, - ); - expect(projectMetadata.projectType, FlutterProjectType.plugin); - expect(projectMetadata.versionChannel, isNull); - expect(projectMetadata.versionRevision, isNull); - - expect( - logger.traceText, - contains( - 'The value of key `version` in .metadata was expected to be YamlMap but was Null', - ), - ); - }); - - testWithoutContext('projectType is populated when version is malformed', () { - metadataFile - ..createSync() - ..writeAsStringSync(''' -version: STRING INSTEAD OF MAP -project_type: plugin - '''); - final FlutterProjectMetadata projectMetadata = FlutterProjectMetadata( - metadataFile, - logger, - ); - expect(projectMetadata.projectType, FlutterProjectType.plugin); - expect(projectMetadata.versionChannel, isNull); - expect(projectMetadata.versionRevision, isNull); - - expect( - logger.traceText, - contains( - 'The value of key `version` in .metadata was expected to be YamlMap but was String', - ), - ); - }); - - testWithoutContext('version is populated when projectType is malformed', () { - metadataFile - ..createSync() - ..writeAsStringSync(''' -version: - revision: b59b226a49391949247e3d6122e34bb001049ae4 - channel: stable -project_type: {} - '''); - final FlutterProjectMetadata projectMetadata = FlutterProjectMetadata( - metadataFile, - logger, - ); - expect(projectMetadata.projectType, isNull); - expect(projectMetadata.versionChannel, 'stable'); - expect( - projectMetadata.versionRevision, - 'b59b226a49391949247e3d6122e34bb001049ae4', - ); - - expect( - logger.traceText, - contains( - 'The value of key `project_type` in .metadata was expected to be String but was YamlMap', - ), - ); - }); - - testWithoutContext('migrate config is populated when version is malformed', () { - metadataFile - ..createSync() - ..writeAsStringSync(''' -version: STRING INSTEAD OF MAP -project_type: {} - -migration: - platforms: - - platform: root - create_revision: abcdefg - base_revision: baserevision - - unmanaged_files: - - 'file1' - '''); - final FlutterProjectMetadata projectMetadata = FlutterProjectMetadata( - metadataFile, - logger, - ); - expect(projectMetadata.projectType, isNull); - expect( - projectMetadata - .migrateConfig - .platformConfigs[FlutterProjectComponent.root] - ?.createRevision, - 'abcdefg', - ); - expect( - projectMetadata - .migrateConfig - .platformConfigs[FlutterProjectComponent.root] - ?.baseRevision, - 'baserevision', - ); - expect(projectMetadata.migrateConfig.unmanagedFiles[0], 'file1'); - - expect( - logger.traceText, - contains( - 'The value of key `version` in .metadata was expected to be YamlMap but was String', - ), - ); - expect( - logger.traceText, - contains( - 'The value of key `project_type` in .metadata was expected to be String but was YamlMap', - ), - ); - }); - - testWithoutContext( - 'migrate config is populated when unmanaged_files is malformed', - () { - metadataFile - ..createSync() - ..writeAsStringSync(''' -version: - revision: b59b226a49391949247e3d6122e34bb001049ae4 - channel: stable -project_type: app - -migration: - platforms: - - platform: root - create_revision: abcdefg - base_revision: baserevision - - unmanaged_files: {} - '''); - final FlutterProjectMetadata projectMetadata = FlutterProjectMetadata( - metadataFile, - logger, - ); - expect(projectMetadata.projectType, FlutterProjectType.app); - expect( - projectMetadata - .migrateConfig - .platformConfigs[FlutterProjectComponent.root] - ?.createRevision, - 'abcdefg', - ); - expect( - projectMetadata - .migrateConfig - .platformConfigs[FlutterProjectComponent.root] - ?.baseRevision, - 'baserevision', - ); - // Tool uses default unamanged files list when malformed. - expect(projectMetadata.migrateConfig.unmanagedFiles[0], 'lib/main.dart'); - - expect( - logger.traceText, - contains( - 'The value of key `unmanaged_files` in .metadata was expected to be YamlList but was YamlMap', - ), - ); - }, - ); - - testWithoutContext('platforms is populated with a malformed entry', () { - metadataFile - ..createSync() - ..writeAsStringSync(''' -version: - revision: b59b226a49391949247e3d6122e34bb001049ae4 - channel: stable -project_type: app - -migration: - platforms: - - platform: root - create_revision: abcdefg - base_revision: baserevision - - platform: android - base_revision: baserevision - - platform: ios - create_revision: abcdefg - base_revision: baserevision - - unmanaged_files: - - 'file1' - '''); - final FlutterProjectMetadata projectMetadata = FlutterProjectMetadata( - metadataFile, - logger, - ); - expect(projectMetadata.projectType, FlutterProjectType.app); - expect( - projectMetadata - .migrateConfig - .platformConfigs[FlutterProjectComponent.root] - ?.createRevision, - 'abcdefg', - ); - expect( - projectMetadata - .migrateConfig - .platformConfigs[FlutterProjectComponent.root] - ?.baseRevision, - 'baserevision', - ); - expect( - projectMetadata - .migrateConfig - .platformConfigs[FlutterProjectComponent.ios] - ?.createRevision, - 'abcdefg', - ); - expect( - projectMetadata - .migrateConfig - .platformConfigs[FlutterProjectComponent.ios] - ?.baseRevision, - 'baserevision', - ); - expect( - projectMetadata.migrateConfig.platformConfigs.containsKey( - FlutterProjectComponent.android, - ), - false, - ); - expect(projectMetadata.migrateConfig.unmanagedFiles[0], 'file1'); - - expect( - logger.traceText, - contains('The key `create_revision` was not found'), - ); - }); -} diff --git a/packages/flutter_migrate/test/manifest_test.dart b/packages/flutter_migrate/test/manifest_test.dart deleted file mode 100644 index 90658b093ae..00000000000 --- a/packages/flutter_migrate/test/manifest_test.dart +++ /dev/null @@ -1,487 +0,0 @@ -// Copyright 2013 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 'package:file/memory.dart'; -import 'package:flutter_migrate/src/base/file_system.dart'; -import 'package:flutter_migrate/src/base/logger.dart'; -import 'package:flutter_migrate/src/manifest.dart'; -import 'package:flutter_migrate/src/result.dart'; -import 'package:flutter_migrate/src/utils.dart'; - -import 'src/common.dart'; - -void main() { - late FileSystem fileSystem; - late File manifestFile; - late BufferLogger logger; - - setUpAll(() { - fileSystem = MemoryFileSystem.test(); - logger = BufferLogger.test(); - manifestFile = fileSystem.file('.migrate_manifest'); - }); - - group('checkAndPrintMigrateStatus', () { - testWithoutContext('empty MigrateResult produces empty output', () async { - final Directory workingDir = fileSystem.directory('migrate_working_dir'); - workingDir.createSync(recursive: true); - final MigrateManifest manifest = MigrateManifest( - migrateRootDir: workingDir, - migrateResult: MigrateResult( - mergeResults: [], - addedFiles: [], - deletedFiles: [], - mergeTypeMap: {}, - diffMap: {}, - tempDirectories: [], - sdkDirs: {}, - ), - ); - - checkAndPrintMigrateStatus( - manifest, - workingDir, - warnConflict: true, - logger: logger, - ); - - expect(logger.statusText, contains('\n')); - }); - - testWithoutContext( - 'populated MigrateResult produces correct output', - () async { - final Directory workingDir = fileSystem.directory( - 'migrate_working_dir', - ); - workingDir.createSync(recursive: true); - final MigrateManifest manifest = MigrateManifest( - migrateRootDir: workingDir, - migrateResult: MigrateResult( - mergeResults: [ - StringMergeResult.explicit( - localPath: 'merged_file', - mergedString: 'str', - hasConflict: false, - exitCode: 0, - ), - StringMergeResult.explicit( - localPath: 'conflict_file', - mergedString: - 'hello\nwow a bunch of lines\n<<<<<<<\n=======\n<<<<<<<\nhi\n', - hasConflict: true, - exitCode: 1, - ), - ], - addedFiles: [ - FilePendingMigration('added_file', fileSystem.file('added_file')), - ], - deletedFiles: [ - FilePendingMigration( - 'deleted_file', - fileSystem.file('deleted_file'), - ), - ], - // The following are ignored by the manifest. - mergeTypeMap: {'test': MergeType.threeWay}, - diffMap: {}, - tempDirectories: [], - sdkDirs: {}, - ), - ); - - final File conflictFile = workingDir.childFile('conflict_file'); - conflictFile.writeAsStringSync( - 'hello\nwow a bunch of lines\n<<<<<<<\n=======\n<<<<<<<\nhi\n', - flush: true, - ); - - checkAndPrintMigrateStatus( - manifest, - workingDir, - warnConflict: true, - logger: logger, - ); - - expect( - logger.statusText, - contains(''' -Added files: - - added_file -Deleted files: - - deleted_file -Modified files: - - conflict_file - - merged_file -'''), - ); - }, - ); - - testWithoutContext( - 'populated MigrateResult detects fixed conflict', - () async { - final Directory workingDir = fileSystem.directory( - 'migrate_working_dir', - ); - workingDir.createSync(recursive: true); - final MigrateManifest manifest = MigrateManifest( - migrateRootDir: workingDir, - migrateResult: MigrateResult( - mergeResults: [ - StringMergeResult.explicit( - localPath: 'merged_file', - mergedString: 'str', - hasConflict: false, - exitCode: 0, - ), - StringMergeResult.explicit( - localPath: 'conflict_file', - mergedString: - 'hello\nwow a bunch of lines\n<<<<<<<\n=======\n<<<<<<<\nhi\n', - hasConflict: true, - exitCode: 1, - ), - ], - addedFiles: [ - FilePendingMigration('added_file', fileSystem.file('added_file')), - ], - deletedFiles: [ - FilePendingMigration( - 'deleted_file', - fileSystem.file('deleted_file'), - ), - ], - // The following are ignored by the manifest. - mergeTypeMap: {'test': MergeType.threeWay}, - diffMap: {}, - tempDirectories: [], - sdkDirs: {}, - ), - ); - - final File conflictFile = workingDir.childFile('conflict_file'); - conflictFile.writeAsStringSync( - 'hello\nwow a bunch of lines\nhi\n', - flush: true, - ); - - checkAndPrintMigrateStatus( - manifest, - workingDir, - warnConflict: true, - logger: logger, - ); - expect( - logger.statusText, - contains(''' -Added files: - - added_file -Deleted files: - - deleted_file -Modified files: - - conflict_file - - merged_file -'''), - ); - }, - ); - }); - - group('manifest file parsing', () { - testWithoutContext('empty fails', () async { - manifestFile.writeAsStringSync(''); - bool exceptionFound = false; - try { - MigrateManifest.fromFile(manifestFile); - } on Exception catch (e) { - exceptionFound = true; - expect( - e.toString(), - 'Exception: Invalid .migrate_manifest file in the migrate working directory. File is not a Yaml map.', - ); - } - expect(exceptionFound, true); - }); - - testWithoutContext('invalid name fails', () async { - manifestFile.writeAsStringSync(''' - merged_files: - conflict_files: - added_filessssss: - deleted_files: - '''); - bool exceptionFound = false; - try { - MigrateManifest.fromFile(manifestFile); - } on Exception catch (e) { - exceptionFound = true; - expect( - e.toString(), - 'Exception: Invalid .migrate_manifest file in the migrate working directory. File is missing an entry.', - ); - } - expect(exceptionFound, true); - }); - - testWithoutContext('missing name fails', () async { - manifestFile.writeAsStringSync(''' - merged_files: - conflict_files: - deleted_files: - '''); - bool exceptionFound = false; - try { - MigrateManifest.fromFile(manifestFile); - } on Exception catch (e) { - exceptionFound = true; - expect( - e.toString(), - 'Exception: Invalid .migrate_manifest file in the migrate working directory. File is missing an entry.', - ); - } - expect(exceptionFound, true); - }); - - testWithoutContext('wrong entry type fails', () async { - manifestFile.writeAsStringSync(''' - merged_files: - conflict_files: - other_key: - added_files: - deleted_files: - '''); - bool exceptionFound = false; - try { - MigrateManifest.fromFile(manifestFile); - } on Exception catch (e) { - exceptionFound = true; - expect( - e.toString(), - 'Exception: Invalid .migrate_manifest file in the migrate working directory. Entry is not a Yaml list.', - ); - } - expect(exceptionFound, true); - }); - - testWithoutContext('unpopulated succeeds', () async { - manifestFile.writeAsStringSync(''' - merged_files: - conflict_files: - added_files: - deleted_files: - '''); - final MigrateManifest manifest = MigrateManifest.fromFile(manifestFile); - expect(manifest.mergedFiles.isEmpty, true); - expect(manifest.conflictFiles.isEmpty, true); - expect(manifest.addedFiles.isEmpty, true); - expect(manifest.deletedFiles.isEmpty, true); - }); - - testWithoutContext('order does not matter', () async { - manifestFile.writeAsStringSync(''' - added_files: - merged_files: - deleted_files: - conflict_files: - '''); - final MigrateManifest manifest = MigrateManifest.fromFile(manifestFile); - expect(manifest.mergedFiles.isEmpty, true); - expect(manifest.conflictFiles.isEmpty, true); - expect(manifest.addedFiles.isEmpty, true); - expect(manifest.deletedFiles.isEmpty, true); - }); - - testWithoutContext('basic succeeds', () async { - manifestFile.writeAsStringSync(''' - merged_files: - - file1 - conflict_files: - - file2 - added_files: - - file3 - deleted_files: - - file4 - '''); - final MigrateManifest manifest = MigrateManifest.fromFile(manifestFile); - expect(manifest.mergedFiles.isEmpty, false); - expect(manifest.conflictFiles.isEmpty, false); - expect(manifest.addedFiles.isEmpty, false); - expect(manifest.deletedFiles.isEmpty, false); - - expect(manifest.mergedFiles.length, 1); - expect(manifest.conflictFiles.length, 1); - expect(manifest.addedFiles.length, 1); - expect(manifest.deletedFiles.length, 1); - - expect(manifest.mergedFiles[0], 'file1'); - expect(manifest.conflictFiles[0], 'file2'); - expect(manifest.addedFiles[0], 'file3'); - expect(manifest.deletedFiles[0], 'file4'); - }); - - testWithoutContext('basic multi-list succeeds', () async { - manifestFile.writeAsStringSync(''' - merged_files: - - file1 - - file2 - conflict_files: - added_files: - deleted_files: - - file3 - - file4 - '''); - final MigrateManifest manifest = MigrateManifest.fromFile(manifestFile); - expect(manifest.mergedFiles.isEmpty, false); - expect(manifest.conflictFiles.isEmpty, true); - expect(manifest.addedFiles.isEmpty, true); - expect(manifest.deletedFiles.isEmpty, false); - - expect(manifest.mergedFiles.length, 2); - expect(manifest.conflictFiles.length, 0); - expect(manifest.addedFiles.length, 0); - expect(manifest.deletedFiles.length, 2); - - expect(manifest.mergedFiles[0], 'file1'); - expect(manifest.mergedFiles[1], 'file2'); - expect(manifest.deletedFiles[0], 'file3'); - expect(manifest.deletedFiles[1], 'file4'); - }); - }); - - group('manifest MigrateResult creation', () { - testWithoutContext('empty MigrateResult', () async { - final MigrateManifest manifest = MigrateManifest( - migrateRootDir: fileSystem.directory('root'), - migrateResult: MigrateResult( - mergeResults: [], - addedFiles: [], - deletedFiles: [], - mergeTypeMap: {}, - diffMap: {}, - tempDirectories: [], - sdkDirs: {}, - ), - ); - expect(manifest.mergedFiles.isEmpty, true); - expect(manifest.conflictFiles.isEmpty, true); - expect(manifest.addedFiles.isEmpty, true); - expect(manifest.deletedFiles.isEmpty, true); - }); - - testWithoutContext('simple MigrateResult', () async { - final MigrateManifest manifest = MigrateManifest( - migrateRootDir: fileSystem.directory('root'), - migrateResult: MigrateResult( - mergeResults: [ - StringMergeResult.explicit( - localPath: 'merged_file', - mergedString: 'str', - hasConflict: false, - exitCode: 0, - ), - StringMergeResult.explicit( - localPath: 'conflict_file', - mergedString: '<<<<<<<<<<<', - hasConflict: true, - exitCode: 1, - ), - ], - addedFiles: [ - FilePendingMigration('added_file', fileSystem.file('added_file')), - ], - deletedFiles: [ - FilePendingMigration( - 'deleted_file', - fileSystem.file('deleted_file'), - ), - ], - // The following are ignored by the manifest. - mergeTypeMap: {'test': MergeType.threeWay}, - diffMap: {}, - tempDirectories: [], - sdkDirs: {}, - ), - ); - expect(manifest.mergedFiles.isEmpty, false); - expect(manifest.conflictFiles.isEmpty, false); - expect(manifest.addedFiles.isEmpty, false); - expect(manifest.deletedFiles.isEmpty, false); - - expect(manifest.mergedFiles.length, 1); - expect(manifest.conflictFiles.length, 1); - expect(manifest.addedFiles.length, 1); - expect(manifest.deletedFiles.length, 1); - - expect(manifest.mergedFiles[0], 'merged_file'); - expect(manifest.conflictFiles[0], 'conflict_file'); - expect(manifest.addedFiles[0], 'added_file'); - expect(manifest.deletedFiles[0], 'deleted_file'); - }); - }); - - group('manifest write', () { - testWithoutContext('empty', () async { - manifestFile.writeAsStringSync(''' - merged_files: - conflict_files: - added_files: - deleted_files: - '''); - final MigrateManifest manifest = MigrateManifest.fromFile(manifestFile); - expect(manifest.mergedFiles.isEmpty, true); - expect(manifest.conflictFiles.isEmpty, true); - expect(manifest.addedFiles.isEmpty, true); - expect(manifest.deletedFiles.isEmpty, true); - - manifest.writeFile(); - expect(manifestFile.readAsStringSync(), ''' -merged_files: -conflict_files: -added_files: -deleted_files: -'''); - }); - - testWithoutContext('basic multi-list', () async { - manifestFile.writeAsStringSync(''' - merged_files: - - file1 - - file2 - conflict_files: - added_files: - deleted_files: - - file3 - - file4 - '''); - final MigrateManifest manifest = MigrateManifest.fromFile(manifestFile); - expect(manifest.mergedFiles.isEmpty, false); - expect(manifest.conflictFiles.isEmpty, true); - expect(manifest.addedFiles.isEmpty, true); - expect(manifest.deletedFiles.isEmpty, false); - - expect(manifest.mergedFiles.length, 2); - expect(manifest.conflictFiles.length, 0); - expect(manifest.addedFiles.length, 0); - expect(manifest.deletedFiles.length, 2); - - expect(manifest.mergedFiles[0], 'file1'); - expect(manifest.mergedFiles[1], 'file2'); - expect(manifest.deletedFiles[0], 'file3'); - expect(manifest.deletedFiles[1], 'file4'); - - manifest.writeFile(); - expect(manifestFile.readAsStringSync(), ''' -merged_files: - - file1 - - file2 -conflict_files: -added_files: -deleted_files: - - file3 - - file4 -'''); - }); - }); -} diff --git a/packages/flutter_migrate/test/migrate_test.dart b/packages/flutter_migrate/test/migrate_test.dart deleted file mode 100644 index e580e214f58..00000000000 --- a/packages/flutter_migrate/test/migrate_test.dart +++ /dev/null @@ -1,517 +0,0 @@ -// Copyright 2013 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 'package:flutter_migrate/src/base/common.dart'; -import 'package:flutter_migrate/src/base/file_system.dart'; -import 'package:flutter_migrate/src/base/io.dart'; -import 'package:flutter_migrate/src/base/logger.dart'; -import 'package:flutter_migrate/src/base/signals.dart'; -import 'package:flutter_migrate/src/base/terminal.dart'; -import 'package:process/process.dart'; - -import 'src/common.dart'; -import 'src/context.dart'; -import 'test_data/migrate_project.dart'; - -// This file contains E2E test that execute the core migrate commands -// and simulates manual conflict resolution and other manipulations of -// the project files. -void main() { - late Directory tempDir; - late BufferLogger logger; - late ProcessManager processManager; - late FileSystem fileSystem; - - setUp(() async { - logger = BufferLogger.test(); - processManager = const LocalProcessManager(); - fileSystem = LocalFileSystem.test(signals: LocalSignals.instance); - tempDir = fileSystem.systemTempDirectory.createTempSync('flutter_run_test'); - }); - - tearDown(() async { - tryToDelete(tempDir); - }); - - Future hasFlutterEnvironment() async { - final String flutterRoot = getFlutterRoot(); - final String flutterExecutable = fileSystem.path.join( - flutterRoot, - 'bin', - 'flutter${isWindows ? '.bat' : ''}', - ); - final ProcessResult result = await Process.run(flutterExecutable, [ - 'analyze', - '--suggestions', - '--machine', - ]); - if (result.exitCode != 0) { - return false; - } - return true; - } - - // Migrates a clean untouched app generated with flutter create - testUsingContext( - 'vanilla migrate process succeeds', - () async { - // This tool does not support old versions of flutter that dont include - // `flutter analyze --suggestions --machine` command - if (!await hasFlutterEnvironment()) { - return; - } - // Flutter Stable 1.22.6 hash: 9b2d32b605630f28625709ebd9d78ab3016b2bf6 - await MigrateProject.installProject('version:1.22.6_stable', tempDir); - - ProcessResult result = await runMigrateCommand([ - 'start', - '--verbose', - ], workingDirectory: tempDir.path); - expect( - result.stdout.toString(), - contains('Staging directory created at'), - ); - const String linesToMatch = ''' -Added files: - - android/app/src/main/res/values-night/styles.xml - - android/app/src/main/res/drawable-v21/launch_background.xml - - analysis_options.yaml -Modified files: - - .metadata - - ios/Runner/Info.plist - - ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata - - ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme - - ios/Flutter/AppFrameworkInfo.plist - - ios/.gitignore - - pubspec.yaml - - .gitignore - - android/app/build.gradle - - android/app/src/profile/AndroidManifest.xml - - android/app/src/main/res/values/styles.xml - - android/app/src/main/AndroidManifest.xml - - android/app/src/debug/AndroidManifest.xml - - android/gradle/wrapper/gradle-wrapper.properties - - android/.gitignore - - android/build.gradle'''; - for (final String line in linesToMatch.split('\n')) { - expect(result.stdout.toString(), contains(line)); - } - - result = await runMigrateCommand([ - 'apply', - '--verbose', - ], workingDirectory: tempDir.path); - logger.printStatus('${result.exitCode}', color: TerminalColor.blue); - logger.printStatus(result.stdout as String, color: TerminalColor.green); - logger.printStatus(result.stderr as String, color: TerminalColor.red); - expect(result.exitCode, 0); - expect(result.stdout.toString(), contains('Migration complete')); - - expect( - tempDir.childFile('.metadata').readAsStringSync(), - contains('migration:\n platforms:\n - platform: root\n'), - ); - - expect( - tempDir - .childFile('android/app/src/main/res/values-night/styles.xml') - .existsSync(), - true, - ); - expect(tempDir.childFile('analysis_options.yaml').existsSync(), true); - }, - timeout: const Timeout(Duration(seconds: 500)), - // TODO(stuartmorgan): These should not be unit tests, see - // https://github.com/flutter/flutter/issues/121257. - skip: true, - ); - - // Migrates a clean untouched app generated with flutter create - testUsingContext( - 'vanilla migrate builds', - () async { - // This tool does not support old versions of flutter that dont include - // `flutter analyze --suggestions --machine` command - if (!await hasFlutterEnvironment()) { - return; - } - // Flutter Stable 2.0.0 hash: 60bd88df915880d23877bfc1602e8ddcf4c4dd2a - await MigrateProject.installProject( - 'version:2.0.0_stable', - tempDir, - main: ''' -import 'package:flutter/material.dart'; - -void main() { - runApp(const MyApp()); -} - -class MyApp extends StatelessWidget { - const MyApp({Key? key}) : super(key: key); - - // This widget is the root of your application. - @override - Widget build(BuildContext context) { - return MaterialApp( - title: 'Flutter Demo', - theme: ThemeData( - primarySwatch: Colors.blue, - ), - home: Container(), - ); - } -} -''', - ); - ProcessResult result = await runMigrateCommand([ - 'start', - '--verbose', - ], workingDirectory: tempDir.path); - expect( - result.stdout.toString(), - contains('Staging directory created at'), - ); - - result = await runMigrateCommand([ - 'apply', - '--verbose', - ], workingDirectory: tempDir.path); - logger.printStatus('${result.exitCode}', color: TerminalColor.blue); - logger.printStatus(result.stdout as String, color: TerminalColor.green); - logger.printStatus(result.stderr as String, color: TerminalColor.red); - expect(result.exitCode, 0); - expect(result.stdout.toString(), contains('Migration complete')); - - result = await processManager.run([ - 'flutter', - 'build', - 'apk', - '--debug', - ], workingDirectory: tempDir.path); - expect(result.exitCode, 0); - expect(result.stdout.toString(), contains('app-debug.apk')); - // Skipped due to being flaky, the build completes successfully, but sometimes - // Gradle crashes due to resources on the bot. We should fine tune this to - // make it stable. - }, - timeout: const Timeout(Duration(seconds: 900)), - // TODO(stuartmorgan): These should not be unit tests, see - // https://github.com/flutter/flutter/issues/121257. - skip: true, - ); - - testUsingContext( - 'migrate abandon', - () async { - // Abandon in an empty dir fails. - ProcessResult result = await runMigrateCommand([ - 'abandon', - '--verbose', - ], workingDirectory: tempDir.path); - expect(result.exitCode, 0); - expect( - result.stderr.toString(), - contains('Error: No pubspec.yaml file found'), - ); - expect( - result.stderr.toString(), - contains( - 'This command should be run from the root of your Flutter project', - ), - ); - - final File manifestFile = tempDir.childFile( - 'migrate_staging_dir/.migrate_manifest', - ); - expect(manifestFile.existsSync(), false); - - // Flutter Stable 1.22.6 hash: 9b2d32b605630f28625709ebd9d78ab3016b2bf6 - await MigrateProject.installProject('version:1.22.6_stable', tempDir); - - // Initialized repo fails. - result = await runMigrateCommand([ - 'abandon', - '--verbose', - ], workingDirectory: tempDir.path); - expect(result.exitCode, 0); - expect(result.stdout.toString(), contains('No migration in progress')); - - // Create migration. - manifestFile.createSync(recursive: true); - - // Directory with manifest_staging_dir succeeds. - result = await runMigrateCommand([ - 'abandon', - '--verbose', - '--force', - ], workingDirectory: tempDir.path); - expect(result.exitCode, 0); - expect(result.stdout.toString(), contains('Abandon complete')); - }, - timeout: const Timeout(Duration(seconds: 300)), - // TODO(stuartmorgan): These should not be unit tests, see - // https://github.com/flutter/flutter/issues/121257. - skip: true, - ); - - // Migrates a user-modified app - testUsingContext( - 'modified migrate process succeeds', - () async { - // This tool does not support old versions of flutter that dont include - // `flutter analyze --suggestions --machine` command - if (!await hasFlutterEnvironment()) { - return; - } - // Flutter Stable 1.22.6 hash: 9b2d32b605630f28625709ebd9d78ab3016b2bf6 - await MigrateProject.installProject( - 'version:1.22.6_stable', - tempDir, - vanilla: false, - ); - - ProcessResult result = await runMigrateCommand([ - 'apply', - '--verbose', - ], workingDirectory: tempDir.path); - expect(result.exitCode, 0); - expect(result.stdout.toString(), contains('No migration')); - - result = await runMigrateCommand([ - 'status', - '--verbose', - ], workingDirectory: tempDir.path); - expect(result.exitCode, 0); - expect(result.stdout.toString(), contains('No migration')); - - result = await runMigrateCommand([ - 'start', - '--verbose', - ], workingDirectory: tempDir.path); - expect(result.exitCode, 0); - expect( - result.stdout.toString(), - contains('Staging directory created at'), - ); - const String linesToMatch = ''' -Modified files: - - .metadata - - ios/Runner/Info.plist - - ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata - - ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme - - ios/Flutter/AppFrameworkInfo.plist - - ios/.gitignore - - .gitignore - - android/app/build.gradle - - android/app/src/profile/AndroidManifest.xml - - android/app/src/main/res/values/styles.xml - - android/app/src/main/AndroidManifest.xml - - android/app/src/debug/AndroidManifest.xml - - android/gradle/wrapper/gradle-wrapper.properties - - android/.gitignore - - android/build.gradle -Merge conflicted files: - - pubspec.yaml'''; - for (final String line in linesToMatch.split('\n')) { - expect(result.stdout.toString(), contains(line)); - } - - // Call apply with conflicts remaining. Should fail. - result = await runMigrateCommand([ - 'apply', - '--verbose', - ], workingDirectory: tempDir.path); - expect(result.exitCode, 0); - expect( - result.stdout.toString(), - contains( - 'Conflicting files found. Resolve these conflicts and try again.', - ), - ); - expect(result.stdout.toString(), contains('- pubspec.yaml')); - - result = await runMigrateCommand([ - 'status', - '--verbose', - ], workingDirectory: tempDir.path); - expect(result.exitCode, 0); - expect(result.stdout.toString(), contains('Modified files')); - expect(result.stdout.toString(), contains('Merge conflicted files')); - - // Manually resolve conflics. The correct contents for resolution may change over time, - // but it shouldnt matter for this test. - final File metadataFile = tempDir.childFile( - 'migrate_staging_dir/.metadata', - ); - metadataFile.writeAsStringSync(''' -# This file tracks properties of this Flutter project. -# Used by Flutter tool to assess capabilities and perform upgrades etc. -# -# This file should be version controlled and should not be manually edited. - -version: - revision: e96a72392696df66755ca246ff291dfc6ca6c4ad - channel: unknown - -project_type: app - -''', flush: true); - final File pubspecYamlFile = tempDir.childFile( - 'migrate_staging_dir/pubspec.yaml', - ); - pubspecYamlFile.writeAsStringSync(''' -name: vanilla_app_1_22_6_stable -description: This is a modified description from the default. - -# The following line prevents the package from being accidentally published to -# pub.dev using `flutter pub publish`. This is preferred for private packages. -publish_to: 'none' # Remove this line if you wish to publish to pub.dev - -# The following defines the version and build number for your application. -# A version number is three numbers separated by dots, like 1.2.43 -# followed by an optional build number separated by a +. -# Both the version and the builder number may be overridden in flutter -# build by specifying --build-name and --build-number, respectively. -# In Android, build-name is used as versionName while build-number used as versionCode. -# Read more about Android versioning at https://developer.android.com/studio/publish/versioning -# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. -# Read more about iOS versioning at -# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 1.0.0+1 - -environment: - sdk: ">=2.17.0-79.0.dev <3.0.0" - -# Dependencies specify other packages that your package needs in order to work. -# To automatically upgrade your package dependencies to the latest versions -# consider running `flutter pub upgrade --major-versions`. Alternatively, -# dependencies can be manually updated by changing the version numbers below to -# the latest version available on pub.dev. To see which dependencies have newer -# versions available, run `flutter pub outdated`. -dependencies: - flutter: - sdk: flutter - - - # The following adds the Cupertino Icons font to your application. - # Use with the CupertinoIcons class for iOS style icons. - cupertino_icons: ^1.0.2 - -dev_dependencies: - flutter_test: - sdk: flutter - - # The "flutter_lints" package below contains a set of recommended lints to - # encourage good coding practices. The lint set provided by the package is - # activated in the `analysis_options.yaml` file located at the root of your - # package. See that file for information about deactivating specific lint - # rules and activating additional ones. - flutter_lints: ^1.0.0 - -# For information on the generic Dart part of this file, see the -# following page: https://dart.dev/tools/pub/pubspec - -# The following section is specific to Flutter packages. -flutter: - - # The following line ensures that the Material Icons font is - # included with your application, so that you can use the icons in - # the material Icons class. - uses-material-design: true - - # To add assets to your application, add an assets section, like this: - assets: - - images/a_dot_burr.jpeg - - images/a_dot_ham.jpeg - - # An image asset can refer to one or more resolution-specific "variants", see - # https://flutter.dev/to/resolution-aware-images. - - # For details regarding adding assets from package dependencies, see - # https://flutter.dev/to/asset-from-package - - # To add custom fonts to your application, add a fonts section here, - # in this "flutter" section. Each entry in this list should have a - # "family" key with the font family name, and a "fonts" key with a - # list giving the asset and other descriptors for the font. For - # example: - # fonts: - # - family: Schyler - # fonts: - # - asset: fonts/Schyler-Regular.ttf - # - asset: fonts/Schyler-Italic.ttf - # style: italic - # - family: Trajan Pro - # fonts: - # - asset: fonts/TrajanPro.ttf - # - asset: fonts/TrajanPro_Bold.ttf - # weight: 700 - # - # For details regarding fonts from package dependencies, - # see https://flutter.dev/to/font-from-package - -''', flush: true); - - result = await runMigrateCommand([ - 'status', - '--verbose', - ], workingDirectory: tempDir.path); - expect(result.exitCode, 0); - expect(result.stdout.toString(), contains('Modified files')); - expect(result.stdout.toString(), contains('diff --git')); - expect(result.stdout.toString(), contains('@@')); - expect( - result.stdout.toString(), - isNot(contains('Merge conflicted files')), - ); - - result = await runMigrateCommand([ - 'apply', - '--verbose', - ], workingDirectory: tempDir.path); - expect(result.exitCode, 0); - expect(result.stdout.toString(), contains('Migration complete')); - - expect( - tempDir.childFile('.metadata').readAsStringSync(), - contains('e96a72392696df66755ca246ff291dfc6ca6c4ad'), - ); - expect( - tempDir.childFile('pubspec.yaml').readAsStringSync(), - isNot(contains('">=2.6.0 <3.0.0"')), - ); - expect( - tempDir.childFile('pubspec.yaml').readAsStringSync(), - contains('">=2.17.0-79.0.dev <3.0.0"'), - ); - expect( - tempDir.childFile('pubspec.yaml').readAsStringSync(), - contains( - 'description: This is a modified description from the default.', - ), - ); - expect( - tempDir.childFile('lib/main.dart').readAsStringSync(), - contains('OtherWidget()'), - ); - expect(tempDir.childFile('lib/other.dart').existsSync(), true); - expect( - tempDir.childFile('lib/other.dart').readAsStringSync(), - contains('class OtherWidget'), - ); - - expect( - tempDir - .childFile('android/app/src/main/res/values-night/styles.xml') - .existsSync(), - true, - ); - expect(tempDir.childFile('analysis_options.yaml').existsSync(), true); - }, - timeout: const Timeout(Duration(seconds: 500)), - // TODO(stuartmorgan): These should not be unit tests, see - // https://github.com/flutter/flutter/issues/121257. - skip: true, - ); -} diff --git a/packages/flutter_migrate/test/src/common.dart b/packages/flutter_migrate/test/src/common.dart deleted file mode 100644 index 6abed2fff34..00000000000 --- a/packages/flutter_migrate/test/src/common.dart +++ /dev/null @@ -1,208 +0,0 @@ -// Copyright 2013 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 'dart:io' as io; - -import 'package:flutter_migrate/src/base/context.dart'; -import 'package:flutter_migrate/src/base/file_system.dart'; -import 'package:flutter_migrate/src/base/io.dart'; -import 'package:meta/meta.dart'; -import 'package:path/path.dart' as path; // flutter_ignore: package_path_import -import 'package:test/test.dart' as test_package show test; -import 'package:test/test.dart' hide test; - -import 'test_utils.dart'; - -export 'package:test/test.dart' hide isInstanceOf, test; - -bool tryToDelete(FileSystemEntity fileEntity) { - // This should not be necessary, but it turns out that - // on Windows it's common for deletions to fail due to - // bogus (we think) "access denied" errors. - try { - if (fileEntity.existsSync()) { - fileEntity.deleteSync(recursive: true); - return true; - } - } on FileSystemException catch (error) { - // We print this so that it's visible in the logs, to get an idea of how - // common this problem is, and if any patterns are ever noticed by anyone. - // ignore: avoid_print - print('Failed to delete ${fileEntity.path}: $error'); - } - return false; -} - -/// Gets the path to the root of the Flutter repository. -/// -/// This will first look for a `FLUTTER_ROOT` environment variable. If the -/// environment variable is set, it will be returned. Otherwise, this will -/// deduce the path from `platform.script`. -String getFlutterRoot() { - if (io.Platform.environment.containsKey('FLUTTER_ROOT')) { - return io.Platform.environment['FLUTTER_ROOT']!; - } - - Error invalidScript() => StateError( - 'Could not determine flutter_tools/ path from script URL (${io.Platform.script}); consider setting FLUTTER_ROOT explicitly.', - ); - - Uri scriptUri; - switch (io.Platform.script.scheme) { - case 'file': - scriptUri = io.Platform.script; - case 'data': - final RegExp flutterTools = RegExp( - r'(file://[^"]*[/\\]flutter_tools[/\\][^"]+\.dart)', - multiLine: true, - ); - final Match? match = flutterTools.firstMatch( - Uri.decodeFull(io.Platform.script.path), - ); - if (match == null) { - throw invalidScript(); - } - scriptUri = Uri.parse(match.group(1)!); - default: - throw invalidScript(); - } - - final List parts = path.split(fileSystem.path.fromUri(scriptUri)); - final int toolsIndex = parts.indexOf('flutter_tools'); - if (toolsIndex == -1) { - throw invalidScript(); - } - final String toolsPath = path.joinAll(parts.sublist(0, toolsIndex + 1)); - return path.normalize(path.join(toolsPath, '..', '..')); -} - -String getMigratePackageRoot() { - return io.Directory.current.path; -} - -String getMigrateMain() { - return fileSystem.path.join( - getMigratePackageRoot(), - 'bin', - 'flutter_migrate.dart', - ); -} - -Future runMigrateCommand( - List args, { - String? workingDirectory, -}) { - final List commandArgs = ['dart', 'run', getMigrateMain()]; - commandArgs.addAll(args); - return processManager.run(commandArgs, workingDirectory: workingDirectory); -} - -/// The tool overrides `test` to ensure that files created under the -/// system temporary directory are deleted after each test by calling -/// `LocalFileSystem.dispose()`. -@isTest -void test( - String description, - FutureOr Function() body, { - String? testOn, - dynamic skip, - List? tags, - Map? onPlatform, - int? retry, - Timeout? timeout, -}) { - test_package.test( - description, - () async { - addTearDown(() async { - await fileSystem.dispose(); - }); - - return body(); - }, - skip: skip, - tags: tags, - onPlatform: onPlatform, - retry: retry, - testOn: testOn, - timeout: timeout, - // We don't support "timeout"; see ../../dart_test.yaml which - // configures all tests to have a 15 minute timeout which should - // definitely be enough. - ); -} - -/// Executes a test body in zone that does not allow context-based injection. -/// -/// For classes which have been refactored to exclude context-based injection -/// or globals like [fs] or [platform], prefer using this test method as it -/// will prevent accidentally including these context getters in future code -/// changes. -/// -/// For more information, see https://github.com/flutter/flutter/issues/47161 -@isTest -void testWithoutContext( - String description, - FutureOr Function() body, { - String? testOn, - dynamic skip, - List? tags, - Map? onPlatform, - int? retry, - Timeout? timeout, -}) { - return test( - description, - () async { - return runZoned( - body, - zoneValues: {contextKey: const _NoContext()}, - ); - }, - skip: skip, - tags: tags, - onPlatform: onPlatform, - retry: retry, - testOn: testOn, - timeout: timeout, - // We support timeout here due to the packages repo not setting default - // timeout to 15min. - ); -} - -/// An implementation of [AppContext] that throws if context.get is called in the test. -/// -/// The intention of the class is to ensure we do not accidentally regress when -/// moving towards more explicit dependency injection by accidentally using -/// a Zone value in place of a constructor parameter. -class _NoContext implements AppContext { - const _NoContext(); - - @override - T get() { - throw UnsupportedError( - 'context.get<$T> is not supported in test methods. ' - 'Use Testbed or testUsingContext if accessing Zone injected ' - 'values.', - ); - } - - @override - String get name => 'No Context'; - - @override - Future run({ - required FutureOr Function() body, - String? name, - Map? overrides, - Map? fallbacks, - ZoneSpecification? zoneSpecification, - }) async { - return body(); - } -} - -/// Matcher for functions that throw [AssertionError]. -final Matcher throwsAssertionError = throwsA(isA()); diff --git a/packages/flutter_migrate/test/src/context.dart b/packages/flutter_migrate/test/src/context.dart deleted file mode 100644 index 680fa9dd4b0..00000000000 --- a/packages/flutter_migrate/test/src/context.dart +++ /dev/null @@ -1,134 +0,0 @@ -// Copyright 2013 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:flutter_migrate/src/base/context.dart'; -import 'package:flutter_migrate/src/base/file_system.dart'; -import 'package:flutter_migrate/src/base/logger.dart'; -import 'package:flutter_migrate/src/base/terminal.dart'; -import 'package:meta/meta.dart'; -import 'package:process/process.dart'; - -import 'common.dart'; -import 'fakes.dart'; - -/// Return the test logger. This assumes that the current Logger is a BufferLogger. -BufferLogger get testLogger => context.get()! as BufferLogger; - -@isTest -void testUsingContext( - String description, - dynamic Function() testMethod, { - Map overrides = const {}, - bool initializeFlutterRoot = true, - String? testOn, - Timeout? timeout, - bool? - skip, // should default to `false`, but https://github.com/dart-lang/test/issues/545 doesn't allow this -}) { - if (overrides[FileSystem] != null && overrides[ProcessManager] == null) { - throw StateError( - 'If you override the FileSystem context you must also provide a ProcessManager, ' - 'otherwise the processes you launch will not be dealing with the same file system ' - 'that you are dealing with in your test.', - ); - } - - // Ensure we don't rely on the default [Config] constructor which will - // leak a sticky $HOME/.flutter_settings behind! - Directory? configDir; - tearDown(() { - if (configDir != null) { - tryToDelete(configDir!); - configDir = null; - } - }); - - test( - description, - () async { - await runInContext(() { - return context.run( - name: 'mocks', - overrides: { - AnsiTerminal: () => AnsiTerminal(stdio: FakeStdio()), - OutputPreferences: () => OutputPreferences.test(), - Logger: () => BufferLogger.test(), - ProcessManager: () => const LocalProcessManager(), - }, - body: () { - return runZonedGuarded>( - () { - try { - return context.run( - // Apply the overrides to the test context in the zone since their - // instantiation may reference items already stored on the context. - overrides: overrides, - name: 'test-specific overrides', - body: () async { - if (initializeFlutterRoot) { - // Provide a sane default for the flutterRoot directory. Individual - // tests can override this either in the test or during setup. - // Cache.flutterRoot ??= flutterRoot; - } - return await testMethod(); - }, - ); - // This catch rethrows, so doesn't need to catch only Exception. - } catch (error) { - // ignore: avoid_catches_without_on_clauses - _printBufferedErrors(context); - rethrow; - } - }, - (Object error, StackTrace stackTrace) { - // When things fail, it's ok to print to the console! - print(error); // ignore: avoid_print - print(stackTrace); // ignore: avoid_print - _printBufferedErrors(context); - throw error; //ignore: only_throw_errors - }, - ); - }, - ); - }, overrides: {}); - }, - testOn: testOn, - skip: skip, - timeout: timeout, - ); -} - -void _printBufferedErrors(AppContext testContext) { - if (testContext.get() is BufferLogger) { - final BufferLogger bufferLogger = - testContext.get()! as BufferLogger; - if (bufferLogger.errorText.isNotEmpty) { - // This is where the logger outputting errors is implemented, so it has - // to use `print`. - print(bufferLogger.errorText); // ignore: avoid_print - } - bufferLogger.clear(); - } -} - -Future runInContext( - FutureOr Function() runner, { - Map? overrides, -}) async { - // Wrap runner with any asynchronous initialization that should run with the - // overrides and callbacks. - // late bool runningOnBot; - FutureOr runnerWrapper() async { - return runner(); - } - - return context.run( - name: 'global fallbacks', - body: runnerWrapper, - overrides: overrides, - fallbacks: {}, - ); -} diff --git a/packages/flutter_migrate/test/src/fakes.dart b/packages/flutter_migrate/test/src/fakes.dart deleted file mode 100644 index e8dcd5439e7..00000000000 --- a/packages/flutter_migrate/test/src/fakes.dart +++ /dev/null @@ -1,292 +0,0 @@ -// Copyright 2013 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 'dart:convert'; -import 'dart:io' as io show IOSink, Stdout, StdoutException; - -import 'package:flutter_migrate/src/base/io.dart'; -import 'package:flutter_migrate/src/base/logger.dart'; -import 'package:test/fake.dart'; - -/// An IOSink that completes a future with the first line written to it. -class CompleterIOSink extends MemoryIOSink { - CompleterIOSink({this.throwOnAdd = false}); - - final bool throwOnAdd; - - final Completer> _completer = Completer>(); - - Future> get future => _completer.future; - - @override - void add(List data) { - if (!_completer.isCompleted) { - // When throwOnAdd is true, complete with empty so any expected output - // doesn't appear. - _completer.complete(throwOnAdd ? [] : data); - } - if (throwOnAdd) { - throw Exception('CompleterIOSink Error'); - } - super.add(data); - } -} - -/// An IOSink that collects whatever is written to it. -class MemoryIOSink implements IOSink { - @override - Encoding encoding = utf8; - - final List> writes = >[]; - - @override - void add(List data) { - writes.add(data); - } - - @override - Future addStream(Stream> stream) { - final Completer completer = Completer(); - late StreamSubscription> sub; - sub = stream.listen( - (List data) { - try { - add(data); - // Catches all exceptions to propagate them to the completer. - } catch (err, stack) { - // ignore: avoid_catches_without_on_clauses - sub.cancel(); - completer.completeError(err, stack); - } - }, - onError: completer.completeError, - onDone: completer.complete, - cancelOnError: true, - ); - return completer.future; - } - - @override - void writeCharCode(int charCode) { - add([charCode]); - } - - @override - void write(Object? obj) { - add(encoding.encode('$obj')); - } - - @override - void writeln([Object? obj = '']) { - add(encoding.encode('$obj\n')); - } - - @override - void writeAll(Iterable objects, [String separator = '']) { - bool addSeparator = false; - for (final dynamic object in objects) { - if (addSeparator) { - write(separator); - } - write(object); - addSeparator = true; - } - } - - @override - void addError(dynamic error, [StackTrace? stackTrace]) { - throw UnimplementedError(); - } - - @override - Future get done => close(); - - @override - Future close() async {} - - @override - Future flush() async {} - - void clear() { - writes.clear(); - } - - String getAndClear() { - final String result = utf8.decode( - writes.expand((List l) => l).toList(), - ); - clear(); - return result; - } -} - -class MemoryStdout extends MemoryIOSink implements io.Stdout { - @override - bool get hasTerminal => _hasTerminal; - set hasTerminal(bool value) { - _hasTerminal = value; - } - - bool _hasTerminal = true; - - @override - // ignore: override_on_non_overriding_member - String get lineTerminator => '\n'; - @override - // ignore: override_on_non_overriding_member - set lineTerminator(String value) { - throw UnimplementedError('Setting the line terminator is not supported'); - } - - @override - io.IOSink get nonBlocking => this; - - @override - bool get supportsAnsiEscapes => _supportsAnsiEscapes; - set supportsAnsiEscapes(bool value) { - _supportsAnsiEscapes = value; - } - - bool _supportsAnsiEscapes = true; - - @override - int get terminalColumns { - if (_terminalColumns != null) { - return _terminalColumns!; - } - throw const io.StdoutException('unspecified mock value'); - } - - set terminalColumns(int value) => _terminalColumns = value; - int? _terminalColumns; - - @override - int get terminalLines { - if (_terminalLines != null) { - return _terminalLines!; - } - throw const io.StdoutException('unspecified mock value'); - } - - set terminalLines(int value) => _terminalLines = value; - int? _terminalLines; -} - -/// A Stdio that collects stdout and supports simulated stdin. -class FakeStdio extends Stdio { - final MemoryStdout _stdout = MemoryStdout()..terminalColumns = 80; - final MemoryIOSink _stderr = MemoryIOSink(); - final FakeStdin _stdin = FakeStdin(); - - @override - MemoryStdout get stdout => _stdout; - - @override - MemoryIOSink get stderr => _stderr; - - @override - Stream> get stdin => _stdin; - - void simulateStdin(String line) { - _stdin.controller.add(utf8.encode('$line\n')); - } - - @override - bool hasTerminal = true; - - List get writtenToStdout => - _stdout.writes.map(_stdout.encoding.decode).toList(); - List get writtenToStderr => - _stderr.writes.map(_stderr.encoding.decode).toList(); -} - -class FakeStdin extends Fake implements Stdin { - final StreamController> controller = StreamController>(); - - @override - bool echoMode = true; - - @override - bool hasTerminal = true; - - @override - bool echoNewlineMode = true; - - @override - bool lineMode = true; - - @override - Stream transform(StreamTransformer, S> transformer) { - return controller.stream.transform(transformer); - } - - @override - StreamSubscription> listen( - void Function(List event)? onData, { - Function? onError, - void Function()? onDone, - bool? cancelOnError, - }) { - return controller.stream.listen( - onData, - onError: onError, - onDone: onDone, - cancelOnError: cancelOnError, - ); - } -} - -class FakeStopwatch implements Stopwatch { - @override - bool get isRunning => _isRunning; - bool _isRunning = false; - - @override - void start() => _isRunning = true; - - @override - void stop() => _isRunning = false; - - @override - Duration elapsed = Duration.zero; - - @override - int get elapsedMicroseconds => elapsed.inMicroseconds; - - @override - int get elapsedMilliseconds => elapsed.inMilliseconds; - - @override - int get elapsedTicks => elapsed.inMilliseconds; - - @override - int get frequency => 1000; - - @override - void reset() { - _isRunning = false; - elapsed = Duration.zero; - } - - @override - String toString() => 'FakeStopwatch $elapsed $isRunning'; -} - -class FakeStopwatchFactory implements StopwatchFactory { - FakeStopwatchFactory({ - Stopwatch? stopwatch, - Map? stopwatches, - }) : stopwatches = { - if (stopwatches != null) ...stopwatches, - if (stopwatch != null) '': stopwatch, - }; - - Map stopwatches; - - @override - Stopwatch createStopwatch([String name = '']) { - return stopwatches[name] ?? FakeStopwatch(); - } -} diff --git a/packages/flutter_migrate/test/src/io.dart b/packages/flutter_migrate/test/src/io.dart deleted file mode 100644 index fab7ca1d54c..00000000000 --- a/packages/flutter_migrate/test/src/io.dart +++ /dev/null @@ -1,138 +0,0 @@ -// Copyright 2013 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:io' as io show Directory, File, IOOverrides, Link; - -import 'package:flutter_migrate/src/base/file_system.dart'; - -/// An [IOOverrides] that can delegate to [FileSystem] implementation if provided. -/// -/// Does not override any of the socket facilities. -/// -/// Do not provide a [LocalFileSystem] as a delegate. Since internally this calls -/// out to `dart:io` classes, it will result in a stack overflow error as the -/// IOOverrides and LocalFileSystem call each other endlessly. -/// -/// The only safe delegate types are those that do not call out to `dart:io`, -/// like the [MemoryFileSystem]. -final class FlutterIOOverrides extends io.IOOverrides { - FlutterIOOverrides({FileSystem? fileSystem}) - : _fileSystemDelegate = fileSystem; - - final FileSystem? _fileSystemDelegate; - - @override - io.Directory createDirectory(String path) { - if (_fileSystemDelegate == null) { - return super.createDirectory(path); - } - return _fileSystemDelegate.directory(path); - } - - @override - io.File createFile(String path) { - if (_fileSystemDelegate == null) { - return super.createFile(path); - } - return _fileSystemDelegate.file(path); - } - - @override - io.Link createLink(String path) { - if (_fileSystemDelegate == null) { - return super.createLink(path); - } - return _fileSystemDelegate.link(path); - } - - @override - Stream fsWatch(String path, int events, bool recursive) { - if (_fileSystemDelegate == null) { - return super.fsWatch(path, events, recursive); - } - return _fileSystemDelegate - .file(path) - .watch(events: events, recursive: recursive); - } - - @override - bool fsWatchIsSupported() { - if (_fileSystemDelegate == null) { - return super.fsWatchIsSupported(); - } - return _fileSystemDelegate.isWatchSupported; - } - - @override - Future fseGetType(String path, bool followLinks) { - if (_fileSystemDelegate == null) { - return super.fseGetType(path, followLinks); - } - return _fileSystemDelegate.type(path, followLinks: followLinks); - } - - @override - FileSystemEntityType fseGetTypeSync(String path, bool followLinks) { - if (_fileSystemDelegate == null) { - return super.fseGetTypeSync(path, followLinks); - } - return _fileSystemDelegate.typeSync(path, followLinks: followLinks); - } - - @override - Future fseIdentical(String path1, String path2) { - if (_fileSystemDelegate == null) { - return super.fseIdentical(path1, path2); - } - return _fileSystemDelegate.identical(path1, path2); - } - - @override - bool fseIdenticalSync(String path1, String path2) { - if (_fileSystemDelegate == null) { - return super.fseIdenticalSync(path1, path2); - } - return _fileSystemDelegate.identicalSync(path1, path2); - } - - @override - io.Directory getCurrentDirectory() { - if (_fileSystemDelegate == null) { - return super.getCurrentDirectory(); - } - return _fileSystemDelegate.currentDirectory; - } - - @override - io.Directory getSystemTempDirectory() { - if (_fileSystemDelegate == null) { - return super.getSystemTempDirectory(); - } - return _fileSystemDelegate.systemTempDirectory; - } - - @override - void setCurrentDirectory(String path) { - if (_fileSystemDelegate == null) { - return super.setCurrentDirectory(path); - } - _fileSystemDelegate.currentDirectory = path; - } - - @override - Future stat(String path) { - if (_fileSystemDelegate == null) { - return super.stat(path); - } - return _fileSystemDelegate.stat(path); - } - - @override - FileStat statSync(String path) { - if (_fileSystemDelegate == null) { - return super.statSync(path); - } - return _fileSystemDelegate.statSync(path); - } -} diff --git a/packages/flutter_migrate/test/src/test_flutter_command_runner.dart b/packages/flutter_migrate/test/src/test_flutter_command_runner.dart deleted file mode 100644 index fceaa322362..00000000000 --- a/packages/flutter_migrate/test/src/test_flutter_command_runner.dart +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright 2013 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 'package:args/command_runner.dart'; -import 'package:flutter_migrate/src/base/command.dart'; - -export 'package:test/test.dart' hide isInstanceOf, test; - -CommandRunner createTestCommandRunner([MigrateCommand? command]) { - final CommandRunner runner = TestCommandRunner(); - if (command != null) { - runner.addCommand(command); - } - return runner; -} - -class TestCommandRunner extends CommandRunner { - TestCommandRunner() - : super( - 'flutter', - 'Manage your Flutter app development.\n' - '\n' - 'Common commands:\n' - '\n' - ' flutter create \n' - ' Create a new Flutter project in the specified directory.\n' - '\n' - ' flutter run [options]\n' - ' Run your Flutter application on an attached device or in an emulator.', - ); -} diff --git a/packages/flutter_migrate/test/src/test_utils.dart b/packages/flutter_migrate/test/src/test_utils.dart deleted file mode 100644 index 83399eaada2..00000000000 --- a/packages/flutter_migrate/test/src/test_utils.dart +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright 2013 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:io'; - -import 'package:flutter_migrate/src/base/file_system.dart'; -import 'package:flutter_migrate/src/base/io.dart'; -import 'package:flutter_migrate/src/base/signals.dart'; -import 'package:process/process.dart'; - -import 'common.dart'; - -/// The [FileSystem] for the integration test environment. -LocalFileSystem fileSystem = LocalFileSystem.test( - signals: LocalSignals.instance, -); - -/// The [ProcessManager] for the integration test environment. -const ProcessManager processManager = LocalProcessManager(); - -/// Creates a temporary directory but resolves any symlinks to return the real -/// underlying path to avoid issues with breakpoints/hot reload. -/// https://github.com/flutter/flutter/pull/21741 -Directory createResolvedTempDirectorySync(String prefix) { - assert(prefix.endsWith('.')); - final Directory tempDirectory = fileSystem.systemTempDirectory.createTempSync( - 'flutter_$prefix', - ); - return fileSystem.directory(tempDirectory.resolveSymbolicLinksSync()); -} - -void writeFile( - String path, - String content, { - bool writeFutureModifiedDate = false, -}) { - final File file = - fileSystem.file(path) - ..createSync(recursive: true) - ..writeAsStringSync(content, flush: true); - // Some integration tests on Windows to not see this file as being modified - // recently enough for the hot reload to pick this change up unless the - // modified time is written in the future. - if (writeFutureModifiedDate) { - file.setLastModifiedSync(DateTime.now().add(const Duration(seconds: 5))); - } -} - -void writePackages(String folder) { - writeFile(fileSystem.path.join(folder, '.packages'), ''' -test:${fileSystem.path.join(fileSystem.currentDirectory.path, 'lib')}/ -'''); -} - -Future getPackages(String folder) async { - final List command = [ - fileSystem.path.join(getFlutterRoot(), 'bin', 'flutter'), - 'pub', - 'get', - ]; - final ProcessResult result = await processManager.run( - command, - workingDirectory: folder, - ); - if (result.exitCode != 0) { - throw Exception( - 'flutter pub get failed: ${result.stderr}\n${result.stdout}', - ); - } -} diff --git a/packages/flutter_migrate/test/status_test.dart b/packages/flutter_migrate/test/status_test.dart deleted file mode 100644 index 42624f90e01..00000000000 --- a/packages/flutter_migrate/test/status_test.dart +++ /dev/null @@ -1,204 +0,0 @@ -// Copyright 2013 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 'package:flutter_migrate/src/base/context.dart'; -import 'package:flutter_migrate/src/base/file_system.dart'; -import 'package:flutter_migrate/src/base/logger.dart'; -import 'package:flutter_migrate/src/base/signals.dart'; -import 'package:flutter_migrate/src/commands/status.dart'; -import 'package:flutter_migrate/src/utils.dart'; -import 'package:process/process.dart'; - -import 'src/common.dart'; -import 'src/context.dart'; -import 'src/test_flutter_command_runner.dart'; - -void main() { - late FileSystem fileSystem; - late BufferLogger logger; - late ProcessManager processManager; - late Directory appDir; - - setUp(() { - fileSystem = LocalFileSystem.test(signals: LocalSignals.instance); - appDir = fileSystem.systemTempDirectory.createTempSync('apptestdir'); - logger = BufferLogger.test(); - processManager = const LocalProcessManager(); - }); - - tearDown(() async { - tryToDelete(appDir); - }); - - testUsingContext( - 'Status produces all outputs', - () async { - final MigrateStatusCommand command = MigrateStatusCommand( - verbose: true, - logger: logger, - fileSystem: fileSystem, - processManager: processManager, - ); - final Directory stagingDir = appDir.childDirectory( - kDefaultMigrateStagingDirectoryName, - ); - final File pubspecOriginal = appDir.childFile('pubspec.yaml'); - pubspecOriginal.createSync(); - pubspecOriginal.writeAsStringSync(''' -name: originalname -description: A new Flutter project. -version: 1.0.0+1 -environment: - sdk: '>=2.18.0-58.0.dev <3.0.0' -dependencies: - flutter: - sdk: flutter -dev_dependencies: - flutter_test: - sdk: flutter -flutter: - uses-material-design: true''', flush: true); - - final File pubspecModified = stagingDir.childFile('pubspec.yaml'); - pubspecModified.createSync(recursive: true); - pubspecModified.writeAsStringSync(''' -name: newname -description: new description of the test project -version: 1.0.0+1 -environment: - sdk: '>=2.18.0-58.0.dev <3.0.0' -dependencies: - flutter: - sdk: flutter -dev_dependencies: - flutter_test: - sdk: flutter -flutter: - uses-material-design: false - EXTRALINE''', flush: true); - - final File addedFile = stagingDir.childFile('added.file'); - addedFile.createSync(recursive: true); - addedFile.writeAsStringSync('new file contents'); - - final File manifestFile = stagingDir.childFile('.migrate_manifest'); - manifestFile.createSync(recursive: true); - manifestFile.writeAsStringSync(''' -merged_files: - - pubspec.yaml -conflict_files: -added_files: - - added.file -deleted_files: -'''); - - await createTestCommandRunner(command).run([ - 'status', - '--staging-directory=${stagingDir.path}', - '--project-directory=${appDir.path}', - '--flutter-subcommand', - ]); - - expect( - logger.statusText, - contains(''' -Newly added file at added.file: - -new file contents'''), - ); - expect( - logger.statusText, - contains(r''' -Added files: - - added.file -Modified files: - - pubspec.yaml - -All conflicts resolved. Review changes above and apply the migration with: - - $ flutter migrate apply -'''), - ); - - expect( - logger.statusText, - contains(r''' -@@ -1,5 +1,5 @@ --name: originalname --description: A new Flutter project. -+name: newname -+description: new description of the test project - version: 1.0.0+1 - environment: - sdk: '>=2.18.0-58.0.dev <3.0.0' -@@ -10,4 +10,5 @@ dev_dependencies: - flutter_test: - sdk: flutter - flutter: -- uses-material-design: true -\ No newline at end of file -+ uses-material-design: false -+ EXTRALINE'''), - ); - - // Add conflict file - final File conflictFile = stagingDir - .childDirectory('conflict') - .childFile('conflict.file'); - conflictFile.createSync(recursive: true); - conflictFile.writeAsStringSync(''' -line1 -<<<<<<< /conflcit/conflict.file -line2 -======= -linetwo ->>>>>>> /var/folders/md/gm0zgfcj07vcsj6jkh_mp_wh00ff02/T/flutter_tools.4Xdep8/generatedTargetTemplatetlN44S/conflict/conflict.file -line3 -''', flush: true); - final File conflictFileOriginal = appDir - .childDirectory('conflict') - .childFile('conflict.file'); - conflictFileOriginal.createSync(recursive: true); - conflictFileOriginal.writeAsStringSync(''' -line1 -line2 -line3 -''', flush: true); - - manifestFile.writeAsStringSync(''' -merged_files: - - pubspec.yaml -conflict_files: - - conflict/conflict.file -added_files: - - added.file -deleted_files: -'''); - - logger.clear(); - await createTestCommandRunner(command).run([ - 'status', - '--staging-directory=${stagingDir.path}', - '--project-directory=${appDir.path}', - '--flutter-subcommand', - ]); - - expect( - logger.statusText, - contains(''' -@@ -1,3 +1,7 @@ - line1 -+<<<<<<< /conflcit/conflict.file - line2 -+======= -+linetwo -+>>>>>>>'''), - ); - }, - overrides: { - FileSystem: () => fileSystem, - ProcessManager: () => processManager, - }, - ); -} diff --git a/packages/flutter_migrate/test/test_data/full_apps/vanilla_app_1_22_6_stable.ensure b/packages/flutter_migrate/test/test_data/full_apps/vanilla_app_1_22_6_stable.ensure deleted file mode 100644 index b6d130f8a8b..00000000000 --- a/packages/flutter_migrate/test/test_data/full_apps/vanilla_app_1_22_6_stable.ensure +++ /dev/null @@ -1 +0,0 @@ -flutter/test/full_app_fixtures/vanilla version:1.22.6_stable diff --git a/packages/flutter_migrate/test/test_data/migrate_project.dart b/packages/flutter_migrate/test/test_data/migrate_project.dart deleted file mode 100644 index 36a41769c8b..00000000000 --- a/packages/flutter_migrate/test/test_data/migrate_project.dart +++ /dev/null @@ -1,314 +0,0 @@ -// Copyright 2013 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. - -@Timeout(Duration(seconds: 600)) -library; - -import 'dart:io'; -import 'package:file/file.dart'; -import 'package:flutter_migrate/src/base/file_system.dart'; - -import '../src/common.dart'; -import '../src/test_utils.dart'; -import 'project.dart'; - -class MigrateProject extends Project { - MigrateProject(this.version, {this.vanilla = true, this.main}); - - final String version; - - /// Manually set main.dart - @override - final String? main; - - /// Non-vanilla is a set of changed files that guarantee a merge conflict. - final bool vanilla; - - late String _appPath; - - static Future installProject( - String verison, - Directory dir, { - bool vanilla = true, - String? main, - }) async { - final MigrateProject project = MigrateProject( - verison, - vanilla: vanilla, - main: main, - ); - await project.setUpIn(dir); - - // Init a git repo to test uncommitted changes checks - await processManager.run([ - 'git', - 'init', - ], workingDirectory: dir.path); - await processManager.run([ - 'git', - 'checkout', - '-b', - 'master', - ], workingDirectory: dir.path); - await commitChanges(dir); - } - - static Future commitChanges(Directory dir) async { - await processManager.run([ - 'git', - 'add', - '.', - ], workingDirectory: dir.path); - await processManager.run([ - 'git', - 'commit', - '-m', - '"All changes"', - ], workingDirectory: dir.path); - } - - @override - Future setUpIn( - Directory dir, { - bool useSyntheticPackage = false, - }) async { - this.dir = dir; - _appPath = dir.path; - writeFile( - fileSystem.path.join(dir.path, 'android', 'local.properties'), - androidLocalProperties, - ); - final Directory tempDir = createResolvedTempDirectorySync('cipd_dest.'); - final Directory depotToolsDir = createResolvedTempDirectorySync( - 'depot_tools.', - ); - - await processManager.run([ - 'git', - 'clone', - 'https://chromium.googlesource.com/chromium/tools/depot_tools', - depotToolsDir.path, - ], workingDirectory: dir.path); - - final File cipdFile = depotToolsDir.childFile( - Platform.isWindows ? 'cipd.bat' : 'cipd', - ); - await processManager.run([ - cipdFile.path, - 'init', - tempDir.path, - '-force', - ], workingDirectory: dir.path); - - await processManager.run([ - cipdFile.path, - 'install', - 'flutter/test/full_app_fixtures/vanilla', - version, - '-root', - tempDir.path, - ], workingDirectory: dir.path); - - if (Platform.isWindows) { - ProcessResult res = await processManager.run([ - 'robocopy', - tempDir.path, - dir.path, - '*', - '/E', - '/V', - '/mov', - ]); - // Robocopy exit code 1 means some files were copied. 0 means no files were copied. - assert(res.exitCode == 1); - res = await processManager.run(['takeown', '/f', dir.path, '/r']); - res = await processManager.run([ - 'takeown', - '/f', - '${dir.path}\\lib\\main.dart', - '/r', - ]); - res = await processManager.run([ - 'icacls', - dir.path, - ], workingDirectory: dir.path); - // Add full access permissions to Users - res = await processManager.run([ - 'icacls', - dir.path, - '/q', - '/c', - '/t', - '/grant', - 'Users:F', - ]); - } else { - // This cp command changes the symlinks to real files so the tool can edit them. - await processManager.run([ - 'cp', - '-R', - '-L', - '-f', - '${tempDir.path}/.', - dir.path, - ]); - - await processManager.run([ - 'rm', - '-rf', - '.cipd', - ], workingDirectory: dir.path); - - await processManager.run([ - 'chmod', - '-R', - '+w', - dir.path, - ], workingDirectory: dir.path); - - await processManager.run([ - 'chmod', - '-R', - '+r', - dir.path, - ], workingDirectory: dir.path); - } - - if (!vanilla) { - writeFile(fileSystem.path.join(dir.path, 'lib', 'main.dart'), libMain); - writeFile(fileSystem.path.join(dir.path, 'lib', 'other.dart'), libOther); - writeFile(fileSystem.path.join(dir.path, 'pubspec.yaml'), pubspecCustom); - } - if (main != null) { - writeFile(fileSystem.path.join(dir.path, 'lib', 'main.dart'), main!); - } - tryToDelete(tempDir); - tryToDelete(depotToolsDir); - } - - // Maintain the same pubspec as the configured app. - @override - String get pubspec => - fileSystem - .file(fileSystem.path.join(_appPath, 'pubspec.yaml')) - .readAsStringSync(); - - String get androidLocalProperties => ''' - flutter.sdk=${getFlutterRoot()} - '''; - - String get libMain => ''' -import 'package:flutter/material.dart'; -import 'other.dart'; - -void main() { - runApp(MyApp()); -} - -class MyApp extends StatelessWidget { - @override - Widget build(BuildContext context) { - return MaterialApp( - title: 'Flutter Demo', - theme: ThemeData( - primarySwatch: Colors.blue, - visualDensity: VisualDensity.adaptivePlatformDensity, - ), - home: OtherWidget(), - ); - } -} - -'''; - - String get libOther => ''' -class OtherWidget extends StatelessWidget { - @override - Widget build(BuildContext context) { - return Container(width: 100, height: 100); - } -} - -'''; - - String get pubspecCustom => ''' -name: vanilla_app_1_22_6_stable -description: This is a modified description from the default. - -# The following line prevents the package from being accidentally published to -# pub.dev using `pub publish`. This is preferred for private packages. -publish_to: 'none' # Remove this line if you wish to publish to pub.dev - -# The following defines the version and build number for your application. -# A version number is three numbers separated by dots, like 1.2.43 -# followed by an optional build number separated by a +. -# Both the version and the builder number may be overridden in flutter -# build by specifying --build-name and --build-number, respectively. -# In Android, build-name is used as versionName while build-number used as versionCode. -# Read more about Android versioning at https://developer.android.com/studio/publish/versioning -# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. -# Read more about iOS versioning at -# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 1.0.0+1 - -environment: - sdk: ">=2.6.0 <3.0.0" - -dependencies: - flutter: - sdk: flutter - - - # The following adds the Cupertino Icons font to your application. - # Use with the CupertinoIcons class for iOS style icons. - cupertino_icons: ^1.0.0 - -dev_dependencies: - flutter_test: - sdk: flutter - -# For information on the generic Dart part of this file, see the -# following page: https://dart.dev/tools/pub/pubspec - -# The following section is specific to Flutter. -flutter: - - # The following line ensures that the Material Icons font is - # included with your application, so that you can use the icons in - # the material Icons class. - uses-material-design: true - - # To add assets to your application, add an assets section, like this: - assets: - - images/a_dot_burr.jpeg - - images/a_dot_ham.jpeg - - # An image asset can refer to one or more resolution-specific "variants", see - # https://flutter.dev/to/resolution-aware-images. - - # For details regarding adding assets from package dependencies, see - # https://flutter.dev/to/asset-from-package - - # To add custom fonts to your application, add a fonts section here, - # in this "flutter" section. Each entry in this list should have a - # "family" key with the font family name, and a "fonts" key with a - # list giving the asset and other descriptors for the font. For - # example: - # fonts: - # - family: Schyler - # fonts: - # - asset: fonts/Schyler-Regular.ttf - # - asset: fonts/Schyler-Italic.ttf - # style: italic - # - family: Trajan Pro - # fonts: - # - asset: fonts/TrajanPro.ttf - # - asset: fonts/TrajanPro_Bold.ttf - # weight: 700 - # - # For details regarding fonts from package dependencies, - # see https://flutter.dev/to/font-from-package - -'''; -} diff --git a/packages/flutter_migrate/test/test_data/project.dart b/packages/flutter_migrate/test/test_data/project.dart deleted file mode 100644 index 00894442883..00000000000 --- a/packages/flutter_migrate/test/test_data/project.dart +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright 2013 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 'package:file/file.dart'; - -import '../src/test_utils.dart'; - -const String _kDefaultHtml = ''' - - - Hello, World - - - - - -'''; - -abstract class Project { - late Directory dir; - - String get pubspec; - String? get main => null; - String? get test => null; - String? get generatedFile => null; - - Uri get mainDart => Uri.parse('package:test/main.dart'); - - Future setUpIn(Directory dir) async { - this.dir = dir; - writeFile(fileSystem.path.join(dir.path, 'pubspec.yaml'), pubspec); - final String? main = this.main; - if (main != null) { - writeFile(fileSystem.path.join(dir.path, 'lib', 'main.dart'), main); - } - final String? test = this.test; - if (test != null) { - writeFile(fileSystem.path.join(dir.path, 'test', 'test.dart'), test); - } - final String? generatedFile = this.generatedFile; - if (generatedFile != null) { - writeFile( - fileSystem.path.join( - dir.path, - '.dart_tool', - 'flutter_gen', - 'flutter_gen.dart', - ), - generatedFile, - ); - } - writeFile( - fileSystem.path.join(dir.path, 'web', 'index.html'), - _kDefaultHtml, - ); - writePackages(dir.path); - await getPackages(dir.path); - } - - int lineContaining(String contents, String search) { - final int index = contents - .split('\n') - .indexWhere((String l) => l.contains(search)); - if (index == -1) { - throw Exception("Did not find '$search' inside the file"); - } - return index + 1; // first line is line 1, not line 0 - } -} diff --git a/packages/flutter_migrate/test/update_locks_test.dart b/packages/flutter_migrate/test/update_locks_test.dart deleted file mode 100644 index 675ac79e9a7..00000000000 --- a/packages/flutter_migrate/test/update_locks_test.dart +++ /dev/null @@ -1,276 +0,0 @@ -// Copyright 2013 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 'package:flutter_migrate/src/base/file_system.dart'; -import 'package:flutter_migrate/src/base/io.dart'; -import 'package:flutter_migrate/src/base/logger.dart'; -import 'package:flutter_migrate/src/base/project.dart'; -import 'package:flutter_migrate/src/base/signals.dart'; -import 'package:flutter_migrate/src/base/terminal.dart'; -import 'package:flutter_migrate/src/update_locks.dart'; -import 'package:flutter_migrate/src/utils.dart'; -import 'package:process/process.dart'; -import 'package:yaml/yaml.dart'; - -import 'src/common.dart'; -import 'src/test_utils.dart'; - -void main() { - late FileSystem fileSystem; - late BufferLogger logger; - late MigrateUtils utils; - late Directory currentDir; - late FlutterProject flutterProject; - late Terminal terminal; - late ProcessManager processManager; - - setUp(() async { - fileSystem = LocalFileSystem.test(signals: LocalSignals.instance); - currentDir = createResolvedTempDirectorySync('current_app.'); - logger = BufferLogger.test(); - terminal = Terminal.test(); - utils = MigrateUtils( - logger: logger, - fileSystem: fileSystem, - processManager: const LocalProcessManager(), - ); - final FlutterProjectFactory flutterFactory = FlutterProjectFactory(); - flutterProject = flutterFactory.fromDirectory(currentDir); - processManager = const LocalProcessManager(); - }); - - tearDown(() async { - tryToDelete(currentDir); - }); - - testWithoutContext('updates pubspec locks', () async { - final ProcessResult result = await processManager.run([ - 'flutter', - 'create', - currentDir.absolute.path, - '--project-name=testproject', - ]); - expect(result.exitCode, 0); - final File pubspec = currentDir.childFile('pubspec.yaml'); - pubspec.writeAsStringSync(''' -name: testproject -description: A test flutter project. - -environment: - sdk: ">=2.17.0-0 <3.0.0" - -dependencies: - flutter: - sdk: flutter - - characters: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - collection: 1.15.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - js: 0.6.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - material_color_utilities: 0.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - meta: 1.7.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - vector_math: 2.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - -flutter: - uses-material-design: true - -# PUBSPEC CHECKSUM: 1b91 - -''', flush: true); - await updatePubspecDependencies( - flutterProject, - utils, - logger, - terminal, - force: true, - ); - final YamlMap pubspecYaml = loadYaml(pubspec.readAsStringSync()) as YamlMap; - final YamlMap dependenciesMap = pubspecYaml['dependencies'] as YamlMap; - expect( - _VersionCode.fromString(dependenciesMap['characters'] as String) > - _VersionCode.fromString('1.2.0'), - true, - ); - expect( - _VersionCode.fromString(dependenciesMap['collection'] as String) > - _VersionCode.fromString('1.15.0'), - true, - ); - expect( - _VersionCode.fromString(dependenciesMap['js'] as String) > - _VersionCode.fromString('0.6.3'), - true, - ); - expect( - _VersionCode.fromString( - dependenciesMap['material_color_utilities'] as String, - ) > - _VersionCode.fromString('0.1.0'), - true, - ); - expect( - _VersionCode.fromString(dependenciesMap['meta'] as String) > - _VersionCode.fromString('1.7.0'), - true, - ); - }); - - testWithoutContext( - 'updates gradle locks', - () async { - final ProcessResult result = await processManager.run([ - 'flutter', - 'create', - currentDir.absolute.path, - '--project-name=testproject', - ]); - expect(result.exitCode, 0); - final File projectAppLock = currentDir - .childDirectory('android') - .childFile('project-app.lockfile'); - final File buildGradle = currentDir - .childDirectory('android') - .childFile('build.gradle'); - final File gradleProperties = currentDir - .childDirectory('android') - .childFile('gradle.properties'); - gradleProperties.writeAsStringSync(''' -org.gradle.daemon=false -org.gradle.jvmargs=-Xmx4G -android.useAndroidX=true -android.enableJetifier=true -''', flush: true); - final File projectAppLockBackup = currentDir - .childDirectory('android') - .childFile('project-app.lockfile_backup_0'); - expect(projectAppLockBackup.existsSync(), false); - projectAppLock.createSync(recursive: true); - projectAppLock.writeAsStringSync(''' -# This is a Gradle generated file for dependency locking. -# Manual edits can break the build and are not advised. -# This file is expected to be part of source control. -androidx.activity:activity:1.0.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath -androidx.annotation:annotation-experimental:1.1.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath -androidx.annotation:annotation:1.2.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath - -''', flush: true); - buildGradle.writeAsStringSync(r''' -buildscript { - ext.kotlin_version = '1.5.31' - repositories { - google() - mavenCentral() - } - - dependencies { - classpath 'com.android.tools.build:gradle:7.2.0' - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - } - - configurations.classpath { - resolutionStrategy.activateDependencyLocking() - } -} - -allprojects { - repositories { - google() - mavenCentral() - } -} - -rootProject.buildDir = '../build' - -subprojects { - project.buildDir = "${rootProject.buildDir}/${project.name}" -} -subprojects { - project.evaluationDependsOn(':app') - dependencyLocking { - ignoredDependencies.add('io.flutter:*') - lockFile = file("${rootProject.projectDir}/project-${project.name}.lockfile") - if (!project.hasProperty('local-engine-repo')) { - lockAllConfigurations() - } - } -} - -''', flush: true); - expect( - currentDir - .childDirectory('android') - .childFile('gradlew.bat') - .existsSync(), - true, - ); - final Directory dotGradle = currentDir - .childDirectory('android') - .childDirectory('.gradle'); - tryToDelete(dotGradle); - await updateGradleDependencyLocking( - flutterProject, - utils, - logger, - terminal, - true, - fileSystem, - force: true, - ); - expect(projectAppLockBackup.existsSync(), true); - expect(projectAppLock.existsSync(), true); - expect( - projectAppLock.readAsStringSync(), - contains('# This is a Gradle generated file for dependency locking.'), - ); - expect( - projectAppLock.readAsStringSync(), - contains('# Manual edits can break the build and are not advised.'), - ); - expect( - projectAppLock.readAsStringSync(), - contains('# This file is expected to be part of source control.'), - ); - }, - timeout: const Timeout(Duration(seconds: 500)), - skip: true, - ); -} - -class _VersionCode implements Comparable<_VersionCode> { - _VersionCode(this.first, this.second, this.third, this.caret); - _VersionCode.fromString(String str) - : first = 0, - second = 0, - third = 0, - caret = 0 { - caret = str.contains('^') ? 1 : 0; - str = str.replaceAll('^', ''); - final List splits = str.split('.'); - assert(splits.length == 3); - - first = int.parse(splits[0]); - second = int.parse(splits[1]); - third = int.parse(splits[2]); - } - - int caret; - int first; - int second; - int third; - - bool operator >(_VersionCode other) { - return compareTo(other) > 0; - } - - bool operator <(_VersionCode other) { - return !(this > other); - } - - @override - int compareTo(_VersionCode other) { - final int value = first * 10000000 + second * 100000 + third + caret; - final int otherValue = - other.first * 10000000 + other.second * 100000 + other.third + caret; - return value - otherValue; - } -} diff --git a/packages/flutter_migrate/test/utils_test.dart b/packages/flutter_migrate/test/utils_test.dart deleted file mode 100644 index bed6746f6c8..00000000000 --- a/packages/flutter_migrate/test/utils_test.dart +++ /dev/null @@ -1,365 +0,0 @@ -// Copyright 2013 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 'package:flutter_migrate/src/base/common.dart'; -import 'package:flutter_migrate/src/base/file_system.dart'; -import 'package:flutter_migrate/src/base/io.dart'; -import 'package:flutter_migrate/src/base/logger.dart'; -import 'package:flutter_migrate/src/base/signals.dart'; -import 'package:flutter_migrate/src/utils.dart'; -import 'package:process/process.dart'; - -import 'src/common.dart'; - -void main() { - late BufferLogger logger; - late FileSystem fileSystem; - late Directory projectRoot; - late String projectRootPath; - late MigrateUtils utils; - late ProcessManager processManager; - - setUpAll(() async { - fileSystem = LocalFileSystem.test(signals: LocalSignals.instance); - logger = BufferLogger.test(); - processManager = const LocalProcessManager(); - utils = MigrateUtils( - logger: logger, - fileSystem: fileSystem, - processManager: processManager, - ); - }); - - group('git', () { - setUp(() async { - projectRoot = fileSystem.systemTempDirectory.createTempSync( - 'flutter_migrate_utils_test', - ); - projectRoot.createSync(recursive: true); - projectRootPath = projectRoot.path; - }); - - tearDown(() async { - tryToDelete(projectRoot); - }); - - testWithoutContext('init', () async { - expect(projectRoot.existsSync(), true); - expect(projectRoot.childDirectory('.git').existsSync(), false); - await utils.gitInit(projectRootPath); - expect(projectRoot.childDirectory('.git').existsSync(), true); - }); - - testWithoutContext('isGitIgnored', () async { - expect(projectRoot.existsSync(), true); - expect(projectRoot.childDirectory('.git').existsSync(), false); - await utils.gitInit(projectRootPath); - expect(projectRoot.childDirectory('.git').existsSync(), true); - - projectRoot.childFile('.gitignore') - ..createSync() - ..writeAsStringSync('ignored_file.dart', flush: true); - - expect( - await utils.isGitIgnored('ignored_file.dart', projectRootPath), - true, - ); - expect( - await utils.isGitIgnored('other_file.dart', projectRootPath), - false, - ); - }); - - testWithoutContext('isGitRepo', () async { - expect(projectRoot.existsSync(), true); - expect(projectRoot.childDirectory('.git').existsSync(), false); - expect(await utils.isGitRepo(projectRootPath), false); - await utils.gitInit(projectRootPath); - expect(projectRoot.childDirectory('.git').existsSync(), true); - - expect(await utils.isGitRepo(projectRootPath), true); - - expect(await utils.isGitRepo(projectRoot.parent.path), false); - }); - - testWithoutContext('hasUncommittedChanges false on clean repo', () async { - expect(projectRoot.existsSync(), true); - expect(projectRoot.childDirectory('.git').existsSync(), false); - await utils.gitInit(projectRootPath); - expect(projectRoot.childDirectory('.git').existsSync(), true); - - projectRoot.childFile('.gitignore') - ..createSync() - ..writeAsStringSync('ignored_file.dart', flush: true); - - await Process.run('git', [ - 'add', - '.', - ], workingDirectory: projectRootPath); - await Process.run('git', [ - 'commit', - '-m', - 'Initial commit', - ], workingDirectory: projectRootPath); - - expect(await utils.hasUncommittedChanges(projectRootPath), false); - }); - - testWithoutContext('hasUncommittedChanges true on dirty repo', () async { - expect(projectRoot.existsSync(), true); - expect(projectRoot.childDirectory('.git').existsSync(), false); - await utils.gitInit(projectRootPath); - expect(projectRoot.childDirectory('.git').existsSync(), true); - - projectRoot.childFile('some_file.dart') - ..createSync() - ..writeAsStringSync('void main() {}', flush: true); - - expect(await utils.hasUncommittedChanges(projectRootPath), true); - }); - - testWithoutContext( - 'logging hasUncommittedChanges true on dirty repo', - () async { - expect(projectRoot.existsSync(), true); - expect(projectRoot.childDirectory('.git').existsSync(), false); - await utils.gitInit(projectRootPath); - expect(projectRoot.childDirectory('.git').existsSync(), true); - - projectRoot.childFile('some_file.dart') - ..createSync() - ..writeAsStringSync('void main() {}', flush: true); - - expect( - await hasUncommittedChanges(projectRootPath, logger, utils), - true, - ); - }, - ); - - testWithoutContext('diffFiles', () async { - expect(projectRoot.existsSync(), true); - expect(projectRoot.childDirectory('.git').existsSync(), false); - await utils.gitInit(projectRootPath); - expect(projectRoot.childDirectory('.git').existsSync(), true); - - final File file1 = - projectRoot.childFile('some_file.dart') - ..createSync() - ..writeAsStringSync('void main() {}\n', flush: true); - - final File file2 = projectRoot.childFile('some_other_file.dart'); - - DiffResult result = await utils.diffFiles(file1, file2); - expect(result.diff, null); - expect(result.diffType, DiffType.deletion); - expect(result.exitCode, null); - - result = await utils.diffFiles(file2, file1); - expect(result.diff, null); - expect(result.diffType, DiffType.addition); - expect(result.exitCode, null); - - file2.createSync(); - file2.writeAsStringSync('void main() {}\n', flush: true); - - result = await utils.diffFiles(file1, file2); - expect(result.diff, ''); - expect(result.diffType, DiffType.command); - expect(result.exitCode, 0); - - file2.writeAsStringSync( - 'void main() {}\na second line\na third line\n', - flush: true, - ); - - result = await utils.diffFiles(file1, file2); - expect( - result.diff, - contains( - '@@ -1 +1,3 @@\n void main() {}\n+a second line\n+a third line', - ), - ); - expect(result.diffType, DiffType.command); - expect(result.exitCode, 1); - }); - - testWithoutContext('merge', () async { - expect(projectRoot.existsSync(), true); - expect(projectRoot.childDirectory('.git').existsSync(), false); - await utils.gitInit(projectRootPath); - expect(projectRoot.childDirectory('.git').existsSync(), true); - - final File file1 = projectRoot.childFile('some_file.dart'); - file1.createSync(); - file1.writeAsStringSync( - 'void main() {}\n\nline1\nline2\nline3\nline4\nline5\n', - flush: true, - ); - final File file2 = projectRoot.childFile('some_other_file.dart'); - file2.createSync(); - file2.writeAsStringSync( - 'void main() {}\n\nline1\nline2\nline3.0\nline3.5\nline4\nline5\n', - flush: true, - ); - final File file3 = projectRoot.childFile('some_other_third_file.dart'); - file3.createSync(); - file3.writeAsStringSync( - 'void main() {}\n\nline2\nline3\nline4\nline5\n', - flush: true, - ); - - StringMergeResult result = - await utils.gitMergeFile( - base: file1.path, - current: file2.path, - target: file3.path, - localPath: 'some_file.dart', - ) - as StringMergeResult; - - expect( - result.mergedString, - 'void main() {}\n\nline2\nline3.0\nline3.5\nline4\nline5\n', - ); - expect(result.hasConflict, false); - expect(result.exitCode, 0); - - file3.writeAsStringSync( - 'void main() {}\n\nline1\nline2\nline3.1\nline3.5\nline4\nline5\n', - flush: true, - ); - - result = - await utils.gitMergeFile( - base: file1.path, - current: file2.path, - target: file3.path, - localPath: 'some_file.dart', - ) - as StringMergeResult; - - expect( - result.mergedString, - contains('line3.0\n=======\nline3.1\n>>>>>>>'), - ); - expect(result.hasConflict, true); - expect(result.exitCode, 1); - - // Two way merge - result = - await utils.gitMergeFile( - base: file1.path, - current: file1.path, - target: file3.path, - localPath: 'some_file.dart', - ) - as StringMergeResult; - - expect( - result.mergedString, - 'void main() {}\n\nline1\nline2\nline3.1\nline3.5\nline4\nline5\n', - ); - expect(result.hasConflict, false); - expect(result.exitCode, 0); - }); - }); - - testWithoutContext('printCommandText standalone', () async { - printCommandText('test', logger, standalone: false); - expect(logger.statusText, contains('flutter migrate test')); - logger.clear(); - - printCommandText('rawtext', logger, standalone: null); - expect(logger.statusText, contains('rawtext')); - logger.clear(); - - printCommandText('fullstandalone', logger); - if (isWindows) { - expect( - logger.statusText, - contains( - r'dart run \bin\flutter_migrate.dart fullstandalone', - ), - ); - } else { - expect( - logger.statusText, - contains( - 'dart run /bin/flutter_migrate.dart fullstandalone', - ), - ); - } - logger.clear(); - }); - - group( - 'legacy app creation', - () { - testWithoutContext('clone and create', () async { - projectRoot = fileSystem.systemTempDirectory.createTempSync( - 'flutter_sdk_test', - ); - const String revision = '5391447fae6209bb21a89e6a5a6583cac1af9b4b'; - - expect(await utils.cloneFlutter(revision, projectRoot.path), true); - expect(projectRoot.childFile('README.md').existsSync(), true); - - final Directory appDir = fileSystem.systemTempDirectory.createTempSync( - 'flutter_app', - ); - await utils.createFromTemplates( - projectRoot.childDirectory('bin').path, - name: 'testapp', - androidLanguage: 'java', - iosLanguage: 'objc', - outputDirectory: appDir.path, - ); - expect(appDir.childFile('pubspec.yaml').existsSync(), true); - expect(appDir.childFile('.metadata').existsSync(), true); - expect( - appDir.childFile('.metadata').readAsStringSync(), - contains(revision), - ); - expect(appDir.childDirectory('android').existsSync(), true); - expect(appDir.childDirectory('ios').existsSync(), true); - expect(appDir.childDirectory('web').existsSync(), false); - - projectRoot.deleteSync(recursive: true); - }); - }, - timeout: const Timeout(Duration(seconds: 500)), - // TODO(stuartmorgan): These should not be unit tests, see - // https://github.com/flutter/flutter/issues/121257. - skip: 'TODO: Speed up, or move to another type of test', - ); - - testWithoutContext('conflictsResolved', () async { - expect(utils.conflictsResolved(''), true); - expect(utils.conflictsResolved('hello'), true); - expect(utils.conflictsResolved('hello\n'), true); - expect( - utils.conflictsResolved('hello\nwow a bunch of lines\n\nhi\n'), - true, - ); - expect( - utils.conflictsResolved('hello\nwow a bunch of lines\n>>>>>>>\nhi\n'), - false, - ); - expect( - utils.conflictsResolved('hello\nwow a bunch of lines\n=======\nhi\n'), - false, - ); - expect( - utils.conflictsResolved('hello\nwow a bunch of lines\n<<<<<<<\nhi\n'), - false, - ); - expect( - utils.conflictsResolved( - 'hello\nwow a bunch of lines\n<<<<<<<\n=======\n<<<<<<<\nhi\n', - ), - false, - ); - }); -} diff --git a/script/configs/custom_analysis.yaml b/script/configs/custom_analysis.yaml index fdb0784bce2..4c2d8c71e3c 100644 --- a/script/configs/custom_analysis.yaml +++ b/script/configs/custom_analysis.yaml @@ -18,9 +18,6 @@ - animations # Deliberately uses flutter_lints, as that's what it is demonstrating. - flutter_lints/example -# Adopts some flutter_tools rules regarding public api docs due to being an -# extension of the tool and using tools base code. -- flutter_migrate # Has some test files that are intentionally broken to conduct dart fix tests. # Also opts out of unawaited_futures, matching flutter/flutter. - go_router diff --git a/script/configs/windows_unit_tests_exceptions.yaml b/script/configs/windows_unit_tests_exceptions.yaml index 9ea5b0165bf..a2b6327d6ec 100644 --- a/script/configs/windows_unit_tests_exceptions.yaml +++ b/script/configs/windows_unit_tests_exceptions.yaml @@ -1,7 +1,4 @@ # Packages that are excluded from dart unit test on Windows. -# This test is very slow, so isn't worth running a second time -# on Windows; the Linux run is enough coverage. -- flutter_migrate # TODO(stuartmorgan): Fix these tests to run correctly on a # Windows host and enable them. - path_provider_linux