-
Notifications
You must be signed in to change notification settings - Fork 3.3k
[flutter_migrate] Compute #2734
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
0f3eeba
to
b6dce1c
Compare
This PR depends on #2723 for |
} | ||
for (final String dir in _skippedDirectories) { | ||
if (localPath.startsWith( | ||
'${dir.replaceAll('/', fileSystem.path.separator)}${fileSystem.path.separator}')) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
isn't localPath
absolute? so it would never start with, for example '.dart_tool'
, right? Or am I reading this wrong?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, in this PR, localPath
is relative to the project root.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ahh, ok, I see you're right.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similarly I recommend passing both _skippedDirectories and localPath through canonicalize()
from package:path
.
b6dce1c
to
6e0a57b
Compare
This code doesn't even compile for me:
|
This is dependent on #2723, which just landed today. I should have just rebased it. Due to the review comments, I have changed the dependency API a bit, let me fix it real quick. |
Ohh yeah sorry, you mentioned that |
'lib', // Files here are always user owned and we don't want to overwrite their apps. | ||
'test', // Files here are typically user owned and flutter-side changes are not relevant. | ||
'assets', // Common directory for user assets. | ||
'build' // Build artifacts |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would help readability to make the comments here all use the same form (consistency about capitalization and punctuation, consistency about whether to say "ignore the" given that's all ignoring, consistency about whether it's describing the files or the reason for excluding them).
Also, I would suggest keeping this list alphabetized so it's easier to see what's there.
]; | ||
|
||
bool _skipped(String localPath, FileSystem fileSystem, | ||
{Set<String>? blacklistPrefixes}) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
skippedPrefixes
status.pause(); | ||
logger.printStatus('Obtaining revisions.', | ||
indent: 2, color: TerminalColor.grey); | ||
status.resume(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Having UI and core logic intermixed is going to make this hard to maintain over time (and to extend to other uses, like a GUI tool). I would strongly recommend having a state system that this communicates to, and have that state drive UI from completely separate code.
} | ||
status.stop(); | ||
return context.migrateResult; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This function is quite large, and doing a lot of things; extracting smaller, well-named helpers for individual parts would help with understanding the overall flow.
return '5391447fae6209bb21a89e6a5a6583cac1af9b4b'; | ||
} | ||
|
||
abstract class MigrateFlutterProject { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This could use docs.
} | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here too, consider extracting small helpers to allow someone to easily see the overall logical structure of the flow.
} | ||
} | ||
|
||
class MigrateTargetFlutterProject extends MigrateFlutterProject { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This could use docs explaining its role.
04c4245
to
600d98a
Compare
@@ -226,10 +226,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<SupportedPlatform?, MigratePlatformConfig>? platformConfigs, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
how is a null key used here?
@@ -252,7 +252,7 @@ class MigrateConfig { | |||
/// Parses the project for all supported platforms and populates the [MigrateConfig] | |||
/// to reflect the project. | |||
void populate({ | |||
List<SupportedPlatform>? platforms, | |||
List<SupportedPlatform?>? platforms, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i guess same question here, is null a valid platform?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See discussion below, changed to use a new FlutterProjectComponent
enum that includes root as a value.
final List<SupportedPlatform> platforms = includeRoot | ||
? <SupportedPlatform>[SupportedPlatform.root] | ||
: <SupportedPlatform>[]; | ||
List<SupportedPlatform?> getSupportedPlatforms() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It doesn't look like this should need to return a nullable generic.
this.unmanagedFiles = kDefaultUnmanagedFiles}) | ||
: platformConfigs = | ||
platformConfigs ?? <SupportedPlatform, MigratePlatformConfig>{}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be writeable more simply as:
MigrateConfig({
this.platformConfigs = const <...>{},
});
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}"}'; | ||
'\n - platform: ${entry.key == null ? 'root' : 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}"}'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is still conflating "root" with platforms, just at a different level.
Stepping back a bit (since I'm not familiar with the high level flow), what is the thing that you are trying to represent here? Is it directories that make up a project, including both the root and the platform subdirectories?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The purpose of this code is to track the Flutter git revision each project directory was flutter create
-ed with. For this purpose only, the root
directory is equivalent to any other platform-specific directory. It is equivalent because, like a platform directory, the root directory contains a subset of files that are created by the flutter create
command. Upon creation, we log the flutter revision associated with the directory. Here, SupportedPlatform is a reference to directories within a project. As far as the migrate tool is concerned, a SupportedPlatform
is just a directory, and it does not care about what the directory represents.
This tracking is to handle the case where the initial flutter create
is called with, say 1.0.0, and then the windows platform is added using flutter 3.0. We would track different revisions for root+ios+android and windows.
Within the migrate tool, we also treat the root directory in the same way as platform directories. Once a project goes through a successful migration, we also track last successfully migrated revision in the same way.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It sounds like there should be a different class/enum for this that captures the concept then; something like FlutterProjectSection
or FlutterProjectComponent
. That way the confusion that comes from trying to express something other than a platform with SupportedPlatform
is eliminated.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've added FlutterProjectComponent
, and there are places in the code where it is appropriate to use SupportedPlatform
and others where FlutterProjectComponent
makes more sense. Having both does indeed seem like a more reasonable solution than conflating the two.
// 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; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this nullable? I'm not clear from reading this code what the distinction is between "never started" (null) and "started, but then stopped (non-null).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll change this to initialize the spinner in the constructor
b4da944
to
1ecebdc
Compare
@christopherfujino @stuartmorgan Comments addressed. Ready for another look. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
a570d62
to
2c7bdfd
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I left a few small comments, but otherwise looks good! I didn't do a full review, since you have one already, so I just skimmed (mostly around areas where I'd left comments previously) so my previous review won't block landing.
final Map<SupportedPlatform, MigratePlatformConfig> platformConfigs = | ||
<SupportedPlatform, MigratePlatformConfig>{}; | ||
for (final MapEntry<SupportedPlatform, MigratePlatformConfig> entry | ||
final Map<FlutterProjectComponent, MigratePlatformConfig> platformConfigs = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should probably be called projectComponents
, matching the type change.
<SupportedPlatform, MigratePlatformConfig>{}; | ||
for (final MapEntry<SupportedPlatform, MigratePlatformConfig> entry | ||
final Map<FlutterProjectComponent, MigratePlatformConfig> platformConfigs = | ||
<FlutterProjectComponent, MigratePlatformConfig>{}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should the value be a Platform
-related thing if the key is no longer platform-specific? Does that type need a name change or split as well?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, it does. But I think I'll leave a TODO here and do it in a separate PR to sync it here and in the flutter_tools repo. Id prefer to keep these concepts synced.
@@ -9,6 +9,37 @@ import 'base/file_system.dart'; | |||
import 'base/logger.dart'; | |||
import 'base/project.dart'; | |||
|
|||
enum FlutterProjectComponent { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should have a documentation comment.
} | ||
|
||
extension FlutterProjectComponentExtension on FlutterProjectComponent { | ||
SupportedPlatform? toSupportedPlatform() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe remove this? It doesn't appear to be used anywhere, and it's a remnant of conflating components and platforms. Code using this seems like it would be suspect for the same reason the earlier design was problematic.
// final String canonicalizedLocalPath = canonicalize(localPath); | ||
// if (canonicalizedSkippedFiles.contains(canonicalizedLocalPath)) { | ||
// return true; | ||
// } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove? We generally don't want to check in commented-out code, especially not without a comment explaining it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oops, missed this in the cleanup.
/// False for files that should not be merged. Typically, images and binary files. | ||
bool _mergable(String localPath) { | ||
for (final String ext in _doNotMergeFileExtensions) { | ||
if (localPath.endsWith(ext) && !_alwaysMigrateFiles.contains(localPath)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's no need to re-check _alwaysMigrateFiles
in the loop, since that expression doesn't use ext
.
It looks like this whole function could simplify to:
return _alwaysMigrateFiles.contains(localPath) || ! _doNotMergeFileExtensions.any((String ext) => localPath.endsWith(ext));
skippedPrefixes.add(platformToSubdirectoryPrefix(platform)); | ||
} | ||
for (final SupportedPlatform platform in platforms) { | ||
if (platform != null) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This can't happen any more; it's a non-nullable type now.
Adds
compute.dart
which performs the bulk of the migrate tasks.The migration is tracked as a MigrateContext which stores the MigrateResult as well as any intermediate state the migration needs.
The entry point is
computeMigration(...)
which begins the migration process. This is called by thestart
command which is the first command to be called by users.As seen in the dart docs: