-
Notifications
You must be signed in to change notification settings - Fork 27k
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
Migrate create command to null safety #104484
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,8 +2,6 @@ | |
// Use of this source code is governed by a BSD-style license that can be | ||
// found in the LICENSE file. | ||
|
||
// @dart = 2.8 | ||
|
||
import '../android/gradle_utils.dart' as gradle; | ||
import '../base/common.dart'; | ||
import '../base/context.dart'; | ||
|
@@ -32,8 +30,8 @@ const String kPlatformHelp = | |
|
||
class CreateCommand extends CreateBase { | ||
CreateCommand({ | ||
bool verboseHelp = false, | ||
}) : super(verboseHelp: verboseHelp) { | ||
super.verboseHelp = false, | ||
}) { | ||
addPlatformsOptions(customHelp: kPlatformHelp); | ||
argParser.addOption( | ||
'template', | ||
|
@@ -57,7 +55,6 @@ class CreateCommand extends CreateBase { | |
flutterProjectTypeToString(FlutterProjectType.module): 'Generate a project to add a Flutter module to an ' | ||
'existing Android or iOS application.', | ||
}, | ||
defaultsTo: null, | ||
); | ||
argParser.addOption( | ||
'sample', | ||
|
@@ -66,7 +63,6 @@ class CreateCommand extends CreateBase { | |
'"--template=app". The value should be the sample ID of the desired sample from the API ' | ||
'documentation website (http://docs.flutter.dev/). An example can be found at: ' | ||
'https://api.flutter.dev/flutter/widgets/SingleChildScrollView-class.html', | ||
defaultsTo: null, | ||
valueHelp: 'id', | ||
); | ||
argParser.addOption( | ||
|
@@ -88,7 +84,7 @@ class CreateCommand extends CreateBase { | |
String get category => FlutterCommandCategory.project; | ||
|
||
@override | ||
String get invocation => '${runner.executableName} $name <output directory>'; | ||
String get invocation => '${runner?.executableName} $name <output directory>'; | ||
|
||
@override | ||
Future<CustomDimensions> get usageValues async { | ||
|
@@ -100,8 +96,7 @@ class CreateCommand extends CreateBase { | |
} | ||
|
||
// Lazy-initialize the net utilities with values from the context. | ||
Net _cachedNet; | ||
Net get _net => _cachedNet ??= Net( | ||
late final Net _net = Net( | ||
httpClientFactory: context.get<HttpClientFactory>(), | ||
logger: globals.logger, | ||
platform: globals.platform, | ||
|
@@ -112,25 +107,25 @@ class CreateCommand extends CreateBase { | |
? 'api.flutter.dev' | ||
: 'master-api.flutter.dev'; | ||
|
||
Future<String> _fetchSampleFromServer(String sampleId) async { | ||
Future<String?> _fetchSampleFromServer(String sampleId) async { | ||
// Sanity check the sampleId | ||
if (sampleId.contains(RegExp(r'[^-\w\.]'))) { | ||
throwToolExit('Sample ID "$sampleId" contains invalid characters. Check the ID in the ' | ||
'documentation and try again.'); | ||
} | ||
|
||
final Uri snippetsUri = Uri.https(_snippetsHost, 'snippets/$sampleId.dart'); | ||
final List<int> data = await _net.fetchUrl(snippetsUri); | ||
final List<int>? data = await _net.fetchUrl(snippetsUri); | ||
if (data == null || data.isEmpty) { | ||
return null; | ||
} | ||
return utf8.decode(data); | ||
} | ||
|
||
/// Fetches the samples index file from the Flutter docs website. | ||
Future<String> _fetchSamplesIndexFromServer() async { | ||
Future<String?> _fetchSamplesIndexFromServer() async { | ||
final Uri snippetsUri = Uri.https(_snippetsHost, 'snippets/index.json'); | ||
final List<int> data = await _net.fetchUrl(snippetsUri, maxAttempts: 2); | ||
final List<int>? data = await _net.fetchUrl(snippetsUri, maxAttempts: 2); | ||
if (data == null || data.isEmpty) { | ||
return null; | ||
} | ||
|
@@ -145,7 +140,7 @@ class CreateCommand extends CreateBase { | |
if (outputFile.existsSync()) { | ||
throwToolExit('File "$outputFilePath" already exists', exitCode: 1); | ||
} | ||
final String samplesJson = await _fetchSamplesIndexFromServer(); | ||
final String? samplesJson = await _fetchSamplesIndexFromServer(); | ||
if (samplesJson == null) { | ||
throwToolExit('Unable to download samples', exitCode: 2); | ||
} else { | ||
|
@@ -158,11 +153,12 @@ class CreateCommand extends CreateBase { | |
} | ||
|
||
FlutterProjectType _getProjectType(Directory projectDir) { | ||
FlutterProjectType template; | ||
FlutterProjectType detectedProjectType; | ||
FlutterProjectType? template; | ||
FlutterProjectType? detectedProjectType; | ||
final bool metadataExists = projectDir.absolute.childFile('.metadata').existsSync(); | ||
if (argResults['template'] != null) { | ||
template = stringToProjectType(stringArgDeprecated('template')); | ||
final String? templateArgument = stringArg('template'); | ||
if (templateArgument != null) { | ||
template = stringToProjectType(templateArgument); | ||
} | ||
// If the project directory exists and isn't empty, then try to determine the template | ||
// type from the project directory. | ||
|
@@ -188,23 +184,25 @@ class CreateCommand extends CreateBase { | |
|
||
@override | ||
Future<FlutterCommandResult> runCommand() async { | ||
if (argResults['list-samples'] != null) { | ||
final String? listSamples = stringArg('list-samples'); | ||
if (listSamples != null) { | ||
// _writeSamplesJson can potentially be long-lived. | ||
await _writeSamplesJson(stringArgDeprecated('list-samples')); | ||
await _writeSamplesJson(listSamples); | ||
return FlutterCommandResult.success(); | ||
} | ||
|
||
validateOutputDirectoryArg(); | ||
|
||
String sampleCode; | ||
if (argResults['sample'] != null) { | ||
if (argResults['template'] != null && | ||
stringToProjectType(stringArgDeprecated('template') ?? 'app') != FlutterProjectType.app) { | ||
String? sampleCode; | ||
final String? sampleArgument = stringArg('sample'); | ||
if (sampleArgument != null) { | ||
final String? templateArgument = stringArg('template'); | ||
if (templateArgument != null && stringToProjectType(templateArgument) != FlutterProjectType.app) { | ||
throwToolExit('Cannot specify --sample with a project type other than ' | ||
'"${flutterProjectTypeToString(FlutterProjectType.app)}"'); | ||
} | ||
// Fetch the sample from the server. | ||
sampleCode = await _fetchSampleFromServer(stringArgDeprecated('sample')); | ||
sampleCode = await _fetchSampleFromServer(sampleArgument); | ||
} | ||
|
||
final FlutterProjectType template = _getProjectType(projectDir); | ||
|
@@ -215,7 +213,7 @@ class CreateCommand extends CreateBase { | |
|
||
final List<String> platforms = stringsArg('platforms'); | ||
// `--platforms` does not support module or package. | ||
if (argResults.wasParsed('platforms') && (generateModule || generatePackage)) { | ||
if (argResults!.wasParsed('platforms') && (generateModule || generatePackage)) { | ||
final String template = generateModule ? 'module' : 'package'; | ||
throwToolExit( | ||
'The "--platforms" argument is not supported in $template template.', | ||
|
@@ -224,18 +222,18 @@ class CreateCommand extends CreateBase { | |
} else if (platforms == null || platforms.isEmpty) { | ||
throwToolExit('Must specify at least one platform using --platforms', | ||
exitCode: 2); | ||
} else if (generateFfiPlugin && argResults.wasParsed('platforms') && platforms.contains('web')) { | ||
} else if (generateFfiPlugin && argResults!.wasParsed('platforms') && platforms.contains('web')) { | ||
throwToolExit( | ||
'The web platform is not supported in plugin_ffi template.', | ||
exitCode: 2, | ||
); | ||
} else if (generateFfiPlugin && argResults.wasParsed('ios-language')) { | ||
} else if (generateFfiPlugin && argResults!.wasParsed('ios-language')) { | ||
throwToolExit( | ||
'The "ios-language" option is not supported with the plugin_ffi ' | ||
'template: the language will always be C or C++.', | ||
exitCode: 2, | ||
); | ||
} else if (generateFfiPlugin && argResults.wasParsed('android-language')) { | ||
} else if (generateFfiPlugin && argResults!.wasParsed('android-language')) { | ||
throwToolExit( | ||
'The "android-language" option is not supported with the plugin_ffi ' | ||
'template: the language will always be C or C++.', | ||
|
@@ -258,7 +256,7 @@ class CreateCommand extends CreateBase { | |
|
||
final String dartSdk = globals.cache.dartSdkBuild; | ||
final bool includeIos = featureFlags.isIOSEnabled && platforms.contains('ios'); | ||
String developmentTeam; | ||
String? developmentTeam; | ||
if (includeIos) { | ||
developmentTeam = await getCodeSigningIdentityDevelopmentTeam( | ||
processManager: globals.processManager, | ||
|
@@ -272,7 +270,7 @@ class CreateCommand extends CreateBase { | |
// The dart project_name is in snake_case, this variable is the Title Case of the Project Name. | ||
final String titleCaseProjectName = snakeCaseToTitleCase(projectName); | ||
|
||
final Map<String, Object> templateContext = createTemplateContext( | ||
final Map<String, Object?> templateContext = createTemplateContext( | ||
organization: organization, | ||
projectName: projectName, | ||
titleCaseProjectName: titleCaseProjectName, | ||
|
@@ -432,12 +430,12 @@ Your $application code is in $relativeAppMain. | |
|
||
Future<int> _generateModule( | ||
Directory directory, | ||
Map<String, dynamic> templateContext, { | ||
Map<String, Object?> templateContext, { | ||
bool overwrite = false, | ||
bool printStatusWhenWriting = true, | ||
}) async { | ||
int generatedCount = 0; | ||
final String description = argResults.wasParsed('description') | ||
final String? description = argResults!.wasParsed('description') | ||
? stringArgDeprecated('description') | ||
: 'A new Flutter module project.'; | ||
templateContext['description'] = description; | ||
|
@@ -453,7 +451,6 @@ Your $application code is in $relativeAppMain. | |
context: PubContext.create, | ||
directory: directory.path, | ||
offline: boolArgDeprecated('offline'), | ||
generateSyntheticPackage: false, | ||
); | ||
final FlutterProject project = FlutterProject.fromDirectory(directory); | ||
await project.ensureReadyForPlatformSpecificTooling( | ||
|
@@ -466,12 +463,12 @@ Your $application code is in $relativeAppMain. | |
|
||
Future<int> _generatePackage( | ||
Directory directory, | ||
Map<String, dynamic> templateContext, { | ||
Map<String, Object?> templateContext, { | ||
bool overwrite = false, | ||
bool printStatusWhenWriting = true, | ||
}) async { | ||
int generatedCount = 0; | ||
final String description = argResults.wasParsed('description') | ||
final String? description = argResults!.wasParsed('description') | ||
? stringArgDeprecated('description') | ||
: 'A new Flutter package project.'; | ||
templateContext['description'] = description; | ||
|
@@ -487,21 +484,20 @@ Your $application code is in $relativeAppMain. | |
context: PubContext.createPackage, | ||
directory: directory.path, | ||
offline: boolArgDeprecated('offline'), | ||
generateSyntheticPackage: false, | ||
); | ||
} | ||
return generatedCount; | ||
} | ||
|
||
Future<int> _generateMethodChannelPlugin( | ||
Directory directory, | ||
Map<String, dynamic> templateContext, { | ||
Map<String, Object?> templateContext, { | ||
bool overwrite = false, | ||
bool printStatusWhenWriting = true, | ||
FlutterProjectType projectType, | ||
required FlutterProjectType projectType, | ||
}) async { | ||
// Plugins only add a platform if it was requested explicitly by the user. | ||
if (!argResults.wasParsed('platforms')) { | ||
if (!argResults!.wasParsed('platforms')) { | ||
for (final String platform in kAllCreatePlatforms) { | ||
templateContext[platform] = false; | ||
} | ||
|
@@ -517,7 +513,7 @@ Your $application code is in $relativeAppMain. | |
final bool willAddPlatforms = platformsToAdd.isNotEmpty; | ||
templateContext['no_platforms'] = !willAddPlatforms; | ||
int generatedCount = 0; | ||
final String description = argResults.wasParsed('description') | ||
final String? description = argResults!.wasParsed('description') | ||
? stringArgDeprecated('description') | ||
: 'A new Flutter plugin project.'; | ||
templateContext['description'] = description; | ||
|
@@ -534,7 +530,6 @@ Your $application code is in $relativeAppMain. | |
context: PubContext.createPlugin, | ||
directory: directory.path, | ||
offline: boolArgDeprecated('offline'), | ||
generateSyntheticPackage: false, | ||
); | ||
} | ||
|
||
|
@@ -545,9 +540,9 @@ Your $application code is in $relativeAppMain. | |
project: project, requireAndroidSdk: false); | ||
} | ||
|
||
final String projectName = templateContext['projectName'] as String; | ||
final String organization = templateContext['organization'] as String; | ||
final String androidPluginIdentifier = templateContext['androidIdentifier'] as String; | ||
final String? projectName = templateContext['projectName'] as String?; | ||
final String organization = templateContext['organization']! as String; // Required to make the context. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is icky but that's what we get serializing and deserializing into maps. |
||
final String? androidPluginIdentifier = templateContext['androidIdentifier'] as String?; | ||
final String exampleProjectName = '${projectName}_example'; | ||
templateContext['projectName'] = exampleProjectName; | ||
templateContext['androidIdentifier'] = CreateBase.createAndroidIdentifier(organization, exampleProjectName); | ||
|
@@ -572,13 +567,13 @@ Your $application code is in $relativeAppMain. | |
|
||
Future<int> _generateFfiPlugin( | ||
Directory directory, | ||
Map<String, dynamic> templateContext, { | ||
Map<String, Object?> templateContext, { | ||
bool overwrite = false, | ||
bool printStatusWhenWriting = true, | ||
FlutterProjectType projectType, | ||
required FlutterProjectType projectType, | ||
}) async { | ||
// Plugins only add a platform if it was requested explicitly by the user. | ||
if (!argResults.wasParsed('platforms')) { | ||
if (!argResults!.wasParsed('platforms')) { | ||
for (final String platform in kAllCreatePlatforms) { | ||
templateContext[platform] = false; | ||
} | ||
|
@@ -596,7 +591,7 @@ Your $application code is in $relativeAppMain. | |
final bool willAddPlatforms = platformsToAdd.isNotEmpty; | ||
templateContext['no_platforms'] = !willAddPlatforms; | ||
int generatedCount = 0; | ||
final String description = argResults.wasParsed('description') | ||
final String? description = argResults!.wasParsed('description') | ||
? stringArgDeprecated('description') | ||
: 'A new Flutter FFI plugin project.'; | ||
templateContext['description'] = description; | ||
|
@@ -613,7 +608,6 @@ Your $application code is in $relativeAppMain. | |
context: PubContext.createPlugin, | ||
directory: directory.path, | ||
offline: boolArgDeprecated('offline'), | ||
generateSyntheticPackage: false, | ||
); | ||
} | ||
|
||
|
@@ -623,9 +617,9 @@ Your $application code is in $relativeAppMain. | |
gradle.updateLocalProperties(project: project, requireAndroidSdk: false); | ||
} | ||
|
||
final String projectName = templateContext['projectName'] as String; | ||
final String organization = templateContext['organization'] as String; | ||
final String androidPluginIdentifier = templateContext['androidIdentifier'] as String; | ||
final String? projectName = templateContext['projectName'] as String?; | ||
final String organization = templateContext['organization']! as String; // Required to make the context. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same. |
||
final String? androidPluginIdentifier = templateContext['androidIdentifier'] as String?; | ||
final String exampleProjectName = '${projectName}_example'; | ||
templateContext['projectName'] = exampleProjectName; | ||
templateContext['androidIdentifier'] = CreateBase.createAndroidIdentifier(organization, exampleProjectName); | ||
|
@@ -662,7 +656,7 @@ Your $application code is in $relativeAppMain. | |
return -files.length; | ||
} | ||
|
||
List<String> _getSupportedPlatformsFromTemplateContext(Map<String, dynamic> templateContext) { | ||
List<String> _getSupportedPlatformsFromTemplateContext(Map<String, Object?> templateContext) { | ||
return <String>[ | ||
for (String platform in kAllCreatePlatforms) | ||
if (templateContext[platform] == true) platform, | ||
|
@@ -671,7 +665,7 @@ Your $application code is in $relativeAppMain. | |
|
||
// Returns a list of platforms that are explicitly requested by user via `--platforms`. | ||
List<String> _getUserRequestedPlatforms() { | ||
if (!argResults.wasParsed('platforms')) { | ||
if (!argResults!.wasParsed('platforms')) { | ||
return <String>[]; | ||
} | ||
return stringsArg('platforms'); | ||
|
@@ -682,10 +676,11 @@ Your $application code is in $relativeAppMain. | |
// Determine what platforms are supported based on generated files. | ||
List<String> _getSupportedPlatformsInPlugin(Directory projectDir) { | ||
final String pubspecPath = globals.fs.path.join(projectDir.absolute.path, 'pubspec.yaml'); | ||
final FlutterManifest manifest = FlutterManifest.createFromPath(pubspecPath, fileSystem: globals.fs, logger: globals.logger); | ||
final List<String> platforms = manifest.validSupportedPlatforms == null | ||
final FlutterManifest? manifest = FlutterManifest.createFromPath(pubspecPath, fileSystem: globals.fs, logger: globals.logger); | ||
final Map<String, Object?>? validSupportedPlatforms = manifest?.validSupportedPlatforms; | ||
final List<String> platforms = validSupportedPlatforms == null | ||
? <String>[] | ||
: manifest.validSupportedPlatforms.keys.toList(); | ||
: validSupportedPlatforms.keys.toList(); | ||
return platforms; | ||
} | ||
|
||
|
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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 this wasn't safe... #101595