mirror of
https://github.com/flutter/packages.git
synced 2025-06-30 14:47:22 +08:00
[flutter_migrate] Start command and executables (#2735)
* [flutter_migrate] Start command * Sync with upstream changes * Integrate with compute and add E2E tests * E2E tests passing * Resolve hangs, add help messages * Use CommandRunner * Cleanup hooks to prevent hang * Formatting * Softer requirements on test matching to ignore order * Null safety, address minor comments * Address comments * Cleanup and logging improvements * format, normalize * Kick tests * Fix tests * Improve win test robustness * Windows perms copyall * Fix directory typo bug * takeown command in CI * Add logging for windows * windows command debug logging * Skip running on unsupported flutter versions * Add main.dart * Bots memory, dynamic flutter executable * Different temp dir * Takeown of main.dart * test adjustments * Analyzer * Increase resources to dart_unit_tests * Remove logging
This commit is contained in:
28
.cirrus.yml
28
.cirrus.yml
@ -128,19 +128,6 @@ task:
|
||||
- else
|
||||
- echo "Only run in presubmit"
|
||||
- fi
|
||||
- name: dart_unit_tests
|
||||
env:
|
||||
matrix:
|
||||
CHANNEL: "master"
|
||||
CHANNEL: "stable"
|
||||
unit_test_script:
|
||||
- ./script/tool_runner.sh test --exclude=script/configs/dart_unit_tests_exceptions.yaml
|
||||
pathified_unit_test_script:
|
||||
# Run tests with path-based dependencies to ensure that publishing
|
||||
# the changes won't break tests of other packages in the repository
|
||||
# that depend on it.
|
||||
- ./script/tool_runner.sh make-deps-path-based --target-dependencies-with-non-breaking-updates
|
||||
- $PLUGIN_TOOL_COMMAND test --run-on-dirty-packages --exclude=script/configs/dart_unit_tests_exceptions.yaml
|
||||
- name: analyze
|
||||
env:
|
||||
matrix:
|
||||
@ -256,7 +243,7 @@ task:
|
||||
zone: us-central1-a
|
||||
namespace: default
|
||||
cpu: 4
|
||||
memory: 12G
|
||||
memory: 16G
|
||||
matrix:
|
||||
### Android tasks ###
|
||||
- name: android-platform_tests
|
||||
@ -320,6 +307,19 @@ task:
|
||||
- cd ../..
|
||||
- flutter packages get
|
||||
- dart testing/web_benchmarks_test.dart
|
||||
- name: dart_unit_tests
|
||||
env:
|
||||
matrix:
|
||||
CHANNEL: "master"
|
||||
CHANNEL: "stable"
|
||||
unit_test_script:
|
||||
- ./script/tool_runner.sh test --exclude=script/configs/dart_unit_tests_exceptions.yaml
|
||||
pathified_unit_test_script:
|
||||
# Run tests with path-based dependencies to ensure that publishing
|
||||
# the changes won't break tests of other packages in the repository
|
||||
# that depend on it.
|
||||
- ./script/tool_runner.sh make-deps-path-based --target-dependencies-with-non-breaking-updates
|
||||
- $PLUGIN_TOOL_COMMAND test --run-on-dirty-packages --exclude=script/configs/dart_unit_tests_exceptions.yaml
|
||||
|
||||
# ARM macOS tasks.
|
||||
task:
|
||||
|
@ -24,7 +24,11 @@ any migrations that are broken.
|
||||
|
||||
## Usage
|
||||
|
||||
The core command sequence to use is `start`, `apply`.
|
||||
To run the tool enter the root directory of your flutter project and run:
|
||||
|
||||
`dart run <path_to_flutter_migrate_package>/bin/flutter_migrate.dart <subcommand> [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.
|
||||
|
9
packages/flutter_migrate/bin/flutter_migrate.dart
Normal file
9
packages/flutter_migrate/bin/flutter_migrate.dart
Normal file
@ -0,0 +1,9 @@
|
||||
// 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<String> args) {
|
||||
executable.main(args);
|
||||
}
|
95
packages/flutter_migrate/lib/executable.dart
Normal file
95
packages/flutter_migrate/lib/executable.dart
Normal file
@ -0,0 +1,95 @@
|
||||
// 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<void> main(List<String> args) async {
|
||||
final bool veryVerbose = args.contains('-vv');
|
||||
final bool verbose =
|
||||
args.contains('-v') || args.contains('--verbose') || veryVerbose;
|
||||
|
||||
final MigrateBaseDependencies baseDependencies = MigrateBaseDependencies();
|
||||
|
||||
final List<MigrateCommand> commands = <MigrateCommand>[
|
||||
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<void> {
|
||||
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<int> _exit(int code, MigrateBaseDependencies baseDependencies,
|
||||
{required ShutdownHooks shutdownHooks}) async {
|
||||
// Run shutdown hooks before flushing logs
|
||||
await shutdownHooks.runShutdownHooks(baseDependencies.logger);
|
||||
|
||||
final Completer<void> completer = Completer<void>();
|
||||
|
||||
// Give the task / timer queue one cycle through before we hard exit.
|
||||
Timer.run(() {
|
||||
io.exit(code);
|
||||
});
|
||||
|
||||
await completer.future;
|
||||
return code;
|
||||
}
|
@ -4,6 +4,9 @@
|
||||
|
||||
import 'package:args/command_runner.dart';
|
||||
|
||||
import 'logger.dart';
|
||||
import 'project.dart';
|
||||
|
||||
enum ExitStatus {
|
||||
success,
|
||||
warning,
|
||||
@ -11,6 +14,9 @@ enum ExitStatus {
|
||||
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);
|
||||
|
||||
@ -50,6 +56,7 @@ abstract class MigrateCommand extends Command<void> {
|
||||
@override
|
||||
Future<void> run() async {
|
||||
await runCommand();
|
||||
return;
|
||||
}
|
||||
|
||||
Future<CommandResult> runCommand();
|
||||
@ -71,4 +78,12 @@ abstract class MigrateCommand extends Command<void> {
|
||||
|
||||
/// 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;
|
||||
}
|
||||
}
|
||||
|
87
packages/flutter_migrate/lib/src/base_dependencies.dart
Normal file
87
packages/flutter_migrate/lib/src/base_dependencies.dart
Normal file
@ -0,0 +1,87 @@
|
||||
// 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;
|
||||
}
|
||||
}
|
@ -19,6 +19,7 @@ class MigrateAbandonCommand extends MigrateCommand {
|
||||
required this.fileSystem,
|
||||
required this.terminal,
|
||||
required ProcessManager processManager,
|
||||
this.standalone = false,
|
||||
}) : migrateUtils = MigrateUtils(
|
||||
logger: logger,
|
||||
fileSystem: fileSystem,
|
||||
@ -60,6 +61,8 @@ class MigrateAbandonCommand extends MigrateCommand {
|
||||
|
||||
final MigrateUtils migrateUtils;
|
||||
|
||||
final bool standalone;
|
||||
|
||||
@override
|
||||
final String name = 'abandon';
|
||||
|
||||
@ -75,7 +78,12 @@ class MigrateAbandonCommand extends MigrateCommand {
|
||||
? FlutterProject.current(fileSystem)
|
||||
: flutterProjectFactory
|
||||
.fromDirectory(fileSystem.directory(projectDirectory));
|
||||
final bool isSubcommand = boolArg('flutter-subcommand') ?? false;
|
||||
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');
|
||||
|
@ -25,6 +25,7 @@ class MigrateApplyCommand extends MigrateCommand {
|
||||
required this.fileSystem,
|
||||
required this.terminal,
|
||||
required ProcessManager processManager,
|
||||
this.standalone = false,
|
||||
}) : _verbose = verbose,
|
||||
_processManager = processManager,
|
||||
migrateUtils = MigrateUtils(
|
||||
@ -76,6 +77,8 @@ class MigrateApplyCommand extends MigrateCommand {
|
||||
|
||||
final MigrateUtils migrateUtils;
|
||||
|
||||
final bool standalone;
|
||||
|
||||
@override
|
||||
final String name = 'apply';
|
||||
|
||||
@ -98,12 +101,16 @@ class MigrateApplyCommand extends MigrateCommand {
|
||||
final FlutterToolsEnvironment environment =
|
||||
await FlutterToolsEnvironment.initializeFlutterToolsEnvironment(
|
||||
_processManager, logger);
|
||||
final bool isSubcommand = boolArg('flutter-subcommand') ?? false;
|
||||
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:');
|
||||
printCommandText('git init', logger, standalone: null);
|
||||
printCommand('git init', logger);
|
||||
return const CommandResult(ExitStatus.fail);
|
||||
}
|
||||
|
||||
|
345
packages/flutter_migrate/lib/src/commands/start.dart
Normal file
345
packages/flutter_migrate/lib/src/commands/start.dart
Normal file
@ -0,0 +1,345 @@
|
||||
// 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<CommandResult> 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<SupportedPlatform>? platforms;
|
||||
if (stringArg('platforms') != null) {
|
||||
platforms = <SupportedPlatform>[];
|
||||
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: <String>[],
|
||||
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<String> paths = const <String>[],
|
||||
List<Directory> directories = const <Directory>[]}) {
|
||||
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<void> 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}`');
|
||||
}
|
||||
}
|
@ -20,6 +20,7 @@ class MigrateStatusCommand extends MigrateCommand {
|
||||
required this.logger,
|
||||
required this.fileSystem,
|
||||
required ProcessManager processManager,
|
||||
this.standalone = false,
|
||||
}) : _verbose = verbose,
|
||||
migrateUtils = MigrateUtils(
|
||||
logger: logger,
|
||||
@ -65,6 +66,8 @@ class MigrateStatusCommand extends MigrateCommand {
|
||||
|
||||
final MigrateUtils migrateUtils;
|
||||
|
||||
final bool standalone;
|
||||
|
||||
@override
|
||||
final String name = 'status';
|
||||
|
||||
@ -86,7 +89,11 @@ class MigrateStatusCommand extends MigrateCommand {
|
||||
? FlutterProject.current(fileSystem)
|
||||
: flutterProjectFactory
|
||||
.fromDirectory(fileSystem.directory(projectDirectory));
|
||||
final bool isSubcommand = boolArg('flutter-subcommand') ?? false;
|
||||
final bool isSubcommand = boolArg('flutter-subcommand') ?? !standalone;
|
||||
|
||||
if (!validateWorkingDirectory(project, logger)) {
|
||||
return CommandResult.fail();
|
||||
}
|
||||
|
||||
Directory stagingDirectory =
|
||||
project.directory.childDirectory(kDefaultMigrateStagingDirectoryName);
|
||||
@ -166,7 +173,7 @@ class MigrateStatusCommand extends MigrateCommand {
|
||||
}
|
||||
}
|
||||
|
||||
logger.printBox('Working directory at `${stagingDirectory.path}`');
|
||||
logger.printBox('Staging directory at `${stagingDirectory.path}`');
|
||||
|
||||
checkAndPrintMigrateStatus(manifest, stagingDirectory, logger: logger);
|
||||
|
||||
|
@ -55,11 +55,8 @@ bool _skipped(String localPath, FileSystem fileSystem,
|
||||
}
|
||||
}
|
||||
if (skippedPrefixes != null) {
|
||||
final Iterable<String> canonicalizedSkippedPrefixes =
|
||||
_skippedFiles.map<String>((String path) => canonicalize(path));
|
||||
return canonicalizedSkippedPrefixes.any((String prefix) =>
|
||||
canonicalizedLocalPath
|
||||
.startsWith('${canonicalize(prefix)}${fileSystem.path.separator}'));
|
||||
return skippedPrefixes.any((String prefix) => localPath.startsWith(
|
||||
'${normalize(prefix.replaceAll(r'\', fileSystem.path.separator))}${fileSystem.path.separator}'));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@ -114,7 +111,6 @@ Set<String> _getSkippedPrefixes(List<SupportedPlatform> platforms) {
|
||||
for (final SupportedPlatform platform in platforms) {
|
||||
skippedPrefixes.remove(platformToSubdirectoryPrefix(platform));
|
||||
}
|
||||
skippedPrefixes.remove(null);
|
||||
return skippedPrefixes;
|
||||
}
|
||||
|
||||
@ -275,10 +271,6 @@ Future<MigrateResult?> computeMigration({
|
||||
platforms: platforms,
|
||||
commandParameters: commandParameters,
|
||||
);
|
||||
result.generatedBaseTemplateDirectory =
|
||||
referenceProjects.baseProject.directory;
|
||||
result.generatedTargetTemplateDirectory =
|
||||
referenceProjects.targetProject.directory;
|
||||
|
||||
// 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
|
||||
@ -377,8 +369,10 @@ Future<ReferenceProjects> _generateBaseAndTargetReferenceProjects({
|
||||
// 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;
|
||||
Directory? targetProjectDir;
|
||||
Directory baseProjectDir =
|
||||
context.fileSystem.systemTempDirectory.createTempSync('baseProject');
|
||||
Directory targetProjectDir =
|
||||
context.fileSystem.systemTempDirectory.createTempSync('targetProject');
|
||||
if (customBaseProjectDir) {
|
||||
baseProjectDir =
|
||||
context.fileSystem.directory(commandParameters.baseAppPath);
|
||||
@ -402,6 +396,9 @@ Future<ReferenceProjects> _generateBaseAndTargetReferenceProjects({
|
||||
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 =
|
||||
@ -821,7 +818,7 @@ class MigrateBaseFlutterProject extends MigrateFlutterProject {
|
||||
final List<String> platforms = <String>[];
|
||||
for (final MigratePlatformConfig config
|
||||
in revisionToConfigs[revision]!) {
|
||||
if (config.component == null) {
|
||||
if (config.component == FlutterProjectComponent.root) {
|
||||
continue;
|
||||
}
|
||||
platforms.add(config.component.toString().split('.').last);
|
||||
|
@ -220,6 +220,7 @@ class MigrateUtils {
|
||||
'--deleted',
|
||||
'--modified',
|
||||
'--others',
|
||||
'--exclude-standard',
|
||||
'--exclude=${migrateStagingDir ?? kDefaultMigrateStagingDirectoryName}'
|
||||
];
|
||||
final ProcessResult result =
|
||||
@ -332,7 +333,7 @@ Future<bool> gitRepoExists(
|
||||
}
|
||||
logger.printStatus(
|
||||
'Project is not a git repo. Please initialize a git repo and try again.');
|
||||
printCommandText('git init', logger);
|
||||
printCommand('git init', logger);
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -341,27 +342,34 @@ Future<bool> hasUncommittedChanges(
|
||||
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 \'git add .\' and \'git commit -m "<message"\'');
|
||||
logger.printStatus('You may commit your changes using');
|
||||
printCommand('git add .', logger, newlineAfter: false);
|
||||
printCommand('git commit -m "<message>"', logger);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Prints a command to logger with appropriate formatting.
|
||||
void printCommandText(String command, Logger logger,
|
||||
{bool? standalone = true}) {
|
||||
final String prefix = standalone == null
|
||||
? ''
|
||||
: (standalone ? './bin/flutter_migrate ' : 'flutter migrate ');
|
||||
void printCommand(String command, Logger logger, {bool newlineAfter = true}) {
|
||||
logger.printStatus(
|
||||
'\n\$ $prefix$command\n',
|
||||
'\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 <flutter_migrate_dir>${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,
|
||||
|
440
packages/flutter_migrate/test/migrate_test.dart
Normal file
440
packages/flutter_migrate/test/migrate_test.dart
Normal file
@ -0,0 +1,440 @@
|
||||
// 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<bool> hasFlutterEnvironment() async {
|
||||
final String flutterRoot = getFlutterRoot();
|
||||
final String flutterExecutable = fileSystem.path
|
||||
.join(flutterRoot, 'bin', 'flutter${isWindows ? '.bat' : ''}');
|
||||
final ProcessResult result = await Process.run(
|
||||
flutterExecutable, <String>['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(<String>[
|
||||
'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(<String>[
|
||||
'apply',
|
||||
'--verbose',
|
||||
], workingDirectory: tempDir.path);
|
||||
logger.printStatus('${result.exitCode}', color: TerminalColor.blue);
|
||||
logger.printStatus(result.stdout, color: TerminalColor.green);
|
||||
logger.printStatus(result.stderr, 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)), skip: isWindows);
|
||||
|
||||
// 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(<String>[
|
||||
'start',
|
||||
'--verbose',
|
||||
], workingDirectory: tempDir.path);
|
||||
expect(result.stdout.toString(), contains('Staging directory created at'));
|
||||
|
||||
result = await runMigrateCommand(<String>[
|
||||
'apply',
|
||||
'--verbose',
|
||||
], workingDirectory: tempDir.path);
|
||||
logger.printStatus('${result.exitCode}', color: TerminalColor.blue);
|
||||
logger.printStatus(result.stdout, color: TerminalColor.green);
|
||||
logger.printStatus(result.stderr, color: TerminalColor.red);
|
||||
expect(result.exitCode, 0);
|
||||
expect(result.stdout.toString(), contains('Migration complete'));
|
||||
|
||||
result = await processManager.run(<String>[
|
||||
'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)), skip: true);
|
||||
|
||||
testUsingContext('migrate abandon', () async {
|
||||
// Abandon in an empty dir fails.
|
||||
ProcessResult result = await runMigrateCommand(<String>[
|
||||
'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(<String>[
|
||||
'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(<String>[
|
||||
'abandon',
|
||||
'--verbose',
|
||||
'--force',
|
||||
], workingDirectory: tempDir.path);
|
||||
expect(result.exitCode, 0);
|
||||
expect(result.stdout.toString(), contains('Abandon complete'));
|
||||
}, timeout: const Timeout(Duration(seconds: 300)));
|
||||
|
||||
// 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(<String>[
|
||||
'apply',
|
||||
'--verbose',
|
||||
], workingDirectory: tempDir.path);
|
||||
expect(result.exitCode, 0);
|
||||
expect(result.stdout.toString(), contains('No migration'));
|
||||
|
||||
result = await runMigrateCommand(<String>[
|
||||
'status',
|
||||
'--verbose',
|
||||
], workingDirectory: tempDir.path);
|
||||
expect(result.exitCode, 0);
|
||||
expect(result.stdout.toString(), contains('No migration'));
|
||||
|
||||
result = await runMigrateCommand(<String>[
|
||||
'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(<String>[
|
||||
'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(<String>[
|
||||
'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/assets-and-images/#resolution-aware.
|
||||
|
||||
# For details regarding adding assets from package dependencies, see
|
||||
# https://flutter.dev/assets-and-images/#from-packages
|
||||
|
||||
# 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/custom-fonts/#from-packages
|
||||
|
||||
''', flush: true);
|
||||
|
||||
result = await runMigrateCommand(<String>[
|
||||
'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(<String>[
|
||||
'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)), skip: isWindows);
|
||||
}
|
@ -56,7 +56,7 @@ class MigrateProject extends Project {
|
||||
'git',
|
||||
'commit',
|
||||
'-m',
|
||||
'"Initial commit"',
|
||||
'"All changes"',
|
||||
], workingDirectory: dir.path);
|
||||
}
|
||||
|
||||
@ -101,18 +101,37 @@ class MigrateProject extends Project {
|
||||
], workingDirectory: dir.path);
|
||||
|
||||
if (Platform.isWindows) {
|
||||
await processManager.run(<String>[
|
||||
ProcessResult res = await processManager.run(<String>[
|
||||
'robocopy',
|
||||
tempDir.path,
|
||||
dir.path,
|
||||
'*',
|
||||
'/E',
|
||||
'/V',
|
||||
'/mov',
|
||||
]);
|
||||
// Add full access permissions to Users
|
||||
await processManager.run(<String>[
|
||||
// Robocopy exit code 1 means some files were copied. 0 means no files were copied.
|
||||
assert(res.exitCode == 1);
|
||||
res = await processManager.run(<String>[
|
||||
'takeown',
|
||||
'/f',
|
||||
dir.path,
|
||||
'/r',
|
||||
]);
|
||||
res = await processManager.run(<String>[
|
||||
'takeown',
|
||||
'/f',
|
||||
'${dir.path}\\lib\\main.dart',
|
||||
'/r',
|
||||
]);
|
||||
res = await processManager.run(<String>[
|
||||
'icacls',
|
||||
tempDir.path,
|
||||
dir.path,
|
||||
], workingDirectory: dir.path);
|
||||
// Add full access permissions to Users
|
||||
res = await processManager.run(<String>[
|
||||
'icacls',
|
||||
dir.path,
|
||||
'/q',
|
||||
'/c',
|
||||
'/t',
|
||||
@ -138,8 +157,16 @@ class MigrateProject extends Project {
|
||||
|
||||
await processManager.run(<String>[
|
||||
'chmod',
|
||||
'-R',
|
||||
'+w',
|
||||
'${dir.path}${fileSystem.path.separator}*',
|
||||
dir.path,
|
||||
], workingDirectory: dir.path);
|
||||
|
||||
await processManager.run(<String>[
|
||||
'chmod',
|
||||
'-R',
|
||||
'+r',
|
||||
dir.path,
|
||||
], workingDirectory: dir.path);
|
||||
}
|
||||
|
||||
|
@ -2,6 +2,7 @@
|
||||
// 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';
|
||||
@ -238,7 +239,17 @@ void main() {
|
||||
logger.clear();
|
||||
|
||||
printCommandText('fullstandalone', logger);
|
||||
expect(logger.statusText, contains('./bin/flutter_migrate fullstandalone'));
|
||||
if (isWindows) {
|
||||
expect(
|
||||
logger.statusText,
|
||||
contains(
|
||||
r'dart run <flutter_migrate_dir>\bin\flutter_migrate.dart fullstandalone'));
|
||||
} else {
|
||||
expect(
|
||||
logger.statusText,
|
||||
contains(
|
||||
'dart run <flutter_migrate_dir>/bin/flutter_migrate.dart fullstandalone'));
|
||||
}
|
||||
logger.clear();
|
||||
});
|
||||
|
||||
|
Reference in New Issue
Block a user