[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:
Gary Qian
2022-12-27 13:45:05 -05:00
committed by GitHub
parent a978884712
commit e425eea6c7
15 changed files with 1110 additions and 50 deletions

View File

@ -128,19 +128,6 @@ task:
- else - else
- echo "Only run in presubmit" - echo "Only run in presubmit"
- fi - 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 - name: analyze
env: env:
matrix: matrix:
@ -256,7 +243,7 @@ task:
zone: us-central1-a zone: us-central1-a
namespace: default namespace: default
cpu: 4 cpu: 4
memory: 12G memory: 16G
matrix: matrix:
### Android tasks ### ### Android tasks ###
- name: android-platform_tests - name: android-platform_tests
@ -320,6 +307,19 @@ task:
- cd ../.. - cd ../..
- flutter packages get - flutter packages get
- dart testing/web_benchmarks_test.dart - 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. # ARM macOS tasks.
task: task:

View File

@ -24,7 +24,11 @@ any migrations that are broken.
## Usage ## 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` * `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. in your project home. This command may take some time to complete depending on network speed.

View 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);
}

View 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;
}

View File

@ -4,6 +4,9 @@
import 'package:args/command_runner.dart'; import 'package:args/command_runner.dart';
import 'logger.dart';
import 'project.dart';
enum ExitStatus { enum ExitStatus {
success, success,
warning, warning,
@ -11,6 +14,9 @@ enum ExitStatus {
killed, 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 { class CommandResult {
const CommandResult(this.exitStatus); const CommandResult(this.exitStatus);
@ -50,6 +56,7 @@ abstract class MigrateCommand extends Command<void> {
@override @override
Future<void> run() async { Future<void> run() async {
await runCommand(); await runCommand();
return;
} }
Future<CommandResult> runCommand(); Future<CommandResult> runCommand();
@ -71,4 +78,12 @@ abstract class MigrateCommand extends Command<void> {
/// Gets the parsed command-line option named [name] as an `int`. /// Gets the parsed command-line option named [name] as an `int`.
int? intArg(String name) => argResults?[name] as 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;
}
} }

View 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;
}
}

View File

@ -19,6 +19,7 @@ class MigrateAbandonCommand extends MigrateCommand {
required this.fileSystem, required this.fileSystem,
required this.terminal, required this.terminal,
required ProcessManager processManager, required ProcessManager processManager,
this.standalone = false,
}) : migrateUtils = MigrateUtils( }) : migrateUtils = MigrateUtils(
logger: logger, logger: logger,
fileSystem: fileSystem, fileSystem: fileSystem,
@ -60,6 +61,8 @@ class MigrateAbandonCommand extends MigrateCommand {
final MigrateUtils migrateUtils; final MigrateUtils migrateUtils;
final bool standalone;
@override @override
final String name = 'abandon'; final String name = 'abandon';
@ -75,7 +78,12 @@ class MigrateAbandonCommand extends MigrateCommand {
? FlutterProject.current(fileSystem) ? FlutterProject.current(fileSystem)
: flutterProjectFactory : flutterProjectFactory
.fromDirectory(fileSystem.directory(projectDirectory)); .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 = Directory stagingDirectory =
project.directory.childDirectory(kDefaultMigrateStagingDirectoryName); project.directory.childDirectory(kDefaultMigrateStagingDirectoryName);
final String? customStagingDirectoryPath = stringArg('staging-directory'); final String? customStagingDirectoryPath = stringArg('staging-directory');

View File

@ -25,6 +25,7 @@ class MigrateApplyCommand extends MigrateCommand {
required this.fileSystem, required this.fileSystem,
required this.terminal, required this.terminal,
required ProcessManager processManager, required ProcessManager processManager,
this.standalone = false,
}) : _verbose = verbose, }) : _verbose = verbose,
_processManager = processManager, _processManager = processManager,
migrateUtils = MigrateUtils( migrateUtils = MigrateUtils(
@ -76,6 +77,8 @@ class MigrateApplyCommand extends MigrateCommand {
final MigrateUtils migrateUtils; final MigrateUtils migrateUtils;
final bool standalone;
@override @override
final String name = 'apply'; final String name = 'apply';
@ -98,12 +101,16 @@ class MigrateApplyCommand extends MigrateCommand {
final FlutterToolsEnvironment environment = final FlutterToolsEnvironment environment =
await FlutterToolsEnvironment.initializeFlutterToolsEnvironment( await FlutterToolsEnvironment.initializeFlutterToolsEnvironment(
_processManager, logger); _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)) { if (!await gitRepoExists(project.directory.path, logger, migrateUtils)) {
logger.printStatus('No git repo found. Please run in a project with an ' logger.printStatus('No git repo found. Please run in a project with an '
'initialized git repo or initialize one with:'); 'initialized git repo or initialize one with:');
printCommandText('git init', logger, standalone: null); printCommand('git init', logger);
return const CommandResult(ExitStatus.fail); return const CommandResult(ExitStatus.fail);
} }

View 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}`');
}
}

View File

@ -20,6 +20,7 @@ class MigrateStatusCommand extends MigrateCommand {
required this.logger, required this.logger,
required this.fileSystem, required this.fileSystem,
required ProcessManager processManager, required ProcessManager processManager,
this.standalone = false,
}) : _verbose = verbose, }) : _verbose = verbose,
migrateUtils = MigrateUtils( migrateUtils = MigrateUtils(
logger: logger, logger: logger,
@ -65,6 +66,8 @@ class MigrateStatusCommand extends MigrateCommand {
final MigrateUtils migrateUtils; final MigrateUtils migrateUtils;
final bool standalone;
@override @override
final String name = 'status'; final String name = 'status';
@ -86,7 +89,11 @@ class MigrateStatusCommand extends MigrateCommand {
? FlutterProject.current(fileSystem) ? FlutterProject.current(fileSystem)
: flutterProjectFactory : flutterProjectFactory
.fromDirectory(fileSystem.directory(projectDirectory)); .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 = Directory stagingDirectory =
project.directory.childDirectory(kDefaultMigrateStagingDirectoryName); 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); checkAndPrintMigrateStatus(manifest, stagingDirectory, logger: logger);

View File

@ -55,11 +55,8 @@ bool _skipped(String localPath, FileSystem fileSystem,
} }
} }
if (skippedPrefixes != null) { if (skippedPrefixes != null) {
final Iterable<String> canonicalizedSkippedPrefixes = return skippedPrefixes.any((String prefix) => localPath.startsWith(
_skippedFiles.map<String>((String path) => canonicalize(path)); '${normalize(prefix.replaceAll(r'\', fileSystem.path.separator))}${fileSystem.path.separator}'));
return canonicalizedSkippedPrefixes.any((String prefix) =>
canonicalizedLocalPath
.startsWith('${canonicalize(prefix)}${fileSystem.path.separator}'));
} }
return false; return false;
} }
@ -114,7 +111,6 @@ Set<String> _getSkippedPrefixes(List<SupportedPlatform> platforms) {
for (final SupportedPlatform platform in platforms) { for (final SupportedPlatform platform in platforms) {
skippedPrefixes.remove(platformToSubdirectoryPrefix(platform)); skippedPrefixes.remove(platformToSubdirectoryPrefix(platform));
} }
skippedPrefixes.remove(null);
return skippedPrefixes; return skippedPrefixes;
} }
@ -275,10 +271,6 @@ Future<MigrateResult?> computeMigration({
platforms: platforms, platforms: platforms,
commandParameters: commandParameters, 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, // 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 // 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. // Use user-provided projects if provided, if not, generate them internally.
final bool customBaseProjectDir = commandParameters.baseAppPath != null; final bool customBaseProjectDir = commandParameters.baseAppPath != null;
final bool customTargetProjectDir = commandParameters.targetAppPath != null; final bool customTargetProjectDir = commandParameters.targetAppPath != null;
Directory? baseProjectDir; Directory baseProjectDir =
Directory? targetProjectDir; context.fileSystem.systemTempDirectory.createTempSync('baseProject');
Directory targetProjectDir =
context.fileSystem.systemTempDirectory.createTempSync('targetProject');
if (customBaseProjectDir) { if (customBaseProjectDir) {
baseProjectDir = baseProjectDir =
context.fileSystem.directory(commandParameters.baseAppPath); context.fileSystem.directory(commandParameters.baseAppPath);
@ -402,6 +396,9 @@ Future<ReferenceProjects> _generateBaseAndTargetReferenceProjects({
await context.migrateUtils.gitInit(baseProjectDir.absolute.path); await context.migrateUtils.gitInit(baseProjectDir.absolute.path);
await context.migrateUtils.gitInit(targetProjectDir.absolute.path); await context.migrateUtils.gitInit(targetProjectDir.absolute.path);
result.generatedBaseTemplateDirectory = baseProjectDir;
result.generatedTargetTemplateDirectory = targetProjectDir;
final String name = final String name =
context.environment['FlutterProject.manifest.appname']! as String; context.environment['FlutterProject.manifest.appname']! as String;
final String androidLanguage = final String androidLanguage =
@ -821,7 +818,7 @@ class MigrateBaseFlutterProject extends MigrateFlutterProject {
final List<String> platforms = <String>[]; final List<String> platforms = <String>[];
for (final MigratePlatformConfig config for (final MigratePlatformConfig config
in revisionToConfigs[revision]!) { in revisionToConfigs[revision]!) {
if (config.component == null) { if (config.component == FlutterProjectComponent.root) {
continue; continue;
} }
platforms.add(config.component.toString().split('.').last); platforms.add(config.component.toString().split('.').last);

View File

@ -220,6 +220,7 @@ class MigrateUtils {
'--deleted', '--deleted',
'--modified', '--modified',
'--others', '--others',
'--exclude-standard',
'--exclude=${migrateStagingDir ?? kDefaultMigrateStagingDirectoryName}' '--exclude=${migrateStagingDir ?? kDefaultMigrateStagingDirectoryName}'
]; ];
final ProcessResult result = final ProcessResult result =
@ -332,7 +333,7 @@ Future<bool> gitRepoExists(
} }
logger.printStatus( logger.printStatus(
'Project is not a git repo. Please initialize a git repo and try again.'); 'Project is not a git repo. Please initialize a git repo and try again.');
printCommandText('git init', logger); printCommand('git init', logger);
return false; return false;
} }
@ -341,27 +342,34 @@ Future<bool> hasUncommittedChanges(
if (await migrateUtils.hasUncommittedChanges(projectDirectory)) { if (await migrateUtils.hasUncommittedChanges(projectDirectory)) {
logger.printStatus( logger.printStatus(
'There are uncommitted changes in your project. Please git commit, abandon, or stash your changes before trying again.'); 'There are uncommitted changes in your project. Please git commit, abandon, or stash your changes before trying again.');
logger.printStatus( logger.printStatus('You may commit your changes using');
'You may commit your changes using \'git add .\' and \'git commit -m "<message"\''); printCommand('git add .', logger, newlineAfter: false);
printCommand('git commit -m "<message>"', logger);
return true; return true;
} }
return false; return false;
} }
/// Prints a command to logger with appropriate formatting. void printCommand(String command, Logger logger, {bool newlineAfter = true}) {
void printCommandText(String command, Logger logger,
{bool? standalone = true}) {
final String prefix = standalone == null
? ''
: (standalone ? './bin/flutter_migrate ' : 'flutter migrate ');
logger.printStatus( logger.printStatus(
'\n\$ $prefix$command\n', '\n\$ $command${newlineAfter ? '\n' : ''}',
color: TerminalColor.grey, color: TerminalColor.grey,
indent: 4, indent: 4,
newline: false, 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. /// Defines the classification of difference between files.
enum DiffType { enum DiffType {
command, command,

View 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);
}

View File

@ -56,7 +56,7 @@ class MigrateProject extends Project {
'git', 'git',
'commit', 'commit',
'-m', '-m',
'"Initial commit"', '"All changes"',
], workingDirectory: dir.path); ], workingDirectory: dir.path);
} }
@ -101,18 +101,37 @@ class MigrateProject extends Project {
], workingDirectory: dir.path); ], workingDirectory: dir.path);
if (Platform.isWindows) { if (Platform.isWindows) {
await processManager.run(<String>[ ProcessResult res = await processManager.run(<String>[
'robocopy', 'robocopy',
tempDir.path, tempDir.path,
dir.path, dir.path,
'*', '*',
'/E', '/E',
'/V',
'/mov', '/mov',
]); ]);
// Add full access permissions to Users // Robocopy exit code 1 means some files were copied. 0 means no files were copied.
await processManager.run(<String>[ 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', 'icacls',
tempDir.path, dir.path,
], workingDirectory: dir.path);
// Add full access permissions to Users
res = await processManager.run(<String>[
'icacls',
dir.path,
'/q', '/q',
'/c', '/c',
'/t', '/t',
@ -138,8 +157,16 @@ class MigrateProject extends Project {
await processManager.run(<String>[ await processManager.run(<String>[
'chmod', 'chmod',
'-R',
'+w', '+w',
'${dir.path}${fileSystem.path.separator}*', dir.path,
], workingDirectory: dir.path);
await processManager.run(<String>[
'chmod',
'-R',
'+r',
dir.path,
], workingDirectory: dir.path); ], workingDirectory: dir.path);
} }

View File

@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // 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/file_system.dart';
import 'package:flutter_migrate/src/base/io.dart'; import 'package:flutter_migrate/src/base/io.dart';
import 'package:flutter_migrate/src/base/logger.dart'; import 'package:flutter_migrate/src/base/logger.dart';
@ -238,7 +239,17 @@ void main() {
logger.clear(); logger.clear();
printCommandText('fullstandalone', logger); 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(); logger.clear();
}); });