Skip to content

Conversation

@GaryQian
Copy link
Contributor

@GaryQian GaryQian commented Feb 24, 2022

This PR is a subset of #95708

This adds MigrateConfig, which handles tracking migration metadata by writing a .migrate_config file in each platform directory.

Each migrate config file tracks the initial create revision, base revision (revision that the app was last successfully migrated with), the platform the file is addressing, and a list of files and directories that should be unmanaged by the migration tool.

This PR also adds the core migrate_project.dart to enable integration testing of the tool. The testing framework downloads copies of fully generated apps off of CIPD.

Resolves #95360

@flutter-dashboard flutter-dashboard bot added the tool Affects the "flutter" command-line tool. See also t: labels. label Feb 24, 2022
@GaryQian GaryQian requested a review from blasten February 26, 2022 01:07
Copy link

@blasten blasten Mar 2, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

to clarify, will flutter create . include a .migrate_config file in the app's root directory?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there any issue with using the existing .metadata file for the root project directory, and rename .migrate_config as .metadata for the platform directories?

Migrate is adding a few entries to the dictionary, I'm not sure if we need a dedicated file.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the existing .metadata file is being managed via templates, while the migrate_config is being managed by the MigrateConfig class. It is possible to merge the two of course, but we would also have to add it to each platform and handle the case where we need to modify it contrary to what the template would generate.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And yes, flutter create will create the .migrate_config file in the root as well as each platform dir.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the existing .metadata file is being managed via templates, while the migrate_config is being managed by the MigrateConfig class.

That sounds good. The .metadata file was created with these goals in mind:

# 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.

It sounds like the use cases of .migrate_config should be covered by this file. Any thoughts?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that we shouldnt have too many metadata files. I'll see if we can merge the existing .metadata file with the .migrate_config file. Though the new version of .metadata will probably no longer be a read-only template generated file anymore.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here is a proposed extended .metadata file. This unifies separate config files in each platform dir to one single migrate section in the root .metadata file to remain consistent with the existing metadata tracking system. Since this file will now be owned by the tool, we can easily update the values in it as new platforms are added/edited.

# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled.

version:
  revision: {{flutterRevision}}
  channel: {{flutterChannel}}

project_type: app

# Tracks metadata for the flutter migrate command
migrate:
  platforms:
    - platform: android
      createRevision: fj19vkla9vnlka9vni3n808v3nch8cd
      baseRevision: 93kf9v3njfa90vnidfjvn39nvi3vnie
    - platform: ios
      createRevision: fj19vkla9vnlka9vni3n808v3nch8cd
      baseRevision: 93kf9v3njfa90vnidfjvn39nvi3vnie

  # 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.
  unmanagedFiles:
    - lib/file1/etc.dart
    - android/my_file.java

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the only minor comment is to maybe use a noun for consistency, so migrate -> migration

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

an enum may be safer here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I might go through the whole tools repo and unify references to the different platforms into a single enum. We currently have a few different ways of id-ing platforms already.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer an enum; if not, it doesn't seem like the String? platform arg should be nullable?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why would a consumer of this API avoid passing the platform?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Made non-nullable

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: projectDirectory docs

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this can be avoided if an enum is used instead. I believe Dart will enforce all cases in the enum to be handled in the switch statement. If that's not true, I've been doing too much golang on the side then :)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this seems like a utility that belongs in FlutterProject. A method that provides all the supported platforms.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what about if result.exitCode != 0 ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll be moving this into a utility class in the next PR where I handle all of this. I'd prefer to not replicate all the error handling here temporarily as it will be removed very soon.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

projectPath sounds like the app's project path, but this is the directory where Flutter is installed

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

did it mean .metadata?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope, this doc is correct. The fallback here is for when the migration config doesn't know the revision, so we try to parse it out of .metadata and other means.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can this throw?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have this, but it needs to be run on a Mac with Xcode installed.

