Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Sign in
Toggle navigation
F
Front-End
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
abdullh.alsoleman
Front-End
Commits
4e88f6a1
Unverified
Commit
4e88f6a1
authored
Nov 20, 2020
by
Chris Yang
Committed by
GitHub
Nov 20, 2020
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
flutter_tools: refactor `CreateCommand`. (#70874)
parent
c2d1203f
Changes
3
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
663 additions
and
542 deletions
+663
-542
create.dart
packages/flutter_tools/lib/src/commands/create.dart
+22
-541
create_base.dart
packages/flutter_tools/lib/src/commands/create_base.dart
+640
-0
create_config_test.dart
.../flutter_tools/test/general.shard/create_config_test.dart
+1
-1
No files found.
packages/flutter_tools/lib/src/commands/create.dart
View file @
4e88f6a1
...
...
@@ -2,12 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import
'package:meta/meta.dart'
;
import
'package:uuid/uuid.dart'
;
import
'package:yaml/yaml.dart'
;
import
'../android/android.dart'
as
android_common
;
import
'../android/android_workflow.dart'
;
import
'../android/gradle_utils.dart'
as
gradle
;
import
'../base/common.dart'
;
import
'../base/context.dart'
;
...
...
@@ -15,8 +11,6 @@ import '../base/file_system.dart';
import
'../base/io.dart'
;
import
'../base/net.dart'
;
import
'../base/terminal.dart'
;
import
'../base/utils.dart'
;
import
'../cache.dart'
;
import
'../convert.dart'
;
import
'../dart/pub.dart'
;
import
'../features.dart'
;
...
...
@@ -27,16 +21,7 @@ import '../plugins.dart';
import
'../project.dart'
;
import
'../reporting/reporting.dart'
;
import
'../runner/flutter_command.dart'
;
import
'../template.dart'
;
const
List
<
String
>
_kAvailablePlatforms
=
<
String
>[
'ios'
,
'android'
,
'windows'
,
'linux'
,
'macos'
,
'web'
,
];
import
'create_base.dart'
;
const
String
_kNoPlatformsErrorMessage
=
'''
The plugin project was generated without specifying the `--platforms` flag, no new platforms are added.
...
...
@@ -45,27 +30,13 @@ directory. You can also find detailed instructions on how to add platforms in th
at https://flutter.dev/docs/development/packages-and-plugins/developing-packages#plugin-platforms.
'''
;
class
CreateCommand
extends
FlutterCommand
{
class
CreateCommand
extends
CreateBase
{
CreateCommand
()
{
_addPlatformsOptions
();
argParser
.
addFlag
(
'pub'
,
defaultsTo:
true
,
help:
'Whether to run "flutter pub get" after the project has been created.'
,
);
argParser
.
addFlag
(
'offline'
,
defaultsTo:
false
,
help:
'When "flutter pub get" is run by the create command, this indicates '
'whether to run it in offline mode or not. In offline mode, it will need to '
'have all dependencies already available in the pub cache to succeed.'
,
);
argParser
.
addFlag
(
'with-driver-test'
,
negatable:
true
,
defaultsTo:
false
,
help:
'(Deprecated) Also add a flutter_driver dependency and generate a '
"sample 'flutter drive' test. This flag has been deprecated, instead see "
'package:integration_test at https://pub.dev/packages/integration_test .'
,
);
addPlatformsOptions
(
customHelp:
'The platforms supported by this project. '
'This argument only works when the --template is set to app or plugin. '
'Platform folders (e.g. android/) will be generated in the target project. '
'When adding platforms to a plugin project, the pubspec.yaml will be updated with the requested platform. '
'Adding desktop platforms requires the corresponding desktop config setting to be enabled.'
);
argParser
.
addOption
(
'template'
,
abbr:
't'
,
...
...
@@ -100,46 +71,6 @@ class CreateCommand extends FlutterCommand {
'that can be created with --sample.'
,
valueHelp:
'path'
,
);
argParser
.
addFlag
(
'overwrite'
,
negatable:
true
,
defaultsTo:
false
,
help:
'When performing operations, overwrite existing files.'
,
);
argParser
.
addOption
(
'description'
,
defaultsTo:
'A new Flutter project.'
,
help:
'The description to use for your new Flutter project. This string ends up in the pubspec.yaml file.'
,
);
argParser
.
addOption
(
'org'
,
defaultsTo:
'com.example'
,
help:
'The organization responsible for your new Flutter project, in reverse domain name notation. '
'This string is used in Java package names and as prefix in the iOS bundle identifier.'
,
);
argParser
.
addOption
(
'project-name'
,
defaultsTo:
null
,
help:
'The project name for this new Flutter project. This must be a valid dart package name.'
,
);
argParser
.
addOption
(
'ios-language'
,
abbr:
'i'
,
defaultsTo:
'swift'
,
allowed:
<
String
>[
'objc'
,
'swift'
],
);
argParser
.
addOption
(
'android-language'
,
abbr:
'a'
,
defaultsTo:
'kotlin'
,
allowed:
<
String
>[
'java'
,
'kotlin'
],
);
argParser
.
addFlag
(
'skip-name-checks'
,
help:
'integration test only parameter to allow creating applications/plugins with '
'invalid names.'
,
hide:
true
,
);
}
@override
...
...
@@ -169,35 +100,6 @@ class CreateCommand extends FlutterCommand {
platform:
globals
.
platform
,
);
// If it has a .metadata file with the project_type in it, use that.
// If it has an android dir and an android/app dir, it's a legacy app
// If it has an ios dir and an ios/Flutter dir, it's a legacy app
// Otherwise, we don't presume to know what type of project it could be, since
// many of the files could be missing, and we can't really tell definitively.
FlutterProjectType
_determineTemplateType
(
Directory
projectDir
)
{
final
File
metadataFile
=
globals
.
fs
.
file
(
globals
.
fs
.
path
.
join
(
projectDir
.
absolute
.
path
,
'.metadata'
));
final
FlutterProjectMetadata
projectMetadata
=
FlutterProjectMetadata
(
metadataFile
,
globals
.
logger
);
if
(
projectMetadata
.
projectType
!=
null
)
{
return
projectMetadata
.
projectType
;
}
bool
exists
(
List
<
String
>
path
)
{
return
globals
.
fs
.
directory
(
globals
.
fs
.
path
.
joinAll
(<
String
>[
projectDir
.
absolute
.
path
,
...
path
])).
existsSync
();
}
// There either wasn't any metadata, or it didn't contain the project type,
// so try and figure out what type of project it is from the existing
// directory structure.
if
(
exists
(<
String
>[
'android'
,
'app'
])
||
exists
(<
String
>[
'ios'
,
'Runner'
])
||
exists
(<
String
>[
'ios'
,
'Flutter'
]))
{
return
FlutterProjectType
.
app
;
}
// Since we can't really be definitive on nearly-empty directories, err on
// the side of prudence and just say we don't know.
return
null
;
}
/// The hostname for the Flutter docs for the current channel.
String
get
_snippetsHost
=>
globals
.
flutterVersion
.
channel
==
'stable'
?
'docs.flutter.io'
...
...
@@ -258,7 +160,7 @@ class CreateCommand extends FlutterCommand {
// If the project directory exists and isn't empty, then try to determine the template
// type from the project directory.
if
(
projectDir
.
existsSync
()
&&
projectDir
.
listSync
().
isNotEmpty
)
{
detectedProjectType
=
_
determineTemplateType
(
projectDir
);
detectedProjectType
=
determineTemplateType
(
projectDir
);
if
(
detectedProjectType
==
null
&&
metadataExists
)
{
// We can only be definitive that this is the wrong type if the .metadata file
// exists and contains a type that we don't understand, or doesn't contain a type.
...
...
@@ -278,29 +180,6 @@ class CreateCommand extends FlutterCommand {
return
template
;
}
Set
<
Uri
>
get
templateManifest
=>
_templateManifest
??=
_computeTemplateManifest
();
Set
<
Uri
>
_templateManifest
;
Set
<
Uri
>
_computeTemplateManifest
()
{
final
String
flutterToolsAbsolutePath
=
globals
.
fs
.
path
.
join
(
Cache
.
flutterRoot
,
'packages'
,
'flutter_tools'
,
);
final
String
manifestPath
=
globals
.
fs
.
path
.
join
(
flutterToolsAbsolutePath
,
'templates'
,
'template_manifest.json'
,
);
final
Map
<
String
,
Object
>
manifest
=
json
.
decode
(
globals
.
fs
.
file
(
manifestPath
).
readAsStringSync
(),
)
as
Map
<
String
,
Object
>;
return
Set
<
Uri
>.
from
(
(
manifest
[
'files'
]
as
List
<
Object
>)
.
cast
<
String
>()
.
map
<
Uri
>((
String
path
)
=>
Uri
.
file
(
globals
.
fs
.
path
.
join
(
flutterToolsAbsolutePath
,
path
))),
);
}
@override
Future
<
FlutterCommandResult
>
runCommand
()
async
{
if
(
argResults
[
'list-samples'
]
!=
null
)
{
...
...
@@ -309,38 +188,8 @@ class CreateCommand extends FlutterCommand {
return
FlutterCommandResult
.
success
();
}
if
(
argResults
.
rest
.
isEmpty
)
{
throwToolExit
(
'No option specified for the output directory.
\n
$usage
'
,
exitCode:
2
);
}
if
(
argResults
.
rest
.
length
>
1
)
{
String
message
=
'Multiple output directories specified.'
;
for
(
final
String
arg
in
argResults
.
rest
)
{
if
(
arg
.
startsWith
(
'-'
))
{
message
+=
'
\n
Try moving
$arg
to be immediately following
$name
'
;
break
;
}
}
throwToolExit
(
message
,
exitCode:
2
);
}
if
(
Cache
.
flutterRoot
==
null
)
{
throwToolExit
(
'Neither the --flutter-root command line flag nor the FLUTTER_ROOT environment '
'variable was specified. Unable to find package:flutter.'
,
exitCode:
2
);
}
final
String
flutterRoot
=
globals
.
fs
.
path
.
absolute
(
Cache
.
flutterRoot
);
final
String
flutterPackagesDirectory
=
globals
.
fs
.
path
.
join
(
flutterRoot
,
'packages'
);
final
String
flutterPackagePath
=
globals
.
fs
.
path
.
join
(
flutterPackagesDirectory
,
'flutter'
);
if
(!
globals
.
fs
.
isFileSync
(
globals
.
fs
.
path
.
join
(
flutterPackagePath
,
'pubspec.yaml'
)))
{
throwToolExit
(
'Unable to find package:flutter in
$flutterPackagePath
'
,
exitCode:
2
);
}
final
String
flutterDriverPackagePath
=
globals
.
fs
.
path
.
join
(
flutterRoot
,
'packages'
,
'flutter_driver'
);
if
(!
globals
.
fs
.
isFileSync
(
globals
.
fs
.
path
.
join
(
flutterDriverPackagePath
,
'pubspec.yaml'
)))
{
throwToolExit
(
'Unable to find package:flutter_driver in
$flutterDriverPackagePath
'
,
exitCode:
2
);
}
validateOutputDirectoryArg
();
final
String
flutterRoot
=
getFlutterRoot
();
final
Directory
projectDir
=
globals
.
fs
.
directory
(
argResults
.
rest
.
first
);
final
String
projectDirPath
=
globals
.
fs
.
path
.
normalize
(
projectDir
.
absolute
.
path
);
...
...
@@ -374,33 +223,11 @@ class CreateCommand extends FlutterCommand {
exitCode:
2
);
}
String
organization
=
stringArg
(
'org'
);
if
(!
argResults
.
wasParsed
(
'org'
))
{
final
FlutterProject
project
=
FlutterProject
.
fromDirectory
(
projectDir
);
final
Set
<
String
>
existingOrganizations
=
await
project
.
organizationNames
;
if
(
existingOrganizations
.
length
==
1
)
{
organization
=
existingOrganizations
.
first
;
}
else
if
(
existingOrganizations
.
length
>
1
)
{
throwToolExit
(
'Ambiguous organization in existing files:
$existingOrganizations
. '
'The --org command line argument must be specified to recreate project.'
);
}
}
final
String
organization
=
await
getOrganization
(
projectDir
);
final
bool
overwrite
=
boolArg
(
'overwrite'
);
String
error
=
_validateProjectDir
(
projectDirPath
,
flutterRoot:
flutterRoot
,
overwrite:
overwrite
);
if
(
error
!=
null
)
{
throwToolExit
(
error
);
}
final
String
projectName
=
stringArg
(
'project-name'
)
??
globals
.
fs
.
path
.
basename
(
projectDirPath
);
if
(!
boolArg
(
'skip-name-checks'
))
{
error
=
_validateProjectName
(
projectName
);
if
(
error
!=
null
)
{
throwToolExit
(
error
);
}
}
validateProjectDir
(
projectDirPath
,
flutterRoot:
flutterRoot
,
overwrite:
overwrite
);
final
String
projectName
=
getProjectName
(
projectDirPath
);
if
(
boolArg
(
'with-driver-test'
))
{
globals
.
printError
(
...
...
@@ -410,7 +237,7 @@ class CreateCommand extends FlutterCommand {
);
}
final
Map
<
String
,
dynamic
>
templateContext
=
_
createTemplateContext
(
final
Map
<
String
,
dynamic
>
templateContext
=
createTemplateContext
(
organization:
organization
,
projectName:
projectName
,
projectDescription:
stringArg
(
'description'
),
...
...
@@ -441,7 +268,7 @@ class CreateCommand extends FlutterCommand {
int
generatedFileCount
=
0
;
switch
(
template
)
{
case
FlutterProjectType
.
app
:
generatedFileCount
+=
await
_
generateApp
(
relativeDir
,
templateContext
,
overwrite:
overwrite
);
generatedFileCount
+=
await
generateApp
(
relativeDir
,
templateContext
,
overwrite:
overwrite
);
break
;
case
FlutterProjectType
.
module
:
generatedFileCount
+=
await
_generateModule
(
relativeDir
,
templateContext
,
overwrite:
overwrite
);
...
...
@@ -505,24 +332,13 @@ To edit platform code in an IDE see https://flutter.dev/developing-packages/#edi
return
FlutterCommandResult
.
success
();
}
void
_addPlatformsOptions
()
{
argParser
.
addMultiOption
(
'platforms'
,
help:
'The platforms supported by this project. '
'This argument only works when the --template is set to app or plugin. '
'Platform folders (e.g. android/) will be generated in the target project. '
'When adding platforms to a plugin project, the pubspec.yaml will be updated with the requested platform. '
'Adding desktop platforms requires the corresponding desktop config setting to be enabled.'
,
defaultsTo:
_kAvailablePlatforms
,
allowed:
_kAvailablePlatforms
);
}
Future
<
int
>
_generateModule
(
Directory
directory
,
Map
<
String
,
dynamic
>
templateContext
,
{
bool
overwrite
=
false
})
async
{
int
generatedCount
=
0
;
final
String
description
=
argResults
.
wasParsed
(
'description'
)
?
stringArg
(
'description'
)
:
'A new flutter module project.'
;
templateContext
[
'description'
]
=
description
;
generatedCount
+=
await
_
renderTemplate
(
globals
.
fs
.
path
.
join
(
'module'
,
'common'
),
directory
,
templateContext
,
overwrite:
overwrite
);
generatedCount
+=
await
renderTemplate
(
globals
.
fs
.
path
.
join
(
'module'
,
'common'
),
directory
,
templateContext
,
overwrite:
overwrite
);
if
(
boolArg
(
'pub'
))
{
await
pub
.
get
(
context:
PubContext
.
create
,
...
...
@@ -545,7 +361,7 @@ To edit platform code in an IDE see https://flutter.dev/developing-packages/#edi
?
stringArg
(
'description'
)
:
'A new Flutter package project.'
;
templateContext
[
'description'
]
=
description
;
generatedCount
+=
await
_
renderTemplate
(
'package'
,
directory
,
templateContext
,
overwrite:
overwrite
);
generatedCount
+=
await
renderTemplate
(
'package'
,
directory
,
templateContext
,
overwrite:
overwrite
);
if
(
boolArg
(
'pub'
))
{
await
pub
.
get
(
context:
PubContext
.
createPackage
,
...
...
@@ -589,7 +405,7 @@ To edit platform code in an IDE see https://flutter.dev/developing-packages/#edi
?
stringArg
(
'description'
)
:
'A new flutter plugin project.'
;
templateContext
[
'description'
]
=
description
;
generatedCount
+=
await
_
renderTemplate
(
'plugin'
,
directory
,
templateContext
,
overwrite:
overwrite
);
generatedCount
+=
await
renderTemplate
(
'plugin'
,
directory
,
templateContext
,
overwrite:
overwrite
);
if
(
boolArg
(
'pub'
))
{
await
pub
.
get
(
...
...
@@ -641,45 +457,14 @@ https://flutter.dev/docs/development/packages-and-plugins/developing-packages#pl
final
String
androidPluginIdentifier
=
templateContext
[
'androidIdentifier'
]
as
String
;
final
String
exampleProjectName
=
projectName
+
'_example'
;
templateContext
[
'projectName'
]
=
exampleProjectName
;
templateContext
[
'androidIdentifier'
]
=
_
createAndroidIdentifier
(
organization
,
exampleProjectName
);
templateContext
[
'iosIdentifier'
]
=
_
createUTIIdentifier
(
organization
,
exampleProjectName
);
templateContext
[
'macosIdentifier'
]
=
_
createUTIIdentifier
(
organization
,
exampleProjectName
);
templateContext
[
'androidIdentifier'
]
=
createAndroidIdentifier
(
organization
,
exampleProjectName
);
templateContext
[
'iosIdentifier'
]
=
createUTIIdentifier
(
organization
,
exampleProjectName
);
templateContext
[
'macosIdentifier'
]
=
createUTIIdentifier
(
organization
,
exampleProjectName
);
templateContext
[
'description'
]
=
'Demonstrates how to use the
$projectName
plugin.'
;
templateContext
[
'pluginProjectName'
]
=
projectName
;
templateContext
[
'androidPluginIdentifier'
]
=
androidPluginIdentifier
;
generatedCount
+=
await
_generateApp
(
project
.
example
.
directory
,
templateContext
,
overwrite:
overwrite
,
pluginExampleApp:
true
);
return
generatedCount
;
}
Future
<
int
>
_generateApp
(
Directory
directory
,
Map
<
String
,
dynamic
>
templateContext
,
{
bool
overwrite
=
false
,
bool
pluginExampleApp
=
false
})
async
{
int
generatedCount
=
0
;
generatedCount
+=
await
_renderTemplate
(
'app'
,
directory
,
templateContext
,
overwrite:
overwrite
);
final
FlutterProject
project
=
FlutterProject
.
fromDirectory
(
directory
);
if
(
templateContext
[
'android'
]
==
true
)
{
generatedCount
+=
_injectGradleWrapper
(
project
);
}
if
(
boolArg
(
'pub'
))
{
await
pub
.
get
(
context:
PubContext
.
create
,
directory:
directory
.
path
,
offline:
boolArg
(
'offline'
),
generateSyntheticPackage:
false
,
);
await
project
.
ensureReadyForPlatformSpecificTooling
(
androidPlatform:
templateContext
[
'android'
]
as
bool
??
false
,
iosPlatform:
templateContext
[
'ios'
]
as
bool
??
false
,
linuxPlatform:
templateContext
[
'linux'
]
as
bool
??
false
,
macOSPlatform:
templateContext
[
'macos'
]
as
bool
??
false
,
windowsPlatform:
templateContext
[
'windows'
]
as
bool
??
false
,
webPlatform:
templateContext
[
'web'
]
as
bool
??
false
,
);
}
if
(
templateContext
[
'android'
]
==
true
)
{
gradle
.
updateLocalProperties
(
project:
project
,
requireAndroidSdk:
false
);
}
generatedCount
+=
await
generateApp
(
project
.
example
.
directory
,
templateContext
,
overwrite:
overwrite
,
pluginExampleApp:
true
);
return
generatedCount
;
}
...
...
@@ -713,308 +498,4 @@ https://flutter.dev/docs/development/packages-and-plugins/developing-packages#pl
'macos'
,
];
}
Map
<
String
,
dynamic
>
_createTemplateContext
({
String
organization
,
String
projectName
,
String
projectDescription
,
String
androidLanguage
,
String
iosLanguage
,
String
flutterRoot
,
bool
withPluginHook
=
false
,
bool
ios
=
false
,
bool
android
=
false
,
bool
web
=
false
,
bool
linux
=
false
,
bool
macos
=
false
,
bool
windows
=
false
,
})
{
flutterRoot
=
globals
.
fs
.
path
.
normalize
(
flutterRoot
);
final
String
pluginDartClass
=
_createPluginClassName
(
projectName
);
final
String
pluginClass
=
pluginDartClass
.
endsWith
(
'Plugin'
)
?
pluginDartClass
:
pluginDartClass
+
'Plugin'
;
final
String
pluginClassSnakeCase
=
snakeCase
(
pluginClass
);
final
String
pluginClassCapitalSnakeCase
=
pluginClassSnakeCase
.
toUpperCase
();
final
String
appleIdentifier
=
_createUTIIdentifier
(
organization
,
projectName
);
final
String
androidIdentifier
=
_createAndroidIdentifier
(
organization
,
projectName
);
// Linux uses the same scheme as the Android identifier.
// https://developer.gnome.org/gio/stable/GApplication.html#g-application-id-is-valid
final
String
linuxIdentifier
=
androidIdentifier
;
return
<
String
,
dynamic
>{
'organization'
:
organization
,
'projectName'
:
projectName
,
'androidIdentifier'
:
androidIdentifier
,
'iosIdentifier'
:
appleIdentifier
,
'macosIdentifier'
:
appleIdentifier
,
'linuxIdentifier'
:
linuxIdentifier
,
'description'
:
projectDescription
,
'dartSdk'
:
'
$flutterRoot
/bin/cache/dart-sdk'
,
'androidMinApiLevel'
:
android_common
.
minApiLevel
,
'androidSdkVersion'
:
kAndroidSdkMinVersion
,
'pluginClass'
:
pluginClass
,
'pluginClassSnakeCase'
:
pluginClassSnakeCase
,
'pluginClassCapitalSnakeCase'
:
pluginClassCapitalSnakeCase
,
'pluginDartClass'
:
pluginDartClass
,
'pluginProjectUUID'
:
Uuid
().
v4
().
toUpperCase
(),
'withPluginHook'
:
withPluginHook
,
'androidLanguage'
:
androidLanguage
,
'iosLanguage'
:
iosLanguage
,
'flutterRevision'
:
globals
.
flutterVersion
.
frameworkRevision
,
'flutterChannel'
:
globals
.
flutterVersion
.
channel
,
'ios'
:
ios
,
'android'
:
android
,
'web'
:
web
,
'linux'
:
linux
,
'macos'
:
macos
,
'windows'
:
windows
,
'year'
:
DateTime
.
now
().
year
,
};
}
Future
<
int
>
_renderTemplate
(
String
templateName
,
Directory
directory
,
Map
<
String
,
dynamic
>
context
,
{
bool
overwrite
=
false
})
async
{
final
Template
template
=
await
Template
.
fromName
(
templateName
,
fileSystem:
globals
.
fs
,
logger:
globals
.
logger
,
templateRenderer:
globals
.
templateRenderer
,
templateManifest:
templateManifest
,
);
return
template
.
render
(
directory
,
context
,
overwriteExisting:
overwrite
);
}
int
_injectGradleWrapper
(
FlutterProject
project
)
{
int
filesCreated
=
0
;
globals
.
fsUtils
.
copyDirectorySync
(
globals
.
cache
.
getArtifactDirectory
(
'gradle_wrapper'
),
project
.
android
.
hostAppGradleRoot
,
onFileCopied:
(
File
sourceFile
,
File
destinationFile
)
{
filesCreated
++;
final
String
modes
=
sourceFile
.
statSync
().
modeString
();
if
(
modes
!=
null
&&
modes
.
contains
(
'x'
))
{
globals
.
os
.
makeExecutable
(
destinationFile
);
}
},
);
return
filesCreated
;
}
}
String
_createAndroidIdentifier
(
String
organization
,
String
name
)
{
// Android application ID is specified in: https://developer.android.com/studio/build/application-id
// All characters must be alphanumeric or an underscore [a-zA-Z0-9_].
String
tmpIdentifier
=
'
$organization
.
$name
'
;
final
RegExp
disallowed
=
RegExp
(
r'[^\w\.]'
);
tmpIdentifier
=
tmpIdentifier
.
replaceAll
(
disallowed
,
''
);
// It must have at least two segments (one or more dots).
final
List
<
String
>
segments
=
tmpIdentifier
.
split
(
'.'
)
.
where
((
String
segment
)
=>
segment
.
isNotEmpty
)
.
toList
();
while
(
segments
.
length
<
2
)
{
segments
.
add
(
'untitled'
);
}
// Each segment must start with a letter.
final
RegExp
segmentPatternRegex
=
RegExp
(
r'^[a-zA-Z][\w]*$'
);
final
List
<
String
>
prefixedSegments
=
segments
.
map
((
String
segment
)
{
if
(!
segmentPatternRegex
.
hasMatch
(
segment
))
{
return
'u'
+
segment
;
}
return
segment
;
})
.
toList
();
return
prefixedSegments
.
join
(
'.'
);
}
String
_createPluginClassName
(
String
name
)
{
final
String
camelizedName
=
camelCase
(
name
);
return
camelizedName
[
0
].
toUpperCase
()
+
camelizedName
.
substring
(
1
);
}
String
_createUTIIdentifier
(
String
organization
,
String
name
)
{
// Create a UTI (https://en.wikipedia.org/wiki/Uniform_Type_Identifier) from a base name
name
=
camelCase
(
name
);
String
tmpIdentifier
=
'
$organization
.
$name
'
;
final
RegExp
disallowed
=
RegExp
(
r'[^a-zA-Z0-9\-\.\u0080-\uffff]+'
);
tmpIdentifier
=
tmpIdentifier
.
replaceAll
(
disallowed
,
''
);
// It must have at least two segments (one or more dots).
final
List
<
String
>
segments
=
tmpIdentifier
.
split
(
'.'
)
.
where
((
String
segment
)
=>
segment
.
isNotEmpty
)
.
toList
();
while
(
segments
.
length
<
2
)
{
segments
.
add
(
'untitled'
);
}
return
segments
.
join
(
'.'
);
}
const
Set
<
String
>
_packageDependencies
=
<
String
>{
'analyzer'
,
'args'
,
'async'
,
'collection'
,
'convert'
,
'crypto'
,
'flutter'
,
'flutter_test'
,
'front_end'
,
'html'
,
'http'
,
'intl'
,
'io'
,
'isolate'
,
'kernel'
,
'logging'
,
'matcher'
,
'meta'
,
'mime'
,
'path'
,
'plugin'
,
'pool'
,
'test'
,
'utf'
,
'watcher'
,
'yaml'
,
};
// A valid Dart identifier that can be used for a package, i.e. no
// capital letters.
// https://dart.dev/guides/language/language-tour#important-concepts
final
RegExp
_identifierRegExp
=
RegExp
(
'[a-z_][a-z0-9_]*'
);
// non-contextual dart keywords.
//' https://dart.dev/guides/language/language-tour#keywords
const
Set
<
String
>
_keywords
=
<
String
>{
'abstract'
,
'as'
,
'assert'
,
'async'
,
'await'
,
'break'
,
'case'
,
'catch'
,
'class'
,
'const'
,
'continue'
,
'covariant'
,
'default'
,
'deferred'
,
'do'
,
'dynamic'
,
'else'
,
'enum'
,
'export'
,
'extends'
,
'extension'
,
'external'
,
'factory'
,
'false'
,
'final'
,
'finally'
,
'for'
,
'function'
,
'get'
,
'hide'
,
'if'
,
'implements'
,
'import'
,
'in'
,
'inout'
,
'interface'
,
'is'
,
'late'
,
'library'
,
'mixin'
,
'native'
,
'new'
,
'null'
,
'of'
,
'on'
,
'operator'
,
'out'
,
'part'
,
'patch'
,
'required'
,
'rethrow'
,
'return'
,
'set'
,
'show'
,
'source'
,
'static'
,
'super'
,
'switch'
,
'sync'
,
'this'
,
'throw'
,
'true'
,
'try'
,
'typedef'
,
'var'
,
'void'
,
'while'
,
'with'
,
'yield'
,
};
/// Whether [name] is a valid Pub package.
@visibleForTesting
bool
isValidPackageName
(
String
name
)
{
final
Match
match
=
_identifierRegExp
.
matchAsPrefix
(
name
);
return
match
!=
null
&&
match
.
end
==
name
.
length
&&
!
_keywords
.
contains
(
name
);
}
/// Return null if the project name is legal. Return a validation message if
/// we should disallow the project name.
String
_validateProjectName
(
String
projectName
)
{
if
(!
isValidPackageName
(
projectName
))
{
return
'"
$projectName
" is not a valid Dart package name.
\n\n
'
'See https://dart.dev/tools/pub/pubspec#name for more information.'
;
}
if
(
_packageDependencies
.
contains
(
projectName
))
{
return
"Invalid project name: '
$projectName
' - this will conflict with Flutter "
'package dependencies.'
;
}
return
null
;
}
/// Return null if the project directory is legal. Return a validation message
/// if we should disallow the directory name.
String
_validateProjectDir
(
String
dirPath
,
{
String
flutterRoot
,
bool
overwrite
=
false
})
{
if
(
globals
.
fs
.
path
.
isWithin
(
flutterRoot
,
dirPath
))
{
return
'Cannot create a project within the Flutter SDK. '
"Target directory '
$dirPath
' is within the Flutter SDK at '
$flutterRoot
'."
;
}
// If the destination directory is actually a file, then we refuse to
// overwrite, on the theory that the user probably didn't expect it to exist.
if
(
globals
.
fs
.
isFileSync
(
dirPath
))
{
final
String
message
=
"Invalid project name: '
$dirPath
' - refers to an existing file."
;
return
overwrite
?
'
$message
Refusing to overwrite a file with a directory.'
:
message
;
}
if
(
overwrite
)
{
return
null
;
}
final
FileSystemEntityType
type
=
globals
.
fs
.
typeSync
(
dirPath
);
switch
(
type
)
{
case
FileSystemEntityType
.
file
:
// Do not overwrite files.
return
"Invalid project name: '
$dirPath
' - file exists."
;
case
FileSystemEntityType
.
link
:
// Do not overwrite links.
return
"Invalid project name: '
$dirPath
' - refers to a link."
;
default
:
return
null
;
}
}
packages/flutter_tools/lib/src/commands/create_base.dart
0 → 100644
View file @
4e88f6a1
// Copyright 2014 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:meta/meta.dart'
;
import
'package:uuid/uuid.dart'
;
import
'../android/android.dart'
as
android_common
;
import
'../android/android_workflow.dart'
;
import
'../android/gradle_utils.dart'
as
gradle
;
import
'../base/common.dart'
;
import
'../base/file_system.dart'
;
import
'../base/utils.dart'
;
import
'../cache.dart'
;
import
'../convert.dart'
;
import
'../dart/pub.dart'
;
import
'../flutter_project_metadata.dart'
;
import
'../globals.dart'
as
globals
;
import
'../project.dart'
;
import
'../runner/flutter_command.dart'
;
import
'../template.dart'
;
const
List
<
String
>
_kAvailablePlatforms
=
<
String
>[
'ios'
,
'android'
,
'windows'
,
'linux'
,
'macos'
,
'web'
,
];
const
String
_kDefaultPlatformArgumentHelp
=
'Required: The platforms supported by this project. '
'Platform folders (e.g. android/) will be generated in the target project. '
'Adding desktop platforms requires the corresponding desktop config setting to be enabled.'
;
/// Common behavior for `flutter create` commands.
abstract
class
CreateBase
extends
FlutterCommand
{
CreateBase
()
{
argParser
.
addFlag
(
'pub'
,
defaultsTo:
true
,
help:
'Whether to run "flutter pub get" after the project has been created.'
,
);
argParser
.
addFlag
(
'offline'
,
defaultsTo:
false
,
help:
'When "flutter pub get" is run by the create command, this indicates '
'whether to run it in offline mode or not. In offline mode, it will need to '
'have all dependencies already available in the pub cache to succeed.'
,
);
argParser
.
addFlag
(
'with-driver-test'
,
negatable:
true
,
defaultsTo:
false
,
help:
'(Deprecated) Also add a flutter_driver dependency and generate a '
"sample 'flutter drive' test. This flag has been deprecated, instead see "
'package:integration_test at https://pub.dev/packages/integration_test .'
,
);
argParser
.
addFlag
(
'overwrite'
,
negatable:
true
,
defaultsTo:
false
,
help:
'When performing operations, overwrite existing files.'
,
);
argParser
.
addOption
(
'description'
,
defaultsTo:
'A new Flutter project.'
,
help:
'The description to use for your new Flutter project. This string ends up in the pubspec.yaml file.'
,
);
argParser
.
addOption
(
'org'
,
defaultsTo:
'com.example'
,
help:
'The organization responsible for your new Flutter project, in reverse domain name notation. '
'This string is used in Java package names and as prefix in the iOS bundle identifier.'
,
);
argParser
.
addOption
(
'project-name'
,
defaultsTo:
null
,
help:
'The project name for this new Flutter project. This must be a valid dart package name.'
,
);
argParser
.
addOption
(
'ios-language'
,
abbr:
'i'
,
defaultsTo:
'swift'
,
allowed:
<
String
>[
'objc'
,
'swift'
],
);
argParser
.
addOption
(
'android-language'
,
abbr:
'a'
,
defaultsTo:
'kotlin'
,
allowed:
<
String
>[
'java'
,
'kotlin'
],
);
argParser
.
addFlag
(
'skip-name-checks'
,
help:
'integration test only parameter to allow creating applications/plugins with '
'invalid names.'
,
hide:
true
,
);
}
/// Adds a `--platforms` argument.
///
/// The help message of the argument is replaced with `customHelp` if `customHelp` is not null.
@protected
void
addPlatformsOptions
({
String
customHelp
})
{
argParser
.
addMultiOption
(
'platforms'
,
help:
customHelp
??
_kDefaultPlatformArgumentHelp
,
defaultsTo:
_kAvailablePlatforms
,
allowed:
_kAvailablePlatforms
);
}
/// Throw with exit code 2 if the output directory is invalid.
@protected
void
validateOutputDirectoryArg
()
{
if
(
argResults
.
rest
.
isEmpty
)
{
throwToolExit
(
'No option specified for the output directory.
\n
$usage
'
,
exitCode:
2
);
}
if
(
argResults
.
rest
.
length
>
1
)
{
String
message
=
'Multiple output directories specified.'
;
for
(
final
String
arg
in
argResults
.
rest
)
{
if
(
arg
.
startsWith
(
'-'
))
{
message
+=
'
\n
Try moving
$arg
to be immediately following
$name
'
;
break
;
}
}
throwToolExit
(
message
,
exitCode:
2
);
}
}
/// Gets the flutter root directory.
///
/// Throw with exit code 2 if the flutter sdk installed is invalid.
@protected
String
getFlutterRoot
()
{
if
(
Cache
.
flutterRoot
==
null
)
{
throwToolExit
(
'The FLUTTER_ROOT environment variable was not specified. Unable to find package:flutter.'
,
exitCode:
2
);
}
final
String
flutterRoot
=
globals
.
fs
.
path
.
absolute
(
Cache
.
flutterRoot
);
final
String
flutterPackagesDirectory
=
globals
.
fs
.
path
.
join
(
flutterRoot
,
'packages'
);
final
String
flutterPackagePath
=
globals
.
fs
.
path
.
join
(
flutterPackagesDirectory
,
'flutter'
);
if
(!
globals
.
fs
.
isFileSync
(
globals
.
fs
.
path
.
join
(
flutterPackagePath
,
'pubspec.yaml'
)))
{
throwToolExit
(
'Unable to find package:flutter in
$flutterPackagePath
'
,
exitCode:
2
);
}
final
String
flutterDriverPackagePath
=
globals
.
fs
.
path
.
join
(
flutterRoot
,
'packages'
,
'flutter_driver'
);
if
(!
globals
.
fs
.
isFileSync
(
globals
.
fs
.
path
.
join
(
flutterDriverPackagePath
,
'pubspec.yaml'
)))
{
throwToolExit
(
'Unable to find package:flutter_driver in
$flutterDriverPackagePath
'
,
exitCode:
2
);
}
return
flutterRoot
;
}
/// Determines the project type in an existing flutter project.
///
/// If it has a .metadata file with the project_type in it, use that.
/// If it has an android dir and an android/app dir, it's a legacy app
/// If it has an ios dir and an ios/Flutter dir, it's a legacy app
/// Otherwise, we don't presume to know what type of project it could be, since
/// many of the files could be missing, and we can't really tell definitively.
///
/// Throws assertion if projectDir does not exist or empty.
/// Returns null if no project type can be determined.
@protected
FlutterProjectType
determineTemplateType
(
Directory
projectDir
)
{
assert
(
projectDir
.
existsSync
()
&&
projectDir
.
listSync
().
isNotEmpty
);
final
File
metadataFile
=
globals
.
fs
.
file
(
globals
.
fs
.
path
.
join
(
projectDir
.
absolute
.
path
,
'.metadata'
));
final
FlutterProjectMetadata
projectMetadata
=
FlutterProjectMetadata
(
metadataFile
,
globals
.
logger
);
if
(
projectMetadata
.
projectType
!=
null
)
{
return
projectMetadata
.
projectType
;
}
bool
exists
(
List
<
String
>
path
)
{
return
globals
.
fs
.
directory
(
globals
.
fs
.
path
.
joinAll
(<
String
>[
projectDir
.
absolute
.
path
,
...
path
]))
.
existsSync
();
}
// There either wasn't any metadata, or it didn't contain the project type,
// so try and figure out what type of project it is from the existing
// directory structure.
if
(
exists
(<
String
>[
'android'
,
'app'
])
||
exists
(<
String
>[
'ios'
,
'Runner'
])
||
exists
(<
String
>[
'ios'
,
'Flutter'
]))
{
return
FlutterProjectType
.
app
;
}
// Since we can't really be definitive on nearly-empty directories, err on
// the side of prudence and just say we don't know.
return
null
;
}
/// Determines the organization.
///
/// If `--org` is specified in the command, returns that directly.
/// If `--org` is not specified, returns the organization from the existing project.
@protected
Future
<
String
>
getOrganization
(
Directory
projectDir
)
async
{
String
organization
=
stringArg
(
'org'
);
if
(!
argResults
.
wasParsed
(
'org'
))
{
final
FlutterProject
project
=
FlutterProject
.
fromDirectory
(
projectDir
);
final
Set
<
String
>
existingOrganizations
=
await
project
.
organizationNames
;
if
(
existingOrganizations
.
length
==
1
)
{
organization
=
existingOrganizations
.
first
;
}
else
if
(
existingOrganizations
.
length
>
1
)
{
throwToolExit
(
'Ambiguous organization in existing files:
$existingOrganizations
. '
'The --org command line argument must be specified to recreate project.'
);
}
}
return
organization
;
}
/// Throws with exit 2 if the project directory is illegal.
@protected
void
validateProjectDir
(
String
dirPath
,
{
String
flutterRoot
,
bool
overwrite
=
false
})
{
if
(
globals
.
fs
.
path
.
isWithin
(
flutterRoot
,
dirPath
))
{
throwToolExit
(
'Cannot create a project within the Flutter SDK. '
"Target directory '
$dirPath
' is within the Flutter SDK at '
$flutterRoot
'."
,
exitCode:
2
);
}
// If the destination directory is actually a file, then we refuse to
// overwrite, on the theory that the user probably didn't expect it to exist.
if
(
globals
.
fs
.
isFileSync
(
dirPath
))
{
final
String
message
=
"Invalid project name: '
$dirPath
' - refers to an existing file."
;
throwToolExit
(
overwrite
?
'
$message
Refusing to overwrite a file with a directory.'
:
message
,
exitCode:
2
);
}
if
(
overwrite
)
{
return
;
}
final
FileSystemEntityType
type
=
globals
.
fs
.
typeSync
(
dirPath
);
switch
(
type
)
{
case
FileSystemEntityType
.
file
:
// Do not overwrite files.
throwToolExit
(
"Invalid project name: '
$dirPath
' - file exists."
,
exitCode:
2
);
break
;
case
FileSystemEntityType
.
link
:
// Do not overwrite links.
throwToolExit
(
"Invalid project name: '
$dirPath
' - refers to a link."
,
exitCode:
2
);
break
;
default
:
}
}
/// Gets the project name based.
///
/// Use the current directory path name if the `--project-name` is not specified explicitly.
@protected
String
getProjectName
(
String
projectDirPath
)
{
final
String
projectName
=
stringArg
(
'project-name'
)
??
globals
.
fs
.
path
.
basename
(
projectDirPath
);
if
(!
boolArg
(
'skip-name-checks'
))
{
final
String
error
=
_validateProjectName
(
projectName
);
if
(
error
!=
null
)
{
throwToolExit
(
error
);
}
}
return
projectName
;
}
/// Creates a template to use for [renderTemplate].
@protected
Map
<
String
,
dynamic
>
createTemplateContext
({
String
organization
,
String
projectName
,
String
projectDescription
,
String
androidLanguage
,
String
iosLanguage
,
String
flutterRoot
,
bool
withPluginHook
=
false
,
bool
ios
=
false
,
bool
android
=
false
,
bool
web
=
false
,
bool
linux
=
false
,
bool
macos
=
false
,
bool
windows
=
false
,
})
{
flutterRoot
=
globals
.
fs
.
path
.
normalize
(
flutterRoot
);
final
String
pluginDartClass
=
_createPluginClassName
(
projectName
);
final
String
pluginClass
=
pluginDartClass
.
endsWith
(
'Plugin'
)
?
pluginDartClass
:
pluginDartClass
+
'Plugin'
;
final
String
pluginClassSnakeCase
=
snakeCase
(
pluginClass
);
final
String
pluginClassCapitalSnakeCase
=
pluginClassSnakeCase
.
toUpperCase
();
final
String
appleIdentifier
=
createUTIIdentifier
(
organization
,
projectName
);
final
String
androidIdentifier
=
createAndroidIdentifier
(
organization
,
projectName
);
// Linux uses the same scheme as the Android identifier.
// https://developer.gnome.org/gio/stable/GApplication.html#g-application-id-is-valid
final
String
linuxIdentifier
=
androidIdentifier
;
return
<
String
,
dynamic
>{
'organization'
:
organization
,
'projectName'
:
projectName
,
'androidIdentifier'
:
androidIdentifier
,
'iosIdentifier'
:
appleIdentifier
,
'macosIdentifier'
:
appleIdentifier
,
'linuxIdentifier'
:
linuxIdentifier
,
'description'
:
projectDescription
,
'dartSdk'
:
'
$flutterRoot
/bin/cache/dart-sdk'
,
'androidMinApiLevel'
:
android_common
.
minApiLevel
,
'androidSdkVersion'
:
kAndroidSdkMinVersion
,
'pluginClass'
:
pluginClass
,
'pluginClassSnakeCase'
:
pluginClassSnakeCase
,
'pluginClassCapitalSnakeCase'
:
pluginClassCapitalSnakeCase
,
'pluginDartClass'
:
pluginDartClass
,
'pluginProjectUUID'
:
Uuid
().
v4
().
toUpperCase
(),
'withPluginHook'
:
withPluginHook
,
'androidLanguage'
:
androidLanguage
,
'iosLanguage'
:
iosLanguage
,
'flutterRevision'
:
globals
.
flutterVersion
.
frameworkRevision
,
'flutterChannel'
:
globals
.
flutterVersion
.
channel
,
'ios'
:
ios
,
'android'
:
android
,
'web'
:
web
,
'linux'
:
linux
,
'macos'
:
macos
,
'windows'
:
windows
,
'year'
:
DateTime
.
now
().
year
,
};
}
/// Renders the template, generate files into `directory`.
///
/// `templateName` should match one of directory names under flutter_tools/template/.
/// If `overwrite` is true, overwrites existing files, `overwrite` defaults to `false`.
@protected
Future
<
int
>
renderTemplate
(
String
templateName
,
Directory
directory
,
Map
<
String
,
dynamic
>
context
,
{
bool
overwrite
=
false
})
async
{
final
Template
template
=
await
Template
.
fromName
(
templateName
,
fileSystem:
globals
.
fs
,
logger:
globals
.
logger
,
templateRenderer:
globals
.
templateRenderer
,
templateManifest:
_templateManifest
,
);
return
template
.
render
(
directory
,
context
,
overwriteExisting:
overwrite
);
}
/// Generate application project in the `directory` using `templateContext`.
///
/// If `overwrite` is true, overwrites existing files, `overwrite` defaults to `false`.
@protected
Future
<
int
>
generateApp
(
Directory
directory
,
Map
<
String
,
dynamic
>
templateContext
,
{
bool
overwrite
=
false
,
bool
pluginExampleApp
=
false
})
async
{
int
generatedCount
=
0
;
generatedCount
+=
await
renderTemplate
(
'app'
,
directory
,
templateContext
,
overwrite:
overwrite
);
final
FlutterProject
project
=
FlutterProject
.
fromDirectory
(
directory
);
if
(
templateContext
[
'android'
]
==
true
)
{
generatedCount
+=
_injectGradleWrapper
(
project
);
}
if
(
boolArg
(
'pub'
))
{
await
pub
.
get
(
context:
PubContext
.
create
,
directory:
directory
.
path
,
offline:
boolArg
(
'offline'
),
generateSyntheticPackage:
false
,
);
await
project
.
ensureReadyForPlatformSpecificTooling
(
androidPlatform:
templateContext
[
'android'
]
as
bool
??
false
,
iosPlatform:
templateContext
[
'ios'
]
as
bool
??
false
,
linuxPlatform:
templateContext
[
'linux'
]
as
bool
??
false
,
macOSPlatform:
templateContext
[
'macos'
]
as
bool
??
false
,
windowsPlatform:
templateContext
[
'windows'
]
as
bool
??
false
,
webPlatform:
templateContext
[
'web'
]
as
bool
??
false
,
);
}
if
(
templateContext
[
'android'
]
==
true
)
{
gradle
.
updateLocalProperties
(
project:
project
,
requireAndroidSdk:
false
);
}
return
generatedCount
;
}
/// Creates an android identifier.
///
/// Android application ID is specified in: https://developer.android.com/studio/build/application-id
/// All characters must be alphanumeric or an underscore [a-zA-Z0-9_].
@protected
String
createAndroidIdentifier
(
String
organization
,
String
name
)
{
String
tmpIdentifier
=
'
$organization
.
$name
'
;
final
RegExp
disallowed
=
RegExp
(
r'[^\w\.]'
);
tmpIdentifier
=
tmpIdentifier
.
replaceAll
(
disallowed
,
''
);
// It must have at least two segments (one or more dots).
final
List
<
String
>
segments
=
tmpIdentifier
.
split
(
'.'
)
.
where
((
String
segment
)
=>
segment
.
isNotEmpty
)
.
toList
();
while
(
segments
.
length
<
2
)
{
segments
.
add
(
'untitled'
);
}
// Each segment must start with a letter.
final
RegExp
segmentPatternRegex
=
RegExp
(
r'^[a-zA-Z][\w]*$'
);
final
List
<
String
>
prefixedSegments
=
segments
.
map
((
String
segment
)
{
if
(!
segmentPatternRegex
.
hasMatch
(
segment
))
{
return
'u'
+
segment
;
}
return
segment
;
}).
toList
();
return
prefixedSegments
.
join
(
'.'
);
}
String
_createPluginClassName
(
String
name
)
{
final
String
camelizedName
=
camelCase
(
name
);
return
camelizedName
[
0
].
toUpperCase
()
+
camelizedName
.
substring
(
1
);
}
/// Create a UTI (https://en.wikipedia.org/wiki/Uniform_Type_Identifier) from a base name
@protected
String
createUTIIdentifier
(
String
organization
,
String
name
)
{
name
=
camelCase
(
name
);
String
tmpIdentifier
=
'
$organization
.
$name
'
;
final
RegExp
disallowed
=
RegExp
(
r'[^a-zA-Z0-9\-\.\u0080-\uffff]+'
);
tmpIdentifier
=
tmpIdentifier
.
replaceAll
(
disallowed
,
''
);
// It must have at least two segments (one or more dots).
final
List
<
String
>
segments
=
tmpIdentifier
.
split
(
'.'
)
.
where
((
String
segment
)
=>
segment
.
isNotEmpty
)
.
toList
();
while
(
segments
.
length
<
2
)
{
segments
.
add
(
'untitled'
);
}
return
segments
.
join
(
'.'
);
}
Set
<
Uri
>
get
_templateManifest
=>
__templateManifest
??=
_computeTemplateManifest
();
Set
<
Uri
>
__templateManifest
;
Set
<
Uri
>
_computeTemplateManifest
()
{
final
String
flutterToolsAbsolutePath
=
globals
.
fs
.
path
.
join
(
Cache
.
flutterRoot
,
'packages'
,
'flutter_tools'
,
);
final
String
manifestPath
=
globals
.
fs
.
path
.
join
(
flutterToolsAbsolutePath
,
'templates'
,
'template_manifest.json'
,
);
final
Map
<
String
,
Object
>
manifest
=
json
.
decode
(
globals
.
fs
.
file
(
manifestPath
).
readAsStringSync
(),
)
as
Map
<
String
,
Object
>;
return
Set
<
Uri
>.
from
(
(
manifest
[
'files'
]
as
List
<
Object
>).
cast
<
String
>().
map
<
Uri
>(
(
String
path
)
=>
Uri
.
file
(
globals
.
fs
.
path
.
join
(
flutterToolsAbsolutePath
,
path
))),
);
}
int
_injectGradleWrapper
(
FlutterProject
project
)
{
int
filesCreated
=
0
;
globals
.
fsUtils
.
copyDirectorySync
(
globals
.
cache
.
getArtifactDirectory
(
'gradle_wrapper'
),
project
.
android
.
hostAppGradleRoot
,
onFileCopied:
(
File
sourceFile
,
File
destinationFile
)
{
filesCreated
++;
final
String
modes
=
sourceFile
.
statSync
().
modeString
();
if
(
modes
!=
null
&&
modes
.
contains
(
'x'
))
{
globals
.
os
.
makeExecutable
(
destinationFile
);
}
},
);
return
filesCreated
;
}
}
// A valid Dart identifier that can be used for a package, i.e. no
// capital letters.
// https://dart.dev/guides/language/language-tour#important-concepts
final
RegExp
_identifierRegExp
=
RegExp
(
'[a-z_][a-z0-9_]*'
);
// non-contextual dart keywords.
//' https://dart.dev/guides/language/language-tour#keywords
const
Set
<
String
>
_keywords
=
<
String
>{
'abstract'
,
'as'
,
'assert'
,
'async'
,
'await'
,
'break'
,
'case'
,
'catch'
,
'class'
,
'const'
,
'continue'
,
'covariant'
,
'default'
,
'deferred'
,
'do'
,
'dynamic'
,
'else'
,
'enum'
,
'export'
,
'extends'
,
'extension'
,
'external'
,
'factory'
,
'false'
,
'final'
,
'finally'
,
'for'
,
'function'
,
'get'
,
'hide'
,
'if'
,
'implements'
,
'import'
,
'in'
,
'inout'
,
'interface'
,
'is'
,
'late'
,
'library'
,
'mixin'
,
'native'
,
'new'
,
'null'
,
'of'
,
'on'
,
'operator'
,
'out'
,
'part'
,
'patch'
,
'required'
,
'rethrow'
,
'return'
,
'set'
,
'show'
,
'source'
,
'static'
,
'super'
,
'switch'
,
'sync'
,
'this'
,
'throw'
,
'true'
,
'try'
,
'typedef'
,
'var'
,
'void'
,
'while'
,
'with'
,
'yield'
,
};
const
Set
<
String
>
_packageDependencies
=
<
String
>{
'analyzer'
,
'args'
,
'async'
,
'collection'
,
'convert'
,
'crypto'
,
'flutter'
,
'flutter_test'
,
'front_end'
,
'html'
,
'http'
,
'intl'
,
'io'
,
'isolate'
,
'kernel'
,
'logging'
,
'matcher'
,
'meta'
,
'mime'
,
'path'
,
'plugin'
,
'pool'
,
'test'
,
'utf'
,
'watcher'
,
'yaml'
,
};
/// Whether [name] is a valid Pub package.
@visibleForTesting
bool
isValidPackageName
(
String
name
)
{
final
Match
match
=
_identifierRegExp
.
matchAsPrefix
(
name
);
return
match
!=
null
&&
match
.
end
==
name
.
length
&&
!
_keywords
.
contains
(
name
);
}
// Return null if the project name is legal. Return a validation message if
// we should disallow the project name.
String
_validateProjectName
(
String
projectName
)
{
if
(!
isValidPackageName
(
projectName
))
{
return
'"
$projectName
" is not a valid Dart package name.
\n\n
'
'See https://dart.dev/tools/pub/pubspec#name for more information.'
;
}
if
(
_packageDependencies
.
contains
(
projectName
))
{
return
"Invalid project name: '
$projectName
' - this will conflict with Flutter "
'package dependencies.'
;
}
return
null
;
}
packages/flutter_tools/test/general.shard/create_config_test.dart
View file @
4e88f6a1
...
...
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import
'package:flutter_tools/src/commands/create.dart'
;
import
'package:flutter_tools/src/commands/create
_base
.dart'
;
import
'../src/common.dart'
;
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment