[flutter_migrate] Compute (#2734)

* Compute

* Licenses:

* Licenses for test

* Merge with depenencies

* Address comments p1

* Separate out logging from logic

* Refactor into smaller methods

* Improve logging and verbose usages

* Diferentiate merge skip vs total skip

* More docs

* Remove additional skip

* Fix custom merge tests

* Mocked environment

* Formatting

* TImeouts

* Use separate enum to address project directories including root

* tests passing

* Fix analyzer

* address comments, formatting

* Test robustness

* Fix update locks test

* formatting

* logging for CI test failures

* Canonicalize test paths

* Canonizalize both sides of tests

* Address comments, fix tests

* Formatting

* Gradle locks test
This commit is contained in:
Gary Qian
2022-12-06 00:49:16 -08:00
committed by GitHub
parent 95edfea28d
commit 57e388d997
10 changed files with 2098 additions and 61 deletions

View File

@ -16,7 +16,6 @@ enum SupportedPlatform {
web,
windows,
fuchsia,
root, // Special platform to represent the root project directory
}
class FlutterProjectFactory {
@ -61,10 +60,8 @@ class FlutterProject {
File get metadataFile => directory.childFile('.metadata');
/// Returns a list of platform names that are supported by the project.
List<SupportedPlatform> getSupportedPlatforms({bool includeRoot = false}) {
final List<SupportedPlatform> platforms = includeRoot
? <SupportedPlatform>[SupportedPlatform.root]
: <SupportedPlatform>[];
List<SupportedPlatform> getSupportedPlatforms() {
final List<SupportedPlatform> platforms = <SupportedPlatform>[];
if (directory.childDirectory('android').existsSync()) {
platforms.add(SupportedPlatform.android);
}

File diff suppressed because it is too large Load Diff

View File

@ -4,7 +4,6 @@
import 'base/file_system.dart';
import 'base/logger.dart';
import 'base/project.dart';
import 'flutter_project_metadata.dart';
import 'utils.dart';
@ -82,23 +81,24 @@ class MetadataCustomMerge extends CustomMerge {
MigrateConfig mergeMigrateConfig(
MigrateConfig current, MigrateConfig target) {
// Create the superset of current and target platforms with baseRevision updated to be that of target.
final Map<SupportedPlatform, MigratePlatformConfig> platformConfigs =
<SupportedPlatform, MigratePlatformConfig>{};
for (final MapEntry<SupportedPlatform, MigratePlatformConfig> entry
final Map<FlutterProjectComponent, MigratePlatformConfig>
projectComponentConfigs =
<FlutterProjectComponent, MigratePlatformConfig>{};
for (final MapEntry<FlutterProjectComponent, MigratePlatformConfig> entry
in current.platformConfigs.entries) {
if (target.platformConfigs.containsKey(entry.key)) {
platformConfigs[entry.key] = MigratePlatformConfig(
platform: entry.value.platform,
projectComponentConfigs[entry.key] = MigratePlatformConfig(
component: entry.value.component,
createRevision: entry.value.createRevision,
baseRevision: target.platformConfigs[entry.key]?.baseRevision);
} else {
platformConfigs[entry.key] = entry.value;
projectComponentConfigs[entry.key] = entry.value;
}
}
for (final MapEntry<SupportedPlatform, MigratePlatformConfig> entry
for (final MapEntry<FlutterProjectComponent, MigratePlatformConfig> entry
in target.platformConfigs.entries) {
if (!platformConfigs.containsKey(entry.key)) {
platformConfigs[entry.key] = entry.value;
if (!projectComponentConfigs.containsKey(entry.key)) {
projectComponentConfigs[entry.key] = entry.value;
}
}
@ -112,7 +112,7 @@ class MetadataCustomMerge extends CustomMerge {
}
}
return MigrateConfig(
platformConfigs: platformConfigs,
platformConfigs: projectComponentConfigs,
unmanagedFiles: unmanagedFiles,
);
}

View File

@ -9,6 +9,41 @@ import 'base/file_system.dart';
import 'base/logger.dart';
import 'base/project.dart';
/// Represents subdirectories of the flutter project that can be independently created.
///
/// This includes each supported platform as well as a component that represents the
/// root directory of the project.
enum FlutterProjectComponent {
root,
android,
ios,
linux,
macos,
web,
windows,
fuchsia,
}
extension SupportedPlatformExtension on SupportedPlatform {
FlutterProjectComponent toFlutterProjectComponent() {
final String platformName = toString().split('.').last;
return FlutterProjectComponent.values.firstWhere(
(FlutterProjectComponent e) =>
e.toString() == 'FlutterProjectComponent.$platformName');
}
}
extension FlutterProjectComponentExtension on FlutterProjectComponent {
SupportedPlatform? toSupportedPlatform() {
final String platformName = toString().split('.').last;
if (platformName == 'root') {
return null;
}
return SupportedPlatform.values.firstWhere((SupportedPlatform e) =>
e.toString() == 'SupportedPlatform.$platformName');
}
}
enum FlutterProjectType {
/// This is the default project with the user-managed host code.
/// It is different than the "module" template in that it exposes and doesn't
@ -226,10 +261,10 @@ ${migrateConfig.getOutputFileString()}''';
/// used to add support for new platforms, so the base and create revision may not always be the same.
class MigrateConfig {
MigrateConfig(
{Map<SupportedPlatform, MigratePlatformConfig>? platformConfigs,
{Map<FlutterProjectComponent, MigratePlatformConfig>? platformConfigs,
this.unmanagedFiles = kDefaultUnmanagedFiles})
: platformConfigs =
platformConfigs ?? <SupportedPlatform, MigratePlatformConfig>{};
: platformConfigs = platformConfigs ??
<FlutterProjectComponent, MigratePlatformConfig>{};
/// A mapping of the files that are unmanaged by defult for each platform.
static const List<String> kDefaultUnmanagedFiles = <String>[
@ -238,7 +273,7 @@ class MigrateConfig {
];
/// The metadata for each platform supported by the project.
final Map<SupportedPlatform, MigratePlatformConfig> platformConfigs;
final Map<FlutterProjectComponent, MigratePlatformConfig> platformConfigs;
/// A list of paths relative to this file the migrate tool should ignore.
///
@ -261,17 +296,23 @@ class MigrateConfig {
required Logger logger,
}) {
final FlutterProject flutterProject = FlutterProject(projectDirectory);
platforms ??= flutterProject.getSupportedPlatforms(includeRoot: true);
platforms ??= flutterProject.getSupportedPlatforms();
final List<FlutterProjectComponent> components =
<FlutterProjectComponent>[];
for (final SupportedPlatform platform in platforms) {
if (platformConfigs.containsKey(platform)) {
components.add(platform.toFlutterProjectComponent());
}
components.add(FlutterProjectComponent.root);
for (final FlutterProjectComponent component in components) {
if (platformConfigs.containsKey(component)) {
if (update) {
platformConfigs[platform]!.baseRevision = currentRevision;
platformConfigs[component]!.baseRevision = currentRevision;
}
} else {
if (create) {
platformConfigs[platform] = MigratePlatformConfig(
platform: platform,
platformConfigs[component] = MigratePlatformConfig(
component: component,
createRevision: createRevision,
baseRevision: currentRevision);
}
@ -287,7 +328,7 @@ class MigrateConfig {
}
String platformsString = '';
for (final MapEntry<SupportedPlatform, MigratePlatformConfig> entry
for (final MapEntry<FlutterProjectComponent, MigratePlatformConfig> entry
in platformConfigs.entries) {
platformsString +=
'\n - platform: ${entry.key.toString().split('.').last}\n create_revision: ${entry.value.createRevision == null ? 'null' : "${entry.value.createRevision}"}\n base_revision: ${entry.value.baseRevision == null ? 'null' : "${entry.value.baseRevision}"}';
@ -327,12 +368,13 @@ migration:
'base_revision': String,
},
logger)) {
final SupportedPlatform platformValue = SupportedPlatform.values
.firstWhere((SupportedPlatform val) =>
final FlutterProjectComponent component = FlutterProjectComponent
.values
.firstWhere((FlutterProjectComponent val) =>
val.toString() ==
'SupportedPlatform.${platformYamlMap['platform'] as String}');
platformConfigs[platformValue] = MigratePlatformConfig(
platform: platformValue,
'FlutterProjectComponent.${platformYamlMap['platform'] as String}');
platformConfigs[component] = MigratePlatformConfig(
component: component,
createRevision: platformYamlMap['create_revision'] as String?,
baseRevision: platformYamlMap['base_revision'] as String?,
);
@ -357,10 +399,10 @@ migration:
/// Holds the revisions for a single platform for use by the flutter migrate command.
class MigratePlatformConfig {
MigratePlatformConfig(
{required this.platform, this.createRevision, this.baseRevision});
{required this.component, this.createRevision, this.baseRevision});
/// The platform this config describes.
SupportedPlatform platform;
FlutterProjectComponent component;
/// The Flutter SDK revision this platform was created by.
///
@ -373,7 +415,7 @@ class MigratePlatformConfig {
String? baseRevision;
bool equals(MigratePlatformConfig other) {
return platform == other.platform &&
return component == other.component &&
createRevision == other.createRevision &&
baseRevision == other.baseRevision;
}

View File

@ -0,0 +1,73 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'base/logger.dart';
import 'base/terminal.dart';
const int kDefaultStatusIndent = 2;
class MigrateLogger {
MigrateLogger({
required this.logger,
this.verbose = false,
this.silent = false,
}) : status = logger.startSpinner();
final Logger logger;
// We keep a spinner going and print periodic progress messages
// to assure the developer that the command is still working due to
// the long expected runtime.
Status status;
final bool verbose;
final bool silent;
void start() {
status = logger.startSpinner();
}
void stop() {
status.stop();
}
static final Map<String, String> _stepStringsMap = <String, String>{
'start': 'Computing migration - this command may take a while to complete.',
'revisions': 'Obtaining revisions.',
'unmanaged': 'Parsing unmanagedFiles.',
'generating_base': 'Generating base reference app.',
'diff': 'Diffing base and target reference app.',
'new_files': 'Finding newly added files',
'merging': 'Merging changes with existing project.',
'cleaning': 'Cleaning up temp directories.',
'modified_count':
'Could not determine base revision, falling back on `v1.0.0`, revision 5391447fae6209bb21a89e6a5a6583cac1af9b4b',
};
void printStatus(String message, {int indent = kDefaultStatusIndent}) {
if (silent) {
return;
}
status.pause();
logger.printStatus(message, indent: indent, color: TerminalColor.grey);
status.resume();
}
void printError(String message, {int indent = 0}) {
status.pause();
logger.printError(message, indent: indent);
status.resume();
}
void logStep(String key) {
if (!_stepStringsMap.containsKey(key)) {
return;
}
printStatus(_stepStringsMap[key]!);
}
void printIfVerbose(String message, {int indent = kDefaultStatusIndent}) {
if (verbose) {
printStatus(message, indent: indent);
}
}
}

View File

@ -0,0 +1,869 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter_migrate/src/base/common.dart';
import 'package:flutter_migrate/src/base/file_system.dart';
import 'package:flutter_migrate/src/base/logger.dart';
import 'package:flutter_migrate/src/base/project.dart';
import 'package:flutter_migrate/src/base/signals.dart';
import 'package:flutter_migrate/src/compute.dart';
import 'package:flutter_migrate/src/environment.dart';
import 'package:flutter_migrate/src/flutter_project_metadata.dart';
import 'package:flutter_migrate/src/migrate_logger.dart';
import 'package:flutter_migrate/src/result.dart';
import 'package:flutter_migrate/src/utils.dart';
import 'package:path/path.dart';
import 'package:process/process.dart';
import 'environment_test.dart';
import 'src/common.dart';
import 'src/context.dart';
import 'src/test_utils.dart';
import 'test_data/migrate_project.dart';
void main() {
late FileSystem fileSystem;
late BufferLogger logger;
late MigrateUtils utils;
late MigrateContext context;
late MigrateResult result;
late Directory targetFlutterDirectory;
late Directory newerTargetFlutterDirectory;
late Directory currentDir;
late FlutterToolsEnvironment environment;
late ProcessManager processManager;
late FakeProcessManager envProcessManager;
late String separator;
const String oldSdkRevision = '5391447fae6209bb21a89e6a5a6583cac1af9b4b';
const String newSdkRevision = '85684f9300908116a78138ea4c6036c35c9a1236';
Future<void> setUpFullEnv() async {
fileSystem = LocalFileSystem.test(signals: LocalSignals.instance);
currentDir = createResolvedTempDirectorySync('current_app.');
logger = BufferLogger.test();
processManager = const LocalProcessManager();
utils = MigrateUtils(
logger: logger,
fileSystem: fileSystem,
processManager: processManager,
);
await MigrateProject.installProject('version:1.22.6_stable', currentDir);
final FlutterProjectFactory flutterFactory = FlutterProjectFactory();
final FlutterProject flutterProject =
flutterFactory.fromDirectory(currentDir);
result = MigrateResult.empty();
final MigrateLogger migrateLogger =
MigrateLogger(logger: logger, verbose: true);
migrateLogger.start();
separator = isWindows ? r'\\' : '/';
envProcessManager = FakeProcessManager('''
{
"FlutterProject.directory": "/Users/test/flutter",
"FlutterProject.metadataFile": "/Users/test/flutter/.metadata",
"FlutterProject.android.exists": false,
"FlutterProject.ios.exists": false,
"FlutterProject.web.exists": false,
"FlutterProject.macos.exists": false,
"FlutterProject.linux.exists": false,
"FlutterProject.windows.exists": false,
"FlutterProject.fuchsia.exists": false,
"FlutterProject.android.isKotlin": false,
"FlutterProject.ios.isSwift": false,
"FlutterProject.isModule": false,
"FlutterProject.isPlugin": false,
"FlutterProject.manifest.appname": "test_app_name",
"FlutterVersion.frameworkRevision": "4e181f012c717777681862e4771af5a941774bb9",
"Platform.operatingSystem": "macos",
"Platform.isAndroid": true,
"Platform.isIOS": false,
"Platform.isWindows": ${isWindows ? 'true' : 'false'},
"Platform.isMacOS": ${isMacOS ? 'true' : 'false'},
"Platform.isFuchsia": false,
"Platform.pathSeparator": "$separator",
"Cache.flutterRoot": "/Users/test/flutter"
}
''');
environment =
await FlutterToolsEnvironment.initializeFlutterToolsEnvironment(
envProcessManager, logger);
context = MigrateContext(
flutterProject: flutterProject,
skippedPrefixes: <String>{},
fileSystem: fileSystem,
migrateLogger: migrateLogger,
migrateUtils: utils,
environment: environment,
);
targetFlutterDirectory =
createResolvedTempDirectorySync('targetFlutterDir.');
newerTargetFlutterDirectory =
createResolvedTempDirectorySync('newerTargetFlutterDir.');
await context.migrateUtils
.cloneFlutter(oldSdkRevision, targetFlutterDirectory.absolute.path);
await context.migrateUtils.cloneFlutter(
newSdkRevision, newerTargetFlutterDirectory.absolute.path);
}
group('MigrateFlutterProject', () {
setUp(() async {
await setUpFullEnv();
});
tearDown(() async {
tryToDelete(targetFlutterDirectory);
tryToDelete(newerTargetFlutterDirectory);
});
testUsingContext('MigrateTargetFlutterProject creates', () async {
final Directory workingDir =
createResolvedTempDirectorySync('migrate_working_dir.');
final Directory targetDir =
createResolvedTempDirectorySync('target_dir.');
result.generatedTargetTemplateDirectory = targetDir;
workingDir.createSync(recursive: true);
final MigrateTargetFlutterProject targetProject =
MigrateTargetFlutterProject(
path: null,
directory: targetDir,
name: 'base',
androidLanguage: 'java',
iosLanguage: 'objc',
);
await targetProject.createProject(
context,
result,
oldSdkRevision, //targetRevision
targetFlutterDirectory, //targetFlutterDirectory
);
expect(targetDir.childFile('pubspec.yaml').existsSync(), true);
expect(
targetDir
.childDirectory('android')
.childFile('build.gradle')
.existsSync(),
true);
}, timeout: const Timeout(Duration(seconds: 500)));
testUsingContext('MigrateBaseFlutterProject creates', () async {
final Directory workingDir =
createResolvedTempDirectorySync('migrate_working_dir.');
final Directory baseDir = createResolvedTempDirectorySync('base_dir.');
result.generatedBaseTemplateDirectory = baseDir;
workingDir.createSync(recursive: true);
final MigrateBaseFlutterProject baseProject = MigrateBaseFlutterProject(
path: null,
directory: baseDir,
name: 'base',
androidLanguage: 'java',
iosLanguage: 'objc',
);
await baseProject.createProject(
context,
result,
<String>[oldSdkRevision], //revisionsList
<String, List<MigratePlatformConfig>>{
oldSdkRevision: <MigratePlatformConfig>[
MigratePlatformConfig(component: FlutterProjectComponent.android),
MigratePlatformConfig(component: FlutterProjectComponent.ios)
],
}, //revisionToConfigs
oldSdkRevision, //fallbackRevision
oldSdkRevision, //targetRevision
targetFlutterDirectory, //targetFlutterDirectory
);
expect(baseDir.childFile('pubspec.yaml').existsSync(), true);
expect(
baseDir
.childDirectory('android')
.childFile('build.gradle')
.existsSync(),
true);
}, timeout: const Timeout(Duration(seconds: 500)));
testUsingContext('Migrate___FlutterProject skips when path exists',
() async {
final Directory workingDir =
createResolvedTempDirectorySync('migrate_working_dir.');
final Directory targetDir =
createResolvedTempDirectorySync('target_dir.');
final Directory baseDir = createResolvedTempDirectorySync('base_dir.');
result.generatedTargetTemplateDirectory = targetDir;
result.generatedBaseTemplateDirectory = baseDir;
workingDir.createSync(recursive: true);
final MigrateBaseFlutterProject baseProject = MigrateBaseFlutterProject(
path: 'some_existing_base_path',
directory: baseDir,
name: 'base',
androidLanguage: 'java',
iosLanguage: 'objc',
);
final MigrateTargetFlutterProject targetProject =
MigrateTargetFlutterProject(
path: 'some_existing_target_path',
directory: targetDir,
name: 'base',
androidLanguage: 'java',
iosLanguage: 'objc',
);
await baseProject.createProject(
context,
result,
<String>[oldSdkRevision], //revisionsList
<String, List<MigratePlatformConfig>>{
oldSdkRevision: <MigratePlatformConfig>[
MigratePlatformConfig(component: FlutterProjectComponent.android),
MigratePlatformConfig(component: FlutterProjectComponent.ios)
],
}, //revisionToConfigs
oldSdkRevision, //fallbackRevision
oldSdkRevision, //targetRevision
targetFlutterDirectory, //targetFlutterDirectory
);
expect(baseDir.childFile('pubspec.yaml').existsSync(), false);
expect(
baseDir
.childDirectory('android')
.childFile('build.gradle')
.existsSync(),
false);
await targetProject.createProject(
context,
result,
oldSdkRevision, //revisionsList
targetFlutterDirectory, //targetFlutterDirectory
);
expect(targetDir.childFile('pubspec.yaml').existsSync(), false);
expect(
targetDir
.childDirectory('android')
.childFile('build.gradle')
.existsSync(),
false);
}, timeout: const Timeout(Duration(seconds: 500)));
});
group('MigrateRevisions', () {
setUp(() async {
fileSystem = LocalFileSystem.test(signals: LocalSignals.instance);
currentDir = createResolvedTempDirectorySync('current_app.');
logger = BufferLogger.test();
utils = MigrateUtils(
logger: logger,
fileSystem: fileSystem,
processManager: const LocalProcessManager(),
);
await MigrateProject.installProject('version:1.22.6_stable', currentDir);
final FlutterProjectFactory flutterFactory = FlutterProjectFactory();
final FlutterProject flutterProject =
flutterFactory.fromDirectory(currentDir);
result = MigrateResult.empty();
final MigrateLogger migrateLogger =
MigrateLogger(logger: logger, verbose: true);
migrateLogger.start();
context = MigrateContext(
flutterProject: flutterProject,
skippedPrefixes: <String>{},
fileSystem: fileSystem,
migrateLogger: migrateLogger,
migrateUtils: utils,
environment: environment,
);
});
testUsingContext('extracts revisions underpopulated metadata', () async {
final MigrateRevisions revisions = MigrateRevisions(
context: context,
baseRevision: oldSdkRevision,
allowFallbackBaseRevision: true,
platforms: <SupportedPlatform>[
SupportedPlatform.android,
SupportedPlatform.ios
],
environment: environment,
);
expect(revisions.revisionsList, <String>[oldSdkRevision]);
expect(revisions.fallbackRevision, oldSdkRevision);
expect(revisions.metadataRevision,
'9b2d32b605630f28625709ebd9d78ab3016b2bf6');
expect(revisions.config.unmanagedFiles.isEmpty, false);
expect(revisions.config.platformConfigs.isEmpty, false);
expect(revisions.config.platformConfigs.length, 3);
expect(
revisions.config.platformConfigs
.containsKey(FlutterProjectComponent.root),
true);
expect(
revisions.config.platformConfigs
.containsKey(FlutterProjectComponent.android),
true);
expect(
revisions.config.platformConfigs
.containsKey(FlutterProjectComponent.ios),
true);
});
testUsingContext('extracts revisions full metadata', () async {
final File metadataFile =
context.flutterProject.directory.childFile('.metadata');
if (metadataFile.existsSync()) {
if (!tryToDelete(metadataFile)) {
// TODO(garyq): Inaccessible .metadata on windows.
// Skip test for now. We should reneable.
return;
}
}
metadataFile.createSync(recursive: true);
metadataFile.writeAsStringSync('''
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: 9b2d32b605630f28625709ebd9d78ab3016b2bf6
channel: unknown
project_type: app
# Tracks metadata for the flutter migrate command
migration:
platforms:
- platform: root
create_revision: 9b2d32b605630f28625709ebd9d78ab3016b2bf6
base_revision: 9b2d32b605630f28625709ebd9d78ab3016b2bf6
- platform: android
create_revision: 9b2d32b605630f28625709ebd9d78ab3016b2bf6
base_revision: 9b2d32b605630f28625709ebd9d78ab3016b2bf6
- platform: ios
create_revision: 9b2d32b605630f28625709ebd9d78ab3016b2bf6
base_revision: 9b2d32b605630f28625709ebd9d78ab3016b2bf6
- platform: linux
create_revision: 9b2d32b605630f28625709ebd9d78ab3016b2bf6
base_revision: 9b2d32b605630f28625709ebd9d78ab3016b2bf6
- platform: macos
create_revision: 9b2d32b605630f28625709ebd9d78ab3016b2bf6
base_revision: 9b2d32b605630f28625709ebd9d78ab3016b2bf6
- platform: web
create_revision: 9b2d32b605630f28625709ebd9d78ab3016b2bf6
base_revision: 9b2d32b605630f28625709ebd9d78ab3016b2bf6
- platform: windows
create_revision: 36427af29421f406ac95ff55ea31d1dc49a45b5f
base_revision: 36427af29421f406ac95ff55ea31d1dc49a45b5f
# User provided section
# List of Local paths (relative to this file) that should be
# ignored by the migrate tool.
#
# Files that are not part of the templates will be ignored by default.
unmanaged_files:
- 'lib/main.dart'
- 'blah.dart'
- 'ios/Runner.xcodeproj/project.pbxproj'
''', flush: true);
final MigrateRevisions revisions = MigrateRevisions(
context: context,
baseRevision: oldSdkRevision,
allowFallbackBaseRevision: true,
platforms: <SupportedPlatform>[
SupportedPlatform.android,
SupportedPlatform.ios
],
environment: environment,
);
expect(revisions.revisionsList, <String>[oldSdkRevision]);
expect(revisions.fallbackRevision, oldSdkRevision);
expect(revisions.metadataRevision,
'9b2d32b605630f28625709ebd9d78ab3016b2bf6');
expect(revisions.config.unmanagedFiles.isEmpty, false);
expect(revisions.config.unmanagedFiles.length, 3);
expect(revisions.config.unmanagedFiles.contains('lib/main.dart'), true);
expect(revisions.config.unmanagedFiles.contains('blah.dart'), true);
expect(
revisions.config.unmanagedFiles
.contains('ios/Runner.xcodeproj/project.pbxproj'),
true);
expect(revisions.config.platformConfigs.length, 7);
expect(
revisions.config.platformConfigs
.containsKey(FlutterProjectComponent.root),
true);
expect(
revisions.config.platformConfigs
.containsKey(FlutterProjectComponent.android),
true);
expect(
revisions.config.platformConfigs
.containsKey(FlutterProjectComponent.ios),
true);
expect(
revisions.config.platformConfigs
.containsKey(FlutterProjectComponent.linux),
true);
expect(
revisions.config.platformConfigs
.containsKey(FlutterProjectComponent.macos),
true);
expect(
revisions.config.platformConfigs
.containsKey(FlutterProjectComponent.web),
true);
expect(
revisions.config.platformConfigs
.containsKey(FlutterProjectComponent.windows),
true);
expect(
revisions.config.platformConfigs[FlutterProjectComponent.root]!
.createRevision,
'9b2d32b605630f28625709ebd9d78ab3016b2bf6');
expect(
revisions.config.platformConfigs[FlutterProjectComponent.android]!
.createRevision,
'9b2d32b605630f28625709ebd9d78ab3016b2bf6');
expect(
revisions.config.platformConfigs[FlutterProjectComponent.ios]!
.createRevision,
'9b2d32b605630f28625709ebd9d78ab3016b2bf6');
expect(
revisions.config.platformConfigs[FlutterProjectComponent.linux]!
.createRevision,
'9b2d32b605630f28625709ebd9d78ab3016b2bf6');
expect(
revisions.config.platformConfigs[FlutterProjectComponent.macos]!
.createRevision,
'9b2d32b605630f28625709ebd9d78ab3016b2bf6');
expect(
revisions.config.platformConfigs[FlutterProjectComponent.web]!
.createRevision,
'9b2d32b605630f28625709ebd9d78ab3016b2bf6');
expect(
revisions.config.platformConfigs[FlutterProjectComponent.windows]!
.createRevision,
'36427af29421f406ac95ff55ea31d1dc49a45b5f');
expect(
revisions.config.platformConfigs[FlutterProjectComponent.root]!
.baseRevision,
'9b2d32b605630f28625709ebd9d78ab3016b2bf6');
expect(
revisions.config.platformConfigs[FlutterProjectComponent.android]!
.baseRevision,
'9b2d32b605630f28625709ebd9d78ab3016b2bf6');
expect(
revisions.config.platformConfigs[FlutterProjectComponent.ios]!
.baseRevision,
'9b2d32b605630f28625709ebd9d78ab3016b2bf6');
expect(
revisions.config.platformConfigs[FlutterProjectComponent.linux]!
.baseRevision,
'9b2d32b605630f28625709ebd9d78ab3016b2bf6');
expect(
revisions.config.platformConfigs[FlutterProjectComponent.macos]!
.baseRevision,
'9b2d32b605630f28625709ebd9d78ab3016b2bf6');
expect(
revisions.config.platformConfigs[FlutterProjectComponent.web]!
.baseRevision,
'9b2d32b605630f28625709ebd9d78ab3016b2bf6');
expect(
revisions.config.platformConfigs[FlutterProjectComponent.windows]!
.baseRevision,
'36427af29421f406ac95ff55ea31d1dc49a45b5f');
});
});
group('project operations', () {
setUp(() async {
await setUpFullEnv();
});
tearDown(() async {
tryToDelete(targetFlutterDirectory);
tryToDelete(newerTargetFlutterDirectory);
});
testUsingContext('diff base and target', () async {
final Directory workingDir =
createResolvedTempDirectorySync('migrate_working_dir.');
final Directory targetDir =
createResolvedTempDirectorySync('target_dir.');
final Directory baseDir = createResolvedTempDirectorySync('base_dir.');
result.generatedTargetTemplateDirectory = targetDir;
result.generatedBaseTemplateDirectory = baseDir;
workingDir.createSync(recursive: true);
final MigrateBaseFlutterProject baseProject = MigrateBaseFlutterProject(
path: null,
directory: baseDir,
name: 'base',
androidLanguage: 'java',
iosLanguage: 'objc',
);
final MigrateTargetFlutterProject targetProject =
MigrateTargetFlutterProject(
path: null,
directory: targetDir,
name: 'base',
androidLanguage: 'java',
iosLanguage: 'objc',
);
await baseProject.createProject(
context,
result,
<String>[oldSdkRevision], //revisionsList
<String, List<MigratePlatformConfig>>{
oldSdkRevision: <MigratePlatformConfig>[
MigratePlatformConfig(component: FlutterProjectComponent.android),
MigratePlatformConfig(component: FlutterProjectComponent.ios)
],
}, //revisionToConfigs
oldSdkRevision, //fallbackRevision
oldSdkRevision, //targetRevision
targetFlutterDirectory, //targetFlutterDirectory
);
expect(baseDir.childFile('pubspec.yaml').existsSync(), true);
expect(
baseDir
.childDirectory('android')
.childFile('build.gradle')
.existsSync(),
true);
await targetProject.createProject(
context,
result,
newSdkRevision, //revisionsList
newerTargetFlutterDirectory, //targetFlutterDirectory
);
expect(targetDir.childFile('pubspec.yaml').existsSync(), true);
expect(
targetDir
.childDirectory('android')
.childFile('build.gradle')
.existsSync(),
true);
final Map<String, DiffResult> diffResults =
await baseProject.diff(context, targetProject);
final Map<String, DiffResult> canonicalizedDiffResults =
<String, DiffResult>{};
for (final MapEntry<String, DiffResult> entry in diffResults.entries) {
canonicalizedDiffResults[canonicalize(entry.key)] = entry.value;
}
result.diffMap.addAll(diffResults);
List<String> expectedFiles = <String>[
'.metadata',
'ios/Runner.xcworkspace/contents.xcworkspacedata',
'ios/Runner/AppDelegate.h',
'ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png',
'ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png',
'ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md',
'ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json',
'ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png',
'ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png',
'ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png',
'ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png',
'ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png',
'ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png',
'ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png',
'ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png',
'ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json',
'ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png',
'ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png',
'ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png',
'ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png',
'ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png',
'ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png',
'ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png',
'ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png',
'ios/Runner/Base.lproj/LaunchScreen.storyboard',
'ios/Runner/Base.lproj/Main.storyboard',
'ios/Runner/main.m',
'ios/Runner/AppDelegate.m',
'ios/Runner/Info.plist',
'ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata',
'ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme',
'ios/Flutter/Debug.xcconfig',
'ios/Flutter/Release.xcconfig',
'ios/Flutter/AppFrameworkInfo.plist',
'pubspec.yaml',
'.gitignore',
'android/base_android.iml',
'android/app/build.gradle',
'android/app/src/main/res/mipmap-mdpi/ic_launcher.png',
'android/app/src/main/res/mipmap-hdpi/ic_launcher.png',
'android/app/src/main/res/drawable/launch_background.xml',
'android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png',
'android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png',
'android/app/src/main/res/values/styles.xml',
'android/app/src/main/res/mipmap-xhdpi/ic_launcher.png',
'android/app/src/main/AndroidManifest.xml',
'android/app/src/main/java/com/example/base/MainActivity.java',
'android/local.properties',
'android/gradle/wrapper/gradle-wrapper.jar',
'android/gradle/wrapper/gradle-wrapper.properties',
'android/gradlew',
'android/build.gradle',
'android/gradle.properties',
'android/gradlew.bat',
'android/settings.gradle',
'base.iml',
'.idea/runConfigurations/main_dart.xml',
'.idea/libraries/Dart_SDK.xml',
'.idea/libraries/KotlinJavaRuntime.xml',
'.idea/libraries/Flutter_for_Android.xml',
'.idea/workspace.xml',
'.idea/modules.xml',
];
expectedFiles =
List<String>.from(expectedFiles.map((String e) => canonicalize(e)));
expect(diffResults.length, 62);
expect(expectedFiles.length, 62);
for (final String diffResultPath in canonicalizedDiffResults.keys) {
expect(expectedFiles.contains(diffResultPath), true);
}
// Spot check diffs on key files:
expect(
canonicalizedDiffResults[canonicalize('android/build.gradle')]!.diff,
contains(r'''
@@ -1,18 +1,20 @@
buildscript {
+ ext.kotlin_version = '1.6.10'
repositories {
google()
- jcenter()
+ mavenCentral()
}'''));
expect(
canonicalizedDiffResults[canonicalize('android/build.gradle')]!.diff,
contains(r'''
dependencies {
- classpath 'com.android.tools.build:gradle:3.2.1'
+ classpath 'com.android.tools.build:gradle:7.1.2'
+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}'''));
expect(
canonicalizedDiffResults[canonicalize('android/build.gradle')]!.diff,
contains(r'''
allprojects {
repositories {
google()
- jcenter()
+ mavenCentral()
}
}'''));
expect(
canonicalizedDiffResults[
canonicalize('android/app/src/main/AndroidManifest.xml')]!
.diff,
contains(r'''
@@ -1,39 +1,34 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.base">
-
- <!-- The INTERNET permission is required for development. Specifically,
- flutter needs it to communicate with the running application
- to allow setting breakpoints, to provide hot reload, etc.
- -->
- <uses-permission android:name="android.permission.INTERNET"/>
-
- <!-- io.flutter.app.FlutterApplication is an android.app.Application that
- calls FlutterMain.startInitialization(this); in its onCreate method.
- In most cases you can leave this as-is, but you if you want to provide
- additional functionality it is fine to subclass or reimplement
- FlutterApplication and put your custom class here. -->
- <application
- android:name="io.flutter.app.FlutterApplication"
+ <application
android:label="base"
+ android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity"
+ android:exported="true"
android:launchMode="singleTop"
android:theme="@style/LaunchTheme"
- android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density"
+ android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
- <!-- This keeps the window background of the activity showing
- until Flutter renders its first frame. It can be removed if
- there is no splash screen (such as the default splash screen
- defined in @style/LaunchTheme). -->
+ <!-- Specifies an Android theme to apply to this Activity as soon as
+ the Android process has started. This theme is visible to the user
+ while the Flutter UI initializes. After that, this theme continues
+ to determine the Window background behind the Flutter UI. -->
<meta-data
- android:name="io.flutter.app.android.SplashScreenUntilFirstFrame"
- android:value="true" />
+ android:name="io.flutter.embedding.android.NormalTheme"
+ android:resource="@style/NormalTheme"
+ />
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
+ <!-- Don't delete the meta-data below.
+ This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
+ <meta-data
+ android:name="flutterEmbedding"
+ android:value="2" />
</application>
</manifest>'''));
}, timeout: const Timeout(Duration(seconds: 500)));
testUsingContext('Merge succeeds', () async {
final Directory workingDir =
createResolvedTempDirectorySync('migrate_working_dir.');
final Directory targetDir =
createResolvedTempDirectorySync('target_dir.');
final Directory baseDir = createResolvedTempDirectorySync('base_dir.');
result.generatedTargetTemplateDirectory = targetDir;
result.generatedBaseTemplateDirectory = baseDir;
workingDir.createSync(recursive: true);
final MigrateBaseFlutterProject baseProject = MigrateBaseFlutterProject(
path: null,
directory: baseDir,
name: 'base',
androidLanguage: 'java',
iosLanguage: 'objc',
);
final MigrateTargetFlutterProject targetProject =
MigrateTargetFlutterProject(
path: null,
directory: targetDir,
name: 'base',
androidLanguage: 'java',
iosLanguage: 'objc',
);
await baseProject.createProject(
context,
result,
<String>[oldSdkRevision], //revisionsList
<String, List<MigratePlatformConfig>>{
oldSdkRevision: <MigratePlatformConfig>[
MigratePlatformConfig(component: FlutterProjectComponent.android),
MigratePlatformConfig(component: FlutterProjectComponent.ios)
],
}, //revisionToConfigs
oldSdkRevision, //fallbackRevision
oldSdkRevision, //targetRevision
targetFlutterDirectory, //targetFlutterDirectory
);
expect(baseDir.childFile('pubspec.yaml').existsSync(), true);
expect(baseDir.childFile('.metadata').existsSync(), true);
expect(
baseDir
.childDirectory('android')
.childFile('build.gradle')
.existsSync(),
true);
await targetProject.createProject(
context,
result,
newSdkRevision, //revisionsList
newerTargetFlutterDirectory, //targetFlutterDirectory
);
expect(targetDir.childFile('pubspec.yaml').existsSync(), true);
expect(targetDir.childFile('.metadata').existsSync(), true);
expect(
targetDir
.childDirectory('android')
.childFile('build.gradle')
.existsSync(),
true);
result.diffMap.addAll(await baseProject.diff(context, targetProject));
await MigrateFlutterProject.merge(
context,
result,
baseProject,
targetProject,
<String>[], // unmanagedFiles
<String>[], // unmanagedDirectories
false, // preferTwoWayMerge
);
List<String> expectedMergedPaths = <String>[
'.metadata',
'ios/Runner/Info.plist',
'ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata',
'ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme',
'ios/Flutter/AppFrameworkInfo.plist',
'pubspec.yaml',
'.gitignore',
'android/app/build.gradle',
'android/app/src/main/res/values/styles.xml',
'android/app/src/main/AndroidManifest.xml',
'android/gradle/wrapper/gradle-wrapper.properties',
'android/build.gradle',
];
expectedMergedPaths = List<String>.from(
expectedMergedPaths.map((String e) => canonicalize(e)));
expect(result.mergeResults.length, 12);
expect(expectedMergedPaths.length, 12);
for (final MergeResult mergeResult in result.mergeResults) {
expect(
expectedMergedPaths.contains(canonicalize(mergeResult.localPath)),
true);
}
expect(result.mergeResults[0].exitCode, 0);
expect(result.mergeResults[1].exitCode, 0);
expect(result.mergeResults[2].exitCode, 0);
expect(result.mergeResults[3].exitCode, 0);
expect(result.mergeResults[4].exitCode, 0);
expect(result.mergeResults[5].exitCode, 0);
expect(result.mergeResults[6].exitCode, 0);
expect(result.mergeResults[7].exitCode, 0);
expect(result.mergeResults[8].exitCode, 0);
expect(result.mergeResults[9].exitCode, 0);
expect(result.mergeResults[10].exitCode, 0);
expect(result.mergeResults[11].exitCode, 0);
expect(result.mergeResults[0].hasConflict, false);
expect(result.mergeResults[1].hasConflict, false);
expect(result.mergeResults[2].hasConflict, false);
expect(result.mergeResults[3].hasConflict, false);
expect(result.mergeResults[4].hasConflict, false);
expect(result.mergeResults[5].hasConflict, false);
expect(result.mergeResults[6].hasConflict, false);
expect(result.mergeResults[7].hasConflict, false);
expect(result.mergeResults[8].hasConflict, false);
expect(result.mergeResults[9].hasConflict, false);
expect(result.mergeResults[10].hasConflict, false);
expect(result.mergeResults[11].hasConflict, false);
}, timeout: const Timeout(Duration(seconds: 500)));
});
}

View File

@ -5,7 +5,6 @@
import 'package:file/memory.dart';
import 'package:flutter_migrate/src/base/file_system.dart';
import 'package:flutter_migrate/src/base/logger.dart';
import 'package:flutter_migrate/src/base/project.dart';
import 'package:flutter_migrate/src/flutter_project_metadata.dart';
import 'src/common.dart';
@ -139,12 +138,12 @@ migration:
FlutterProjectMetadata(metadataFile, logger);
expect(projectMetadata.projectType, isNull);
expect(
projectMetadata.migrateConfig.platformConfigs[SupportedPlatform.root]
?.createRevision,
projectMetadata.migrateConfig
.platformConfigs[FlutterProjectComponent.root]?.createRevision,
'abcdefg');
expect(
projectMetadata.migrateConfig.platformConfigs[SupportedPlatform.root]
?.baseRevision,
projectMetadata.migrateConfig
.platformConfigs[FlutterProjectComponent.root]?.baseRevision,
'baserevision');
expect(projectMetadata.migrateConfig.unmanagedFiles[0], 'file1');
@ -180,12 +179,12 @@ migration:
FlutterProjectMetadata(metadataFile, logger);
expect(projectMetadata.projectType, FlutterProjectType.app);
expect(
projectMetadata.migrateConfig.platformConfigs[SupportedPlatform.root]
?.createRevision,
projectMetadata.migrateConfig
.platformConfigs[FlutterProjectComponent.root]?.createRevision,
'abcdefg');
expect(
projectMetadata.migrateConfig.platformConfigs[SupportedPlatform.root]
?.baseRevision,
projectMetadata.migrateConfig
.platformConfigs[FlutterProjectComponent.root]?.baseRevision,
'baserevision');
// Tool uses default unamanged files list when malformed.
expect(projectMetadata.migrateConfig.unmanagedFiles[0], 'lib/main.dart');
@ -223,24 +222,24 @@ migration:
FlutterProjectMetadata(metadataFile, logger);
expect(projectMetadata.projectType, FlutterProjectType.app);
expect(
projectMetadata.migrateConfig.platformConfigs[SupportedPlatform.root]
?.createRevision,
projectMetadata.migrateConfig
.platformConfigs[FlutterProjectComponent.root]?.createRevision,
'abcdefg');
expect(
projectMetadata.migrateConfig.platformConfigs[SupportedPlatform.root]
?.baseRevision,
projectMetadata.migrateConfig
.platformConfigs[FlutterProjectComponent.root]?.baseRevision,
'baserevision');
expect(
projectMetadata.migrateConfig.platformConfigs[SupportedPlatform.ios]
?.createRevision,
projectMetadata.migrateConfig
.platformConfigs[FlutterProjectComponent.ios]?.createRevision,
'abcdefg');
expect(
projectMetadata
.migrateConfig.platformConfigs[SupportedPlatform.ios]?.baseRevision,
projectMetadata.migrateConfig
.platformConfigs[FlutterProjectComponent.ios]?.baseRevision,
'baserevision');
expect(
projectMetadata.migrateConfig.platformConfigs
.containsKey(SupportedPlatform.android),
.containsKey(FlutterProjectComponent.android),
false);
expect(projectMetadata.migrateConfig.unmanagedFiles[0], 'file1');

View File

@ -23,13 +23,14 @@ export 'package:test_api/test_api.dart' // ignore: deprecated_member_use
isInstanceOf,
test;
void tryToDelete(FileSystemEntity fileEntity) {
bool tryToDelete(FileSystemEntity fileEntity) {
// This should not be necessary, but it turns out that
// on Windows it's common for deletions to fail due to
// bogus (we think) "access denied" errors.
try {
if (fileEntity.existsSync()) {
fileEntity.deleteSync(recursive: true);
return true;
}
} on FileSystemException catch (error) {
// We print this so that it's visible in the logs, to get an idea of how
@ -37,6 +38,7 @@ void tryToDelete(FileSystemEntity fileEntity) {
// ignore: avoid_print
print('Failed to delete ${fileEntity.path}: $error');
}
return false;
}
/// Gets the path to the root of the Flutter repository.

View File

@ -24,6 +24,7 @@ void testUsingContext(
Map<Type, Generator> overrides = const <Type, Generator>{},
bool initializeFlutterRoot = true,
String? testOn,
Timeout? timeout,
bool?
skip, // should default to `false`, but https://github.com/dart-lang/test/issues/545 doesn't allow this
}) {
@ -87,10 +88,7 @@ void testUsingContext(
},
);
}, overrides: <Type, Generator>{});
}, testOn: testOn, skip: skip);
// We don't support "timeout"; see ../../dart_test.yaml which
// configures all tests to have a 15 minute timeout which should
// definitely be enough.
}, testOn: testOn, skip: skip, timeout: timeout);
}
void _printBufferedErrors(AppContext testContext) {

View File

@ -105,19 +105,25 @@ flutter:
});
testWithoutContext('updates gradle locks', () async {
ProcessResult result = await processManager.run(<String>[
final ProcessResult result = await processManager.run(<String>[
'flutter',
'create',
currentDir.absolute.path,
'--project-name=testproject'
]);
result = await Process.run('dir', <String>[],
workingDirectory: currentDir.path, runInShell: true);
expect(result.exitCode, 0);
final File projectAppLock =
currentDir.childDirectory('android').childFile('project-app.lockfile');
final File buildGradle =
currentDir.childDirectory('android').childFile('build.gradle');
final File gradleProperties =
currentDir.childDirectory('android').childFile('gradle.properties');
gradleProperties.writeAsStringSync('''
org.gradle.daemon=false
org.gradle.jvmargs=-Xmx1536M
android.useAndroidX=true
android.enableJetifier=true
''', flush: true);
final File projectAppLockBackup = currentDir
.childDirectory('android')
.childFile('project-app.lockfile_backup_0');
@ -180,6 +186,9 @@ subprojects {
.childFile('gradlew.bat')
.existsSync(),
true);
final Directory dotGradle =
currentDir.childDirectory('android').childDirectory('.gradle');
tryToDelete(dotGradle);
await updateGradleDependencyLocking(
flutterProject, utils, logger, terminal, true, fileSystem,
force: true);
@ -191,7 +200,7 @@ subprojects {
contains('# Manual edits can break the build and are not advised.'));
expect(projectAppLock.readAsStringSync(),
contains('# This file is expected to be part of source control.'));
}, timeout: const Timeout(Duration(seconds: 500)));
}, timeout: const Timeout(Duration(seconds: 500)), skip: true);
}
class _VersionCode extends Comparable<_VersionCode> {