final bool isSwift = (await _xcodeProjectInterpreter.getBuildSettings(
runnerProject.path,
buildContext: const XcodeProjectBuildContext(),
)).containsKey('SWIFT_VERSION');

I'm guessing this needs to work on all platforms, like if you ran this on Windows you would still want the ios directory migrated.

A user could rename AppDelegate.swift but they likely wouldn't.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. All platform compatibility is important. I can try to include a few more fallback checks if that is something that could help with robustness.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does it need to be in the app's directory? could it use the temp directory instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The intention of this working dir is to be easily accessible and obvious. Putting it in a temp or hidden away folder makes it hard for developers to enter and work with the files inside.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sgtm

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I specifically don't want this directory hidden as it is meant to be accessed by the developer as part of the migration process. Also, just calling it migrate may not be sufficiently clear that it is meant to be modified/edited/viewed as part of migration imo.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does the tool also support Flutter modules?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No. Not yet.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But if the future if/when we do, it would be nice to have this already in the .gitignore to allow for better backwards compatibility.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this support the glob pattern? for example: lib/**/*.dart seems reasonable to me.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a lib we typically use to support this? It seems we support just simple directories and file URIs in pubspec.yaml

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: EOF

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh TIL

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this file test data or code that runs as part of the test?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This runs as part of the test to set up the environment/sample app.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was under the impression that this was test data since it's under a directory named test_data

Could it be moved to a different directory?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This generates/gets the test fixtures, which is consistent with what the other files in this directory do.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok. This seems like a different pattern. Usually test data doesn't contain code that is executed to generate test data or fixtures.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could this be set in the constructor?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not in the way the current project class is designed. The directory is only known once we call setUpIn(), which must be called first before any of the other getters/methods are valid. We track the appPath here because some of the getters are dynamically filled with the CIPD downloaded contents.

@christopherfujino christopherfujino self-requested a review March 2, 2022 02:21
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you pull these into locals, it's used right above here:

androidPlatform: templateContext['android'] as bool ?? false

and to update the gradle properties.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When would this need to be specified?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is used in the other half of the feature where create is called programmatically to create an app to diff against. We want to specify this so that the metadata around the revisions is using the app's metadata, not creating brand new metadata as a regular create run would do.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this only supposed to be called re-entrantly, let's make it hide: true and specify in the help text this is not supposed to be called by users.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Turns out test/general.shard/args_test.dart: Help for command line arguments is consistently styled and complete enforces that options are not permanently hidden. The description advising not to use the flag should be sufficient.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hide: !verboseHelp seems correct.

'documentation we can add just for ourselves. If it is not intended '
'for developers, then use "hide: !verboseHelp" to only show the '
'help when people run with "--help --verbose".';

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of "revision" maybe more specific "Flutter SDK git commit hash" or something like that @christopherfujino

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use Object? in null safe files, not dynamic.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
unmanagedFiles: _kDefaultUnmanagedFiles[platform] != null ? _kDefaultUnmanagedFiles[platform]! : <String>[],
unmanagedFiles: _kDefaultUnmanagedFiles[platform] ?? <String>[],

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use flutterVersion.frameworkRevision?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let me validate if flutterVersion.frameworkRevision always produces the form that I need.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed to use FlutterVersion!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refactor your new code to not use the global context variables, these should all pass with testWithoutContext.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FlutterProject in project.dart seems to be using globals.projectFactory and I'd prefer to leave this refactoring for a different PR. I've made the tests that can be context free context free.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gotcha, yeah do what you can, and avoid it where possible in new code. There's still a bunch of tricky ones still hanging out.

Comment on lines 30 to 54
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we should be downloading cipd packages in a tools integration test. It should probably be its own test shard, with the cipd package dependency set up in the ci.yaml? @christopherfujino

Copy link
Contributor

@christopherfujino christopherfujino Mar 2, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, if a test requires a cipd dependency, it should be specified in the ci.yaml: https://github.com/flutter/flutter/blob/master/.ci.yaml#L174 and then the actual downloading specified in a recipe module

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The way this has to be tested requires frequent setup and teardown of a sample app, and the recipes+deps system is not designed to support this case efficiently. I would need to frequently re-get the cipd deps and the recipes gets deps once at the beginning.

I could host the vanilla sample apps somewhere else and avoid CIPD. The choice to use CIPD is to avoid having to check in entire vanilla apps as test fixtures.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We already check in quite a few test apps, I say we just check it in. This way also if the app needs to be fixed, it's easy to find.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I checked in a test fixture app for multidex which is a much smaller mini-app, and even that needed to get hosted files such as icon images and stuff.

The test apps required for this must be full apps, which can get quite large. Also, due to the nature of this feature, I intend to eventually branch out to test between many full apps created by different historic flutter versions. Checking in multiple full flutter apps is not sustainable, which is why I turned to hosting on cipd. I could shift the host to github if that makes more sense. I'm not sure if github will appreciate the test infra traffic though, and git makes it much harder to cherrypick individual app fixtures by version and would likely need to be in separate repos to be efficient.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I missed this message. I'll file a tracking issue for discussion on the approach to this.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit:

/// The Flutter SDK revision this platform was created by.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: /// The Flutter SDK revision this platform was last migrated by.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

when would the revision be unknown?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Legacy apps may not have this information available as it was created before we tracked it, in which case we use a fallback revision

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ahh ok.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can this be a bit more programmatic (i.e. easier to extend)? Something like:

  final validations = <String, Type>{
    'platform': String,
    'unmanagedFiles': YamlList,
  };
  bool isValid = true;
  for (final entry in validations.entries) {
    if (map[entry.key].runtimeType != entry.value) {
      isValid = false;
      logger.printError('The value of key ${entry.key} was expected to be ${entry.value} but was ${map[entry.key].runtimeType}');
      break;
    }
  }
  return isValid;

@GaryQian GaryQian requested a review from blasten March 3, 2022 21:28
_versionYaml = versionYaml;
bool get isEmpty => platformConfigs.isEmpty && (unmanagedFiles.isEmpty || unmanagedFiles == _kDefaultUnmanagedFiles);

/// Parses the project for all supported platforms and populates the MigrateConfig
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// Parses the project for all supported platforms and populates the MigrateConfig
/// Parses the project for all supported platforms and populates the [MigrateConfig]

import 'dart:io';
// import 'package:flutter_tools/src/base/io.dart';
import 'package:file/file.dart';
// import 'package:flutter_tools/src/globals.dart' as globals;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are these commented out intentionally?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oops!

Copy link

@blasten blasten left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@fluttergithubbot fluttergithubbot merged commit 63ff7a1 into flutter:master Mar 16, 2022
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Mar 16, 2022
engine-flutter-autoroll added a commit to engine-flutter-autoroll/plugins that referenced this pull request Mar 18, 2022
engine-flutter-autoroll added a commit to engine-flutter-autoroll/plugins that referenced this pull request Mar 18, 2022
engine-flutter-autoroll added a commit to engine-flutter-autoroll/plugins that referenced this pull request Mar 18, 2022
Copy link
Member

@zanderso zanderso left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Out of courtesy to the reviewers, please give a gentle ping when a final pass to give lgtm is needed. In particular, I'd like to see an lgtm from @christopherfujino on future PRs in this area that add large bits of new functionality to the tool, in particular #100284.

await processManager.run(<String>[
'git',
'clone',
'https://chromium.googlesource.com/chromium/tools/depot_tools',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DBC

This should be pinned, and it should do a shallow clone.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For context: #100254

We already have infrastructure for downloading CIPD artifacts for tests

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

tool Affects the "flutter" command-line tool. See also t: labels.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[migrate] Track initial create and migrate command flutter versions per platform

6 participants