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
ba5cc2cb
Unverified
Commit
ba5cc2cb
authored
Feb 18, 2021
by
Gary Qian
Committed by
GitHub
Feb 18, 2021
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
[flutter_tools] Deferred components setup validator (#75739)
parent
bfcb43d2
Changes
3
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
1718 additions
and
0 deletions
+1718
-0
deferred_components_setup_validator.dart
.../lib/src/android/deferred_components_setup_validator.dart
+773
-0
common.dart
...es/flutter_tools/lib/src/build_system/targets/common.dart
+4
-0
deferred_components_setup_validator_test.dart
...ard/android/deferred_components_setup_validator_test.dart
+941
-0
No files found.
packages/flutter_tools/lib/src/android/deferred_components_setup_validator.dart
0 → 100644
View file @
ba5cc2cb
// 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.
// @dart = 2.8
import
'package:meta/meta.dart'
;
import
'package:xml/xml.dart'
;
import
'package:yaml/yaml.dart'
;
import
'../base/common.dart'
;
import
'../base/deferred_component.dart'
;
import
'../base/error_handling_io.dart'
;
import
'../base/file_system.dart'
;
import
'../base/terminal.dart'
;
import
'../build_system/build_system.dart'
;
import
'../globals.dart'
as
globals
;
import
'../project.dart'
;
import
'../template.dart'
;
/// A class to configure and run deferred component setup verification checks
/// and tasks.
///
/// Once constructed, checks and tasks can be executed by calling the respective
/// methods. The results of the checks are stored internally and can be
/// displayed to the user by calling [displayResults].
class
DeferredComponentsSetupValidator
{
/// Constructs a validator instance.
///
/// The [env] property is used to locate the project files that are checked.
///
/// The [templatesDir] parameter is optional. If null, the tool's default
/// templates directory will be used.
///
/// When [exitOnFail] is set to true, the [handleResults] and [attemptToolExit]
/// methods will exit the tool when this validator detects a recommended
/// change. This defaults to true.
DeferredComponentsSetupValidator
(
this
.
env
,
{
this
.
exitOnFail
=
true
,
String
title
,
Directory
templatesDir
,
})
:
_outputDir
=
env
.
projectDir
.
childDirectory
(
'build'
)
.
childDirectory
(
kDeferredComponentsTempDirectory
),
_inputs
=
<
File
>[],
_outputs
=
<
File
>[],
_title
=
title
??
'Deferred components setup verification'
,
_templatesDir
=
templatesDir
,
_generatedFiles
=
<
String
>[],
_modifiedFiles
=
<
String
>[],
_invalidFiles
=
<
String
,
String
>{},
_diffLines
=
<
String
>[];
/// The build environment that should be used to find the input files to run
/// checks against.
///
/// The checks in this class are meant to be used as part of a build process,
/// so an environment should be available.
final
Environment
env
;
/// When true, failed checks and tasks will result in [attemptToolExit]
/// triggering [throwToolExit].
final
bool
exitOnFail
;
/// The name of the golden file that tracks the latest loading units
/// generated.
@visibleForTesting
static
const
String
kDeferredComponentsGoldenFileName
=
'deferred_components_golden.yaml'
;
/// The directory in the build folder to generate missing/modified files into.
@visibleForTesting
static
const
String
kDeferredComponentsTempDirectory
=
'android_deferred_components_setup_files'
;
final
String
_title
;
final
Directory
_templatesDir
;
final
Directory
_outputDir
;
// Files that were newly generated by this validator.
final
List
<
String
>
_generatedFiles
;
// Existing files that were modified by this validator.
final
List
<
String
>
_modifiedFiles
;
// Files that were invalid and unable to be checked. These files are input
// files that the validator tries to read rather than output files the
// validator generates. The key is the file name and the value is the message
// or reason it was invalid.
final
Map
<
String
,
String
>
_invalidFiles
;
// Output of the diff task.
// TODO(garyq): implement the diff task.
final
List
<
String
>
_diffLines
;
// Tracks the new and missing loading units.
Map
<
String
,
dynamic
>
_goldenComparisonResults
;
/// All files read by the validator.
List
<
File
>
get
inputs
=>
_inputs
;
final
List
<
File
>
_inputs
;
/// All files output by the validator.
List
<
File
>
get
outputs
=>
_outputs
;
final
List
<
File
>
_outputs
;
/// Returns true if there were any recommended changes that should
/// be applied.
///
/// Retuns false if no problems or recommendations were detected.
///
/// If no checks are run, then this will default to false and will remain so
/// until a failing check finishes running.
bool
get
changesNeeded
=>
_generatedFiles
.
isNotEmpty
||
_modifiedFiles
.
isNotEmpty
||
_invalidFiles
.
isNotEmpty
||
(
_goldenComparisonResults
!=
null
&&
!(
_goldenComparisonResults
[
'match'
]
as
bool
));
/// Checks if an android dynamic feature module exists for each deferred
/// component.
///
/// Returns true if the check passed with no recommended changes, and false
/// otherwise.
///
/// This method looks for the existence of `android/<componentname>/build.gradle`
/// and `android/<componentname>/src/main/AndroidManifest.xml`. If either of
/// these files does not exist, it will generate it in the validator output
/// directory based off of a template.
///
/// This method does not check if the contents of either of the files are
/// valid, as there are many ways that they can be validly configured.
Future
<
bool
>
checkAndroidDynamicFeature
(
List
<
DeferredComponent
>
components
)
async
{
bool
changesMade
=
false
;
for
(
final
DeferredComponent
component
in
components
)
{
final
_DeferredComponentAndroidFiles
androidFiles
=
_DeferredComponentAndroidFiles
(
name:
component
.
name
,
env:
env
,
templatesDir:
_templatesDir
);
if
(!
androidFiles
.
verifyFilesExist
())
{
// generate into temp directory
final
Map
<
String
,
List
<
File
>>
results
=
await
androidFiles
.
generateFiles
(
alternateAndroidDir:
_outputDir
,
clearAlternateOutputDir:
true
,
);
for
(
final
File
file
in
results
[
'outputs'
])
{
_generatedFiles
.
add
(
file
.
path
);
changesMade
=
true
;
}
_outputs
.
addAll
(
results
[
'outputs'
]);
_inputs
.
addAll
(
results
[
'inputs'
]);
}
}
return
!
changesMade
;
}
// The key used to identify the metadata element as the loading unit id to
// deferred component mapping.
static
const
String
_mappingKey
=
'io.flutter.embedding.engine.deferredcomponents.DeferredComponentManager.loadingUnitMapping'
;
/// Checks if the base module `app`'s `AndroidManifest.xml` contains the
/// required meta-data that maps loading units to deferred components.
///
/// Returns true if the check passed with no recommended changes, and false
/// otherwise.
///
/// Flutter engine uses a manifest meta-data mapping to determine which
/// deferred component includes a particular loading unit id. This method
/// checks if `app`'s `AndroidManifest.xml` contains this metadata. If not, it
/// will generate a modified AndroidManifest.xml with the correct metadata
/// entry.
///
/// An example mapping:
///
/// 2:componentA,3:componentB,4:componentC
///
/// Where loading unit 2 is included in componentA, loading unit 3 is included
/// in componentB, and loading unit 4 is included in componentC.
bool
checkAppAndroidManifestComponentLoadingUnitMapping
(
List
<
DeferredComponent
>
components
,
List
<
LoadingUnit
>
generatedLoadingUnits
)
{
final
Directory
androidDir
=
env
.
projectDir
.
childDirectory
(
'android'
);
// We do not use the Xml package to handle the writing, as we do not want to
// erase any user applied formatting and comments. The changes can be
// applied with dart io and custom parsing.
final
File
appManifestFile
=
androidDir
.
childDirectory
(
'app'
)
.
childDirectory
(
'src'
)
.
childDirectory
(
'main'
)
.
childFile
(
'AndroidManifest.xml'
);
_inputs
.
add
(
appManifestFile
);
if
(!
appManifestFile
.
existsSync
())
{
_invalidFiles
[
appManifestFile
.
path
]
=
'Error:
$appManifestFile
does not '
'exist or could not be found. Please ensure an AndroidManifest.xml '
'exists for the app
\'
s base module.'
;
return
false
;
}
XmlDocument
document
;
try
{
document
=
XmlDocument
.
parse
(
appManifestFile
.
readAsStringSync
());
}
on
XmlParserException
{
_invalidFiles
[
appManifestFile
.
path
]
=
'Error parsing
$appManifestFile
'
'Please ensure that the android manifest is a valid XML document and '
'try again.'
;
return
false
;
}
on
FileSystemException
{
_invalidFiles
[
appManifestFile
.
path
]
=
'Error reading
$appManifestFile
'
'even though it exists. Please ensure that you have read permission for '
'this file and try again.'
;
return
false
;
}
// Create loading unit mapping.
final
Map
<
int
,
String
>
mapping
=
<
int
,
String
>{};
for
(
final
DeferredComponent
component
in
components
)
{
component
.
assignLoadingUnits
(
generatedLoadingUnits
);
for
(
final
LoadingUnit
unit
in
component
.
loadingUnits
)
{
if
(!
mapping
.
containsKey
(
unit
.
id
))
{
mapping
[
unit
.
id
]
=
component
.
name
;
}
}
}
// Encode the mapping as a string.
final
StringBuffer
mappingBuffer
=
StringBuffer
();
for
(
final
int
key
in
mapping
.
keys
)
{
mappingBuffer
.
write
(
'
$key
:
${mapping[key]}
,'
);
}
String
encodedMapping
=
mappingBuffer
.
toString
();
// remove trailing comma.
encodedMapping
=
encodedMapping
.
substring
(
0
,
encodedMapping
.
length
-
1
);
// Check for existing metadata entry and see if needs changes.
bool
exists
=
false
;
bool
modified
=
false
;
for
(
final
XmlElement
metaData
in
document
.
findAllElements
(
'meta-data'
))
{
final
String
name
=
metaData
.
getAttribute
(
'android:name'
);
if
(
name
==
_mappingKey
)
{
exists
=
true
;
final
String
storedMappingString
=
metaData
.
getAttribute
(
'android:value'
);
if
(
storedMappingString
!=
encodedMapping
)
{
metaData
.
setAttribute
(
'android:value'
,
encodedMapping
);
modified
=
true
;
}
}
}
if
(!
exists
)
{
// Create an meta-data XmlElement that contains the mapping.
final
XmlElement
mappingMetadataElement
=
XmlElement
(
XmlName
.
fromString
(
'meta-data'
),
<
XmlAttribute
>[
XmlAttribute
(
XmlName
.
fromString
(
'android:name'
),
_mappingKey
),
XmlAttribute
(
XmlName
.
fromString
(
'android:value'
),
encodedMapping
),
],
);
for
(
final
XmlElement
application
in
document
.
findAllElements
(
'application'
))
{
application
.
children
.
add
(
mappingMetadataElement
);
break
;
}
}
if
(!
exists
||
modified
)
{
final
File
manifestOutput
=
_outputDir
.
childDirectory
(
'app'
)
.
childDirectory
(
'src'
)
.
childDirectory
(
'main'
)
.
childFile
(
'AndroidManifest.xml'
);
ErrorHandlingFileSystem
.
deleteIfExists
(
manifestOutput
);
manifestOutput
.
createSync
(
recursive:
true
);
manifestOutput
.
writeAsStringSync
(
document
.
toXmlString
(
pretty:
true
),
flush:
true
);
_modifiedFiles
.
add
(
manifestOutput
.
path
);
return
false
;
}
return
true
;
}
/// Checks if the base module `app`'s `strings.xml` contain string
/// resources for each component's name.
///
/// Returns true if the check passed with no recommended changes, and false
/// otherwise.
///
/// In each dynamic feature module's AndroidManifest.xml, the
/// name of the module is a string resource. This checks if
/// the needed string resources are in the base module `strings.xml`.
/// If not, this method will generate a modified `strings.xml` (or a
/// completely new one if the original file did not exist) in the
/// validator's output directory.
///
/// For example, if there is a deferred component named `component1`,
/// there should be the following string resource:
///
/// <string name="component1Name">component1</string>
///
/// The string element's name attribute should be the component name with
/// `Name` as a suffix, and the text contents should be the component name.
bool
checkAndroidResourcesStrings
(
List
<
DeferredComponent
>
components
)
{
final
Directory
androidDir
=
env
.
projectDir
.
childDirectory
(
'android'
);
// Add component name mapping to strings.xml
final
File
stringRes
=
androidDir
.
childDirectory
(
'app'
)
.
childDirectory
(
'src'
)
.
childDirectory
(
'main'
)
.
childDirectory
(
'res'
)
.
childDirectory
(
'values'
)
.
childFile
(
'strings.xml'
);
_inputs
.
add
(
stringRes
);
final
File
stringResOutput
=
_outputDir
.
childDirectory
(
'app'
)
.
childDirectory
(
'src'
)
.
childDirectory
(
'main'
)
.
childDirectory
(
'res'
)
.
childDirectory
(
'values'
)
.
childFile
(
'strings.xml'
);
ErrorHandlingFileSystem
.
deleteIfExists
(
stringResOutput
);
final
Map
<
String
,
String
>
requiredEntriesMap
=
<
String
,
String
>{};
for
(
final
DeferredComponent
component
in
components
)
{
requiredEntriesMap
[
'
${component.name}
Name'
]
=
component
.
name
;
}
if
(
stringRes
.
existsSync
())
{
bool
modified
=
false
;
XmlDocument
document
;
try
{
document
=
XmlDocument
.
parse
(
stringRes
.
readAsStringSync
());
}
on
XmlParserException
{
_invalidFiles
[
stringRes
.
path
]
=
'Error parsing
$stringRes
'
'Please ensure that the strings.xml is a valid XML document and '
'try again.'
;
return
false
;
}
// Check if all required lines are present, and fix if name exists, but
// wrong string stored.
for
(
final
XmlElement
resources
in
document
.
findAllElements
(
'resources'
))
{
for
(
final
XmlElement
element
in
resources
.
findElements
(
'string'
))
{
final
String
name
=
element
.
getAttribute
(
'name'
);
if
(
requiredEntriesMap
.
containsKey
(
name
))
{
if
(
element
.
text
!=
null
&&
element
.
text
!=
requiredEntriesMap
[
name
])
{
element
.
innerText
=
requiredEntriesMap
[
name
];
modified
=
true
;
}
requiredEntriesMap
.
remove
(
name
);
}
}
for
(
final
String
key
in
requiredEntriesMap
.
keys
)
{
modified
=
true
;
final
XmlElement
newStringElement
=
XmlElement
(
XmlName
.
fromString
(
'string'
),
<
XmlAttribute
>[
XmlAttribute
(
XmlName
.
fromString
(
'name'
),
key
),
],
<
XmlNode
>[
XmlText
(
requiredEntriesMap
[
key
]),
],
);
resources
.
children
.
add
(
newStringElement
);
}
break
;
}
if
(
modified
)
{
stringResOutput
.
createSync
(
recursive:
true
);
stringResOutput
.
writeAsStringSync
(
document
.
toXmlString
(
pretty:
true
));
_modifiedFiles
.
add
(
stringResOutput
.
path
);
return
false
;
}
return
true
;
}
// strings.xml does not exist, generate completely new file.
stringResOutput
.
createSync
(
recursive:
true
);
final
StringBuffer
buffer
=
StringBuffer
();
buffer
.
writeln
(
'''
<?xml version="1.0" encoding="utf-8"?>
<resources>
'''
);
for
(
final
String
key
in
requiredEntriesMap
.
keys
)
{
buffer
.
write
(
' <string name="
$key
">
${requiredEntriesMap[key]}
</string>
\n
'
);
}
buffer
.
write
(
'''
</resources>
'''
);
stringResOutput
.
writeAsStringSync
(
buffer
.
toString
(),
flush:
true
,
mode:
FileMode
.
append
);
_generatedFiles
.
add
(
stringResOutput
.
path
);
return
false
;
}
/// Compares the provided loading units against the contents of the
/// `deferred_components_golden.yaml` file.
///
/// Returns true if a golden exists and all loading units match, and false
/// otherwise.
///
/// This method will parse the golden file if it exists and compare it to
/// the provided generatedLoadingUnits. It will distinguish between newly
/// added loading units and no longer existing loading units. If the golden
/// file does not exist, then all generatedLoadingUnits will be considered
/// new.
bool
checkAgainstLoadingUnitGolden
(
List
<
LoadingUnit
>
generatedLoadingUnits
)
{
final
List
<
LoadingUnit
>
goldenLoadingUnits
=
_parseGolden
(
env
.
projectDir
.
childFile
(
kDeferredComponentsGoldenFileName
));
_goldenComparisonResults
=
<
String
,
dynamic
>{};
final
Set
<
LoadingUnit
>
unmatchedLoadingUnits
=
<
LoadingUnit
>{};
final
List
<
LoadingUnit
>
newLoadingUnits
=
<
LoadingUnit
>[];
if
(
generatedLoadingUnits
==
null
||
goldenLoadingUnits
==
null
)
{
_goldenComparisonResults
[
'new'
]
=
newLoadingUnits
;
_goldenComparisonResults
[
'missing'
]
=
unmatchedLoadingUnits
;
_goldenComparisonResults
[
'match'
]
=
false
;
return
false
;
}
_inputs
.
add
(
env
.
projectDir
.
childFile
(
kDeferredComponentsGoldenFileName
));
unmatchedLoadingUnits
.
addAll
(
goldenLoadingUnits
);
final
Set
<
int
>
addedNewIds
=
<
int
>{};
for
(
final
LoadingUnit
genUnit
in
generatedLoadingUnits
)
{
bool
matched
=
false
;
for
(
final
LoadingUnit
goldUnit
in
goldenLoadingUnits
)
{
if
(
genUnit
.
equalsIgnoringPath
(
goldUnit
))
{
matched
=
true
;
unmatchedLoadingUnits
.
remove
(
goldUnit
);
break
;
}
}
if
(!
matched
&&
!
addedNewIds
.
contains
(
genUnit
.
id
))
{
newLoadingUnits
.
add
(
genUnit
);
addedNewIds
.
add
(
genUnit
.
id
);
}
}
_goldenComparisonResults
[
'new'
]
=
newLoadingUnits
;
_goldenComparisonResults
[
'missing'
]
=
unmatchedLoadingUnits
;
_goldenComparisonResults
[
'match'
]
=
newLoadingUnits
.
isEmpty
&&
unmatchedLoadingUnits
.
isEmpty
;
return
_goldenComparisonResults
[
'match'
]
as
bool
;
}
List
<
LoadingUnit
>
_parseGolden
(
File
goldenFile
)
{
final
List
<
LoadingUnit
>
loadingUnits
=
<
LoadingUnit
>[];
_inputs
.
add
(
goldenFile
);
if
(!
goldenFile
.
existsSync
())
{
return
loadingUnits
;
}
final
YamlMap
data
=
loadYaml
(
goldenFile
.
readAsStringSync
())
as
YamlMap
;
// validate yaml format.
if
(!
data
.
containsKey
(
'loading-units'
))
{
_invalidFiles
[
goldenFile
.
path
]
=
'Invalid golden yaml file,
\'
loading-units
\'
'
'entry did not exist.'
;
return
loadingUnits
;
}
else
{
if
(
data
[
'loading-units'
]
is
!
YamlList
&&
data
[
'loading-units'
]
!=
null
)
{
_invalidFiles
[
goldenFile
.
path
]
=
'Invalid golden yaml file,
\'
loading-units
\'
'
'is not a list.'
;
return
loadingUnits
;
}
if
(
data
[
'loading-units'
]
!=
null
)
{
for
(
final
dynamic
loadingUnitData
in
data
[
'loading-units'
])
{
if
(
loadingUnitData
is
!
YamlMap
)
{
_invalidFiles
[
goldenFile
.
path
]
=
'Invalid golden yaml file,
\'
loading-units
\'
'
'is not a list of maps.'
;
return
loadingUnits
;
}
final
YamlMap
loadingUnitDataMap
=
loadingUnitData
as
YamlMap
;
if
(
loadingUnitDataMap
[
'id'
]
==
null
)
{
_invalidFiles
[
goldenFile
.
path
]
=
'Invalid golden yaml file, all '
'loading units must have an
\'
id
\'
'
;
return
loadingUnits
;
}
if
(
loadingUnitDataMap
[
'libraries'
]
!=
null
)
{
if
(
loadingUnitDataMap
[
'libraries'
]
is
!
YamlList
)
{
_invalidFiles
[
goldenFile
.
path
]
=
'Invalid golden yaml file,
\'
libraries
\'
'
'is not a list.'
;
return
loadingUnits
;
}
for
(
final
dynamic
node
in
loadingUnitDataMap
[
'libraries'
]
as
YamlList
)
{
if
(
node
is
!
String
)
{
_invalidFiles
[
goldenFile
.
path
]
=
'Invalid golden yaml file,
\'
libraries
\'
'
'is not a list of strings.'
;
return
loadingUnits
;
}
}
}
}
}
}
// Parse out validated yaml.
if
(
data
.
containsKey
(
'loading-units'
))
{
if
(
data
[
'loading-units'
]
!=
null
)
{
for
(
final
dynamic
loadingUnitData
in
data
[
'loading-units'
])
{
final
YamlMap
loadingUnitDataMap
=
loadingUnitData
as
YamlMap
;
final
List
<
String
>
libraries
=
<
String
>[];
if
(
loadingUnitDataMap
[
'libraries'
]
!=
null
)
{
for
(
final
dynamic
node
in
loadingUnitDataMap
[
'libraries'
]
as
YamlList
)
{
libraries
.
add
(
node
as
String
);
}
}
loadingUnits
.
add
(
LoadingUnit
(
id:
loadingUnitDataMap
[
'id'
]
as
int
,
path:
null
,
libraries:
libraries
,
));
}
}
}
return
loadingUnits
;
}
/// Writes the provided generatedLoadingUnits as `deferred_components_golden.yaml`
///
/// This golden file is used to detect any changes in the loading units
/// produced by gen_snapshot. Running [checkAgainstLoadingUnitGolden] with a
/// mismatching or missing golden will result in a failed validation. This
/// prevents unexpected changes in loading units causing misconfigured
/// deferred components.
void
writeGolden
(
List
<
LoadingUnit
>
generatedLoadingUnits
)
{
generatedLoadingUnits
??=
<
LoadingUnit
>[];
final
File
goldenFile
=
env
.
projectDir
.
childFile
(
kDeferredComponentsGoldenFileName
);
_outputs
.
add
(
goldenFile
);
ErrorHandlingFileSystem
.
deleteIfExists
(
goldenFile
);
goldenFile
.
createSync
(
recursive:
true
);
final
StringBuffer
buffer
=
StringBuffer
();
buffer
.
write
(
'''
# ===============================================================================
# The contents of this file are automatically generated and it is not recommended
# to modify this file manually.
# ===============================================================================
#
# In order to prevent unexpected splitting of deferred apps, this golden
# file records the last generated set of loading units. It only possible
# to obtain the final configuration of loading units after compilation is
# complete. This means improperly setup imports can only be detected after
# compilation.
#
# This golden file allows the build tool to detect any changes in the generated
# loading units. During the next build attempt, loading units in this file are
# compared against the newly generated loading units to check for any new or
# removed loading units. In the case where loading units do not match, the build
# will fail and ask the developer to verify that the `deferred-components`
# configuration in `pubspec.yaml` is correct. Developers should make any necessary
# changes to integrate new and changed loading units or remove no longer existing
# loading units from the configuration. The build command should then be
# re-run to continue the build process.
#
# Sometimes, changes to the generated loading units may be unintentional. If
# the list of loading units in this golden is not what is expected, the app'
s
#
deferred
imports
should
be
reviewed
.
Third
party
plugins
and
packages
may
#
also
introduce
deferred
imports
that
result
in
unexpected
loading
units
.
loading
-
units:
''');
final Set<int> usedIds = <int>{};
for (final LoadingUnit unit in generatedLoadingUnits) {
if (usedIds.contains(unit.id)) {
continue;
}
buffer.write('
-
id:
$
{
unit
.
id
}
\
n
');
if (unit.libraries != null && unit.libraries.isNotEmpty) {
buffer.write('
libraries:
\
n
');
for (final String lib in unit.libraries) {
buffer.write('
-
$lib
\
n
');
}
}
usedIds.add(unit.id);
}
goldenFile.writeAsStringSync(buffer.toString(), flush: true);
}
/// Deletes all files inside of the validator'
s
output
directory
.
void
clearOutputDir
()
{
final
Directory
dir
=
env
.
projectDir
.
childDirectory
(
'build'
).
childDirectory
(
kDeferredComponentsTempDirectory
);
ErrorHandlingFileSystem
.
deleteIfExists
(
dir
,
recursive:
true
);
}
/// Handles the results of all executed checks by calling [displayResults] and
/// [attemptToolExit].
///
/// This should be called after all desired checks and tasks are executed.
void
handleResults
()
{
displayResults
();
attemptToolExit
();
}
static
const
String
_thickDivider
=
'================================================================================='
;
static
const
String
_thinDivider
=
'---------------------------------------------------------------------------------'
;
/// Displays the results of this validator's executed checks and tasks in a
/// human readable format.
///
/// All checks that are desired should be run before calling this method.
void
displayResults
()
{
if
(
changesNeeded
)
{
env
.
logger
.
printStatus
(
_thickDivider
);
env
.
logger
.
printStatus
(
_title
,
indent:
(
_thickDivider
.
length
-
_title
.
length
)
~/
2
,
emphasis:
true
);
env
.
logger
.
printStatus
(
_thickDivider
);
// Log any file reading/existence errors.
if
(
_invalidFiles
.
isNotEmpty
)
{
env
.
logger
.
printStatus
(
'Errors checking the following files:
\n
'
,
emphasis:
true
);
for
(
final
String
key
in
_invalidFiles
.
keys
)
{
env
.
logger
.
printStatus
(
' -
$key
:
${_invalidFiles[key]}
\n
'
);
}
}
// Log diff file contents, with color highlighting
if
(
_diffLines
!=
null
&&
_diffLines
.
isNotEmpty
)
{
env
.
logger
.
printStatus
(
'Diff between `android` and expected files:'
,
emphasis:
true
);
env
.
logger
.
printStatus
(
''
);
for
(
final
String
line
in
_diffLines
)
{
// We only care about diffs in files that have
// counterparts.
if
(
line
.
startsWith
(
'Only in android'
))
{
continue
;
}
TerminalColor
color
=
TerminalColor
.
grey
;
if
(
line
.
startsWith
(
'+'
))
{
color
=
TerminalColor
.
green
;
}
else
if
(
line
.
startsWith
(
'-'
))
{
color
=
TerminalColor
.
red
;
}
env
.
logger
.
printStatus
(
line
,
color:
color
);
}
env
.
logger
.
printStatus
(
''
);
}
// Log any newly generated and modified files.
if
(
_generatedFiles
.
isNotEmpty
)
{
env
.
logger
.
printStatus
(
'Newly generated android files:'
,
emphasis:
true
);
for
(
final
String
filePath
in
_generatedFiles
)
{
final
String
shortenedPath
=
filePath
.
substring
(
env
.
projectDir
.
parent
.
path
.
length
+
1
);
env
.
logger
.
printStatus
(
' -
$shortenedPath
'
,
color:
TerminalColor
.
grey
);
}
env
.
logger
.
printStatus
(
''
);
}
if
(
_modifiedFiles
.
isNotEmpty
)
{
env
.
logger
.
printStatus
(
'Modified android files:'
,
emphasis:
true
);
for
(
final
String
filePath
in
_modifiedFiles
)
{
final
String
shortenedPath
=
filePath
.
substring
(
env
.
projectDir
.
parent
.
path
.
length
+
1
);
env
.
logger
.
printStatus
(
' -
$shortenedPath
'
,
color:
TerminalColor
.
grey
);
}
env
.
logger
.
printStatus
(
''
);
}
if
(
_generatedFiles
.
isNotEmpty
||
_modifiedFiles
.
isNotEmpty
)
{
env
.
logger
.
printStatus
(
'''
The above files have been placed into `build/
$kDeferredComponentsTempDirectory
`,
a temporary directory. The files should be reviewed and moved into the project'
s
`
android
`
directory
.
''');
if (_diffLines != null && _diffLines.isNotEmpty && !globals.platform.isWindows) {
env.logger.printStatus(r'''
The
recommended
changes
can
be
quickly
applied
by
running:
$
patch
-
p0
<
build
/
setup_deferred_components
.
diff
''');
}
env.logger.printStatus('
$_thinDivider
\
n
');
}
// Log loading unit golden changes, if any.
if (_goldenComparisonResults != null) {
if ((_goldenComparisonResults['
new
'] as List<LoadingUnit>).isNotEmpty) {
env.logger.printStatus('
New
loading
units
were
found:
', emphasis: true);
for (final LoadingUnit unit in _goldenComparisonResults['
new
'] as List<LoadingUnit>) {
env.logger.printStatus(unit.toString(), color: TerminalColor.grey, indent: 2);
}
env.logger.printStatus('');
}
if ((_goldenComparisonResults['
missing
'] as Set<LoadingUnit>).isNotEmpty) {
env.logger.printStatus('
Previously
existing
loading
units
no
longer
exist:
', emphasis: true);
for (final LoadingUnit unit in _goldenComparisonResults['
missing
'] as Set<LoadingUnit>) {
env.logger.printStatus(unit.toString(), color: TerminalColor.grey, indent: 2);
}
env.logger.printStatus('');
}
if (_goldenComparisonResults['
match
'] as bool) {
env.logger.printStatus('
No
change
in
generated
loading
units
.
\
n
');
} else {
env.logger.printStatus('''
It
is
recommended
to
verify
that
the
changed
loading
units
are
expected
and
to
update
the
`
deferred
-
components
`
section
in
`
pubspec
.
yaml
`
to
incorporate
any
changes
.
The
full
list
of
generated
loading
units
can
be
referenced
in
the
$kDeferredComponentsGoldenFileName
file
located
alongside
pubspec
.
yaml
.
This
loading
unit
check
will
not
fail
again
on
the
next
build
attempt
if
no
additional
changes
to
the
loading
units
are
detected
.
$_thinDivider
\
n
''');
}
}
// TODO(garyq): Add link to web tutorial/guide once it is written.
env.logger.printStatus('''
Setup
verification
can
be
skipped
by
passing
the
`
--
no
-
verify
-
deferred
-
components
`
flag
,
however
,
doing
so
may
put
your
app
at
risk
of
not
functioning
even
if
the
build
is
successful
.
$_thickDivider
''');
return;
}
env.logger.printStatus('
$_title
passed
.
');
}
void attemptToolExit() {
if (exitOnFail && changesNeeded) {
throwToolExit('
Setup
for
deferred
components
incomplete
.
See
recommended
actions
.
', exitCode: 1);
}
}
}
// Handles a single deferred component'
s
android
dynamic
feature
module
// directory.
class
_DeferredComponentAndroidFiles
{
_DeferredComponentAndroidFiles
({
@required
this
.
name
,
@required
this
.
env
,
Directory
templatesDir
,
})
:
_templatesDir
=
templatesDir
;
// The name of the deferred component.
final
String
name
;
final
Environment
env
;
final
Directory
_templatesDir
;
Directory
get
androidDir
=>
env
.
projectDir
.
childDirectory
(
'android'
);
Directory
get
componentDir
=>
androidDir
.
childDirectory
(
name
);
File
get
androidManifestFile
=>
componentDir
.
childDirectory
(
'src'
).
childDirectory
(
'main'
).
childFile
(
'AndroidManifest.xml'
);
File
get
buildGradleFile
=>
componentDir
.
childFile
(
'build.gradle'
);
// True when AndroidManifest.xml and build.gradle exist for the android dynamic feature.
bool
verifyFilesExist
()
{
return
androidManifestFile
.
existsSync
()
&&
buildGradleFile
.
existsSync
();
}
// Generates any missing basic files for the dynamic feature into a temporary directory.
Future
<
Map
<
String
,
List
<
File
>>>
generateFiles
({
Directory
alternateAndroidDir
,
bool
clearAlternateOutputDir
=
false
})
async
{
final
Directory
outputDir
=
alternateAndroidDir
?.
childDirectory
(
name
)
??
componentDir
;
if
(
clearAlternateOutputDir
&&
alternateAndroidDir
!=
null
)
{
ErrorHandlingFileSystem
.
deleteIfExists
(
outputDir
);
}
final
List
<
File
>
inputs
=
<
File
>[];
inputs
.
add
(
androidManifestFile
);
inputs
.
add
(
buildGradleFile
);
final
Map
<
String
,
List
<
File
>>
results
=
<
String
,
List
<
File
>>{
'inputs'
:
inputs
};
results
[
'outputs'
]
=
await
_setupComponentFiles
(
outputDir
);
return
results
;
}
// generates default build.gradle and AndroidManifest.xml for the deferred component.
Future
<
List
<
File
>>
_setupComponentFiles
(
Directory
outputDir
)
async
{
Template
template
;
if
(
_templatesDir
!=
null
)
{
final
Directory
templateComponentDir
=
_templatesDir
.
childDirectory
(
'module
${env.fileSystem.path.separator}
android
${env.fileSystem.path.separator}
deferred_component'
);
template
=
Template
(
templateComponentDir
,
templateComponentDir
,
_templatesDir
,
fileSystem:
env
.
fileSystem
,
templateManifest:
null
,
logger:
env
.
logger
,
templateRenderer:
globals
.
templateRenderer
,
);
}
else
{
template
=
await
Template
.
fromName
(
'module
${env.fileSystem.path.separator}
android
${env.fileSystem.path.separator}
deferred_component'
,
fileSystem:
env
.
fileSystem
,
templateManifest:
null
,
logger:
env
.
logger
,
templateRenderer:
globals
.
templateRenderer
,
);
}
final
Map
<
String
,
dynamic
>
context
=
<
String
,
dynamic
>{
'androidIdentifier'
:
FlutterProject
.
current
().
manifest
.
androidPackage
??
'com.example.
${FlutterProject.current().manifest.appName}
'
,
'componentName'
:
name
,
};
template
.
render
(
outputDir
,
context
);
final
List
<
File
>
generatedFiles
=
<
File
>[];
final
File
tempBuildGradle
=
outputDir
.
childFile
(
'build.gradle'
);
if
(!
buildGradleFile
.
existsSync
())
{
generatedFiles
.
add
(
tempBuildGradle
);
}
else
{
ErrorHandlingFileSystem
.
deleteIfExists
(
tempBuildGradle
);
}
final
File
tempAndroidManifest
=
outputDir
.
childDirectory
(
'src'
)
.
childDirectory
(
'main'
)
.
childFile
(
'AndroidManifest.xml'
);
if
(!
androidManifestFile
.
existsSync
())
{
generatedFiles
.
add
(
tempAndroidManifest
);
}
else
{
ErrorHandlingFileSystem
.
deleteIfExists
(
tempAndroidManifest
);
}
return
generatedFiles
;
}
}
packages/flutter_tools/lib/src/build_system/targets/common.dart
View file @
ba5cc2cb
...
@@ -45,6 +45,10 @@ const String kExtraFrontEndOptions = 'ExtraFrontEndOptions';
...
@@ -45,6 +45,10 @@ const String kExtraFrontEndOptions = 'ExtraFrontEndOptions';
/// This is expected to be a comma separated list of strings.
/// This is expected to be a comma separated list of strings.
const
String
kExtraGenSnapshotOptions
=
'ExtraGenSnapshotOptions'
;
const
String
kExtraGenSnapshotOptions
=
'ExtraGenSnapshotOptions'
;
/// Whether the app should run gen_snapshot as a split aot build for deferred
/// components.
const
String
kSplitAot
=
'SplitAot'
;
/// Whether to strip source code information out of release builds and where to save it.
/// Whether to strip source code information out of release builds and where to save it.
const
String
kSplitDebugInfo
=
'SplitDebugInfo'
;
const
String
kSplitDebugInfo
=
'SplitDebugInfo'
;
...
...
packages/flutter_tools/test/general.shard/android/deferred_components_setup_validator_test.dart
0 → 100644
View file @
ba5cc2cb
// 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.
// @dart = 2.8
import
'package:file/memory.dart'
;
import
'package:flutter_tools/src/android/deferred_components_setup_validator.dart'
;
import
'package:flutter_tools/src/base/deferred_component.dart'
;
import
'package:flutter_tools/src/base/file_system.dart'
;
import
'package:flutter_tools/src/base/logger.dart'
;
import
'package:flutter_tools/src/build_system/build_system.dart'
;
import
'package:flutter_tools/src/build_system/targets/common.dart'
;
import
'package:flutter_tools/src/globals.dart'
as
globals
;
import
'../../src/common.dart'
;
import
'../../src/context.dart'
;
void
main
(
)
{
FileSystem
fileSystem
;
BufferLogger
logger
;
Environment
env
;
Environment
createEnvironment
()
{
final
Map
<
String
,
String
>
defines
=
<
String
,
String
>{
kSplitAot:
'true'
};
final
Environment
result
=
Environment
(
outputDir:
fileSystem
.
directory
(
'/output'
),
buildDir:
fileSystem
.
directory
(
'/build'
),
projectDir:
fileSystem
.
directory
(
'/project'
),
defines:
defines
,
inputs:
<
String
,
String
>{},
cacheDir:
fileSystem
.
directory
(
'/cache'
),
flutterRootDir:
fileSystem
.
directory
(
'/flutter_root'
),
artifacts:
globals
.
artifacts
,
fileSystem:
fileSystem
,
logger:
logger
,
processManager:
globals
.
processManager
,
engineVersion:
'invalidEngineVersion'
,
);
return
result
;
}
setUp
(()
{
fileSystem
=
MemoryFileSystem
.
test
();
logger
=
BufferLogger
.
test
();
env
=
createEnvironment
();
});
testWithoutContext
(
'No checks passes'
,
()
async
{
final
DeferredComponentsSetupValidator
validator
=
DeferredComponentsSetupValidator
(
env
,
exitOnFail:
false
,
title:
'test check'
,
);
validator
.
displayResults
();
validator
.
attemptToolExit
();
expect
(
logger
.
statusText
,
'test check passed.
\n
'
);
});
testWithoutContext
(
'clearTempDir passes'
,
()
async
{
final
DeferredComponentsSetupValidator
validator
=
DeferredComponentsSetupValidator
(
env
,
exitOnFail:
false
,
title:
'test check'
,
);
validator
.
displayResults
();
validator
.
attemptToolExit
();
expect
(
logger
.
statusText
,
'test check passed.
\n
'
);
});
testWithoutContext
(
'writeGolden passes'
,
()
async
{
final
File
goldenFile
=
env
.
projectDir
.
childFile
(
DeferredComponentsSetupValidator
.
kDeferredComponentsGoldenFileName
);
if
(
goldenFile
.
existsSync
())
{
goldenFile
.
deleteSync
();
}
final
DeferredComponentsSetupValidator
validator
=
DeferredComponentsSetupValidator
(
env
,
exitOnFail:
false
,
title:
'test check'
,
);
validator
.
writeGolden
(
<
LoadingUnit
>[
LoadingUnit
(
id:
2
,
libraries:
<
String
>[
'lib1'
]),
LoadingUnit
(
id:
3
,
libraries:
<
String
>[
'lib2'
,
'lib3'
]),
],
);
validator
.
displayResults
();
validator
.
attemptToolExit
();
expect
(
logger
.
statusText
,
'test check passed.
\n
'
);
final
File
expectedFile
=
env
.
projectDir
.
childFile
(
'deferred_components_golden.yaml'
);
expect
(
expectedFile
.
existsSync
(),
true
);
const
String
expectedContents
=
'''
loading-units:
- id: 2
libraries:
- lib1
- id: 3
libraries:
- lib2
- lib3
'''
;
expect
(
expectedFile
.
readAsStringSync
().
contains
(
expectedContents
),
true
);
});
testWithoutContext
(
'loadingUnitGolden identical passes'
,
()
async
{
final
DeferredComponentsSetupValidator
validator
=
DeferredComponentsSetupValidator
(
env
,
exitOnFail:
false
,
title:
'test check'
,
);
final
File
goldenFile
=
env
.
projectDir
.
childFile
(
DeferredComponentsSetupValidator
.
kDeferredComponentsGoldenFileName
);
if
(
goldenFile
.
existsSync
())
{
goldenFile
.
deleteSync
();
}
goldenFile
.
createSync
(
recursive:
true
);
goldenFile
.
writeAsStringSync
(
'''
loading-units:
- id: 2
libraries:
- lib1
- id: 3
libraries:
- lib2
- lib3
'''
,
flush:
true
,
mode:
FileMode
.
append
);
validator
.
checkAgainstLoadingUnitGolden
(
<
LoadingUnit
>[
LoadingUnit
(
id:
2
,
libraries:
<
String
>[
'lib1'
]),
LoadingUnit
(
id:
3
,
libraries:
<
String
>[
'lib2'
,
'lib3'
]),
]
);
validator
.
displayResults
();
validator
.
attemptToolExit
();
expect
(
logger
.
statusText
,
'test check passed.
\n
'
);
});
testWithoutContext
(
'loadingUnitGolden finds new loading units'
,
()
async
{
final
DeferredComponentsSetupValidator
validator
=
DeferredComponentsSetupValidator
(
env
,
exitOnFail:
false
,
title:
'test check'
,
);
final
File
goldenFile
=
env
.
projectDir
.
childFile
(
DeferredComponentsSetupValidator
.
kDeferredComponentsGoldenFileName
);
if
(
goldenFile
.
existsSync
())
{
goldenFile
.
deleteSync
();
}
goldenFile
.
createSync
(
recursive:
true
);
goldenFile
.
writeAsStringSync
(
'''
loading-units:
- id: 3
libraries:
- lib2
- lib3
'''
,
flush:
true
,
mode:
FileMode
.
append
);
validator
.
checkAgainstLoadingUnitGolden
(
<
LoadingUnit
>[
LoadingUnit
(
id:
2
,
libraries:
<
String
>[
'lib1'
]),
LoadingUnit
(
id:
3
,
libraries:
<
String
>[
'lib2'
,
'lib3'
]),
],
);
validator
.
displayResults
();
validator
.
attemptToolExit
();
expect
(
logger
.
statusText
.
contains
(
'New loading units were found:
\n\n
LoadingUnit 2
\n
Libraries:
\n
- lib1
\n
'
),
true
);
});
testWithoutContext
(
'loadingUnitGolden finds missing loading units'
,
()
async
{
final
DeferredComponentsSetupValidator
validator
=
DeferredComponentsSetupValidator
(
env
,
exitOnFail:
false
,
title:
'test check'
,
);
final
File
goldenFile
=
env
.
projectDir
.
childFile
(
DeferredComponentsSetupValidator
.
kDeferredComponentsGoldenFileName
);
if
(
goldenFile
.
existsSync
())
{
goldenFile
.
deleteSync
();
}
goldenFile
.
createSync
(
recursive:
true
);
goldenFile
.
writeAsStringSync
(
'''
loading-units:
- id: 2
libraries:
- lib1
- id: 3
libraries:
- lib2
- lib3
'''
,
flush:
true
,
mode:
FileMode
.
append
);
validator
.
checkAgainstLoadingUnitGolden
(
<
LoadingUnit
>[
LoadingUnit
(
id:
3
,
libraries:
<
String
>[
'lib2'
,
'lib3'
]),
],
);
validator
.
displayResults
();
validator
.
attemptToolExit
();
expect
(
logger
.
statusText
.
contains
(
'Previously existing loading units no longer exist:
\n\n
LoadingUnit 2
\n
Libraries:
\n
- lib1
\n
'
),
true
);
});
testWithoutContext
(
'missing golden file counts as all new loading units'
,
()
async
{
final
DeferredComponentsSetupValidator
validator
=
DeferredComponentsSetupValidator
(
env
,
exitOnFail:
false
,
title:
'test check'
,
);
final
File
goldenFile
=
env
.
projectDir
.
childFile
(
DeferredComponentsSetupValidator
.
kDeferredComponentsGoldenFileName
);
if
(
goldenFile
.
existsSync
())
{
goldenFile
.
deleteSync
();
}
validator
.
checkAgainstLoadingUnitGolden
(
<
LoadingUnit
>[
LoadingUnit
(
id:
2
,
libraries:
<
String
>[
'lib1'
]),
],
);
validator
.
displayResults
();
validator
.
attemptToolExit
();
expect
(
logger
.
statusText
.
contains
(
'New loading units were found:
\n\n
LoadingUnit 2
\n
Libraries:
\n
- lib1
\n
'
),
true
);
});
testWithoutContext
(
'loadingUnitGolden validator detects malformed file: missing main entry'
,
()
async
{
final
DeferredComponentsSetupValidator
validator
=
DeferredComponentsSetupValidator
(
env
,
exitOnFail:
false
,
title:
'test check'
,
);
final
File
goldenFile
=
env
.
projectDir
.
childFile
(
DeferredComponentsSetupValidator
.
kDeferredComponentsGoldenFileName
);
if
(
goldenFile
.
existsSync
())
{
goldenFile
.
deleteSync
();
}
goldenFile
.
createSync
(
recursive:
true
);
goldenFile
.
writeAsStringSync
(
'''
loading-units-spelled-wrong:
- id: 2
libraries:
- lib1
- id: 3
libraries:
- lib2
- lib3
'''
,
flush:
true
,
mode:
FileMode
.
append
);
validator
.
checkAgainstLoadingUnitGolden
(
<
LoadingUnit
>[
LoadingUnit
(
id:
3
,
libraries:
<
String
>[
'lib2'
,
'lib3'
]),
],
);
validator
.
displayResults
();
validator
.
attemptToolExit
();
expect
(
logger
.
statusText
.
contains
(
'Errors checking the following files:'
),
true
);
expect
(
logger
.
statusText
.
contains
(
'Invalid golden yaml file,
\'
loading-units
\'
entry did not exist.'
),
true
);
expect
(
logger
.
statusText
.
contains
(
'Previously existing loading units no longer exist:
\n\n
LoadingUnit 2
\n
Libraries:
\n
- lib1
\n
'
),
false
);
});
testWithoutContext
(
'loadingUnitGolden validator detects malformed file: not a list'
,
()
async
{
final
DeferredComponentsSetupValidator
validator
=
DeferredComponentsSetupValidator
(
env
,
exitOnFail:
false
,
title:
'test check'
,
);
final
File
goldenFile
=
env
.
projectDir
.
childFile
(
DeferredComponentsSetupValidator
.
kDeferredComponentsGoldenFileName
);
if
(
goldenFile
.
existsSync
())
{
goldenFile
.
deleteSync
();
}
goldenFile
.
createSync
(
recursive:
true
);
goldenFile
.
writeAsStringSync
(
'''
loading-units: hello
'''
,
flush:
true
,
mode:
FileMode
.
append
);
validator
.
checkAgainstLoadingUnitGolden
(
<
LoadingUnit
>[
LoadingUnit
(
id:
3
,
libraries:
<
String
>[
'lib2'
,
'lib3'
]),
],
);
validator
.
displayResults
();
validator
.
attemptToolExit
();
expect
(
logger
.
statusText
.
contains
(
'Errors checking the following files:'
),
true
);
expect
(
logger
.
statusText
.
contains
(
'Invalid golden yaml file,
\'
loading-units
\'
is not a list.'
),
true
);
});
testWithoutContext
(
'loadingUnitGolden validator detects malformed file: not a list'
,
()
async
{
final
DeferredComponentsSetupValidator
validator
=
DeferredComponentsSetupValidator
(
env
,
exitOnFail:
false
,
title:
'test check'
,
);
final
File
goldenFile
=
env
.
projectDir
.
childFile
(
DeferredComponentsSetupValidator
.
kDeferredComponentsGoldenFileName
);
if
(
goldenFile
.
existsSync
())
{
goldenFile
.
deleteSync
();
}
goldenFile
.
createSync
(
recursive:
true
);
goldenFile
.
writeAsStringSync
(
'''
loading-units:
- 2
- 3
'''
,
flush:
true
,
mode:
FileMode
.
append
);
validator
.
checkAgainstLoadingUnitGolden
(
<
LoadingUnit
>[
LoadingUnit
(
id:
3
,
libraries:
<
String
>[
'lib2'
,
'lib3'
]),
],
);
validator
.
displayResults
();
validator
.
attemptToolExit
();
expect
(
logger
.
statusText
.
contains
(
'Errors checking the following files:'
),
true
);
expect
(
logger
.
statusText
.
contains
(
'Invalid golden yaml file,
\'
loading-units
\'
is not a list of maps.'
),
true
);
});
testWithoutContext
(
'loadingUnitGolden validator detects malformed file: missing id'
,
()
async
{
final
DeferredComponentsSetupValidator
validator
=
DeferredComponentsSetupValidator
(
env
,
exitOnFail:
false
,
title:
'test check'
,
);
final
File
goldenFile
=
env
.
projectDir
.
childFile
(
DeferredComponentsSetupValidator
.
kDeferredComponentsGoldenFileName
);
if
(
goldenFile
.
existsSync
())
{
goldenFile
.
deleteSync
();
}
goldenFile
.
createSync
(
recursive:
true
);
goldenFile
.
writeAsStringSync
(
'''
loading-units:
- id: 2
libraries:
- lib1
- libraries:
- lib2
- lib3
'''
,
flush:
true
,
mode:
FileMode
.
append
);
validator
.
checkAgainstLoadingUnitGolden
(
<
LoadingUnit
>[
LoadingUnit
(
id:
3
,
libraries:
<
String
>[
'lib2'
,
'lib3'
]),
],
);
validator
.
displayResults
();
validator
.
attemptToolExit
();
expect
(
logger
.
statusText
.
contains
(
'Errors checking the following files:'
),
true
);
expect
(
logger
.
statusText
.
contains
(
'Invalid golden yaml file, all loading units must have an
\'
id
\'
'
),
true
);
});
testWithoutContext
(
'loadingUnitGolden validator detects malformed file: libraries is list'
,
()
async
{
final
DeferredComponentsSetupValidator
validator
=
DeferredComponentsSetupValidator
(
env
,
exitOnFail:
false
,
title:
'test check'
,
);
final
File
goldenFile
=
env
.
projectDir
.
childFile
(
DeferredComponentsSetupValidator
.
kDeferredComponentsGoldenFileName
);
if
(
goldenFile
.
existsSync
())
{
goldenFile
.
deleteSync
();
}
goldenFile
.
createSync
(
recursive:
true
);
goldenFile
.
writeAsStringSync
(
'''
loading-units:
- id: 2
libraries:
- lib1
- id: 3
libraries: hello
'''
,
flush:
true
,
mode:
FileMode
.
append
);
validator
.
checkAgainstLoadingUnitGolden
(
<
LoadingUnit
>[
LoadingUnit
(
id:
3
,
libraries:
<
String
>[
'lib2'
,
'lib3'
]),
],
);
validator
.
displayResults
();
validator
.
attemptToolExit
();
expect
(
logger
.
statusText
.
contains
(
'Errors checking the following files:'
),
true
);
expect
(
logger
.
statusText
.
contains
(
'Invalid golden yaml file,
\'
libraries
\'
is not a list.'
),
true
);
});
testWithoutContext
(
'loadingUnitGolden validator detects malformed file: libraries is list of strings'
,
()
async
{
final
DeferredComponentsSetupValidator
validator
=
DeferredComponentsSetupValidator
(
env
,
exitOnFail:
false
,
title:
'test check'
,
);
final
File
goldenFile
=
env
.
projectDir
.
childFile
(
DeferredComponentsSetupValidator
.
kDeferredComponentsGoldenFileName
);
if
(
goldenFile
.
existsSync
())
{
goldenFile
.
deleteSync
();
}
goldenFile
.
createSync
(
recursive:
true
);
goldenFile
.
writeAsStringSync
(
'''
loading-units:
- id: 2
libraries:
- lib1
- id: 3
libraries:
- blah: hello
blah2: hello2
'''
,
flush:
true
,
mode:
FileMode
.
append
);
validator
.
checkAgainstLoadingUnitGolden
(
<
LoadingUnit
>[
LoadingUnit
(
id:
3
,
libraries:
<
String
>[
'lib2'
,
'lib3'
]),
],
);
validator
.
displayResults
();
validator
.
attemptToolExit
();
expect
(
logger
.
statusText
.
contains
(
'Errors checking the following files:'
),
true
);
expect
(
logger
.
statusText
.
contains
(
'Invalid golden yaml file,
\'
libraries
\'
is not a list of strings.'
),
true
);
});
testWithoutContext
(
'loadingUnitGolden validator detects malformed file: empty libraries allowed'
,
()
async
{
final
DeferredComponentsSetupValidator
validator
=
DeferredComponentsSetupValidator
(
env
,
exitOnFail:
false
,
title:
'test check'
,
);
final
File
goldenFile
=
env
.
projectDir
.
childFile
(
DeferredComponentsSetupValidator
.
kDeferredComponentsGoldenFileName
);
if
(
goldenFile
.
existsSync
())
{
goldenFile
.
deleteSync
();
}
goldenFile
.
createSync
(
recursive:
true
);
goldenFile
.
writeAsStringSync
(
'''
loading-units:
- id: 2
libraries:
- lib1
- id: 3
libraries:
'''
,
flush:
true
,
mode:
FileMode
.
append
);
validator
.
checkAgainstLoadingUnitGolden
(
<
LoadingUnit
>[
LoadingUnit
(
id:
3
,
libraries:
<
String
>[
'lib2'
,
'lib3'
]),
],
);
validator
.
displayResults
();
validator
.
attemptToolExit
();
expect
(
logger
.
statusText
.
contains
(
'Errors checking the following files:'
),
false
);
});
testUsingContext
(
'androidComponentSetup build.gradle does not exist'
,
()
async
{
final
Directory
templatesDir
=
env
.
flutterRootDir
.
childDirectory
(
'templates'
).
childDirectory
(
'deferred_component'
);
final
File
buildGradleTemplate
=
templatesDir
.
childFile
(
'build.gradle.tmpl'
);
final
File
androidManifestTemplate
=
templatesDir
.
childDirectory
(
'src'
).
childDirectory
(
'main'
).
childFile
(
'AndroidManifest.xml.tmpl'
);
if
(
templatesDir
.
existsSync
())
{
templatesDir
.
deleteSync
(
recursive:
true
);
}
buildGradleTemplate
.
createSync
(
recursive:
true
);
androidManifestTemplate
.
createSync
(
recursive:
true
);
buildGradleTemplate
.
writeAsStringSync
(
'fake build.gradle template {{componentName}}'
,
flush:
true
,
mode:
FileMode
.
append
);
androidManifestTemplate
.
writeAsStringSync
(
'fake AndroidManigest.xml template {{componentName}}'
,
flush:
true
,
mode:
FileMode
.
append
);
final
DeferredComponentsSetupValidator
validator
=
DeferredComponentsSetupValidator
(
env
,
exitOnFail:
false
,
title:
'test check'
,
templatesDir:
templatesDir
,
);
final
Directory
componentDir
=
env
.
projectDir
.
childDirectory
(
'android'
).
childDirectory
(
'component1'
);
final
File
file
=
componentDir
.
childDirectory
(
'src'
).
childDirectory
(
'main'
).
childFile
(
'AndroidManifest.xml'
);
if
(
file
.
existsSync
())
{
file
.
deleteSync
();
}
file
.
createSync
(
recursive:
true
);
await
validator
.
checkAndroidDynamicFeature
(
<
DeferredComponent
>[
DeferredComponent
(
name:
'component1'
),
],
);
validator
.
displayResults
();
validator
.
attemptToolExit
();
file
.
deleteSync
();
expect
(
logger
.
statusText
.
contains
(
'Newly generated android files:
\n
'
),
true
);
expect
(
logger
.
statusText
.
contains
(
'build/
${DeferredComponentsSetupValidator.kDeferredComponentsTempDirectory}
/component1/build.gradle
\n
'
),
true
);
});
testUsingContext
(
'androidComponentSetup AndroidManifest.xml does not exist'
,
()
async
{
final
Directory
templatesDir
=
env
.
flutterRootDir
.
childDirectory
(
'templates'
).
childDirectory
(
'deferred_component'
);
final
File
buildGradleTemplate
=
templatesDir
.
childFile
(
'build.gradle.tmpl'
);
final
File
androidManifestTemplate
=
templatesDir
.
childDirectory
(
'src'
).
childDirectory
(
'main'
).
childFile
(
'AndroidManifest.xml.tmpl'
);
if
(
templatesDir
.
existsSync
())
{
templatesDir
.
deleteSync
(
recursive:
true
);
}
buildGradleTemplate
.
createSync
(
recursive:
true
);
androidManifestTemplate
.
createSync
(
recursive:
true
);
buildGradleTemplate
.
writeAsStringSync
(
'fake build.gradle template {{componentName}}'
,
flush:
true
,
mode:
FileMode
.
append
);
androidManifestTemplate
.
writeAsStringSync
(
'fake AndroidManigest.xml template {{componentName}}'
,
flush:
true
,
mode:
FileMode
.
append
);
final
DeferredComponentsSetupValidator
validator
=
DeferredComponentsSetupValidator
(
env
,
exitOnFail:
false
,
title:
'test check'
,
templatesDir:
templatesDir
,
);
final
Directory
componentDir
=
env
.
projectDir
.
childDirectory
(
'android'
).
childDirectory
(
'component1'
);
final
File
file
=
componentDir
.
childFile
(
'build.gradle'
);
if
(
file
.
existsSync
())
{
file
.
deleteSync
();
}
file
.
createSync
(
recursive:
true
);
await
validator
.
checkAndroidDynamicFeature
(
<
DeferredComponent
>[
DeferredComponent
(
name:
'component1'
),
],
);
validator
.
displayResults
();
validator
.
attemptToolExit
();
file
.
deleteSync
();
expect
(
logger
.
statusText
.
contains
(
'Newly generated android files:
\n
'
),
true
);
expect
(
logger
.
statusText
.
contains
(
'build/
${DeferredComponentsSetupValidator.kDeferredComponentsTempDirectory}
/component1/src/main/AndroidManifest.xml
\n
'
),
true
);
});
testUsingContext
(
'androidComponentSetup all files exist passes'
,
()
async
{
final
Directory
templatesDir
=
env
.
flutterRootDir
.
childDirectory
(
'templates'
).
childDirectory
(
'deferred_component'
);
final
File
buildGradleTemplate
=
templatesDir
.
childFile
(
'build.gradle.tmpl'
);
final
File
androidManifestTemplate
=
templatesDir
.
childDirectory
(
'src'
).
childDirectory
(
'main'
).
childFile
(
'AndroidManifest.xml.tmpl'
);
if
(
templatesDir
.
existsSync
())
{
templatesDir
.
deleteSync
(
recursive:
true
);
}
buildGradleTemplate
.
createSync
(
recursive:
true
);
androidManifestTemplate
.
createSync
(
recursive:
true
);
buildGradleTemplate
.
writeAsStringSync
(
'fake build.gradle template {{componentName}}'
,
flush:
true
,
mode:
FileMode
.
append
);
androidManifestTemplate
.
writeAsStringSync
(
'fake AndroidManigest.xml template {{componentName}}'
,
flush:
true
,
mode:
FileMode
.
append
);
final
DeferredComponentsSetupValidator
validator
=
DeferredComponentsSetupValidator
(
env
,
exitOnFail:
false
,
title:
'test check'
,
templatesDir:
templatesDir
,
);
final
Directory
componentDir
=
env
.
projectDir
.
childDirectory
(
'android'
).
childDirectory
(
'component1'
);
final
File
buildGradle
=
componentDir
.
childFile
(
'build.gradle'
);
if
(
buildGradle
.
existsSync
())
{
buildGradle
.
deleteSync
();
}
buildGradle
.
createSync
(
recursive:
true
);
final
File
manifest
=
componentDir
.
childDirectory
(
'src'
).
childDirectory
(
'main'
).
childFile
(
'AndroidManifest.xml'
);
if
(
manifest
.
existsSync
())
{
manifest
.
deleteSync
();
}
manifest
.
createSync
(
recursive:
true
);
await
validator
.
checkAndroidDynamicFeature
(
<
DeferredComponent
>[
DeferredComponent
(
name:
'component1'
),
],
);
validator
.
displayResults
();
validator
.
attemptToolExit
();
manifest
.
deleteSync
();
buildGradle
.
deleteSync
();
expect
(
logger
.
statusText
,
'test check passed.
\n
'
);
});
testWithoutContext
(
'androidStringMapping creates new file'
,
()
async
{
final
DeferredComponentsSetupValidator
validator
=
DeferredComponentsSetupValidator
(
env
,
exitOnFail:
false
,
title:
'test check'
,
);
final
Directory
baseModuleDir
=
env
.
projectDir
.
childDirectory
(
'android'
).
childDirectory
(
'app'
);
final
File
stringRes
=
baseModuleDir
.
childDirectory
(
'src'
).
childDirectory
(
'main'
).
childDirectory
(
'res'
).
childDirectory
(
'values'
).
childFile
(
'strings.xml'
);
if
(
stringRes
.
existsSync
())
{
stringRes
.
deleteSync
();
}
final
File
manifest
=
baseModuleDir
.
childDirectory
(
'src'
).
childDirectory
(
'main'
).
childFile
(
'AndroidManifest.xml'
);
if
(
manifest
.
existsSync
())
{
manifest
.
deleteSync
();
}
manifest
.
createSync
(
recursive:
true
);
manifest
.
writeAsStringSync
(
'''
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.splitaot">
<application
android:name="io.flutter.app.FlutterPlayStoreSplitApplication"
android:label="splitaot"
android:extractNativeLibs="false">
<activity
android:name=".MainActivity"
android:launchMode="singleTop"
android:windowSoftInputMode="adjustResize">
</activity>
<!-- Don'
t
delete
the
meta
-
data
below
.
This
is
used
by
the
Flutter
tool
to
generate
GeneratedPluginRegistrant
.
java
-->
<
meta
-
data
android:
name
=
"flutterEmbedding"
android:
value
=
"2"
/>
<
meta
-
data
android:
name
=
"io.flutter.embedding.engine.deferredcomponents.DeferredComponentManager.loadingUnitMapping"
android:
value
=
"invalidmapping"
/>
</
application
>
</
manifest
>
''', flush: true, mode: FileMode.append);
validator.checkAppAndroidManifestComponentLoadingUnitMapping(
<DeferredComponent>[
DeferredComponent(name: '
component1
', libraries: <String>['
lib2
']),
DeferredComponent(name: '
component2
', libraries: <String>['
lib1
', '
lib4
']),
],
<LoadingUnit>[
LoadingUnit(id: 2, libraries: <String>['
lib1
']),
LoadingUnit(id: 3, libraries: <String>['
lib2
', '
lib3
']),
LoadingUnit(id: 4, libraries: <String>['
lib4
', '
lib5
']),
],
);
validator.checkAndroidResourcesStrings(
<DeferredComponent>[
DeferredComponent(name: '
component1
', libraries: <String>['
lib2
']),
DeferredComponent(name: '
component2
', libraries: <String>['
lib1
', '
lib4
']),
],
);
validator.displayResults();
validator.attemptToolExit();
expect(logger.statusText.contains('
Modified
android
files:
\
n
'), true);
expect(logger.statusText.contains('
Newly
generated
android
files:
\
n
'), true);
expect(logger.statusText.contains('
build
/
$
{
DeferredComponentsSetupValidator
.
kDeferredComponentsTempDirectory
}/
app
/
src
/
main
/
AndroidManifest
.
xml
\
n
'), true);
expect(logger.statusText.contains('
build
/
$
{
DeferredComponentsSetupValidator
.
kDeferredComponentsTempDirectory
}/
app
/
src
/
main
/
res
/
values
/
strings
.
xml
\
n
'), true);
final File stringsOutput = env.projectDir
.childDirectory('
build
')
.childDirectory(DeferredComponentsSetupValidator.kDeferredComponentsTempDirectory)
.childDirectory('
app
')
.childDirectory('
src
')
.childDirectory('
main
')
.childDirectory('
res
')
.childDirectory('
values
')
.childFile('
strings
.
xml
');
expect(stringsOutput.existsSync(), true);
expect(stringsOutput.readAsStringSync().contains('
<
string
name
=
"component1Name"
>
component1
</
string
>
'), true);
expect(stringsOutput.readAsStringSync().contains('
<
string
name
=
"component2Name"
>
component2
</
string
>
'), true);
final File manifestOutput = env.projectDir
.childDirectory('
build
')
.childDirectory(DeferredComponentsSetupValidator.kDeferredComponentsTempDirectory)
.childDirectory('
app
')
.childDirectory('
src
')
.childDirectory('
main
')
.childFile('
AndroidManifest
.
xml
');
expect(manifestOutput.existsSync(), true);
expect(manifestOutput.readAsStringSync().contains('
<
meta
-
data
android:
name
=
"io.flutter.embedding.engine.deferredcomponents.DeferredComponentManager.loadingUnitMapping"
android:
value
=
"3:component1,2:component2,4:component2"
/>
'), true);
expect(manifestOutput.readAsStringSync().contains('
android:
value
=
"invalidmapping"'), false);
expect(manifestOutput.readAsStringSync().contains('
<!--
Don
\
't delete the meta-data below.'
),
true
);
});
testWithoutContext
(
'androidStringMapping modifies strings file'
,
()
async
{
final
DeferredComponentsSetupValidator
validator
=
DeferredComponentsSetupValidator
(
env
,
exitOnFail:
false
,
title:
'test check'
,
);
final
Directory
baseModuleDir
=
env
.
projectDir
.
childDirectory
(
'android'
).
childDirectory
(
'app'
);
final
File
stringRes
=
baseModuleDir
.
childDirectory
(
'src'
).
childDirectory
(
'main'
).
childDirectory
(
'res'
).
childDirectory
(
'values'
).
childFile
(
'strings.xml'
);
if
(
stringRes
.
existsSync
())
{
stringRes
.
deleteSync
();
}
stringRes
.
createSync
(
recursive:
true
);
stringRes
.
writeAsStringSync
(
'''
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="component1Name">component1</string>
</resources>
'''
,
flush:
true
,
mode:
FileMode
.
append
);
final
File
manifest
=
baseModuleDir
.
childDirectory
(
'src'
).
childDirectory
(
'main'
).
childFile
(
'AndroidManifest.xml'
);
if
(
manifest
.
existsSync
())
{
manifest
.
deleteSync
();
}
manifest
.
createSync
(
recursive:
true
);
manifest
.
writeAsStringSync
(
'''
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.splitaot">
<application
android:name="io.flutter.app.FlutterPlayStoreSplitApplication"
android:label="splitaot"
android:extractNativeLibs="false">
<activity
android:name=".MainActivity"
android:launchMode="singleTop"
android:windowSoftInputMode="adjustResize">
</activity>
<!-- Don'
t
delete
the
meta
-
data
below
.
This
is
used
by
the
Flutter
tool
to
generate
GeneratedPluginRegistrant
.
java
-->
<
meta
-
data
android:
name
=
"flutterEmbedding"
android:
value
=
"2"
/>
<
meta
-
data
android:
name
=
"io.flutter.embedding.engine.deferredcomponents.DeferredComponentManager.loadingUnitMapping"
android:
value
=
"invalidmapping"
/>
</
application
>
</
manifest
>
''', flush: true, mode: FileMode.append);
validator.checkAppAndroidManifestComponentLoadingUnitMapping(
<DeferredComponent>[
DeferredComponent(name: '
component1
', libraries: <String>['
lib2
']),
DeferredComponent(name: '
component2
', libraries: <String>['
lib1
', '
lib4
']),
],
<LoadingUnit>[
LoadingUnit(id: 2, libraries: <String>['
lib1
']),
LoadingUnit(id: 3, libraries: <String>['
lib2
', '
lib3
']),
LoadingUnit(id: 4, libraries: <String>['
lib4
', '
lib5
']),
],
);
validator.checkAndroidResourcesStrings(
<DeferredComponent>[
DeferredComponent(name: '
component1
', libraries: <String>['
lib2
']),
DeferredComponent(name: '
component2
', libraries: <String>['
lib1
', '
lib4
']),
],
);
validator.displayResults();
validator.attemptToolExit();
expect(logger.statusText.contains('
Modified
android
files:
\
n
'), true);
expect(logger.statusText.contains('
build
/
$
{
DeferredComponentsSetupValidator
.
kDeferredComponentsTempDirectory
}/
app
/
src
/
main
/
AndroidManifest
.
xml
\
n
'), true);
expect(logger.statusText.contains('
build
/
$
{
DeferredComponentsSetupValidator
.
kDeferredComponentsTempDirectory
}/
app
/
src
/
main
/
res
/
values
/
strings
.
xml
\
n
'), true);
final File stringsOutput = env.projectDir
.childDirectory('
build
')
.childDirectory(DeferredComponentsSetupValidator.kDeferredComponentsTempDirectory)
.childDirectory('
app
')
.childDirectory('
src
')
.childDirectory('
main
')
.childDirectory('
res
')
.childDirectory('
values
')
.childFile('
strings
.
xml
');
expect(stringsOutput.existsSync(), true);
expect(stringsOutput.readAsStringSync().contains('
<
string
name
=
"component1Name"
>
component1
</
string
>
'), true);
expect(stringsOutput.readAsStringSync().contains('
<
string
name
=
"component2Name"
>
component2
</
string
>
'), true);
final File manifestOutput = env.projectDir
.childDirectory('
build
')
.childDirectory(DeferredComponentsSetupValidator.kDeferredComponentsTempDirectory)
.childDirectory('
app
')
.childDirectory('
src
')
.childDirectory('
main
')
.childFile('
AndroidManifest
.
xml
');
expect(manifestOutput.existsSync(), true);
expect(manifestOutput.readAsStringSync().contains('
<
meta
-
data
android:
name
=
"io.flutter.embedding.engine.deferredcomponents.DeferredComponentManager.loadingUnitMapping"
android:
value
=
"3:component1,2:component2,4:component2"
/>
'), true);
expect(manifestOutput.readAsStringSync().contains('
android:
value
=
"invalidmapping"'), false);
expect(manifestOutput.readAsStringSync().contains('
<!--
Don
\
't delete the meta-data below.'
),
true
);
});
testWithoutContext
(
'androidStringMapping adds mapping when no existing mapping'
,
()
async
{
final
DeferredComponentsSetupValidator
validator
=
DeferredComponentsSetupValidator
(
env
,
exitOnFail:
false
,
title:
'test check'
,
);
final
Directory
baseModuleDir
=
env
.
projectDir
.
childDirectory
(
'android'
).
childDirectory
(
'app'
);
final
File
stringRes
=
baseModuleDir
.
childDirectory
(
'src'
).
childDirectory
(
'main'
).
childDirectory
(
'res'
).
childDirectory
(
'values'
).
childFile
(
'strings.xml'
);
if
(
stringRes
.
existsSync
())
{
stringRes
.
deleteSync
();
}
stringRes
.
createSync
(
recursive:
true
);
stringRes
.
writeAsStringSync
(
'''
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="component1Name">component1</string>
</resources>
'''
,
flush:
true
,
mode:
FileMode
.
append
);
final
File
manifest
=
baseModuleDir
.
childDirectory
(
'src'
).
childDirectory
(
'main'
).
childFile
(
'AndroidManifest.xml'
);
if
(
manifest
.
existsSync
())
{
manifest
.
deleteSync
();
}
manifest
.
createSync
(
recursive:
true
);
manifest
.
writeAsStringSync
(
'''
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.splitaot">
<application
android:name="io.flutter.app.FlutterPlayStoreSplitApplication"
android:label="splitaot"
android:extractNativeLibs="false">
<activity
android:name=".MainActivity"
android:launchMode="singleTop"
android:windowSoftInputMode="adjustResize">
</activity>
<!-- Don'
t
delete
the
meta
-
data
below
.
This
is
used
by
the
Flutter
tool
to
generate
GeneratedPluginRegistrant
.
java
-->
<
meta
-
data
android:
name
=
"flutterEmbedding"
android:
value
=
"2"
/>
</
application
>
</
manifest
>
''', flush: true, mode: FileMode.append);
validator.checkAppAndroidManifestComponentLoadingUnitMapping(
<DeferredComponent>[
DeferredComponent(name: '
component1
', libraries: <String>['
lib2
']),
DeferredComponent(name: '
component2
', libraries: <String>['
lib1
', '
lib4
']),
],
<LoadingUnit>[
LoadingUnit(id: 2, libraries: <String>['
lib1
']),
LoadingUnit(id: 3, libraries: <String>['
lib2
', '
lib3
']),
LoadingUnit(id: 4, libraries: <String>['
lib4
', '
lib5
']),
],
);
validator.checkAndroidResourcesStrings(
<DeferredComponent>[
DeferredComponent(name: '
component1
', libraries: <String>['
lib2
']),
DeferredComponent(name: '
component2
', libraries: <String>['
lib1
', '
lib4
']),
],
);
validator.displayResults();
validator.attemptToolExit();
expect(logger.statusText.contains('
Modified
android
files:
\
n
'), true);
expect(logger.statusText.contains('
build
/
$
{
DeferredComponentsSetupValidator
.
kDeferredComponentsTempDirectory
}/
app
/
src
/
main
/
AndroidManifest
.
xml
\
n
'), true);
expect(logger.statusText.contains('
build
/
$
{
DeferredComponentsSetupValidator
.
kDeferredComponentsTempDirectory
}/
app
/
src
/
main
/
res
/
values
/
strings
.
xml
\
n
'), true);
final File stringsOutput = env.projectDir
.childDirectory('
build
')
.childDirectory(DeferredComponentsSetupValidator.kDeferredComponentsTempDirectory)
.childDirectory('
app
')
.childDirectory('
src
')
.childDirectory('
main
')
.childDirectory('
res
')
.childDirectory('
values
')
.childFile('
strings
.
xml
');
expect(stringsOutput.existsSync(), true);
expect(stringsOutput.readAsStringSync().contains('
<
string
name
=
"component1Name"
>
component1
</
string
>
'), true);
expect(stringsOutput.readAsStringSync().contains('
<
string
name
=
"component2Name"
>
component2
</
string
>
'), true);
final File manifestOutput = env.projectDir
.childDirectory('
build
')
.childDirectory(DeferredComponentsSetupValidator.kDeferredComponentsTempDirectory)
.childDirectory('
app
')
.childDirectory('
src
')
.childDirectory('
main
')
.childFile('
AndroidManifest
.
xml
');
expect(manifestOutput.existsSync(), true);
expect(manifestOutput.readAsStringSync().contains('
<
meta
-
data
android:
name
=
"io.flutter.embedding.engine.deferredcomponents.DeferredComponentManager.loadingUnitMapping"
android:
value
=
"3:component1,2:component2,4:component2"
/>
'), true);
expect(manifestOutput.readAsStringSync().contains('
<!--
Don
\
't delete the meta-data below.'
),
true
);
});
// Tests if all of the regexp whitespace detection is working.
testWithoutContext
(
'androidStringMapping handles whitespace within entry'
,
()
async
{
final
DeferredComponentsSetupValidator
validator
=
DeferredComponentsSetupValidator
(
env
,
exitOnFail:
false
,
title:
'test check'
,
);
final
Directory
baseModuleDir
=
env
.
projectDir
.
childDirectory
(
'android'
).
childDirectory
(
'app'
);
final
File
stringRes
=
baseModuleDir
.
childDirectory
(
'src'
).
childDirectory
(
'main'
).
childDirectory
(
'res'
).
childDirectory
(
'values'
).
childFile
(
'strings.xml'
);
if
(
stringRes
.
existsSync
())
{
stringRes
.
deleteSync
();
}
stringRes
.
createSync
(
recursive:
true
);
stringRes
.
writeAsStringSync
(
'''
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="component1Name">component1</string>
</resources>
'''
,
flush:
true
,
mode:
FileMode
.
append
);
final
File
manifest
=
baseModuleDir
.
childDirectory
(
'src'
).
childDirectory
(
'main'
).
childFile
(
'AndroidManifest.xml'
);
if
(
manifest
.
existsSync
())
{
manifest
.
deleteSync
();
}
manifest
.
createSync
(
recursive:
true
);
manifest
.
writeAsStringSync
(
'''
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.splitaot">
<application
android:name="io.flutter.app.FlutterPlayStoreSplitApplication"
android:label="splitaot"
android:extractNativeLibs="false">
<activity
android:name=".MainActivity"
android:launchMode="singleTop"
android:windowSoftInputMode="adjustResize">
</activity>
<!-- Don'
t
delete
the
meta
-
data
below
.
This
is
used
by
the
Flutter
tool
to
generate
GeneratedPluginRegistrant
.
java
-->
<
meta
-
data
android:
name
=
"flutterEmbedding"
android:
value
=
"2"
/>
<
meta
-
data
android:
name
=
"io.flutter.embedding.engine.deferredcomponents.DeferredComponentManager.loadingUnitMapping"
android:
value
=
"invalidmapping"
/>
</
application
>
</
manifest
>
''', flush: true, mode: FileMode.append);
validator.checkAppAndroidManifestComponentLoadingUnitMapping(
<DeferredComponent>[
DeferredComponent(name: '
component1
', libraries: <String>['
lib2
']),
DeferredComponent(name: '
component2
', libraries: <String>['
lib1
', '
lib4
']),
],
<LoadingUnit>[
LoadingUnit(id: 2, libraries: <String>['
lib1
']),
LoadingUnit(id: 3, libraries: <String>['
lib2
', '
lib3
']),
LoadingUnit(id: 4, libraries: <String>['
lib4
', '
lib5
']),
],
);
validator.checkAndroidResourcesStrings(
<DeferredComponent>[
DeferredComponent(name: '
component1
', libraries: <String>['
lib2
']),
DeferredComponent(name: '
component2
', libraries: <String>['
lib1
', '
lib4
']),
],
);
validator.displayResults();
validator.attemptToolExit();
expect(logger.statusText.contains('
Modified
android
files:
\
n
'), true);
expect(logger.statusText.contains('
build
/
$
{
DeferredComponentsSetupValidator
.
kDeferredComponentsTempDirectory
}/
app
/
src
/
main
/
AndroidManifest
.
xml
\
n
'), true);
expect(logger.statusText.contains('
build
/
$
{
DeferredComponentsSetupValidator
.
kDeferredComponentsTempDirectory
}/
app
/
src
/
main
/
res
/
values
/
strings
.
xml
\
n
'), true);
final File stringsOutput = env.projectDir
.childDirectory('
build
')
.childDirectory(DeferredComponentsSetupValidator.kDeferredComponentsTempDirectory)
.childDirectory('
app
')
.childDirectory('
src
')
.childDirectory('
main
')
.childDirectory('
res
')
.childDirectory('
values
')
.childFile('
strings
.
xml
');
expect(stringsOutput.existsSync(), true);
expect(stringsOutput.readAsStringSync().contains('
<
string
name
=
"component1Name"
>
component1
</
string
>
'), true);
expect(stringsOutput.readAsStringSync().contains('
<
string
name
=
"component2Name"
>
component2
</
string
>
'), true);
final File manifestOutput = env.projectDir
.childDirectory('
build
')
.childDirectory(DeferredComponentsSetupValidator.kDeferredComponentsTempDirectory)
.childDirectory('
app
')
.childDirectory('
src
')
.childDirectory('
main
')
.childFile('
AndroidManifest
.
xml
');
expect(manifestOutput.existsSync(), true);
expect(manifestOutput.readAsStringSync().contains('
<
meta
-
data
android:
name
=
"io.flutter.embedding.engine.deferredcomponents.DeferredComponentManager.loadingUnitMapping"
android:
value
=
"3:component1,2:component2,4:component2"
/>
'), true);
expect(manifestOutput.readAsStringSync().contains(RegExp(r'
android:
value
[
\
s
\
n
]*=[
\
s
\
n
]*
"invalidmapping"')), false);
expect(manifestOutput.readAsStringSync().contains('
<!--
Don
\
't delete the meta-data below.'
),
true
);
});
}
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