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
0407aa44
Unverified
Commit
0407aa44
authored
Oct 19, 2021
by
Gary Qian
Committed by
GitHub
Oct 19, 2021
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Reland "Add multidex flag and automatic multidex support (#90944)" (#92049)
parent
34ec94a1
Changes
22
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
22 changed files
with
1124 additions
and
3 deletions
+1124
-3
flutter.gradle
packages/flutter_tools/gradle/flutter.gradle
+32
-0
flutter_multidex_keepfile.txt
packages/flutter_tools/gradle/flutter_multidex_keepfile.txt
+5
-0
android_device.dart
packages/flutter_tools/lib/src/android/android_device.dart
+2
-1
gradle.dart
packages/flutter_tools/lib/src/android/gradle.dart
+18
-0
gradle_errors.dart
packages/flutter_tools/lib/src/android/gradle_errors.dart
+108
-1
multidex.dart
packages/flutter_tools/lib/src/android/multidex.dart
+99
-0
build_info.dart
packages/flutter_tools/lib/src/build_info.dart
+4
-0
build_apk.dart
packages/flutter_tools/lib/src/commands/build_apk.dart
+3
-0
build_appbundle.dart
packages/flutter_tools/lib/src/commands/build_appbundle.dart
+3
-0
drive.dart
packages/flutter_tools/lib/src/commands/drive.dart
+3
-0
run.dart
packages/flutter_tools/lib/src/commands/run.dart
+3
-0
resident_runner.dart
packages/flutter_tools/lib/src/resident_runner.dart
+4
-1
run_cold.dart
packages/flutter_tools/lib/src/run_cold.dart
+2
-0
run_hot.dart
packages/flutter_tools/lib/src/run_hot.dart
+2
-0
flutter_command.dart
packages/flutter_tools/lib/src/runner/flutter_command.dart
+10
-0
AndroidManifest.xml.tmpl
...shared/android.tmpl/app/src/main/AndroidManifest.xml.tmpl
+1
-0
build_apk_test.dart
...r_tools/test/commands.shard/permeable/build_apk_test.dart
+7
-0
android_gradle_builder_test.dart
...st/general.shard/android/android_gradle_builder_test.dart
+18
-0
gradle_errors_test.dart
..._tools/test/general.shard/android/gradle_errors_test.dart
+228
-0
multidex_test.dart
...utter_tools/test/general.shard/android/multidex_test.dart
+189
-0
multidex_build_test.dart
...ter_tools/test/integration.shard/multidex_build_test.dart
+61
-0
multidex_project.dart
...ls/test/integration.shard/test_data/multidex_project.dart
+322
-0
No files found.
packages/flutter_tools/gradle/flutter.gradle
View file @
0407aa44
...
...
@@ -249,6 +249,38 @@ class FlutterPlugin implements Plugin<Project> {
}
}
if
(
project
.
hasProperty
(
"multidex-enabled"
)
&&
project
.
property
(
"multidex-enabled"
).
toBoolean
()
&&
project
.
android
.
defaultConfig
.
minSdkVersion
<=
20
)
{
String
flutterMultidexKeepfile
=
Paths
.
get
(
flutterRoot
.
absolutePath
,
"packages"
,
"flutter_tools"
,
"gradle"
,
"flutter_multidex_keepfile.txt"
)
project
.
android
{
defaultConfig
{
multiDexEnabled
true
manifestPlaceholders
=
[
applicationName:
"io.flutter.app.FlutterMultiDexApplication"
]
}
buildTypes
{
release
{
multiDexKeepFile
project
.
file
(
flutterMultidexKeepfile
)
}
}
}
project
.
dependencies
{
implementation
"androidx.multidex:multidex:2.0.1"
}
}
else
{
String
baseApplicationName
=
"android.app.Application"
if
(
project
.
hasProperty
(
"base-application-name"
))
{
baseApplicationName
=
project
.
property
(
"base-application-name"
)
}
project
.
android
{
defaultConfig
{
// Setting to android.app.Application is the same as omitting the attribute.
manifestPlaceholders
=
[
applicationName:
baseApplicationName
]
}
}
}
if
(
useLocalEngine
())
{
// This is required to pass the local engine to flutter build aot.
String
engineOutPath
=
project
.
property
(
'local-engine-out'
)
...
...
packages/flutter_tools/gradle/flutter_multidex_keepfile.txt
0 → 100644
View file @
0407aa44
io/flutter/app/FlutterApplication.class
io/flutter/app/FlutterMultiDexApplication.class
io/flutter/embedding/engine/loader/FlutterLoader.class
io/flutter/view/FlutterMain.class
io/flutter/util/PathUtils.class
packages/flutter_tools/lib/src/android/android_device.dart
View file @
0407aa44
...
...
@@ -595,7 +595,8 @@ class AndroidDevice extends Device {
androidBuildInfo:
AndroidBuildInfo
(
debuggingOptions
.
buildInfo
,
targetArchs:
<
AndroidArch
>[
androidArch
],
fastStart:
debuggingOptions
.
fastStart
fastStart:
debuggingOptions
.
fastStart
,
multidexEnabled:
(
platformArgs
[
'multidex'
]
as
bool
)
??
false
,
),
);
// Package has been built, so we can get the updated application ID and
...
...
packages/flutter_tools/lib/src/android/gradle.dart
View file @
0407aa44
...
...
@@ -28,6 +28,7 @@ import 'android_builder.dart';
import
'android_studio.dart'
;
import
'gradle_errors.dart'
;
import
'gradle_utils.dart'
;
import
'multidex.dart'
;
/// The directory where the APK artifact is generated.
Directory
getApkDirectory
(
FlutterProject
project
)
{
...
...
@@ -293,6 +294,22 @@ class AndroidGradleBuilder implements AndroidBuilder {
if
(
target
!=
null
)
{
command
.
add
(
'-Ptarget=
$target
'
);
}
// Only attempt adding multidex support if all the flutter generated files exist.
// If the files do not exist and it was unintentional, the app will fail to build
// and prompt the developer if they wish Flutter to add the files again via gradle_error.dart.
if
(
androidBuildInfo
.
multidexEnabled
&&
multiDexApplicationExists
(
project
.
directory
)
&&
androidManifestHasNameVariable
(
project
.
directory
))
{
command
.
add
(
'-Pmultidex-enabled=true'
);
ensureMultiDexApplicationExists
(
project
.
directory
);
_logger
.
printStatus
(
'Building with Flutter multidex support enabled.'
);
}
// If using v1 embedding, we want to use FlutterApplication as the base app.
final
String
baseApplicationName
=
project
.
android
.
getEmbeddingVersion
()
==
AndroidEmbeddingVersion
.
v2
?
'android.app.Application'
:
'io.flutter.app.FlutterApplication'
;
command
.
add
(
'-Pbase-application-name=
$baseApplicationName
'
);
final
List
<
DeferredComponent
>?
deferredComponents
=
project
.
manifest
.
deferredComponents
;
if
(
deferredComponents
!=
null
)
{
if
(
deferredComponentsEnabled
)
{
...
...
@@ -389,6 +406,7 @@ class AndroidGradleBuilder implements AndroidBuilder {
line:
detectedGradleErrorLine
!,
project:
project
,
usesAndroidX:
usesAndroidX
,
multidexEnabled:
androidBuildInfo
.
multidexEnabled
,
);
if
(
retries
>=
1
)
{
...
...
packages/flutter_tools/lib/src/android/gradle_errors.dart
View file @
0407aa44
...
...
@@ -7,10 +7,12 @@ import 'package:meta/meta.dart';
import
'../base/error_handling_io.dart'
;
import
'../base/file_system.dart'
;
import
'../base/process.dart'
;
import
'../base/terminal.dart'
;
import
'../globals_null_migrated.dart'
as
globals
;
import
'../project.dart'
;
import
'../reporting/reporting.dart'
;
import
'android_studio.dart'
;
import
'multidex.dart'
;
typedef
GradleErrorTest
=
bool
Function
(
String
);
...
...
@@ -31,6 +33,7 @@ class GradleHandledError {
required
String
line
,
required
FlutterProject
project
,
required
bool
usesAndroidX
,
required
bool
multidexEnabled
,
})
handler
;
/// The [BuildEvent] label is named gradle-[eventLabel].
...
...
@@ -71,8 +74,104 @@ final List<GradleHandledError> gradleErrors = <GradleHandledError>[
minSdkVersion
,
transformInputIssue
,
lockFileDepMissing
,
multidexErrorHandler
,
];
// Multidex error message.
@visibleForTesting
final
GradleHandledError
multidexErrorHandler
=
GradleHandledError
(
test:
_lineMatcher
(
const
<
String
>[
'com.android.builder.dexing.DexArchiveMergerException: Error while merging dex archives:'
,
'The number of method references in a .dex file cannot exceed 64K.'
,
]),
handler:
({
required
String
line
,
required
FlutterProject
project
,
required
bool
usesAndroidX
,
required
bool
multidexEnabled
,
})
async
{
globals
.
printStatus
(
'
${globals.logger.terminal.warningMark}
App requires Multidex support'
,
emphasis:
true
);
if
(
multidexEnabled
)
{
globals
.
printStatus
(
'Multidex support is required for your android app to build since the number of methods has exceeded 64k. '
"You may pass the --no-multidex flag to skip Flutter's multidex support to use a manual solution.
\n
"
,
indent:
4
,
);
if
(!
androidManifestHasNameVariable
(
project
.
directory
))
{
globals
.
printStatus
(
r'Your `android/app/src/main/AndroidManifest.xml` does not contain `android:name="${applicationName}"` '
'under the `application` element. This may be due to creating your project with an old version of Flutter. '
'Add the `android:name="
\
${applicationName}
"` attribute to your AndroidManifest.xml to enable Flutter
\'
s multidex support:
\n
'
,
indent:
4
,
);
globals
.
printStatus
(
r''
'
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
...
<application
...
android:name='''
,
indent:
8
,
newline:
false
,
color:
TerminalColor
.
grey
,
);
globals
.
printStatus
(
r'"${applicationName}"'
,
color:
TerminalColor
.
green
,
newline:
true
);
globals
.
printStatus
(
r''
'
...>
'''
,
indent:
8
,
color:
TerminalColor
.
grey
,
);
globals
.
printStatus
(
'You may also roll your own multidex support by following the guide at: https://developer.android.com/studio/build/multidex
\n
'
,
indent:
4
,
);
return
GradleBuildStatus
.
exit
;
}
if
(!
multiDexApplicationExists
(
project
.
directory
))
{
globals
.
printStatus
(
'Flutter tool can add multidex support. The following file will be added by flutter:
\n
'
,
indent:
4
,
);
globals
.
printStatus
(
'android/app/src/main/java/io/flutter/app/FlutterMultiDexApplication.java
\n
'
,
indent:
8
,
);
String
selection
=
'n'
;
// Default to 'no' if no interactive terminal.
try
{
selection
=
await
globals
.
terminal
.
promptForCharInput
(
<
String
>[
'y'
,
'n'
],
logger:
globals
.
logger
,
prompt:
'Do you want to continue with adding multidex support for Android?'
,
defaultChoiceIndex:
0
,
);
}
on
StateError
catch
(
e
)
{
globals
.
printError
(
e
.
message
,
indent:
0
,
);
}
if
(
selection
==
'y'
)
{
ensureMultiDexApplicationExists
(
project
.
directory
);
globals
.
printStatus
(
'Multidex enabled. Retrying build.
\n
'
,
indent:
0
,
);
return
GradleBuildStatus
.
retry
;
}
}
}
else
{
globals
.
printStatus
(
'Flutter multidex handling is disabled. If you wish to let the tool configure multidex, use the --mutidex flag.'
,
indent:
4
,
);
}
return
GradleBuildStatus
.
exit
;
},
eventLabel:
'multidex-error'
,
);
// Permission defined error message.
@visibleForTesting
final
GradleHandledError
permissionDeniedErrorHandler
=
GradleHandledError
(
...
...
@@ -83,12 +182,13 @@ final GradleHandledError permissionDeniedErrorHandler = GradleHandledError(
required
String
line
,
required
FlutterProject
project
,
required
bool
usesAndroidX
,
required
bool
multidexEnabled
,
})
async
{
globals
.
printStatus
(
'
${globals.logger.terminal.warningMark}
Gradle does not have execution permission.'
,
emphasis:
true
);
globals
.
printStatus
(
'You should change the ownership of the project directory to your user, '
'or move the project to a directory with execute permissions.'
,
indent:
4
indent:
4
,
);
return
GradleBuildStatus
.
exit
;
},
...
...
@@ -119,6 +219,7 @@ final GradleHandledError networkErrorHandler = GradleHandledError(
required
String
line
,
required
FlutterProject
project
,
required
bool
usesAndroidX
,
required
bool
multidexEnabled
,
})
async
{
globals
.
printError
(
'
${globals.logger.terminal.warningMark}
Gradle threw an error while downloading artifacts from the network. '
...
...
@@ -148,6 +249,7 @@ final GradleHandledError r8FailureHandler = GradleHandledError(
required
String
line
,
required
FlutterProject
project
,
required
bool
usesAndroidX
,
required
bool
multidexEnabled
,
})
async
{
globals
.
printStatus
(
'
${globals.logger.terminal.warningMark}
The shrinker may have failed to optimize the Java bytecode.'
,
emphasis:
true
);
globals
.
printStatus
(
'To disable the shrinker, pass the `--no-shrink` flag to this command.'
,
indent:
4
);
...
...
@@ -169,6 +271,7 @@ final GradleHandledError licenseNotAcceptedHandler = GradleHandledError(
required
String
line
,
required
FlutterProject
project
,
required
bool
usesAndroidX
,
required
bool
multidexEnabled
,
})
async
{
const
String
licenseNotAcceptedMatcher
=
r'You have not accepted the license agreements of the following SDK components:\s*\[(.+)\]'
;
...
...
@@ -202,6 +305,7 @@ final GradleHandledError flavorUndefinedHandler = GradleHandledError(
required
String
line
,
required
FlutterProject
project
,
required
bool
usesAndroidX
,
required
bool
multidexEnabled
,
})
async
{
final
RunResult
tasksRunResult
=
await
globals
.
processUtils
.
run
(
<
String
>[
...
...
@@ -274,6 +378,7 @@ final GradleHandledError minSdkVersion = GradleHandledError(
required
String
line
,
required
FlutterProject
project
,
required
bool
usesAndroidX
,
required
bool
multidexEnabled
,
})
async
{
final
File
gradleFile
=
project
.
directory
.
childDirectory
(
'android'
)
...
...
@@ -314,6 +419,7 @@ final GradleHandledError transformInputIssue = GradleHandledError(
required
String
line
,
required
FlutterProject
project
,
required
bool
usesAndroidX
,
required
bool
multidexEnabled
,
})
async
{
final
File
gradleFile
=
project
.
directory
.
childDirectory
(
'android'
)
...
...
@@ -347,6 +453,7 @@ final GradleHandledError lockFileDepMissing = GradleHandledError(
required
String
line
,
required
FlutterProject
project
,
required
bool
usesAndroidX
,
required
bool
multidexEnabled
,
})
async
{
final
File
gradleFile
=
project
.
directory
.
childDirectory
(
'android'
)
...
...
packages/flutter_tools/lib/src/android/multidex.dart
0 → 100644
View file @
0407aa44
// 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:xml/xml.dart'
;
import
'../base/file_system.dart'
;
// These utility methods are used to generate the code for multidex support as
// well as verifying the project is properly set up.
File
_getMultiDexApplicationFile
(
Directory
projectDir
)
{
return
projectDir
.
childDirectory
(
'android'
)
.
childDirectory
(
'app'
)
.
childDirectory
(
'src'
)
.
childDirectory
(
'main'
)
.
childDirectory
(
'java'
)
.
childDirectory
(
'io'
)
.
childDirectory
(
'flutter'
)
.
childDirectory
(
'app'
)
.
childFile
(
'FlutterMultiDexApplication.java'
);
}
/// Creates the FlutterMultiDexApplication.java if it does not exist.
void
ensureMultiDexApplicationExists
(
final
Directory
projectDir
)
{
final
File
applicationFile
=
_getMultiDexApplicationFile
(
projectDir
);
if
(
applicationFile
.
existsSync
())
{
return
;
}
applicationFile
.
createSync
(
recursive:
true
);
final
StringBuffer
buffer
=
StringBuffer
();
buffer
.
write
(
'''
// Generated file.
// If you wish to remove Flutter'
s
multidex
support
,
delete
this
entire
file
.
package
io
.
flutter
.
app
;
import
android
.
content
.
Context
;
import
androidx
.
annotation
.
CallSuper
;
import
androidx
.
multidex
.
MultiDex
;
/**
* Extension of {@link io.flutter.app.FlutterApplication}, adding multidex support.
*/
public
class
FlutterMultiDexApplication
extends
FlutterApplication
{
@Override
@CallSuper
protected
void
attachBaseContext
(
Context
base
)
{
super
.
attachBaseContext
(
base
);
MultiDex
.
install
(
this
);
}
}
''');
applicationFile.writeAsStringSync(buffer.toString(), flush: true);
}
/// Returns true if FlutterMultiDexApplication.java exists.
///
/// This function does not verify the contents of the file.
bool multiDexApplicationExists(final Directory projectDir) {
if (_getMultiDexApplicationFile(projectDir).existsSync()) {
return true;
}
return false;
}
File _getManifestFile(Directory projectDir) {
return projectDir.childDirectory('
android
')
.childDirectory('
app
')
.childDirectory('
src
')
.childDirectory('
main
')
.childFile('
AndroidManifest
.
xml
');
}
/// Returns true if the `app` module AndroidManifest.xml includes the
/// <application android:name="
${applicationName}
"> attribute.
bool androidManifestHasNameVariable(final Directory projectDir) {
final File manifestFile = _getManifestFile(projectDir);
if (!manifestFile.existsSync()) {
return false;
}
XmlDocument document;
try {
document = XmlDocument.parse(manifestFile.readAsStringSync());
} on XmlParserException {
return false;
} on FileSystemException {
return false;
}
// Check for the
${androidName}
application attribute.
for (final XmlElement application in document.findAllElements('
application
')) {
final String? applicationName = application.getAttribute('
android:
name
');
if (applicationName == r'
$
{
applicationName
}
') {
return true;
}
}
return false;
}
packages/flutter_tools/lib/src/build_info.dart
View file @
0407aa44
...
...
@@ -304,6 +304,7 @@ class AndroidBuildInfo {
],
this
.
splitPerAbi
=
false
,
this
.
fastStart
=
false
,
this
.
multidexEnabled
=
false
,
});
// The build info containing the mode and flavor.
...
...
@@ -321,6 +322,9 @@ class AndroidBuildInfo {
/// Whether to bootstrap an empty application.
final
bool
fastStart
;
/// Whether to enable multidex support for apps with more than 64k methods.
final
bool
multidexEnabled
;
}
/// A summary of the compilation strategy used for Dart.
...
...
packages/flutter_tools/lib/src/commands/build_apk.dart
View file @
0407aa44
...
...
@@ -35,6 +35,7 @@ class BuildApkCommand extends BuildSubCommand {
addNullSafetyModeOptions
(
hide:
!
verboseHelp
);
usesAnalyzeSizeFlag
();
addAndroidSpecificBuildOptions
(
hide:
!
verboseHelp
);
addMultidexOption
();
argParser
..
addFlag
(
'split-per-abi'
,
negatable:
false
,
...
...
@@ -99,9 +100,11 @@ class BuildApkCommand extends BuildSubCommand {
buildInfo
,
splitPerAbi:
boolArg
(
'split-per-abi'
),
targetArchs:
stringsArg
(
'target-platform'
).
map
<
AndroidArch
>(
getAndroidArchForName
),
multidexEnabled:
boolArg
(
'multidex'
),
);
validateBuild
(
androidBuildInfo
);
displayNullSafetyMode
(
androidBuildInfo
.
buildInfo
);
globals
.
terminal
.
usesTerminalUi
=
true
;
await
androidBuilder
.
buildApk
(
project:
FlutterProject
.
current
(),
target:
targetFile
,
...
...
packages/flutter_tools/lib/src/commands/build_appbundle.dart
View file @
0407aa44
...
...
@@ -39,6 +39,7 @@ class BuildAppBundleCommand extends BuildSubCommand {
addEnableExperimentation
(
hide:
!
verboseHelp
);
usesAnalyzeSizeFlag
();
addAndroidSpecificBuildOptions
(
hide:
!
verboseHelp
);
addMultidexOption
();
argParser
.
addMultiOption
(
'target-platform'
,
splitCommas:
true
,
defaultsTo:
<
String
>[
'android-arm'
,
'android-arm64'
,
'android-x64'
],
...
...
@@ -110,6 +111,7 @@ class BuildAppBundleCommand extends BuildSubCommand {
final
AndroidBuildInfo
androidBuildInfo
=
AndroidBuildInfo
(
await
getBuildInfo
(),
targetArchs:
stringsArg
(
'target-platform'
).
map
<
AndroidArch
>(
getAndroidArchForName
),
multidexEnabled:
boolArg
(
'multidex'
),
);
// Do all setup verification that doesn't involve loading units. Checks that
// require generated loading units are done after gen_snapshot in assemble.
...
...
@@ -144,6 +146,7 @@ class BuildAppBundleCommand extends BuildSubCommand {
validateBuild
(
androidBuildInfo
);
displayNullSafetyMode
(
androidBuildInfo
.
buildInfo
);
globals
.
terminal
.
usesTerminalUi
=
true
;
await
androidBuilder
.
buildAab
(
project:
FlutterProject
.
current
(),
target:
targetFile
,
...
...
packages/flutter_tools/lib/src/commands/drive.dart
View file @
0407aa44
...
...
@@ -65,6 +65,7 @@ class DriveCommand extends RunCommandBase {
// to prevent a local network permission dialog on iOS 14+,
// which cannot be accepted or dismissed in a CI environment.
addPublishPort
(
enabledByDefault:
false
,
verboseHelp:
verboseHelp
);
addMultidexOption
();
argParser
..
addFlag
(
'keep-app-running'
,
defaultsTo:
null
,
...
...
@@ -251,6 +252,8 @@ class DriveCommand extends RunCommandBase {
'trace-startup'
:
traceStartup
,
if
(
web
)
'--no-launch-chrome'
:
true
,
if
(
boolArg
(
'multidex'
))
'multidex'
:
true
,
}
);
}
else
{
...
...
packages/flutter_tools/lib/src/commands/run.dart
View file @
0407aa44
...
...
@@ -249,6 +249,7 @@ class RunCommand extends RunCommandBase {
// This will allow subsequent "flutter attach" commands to connect to the VM
// without needing to know the port.
addPublishPort
(
verboseHelp:
verboseHelp
);
addMultidexOption
();
argParser
..
addFlag
(
'enable-software-rendering'
,
negatable:
false
,
...
...
@@ -500,6 +501,7 @@ class RunCommand extends RunCommandBase {
dillOutputPath:
stringArg
(
'output-dill'
),
stayResident:
stayResident
,
ipv6:
ipv6
,
multidexEnabled:
boolArg
(
'multidex'
),
);
}
else
if
(
webMode
)
{
return
webRunnerFactory
.
createWebRunner
(
...
...
@@ -527,6 +529,7 @@ class RunCommand extends RunCommandBase {
:
globals
.
fs
.
file
(
applicationBinaryPath
),
ipv6:
ipv6
,
stayResident:
stayResident
,
multidexEnabled:
boolArg
(
'multidex'
),
);
}
...
...
packages/flutter_tools/lib/src/resident_runner.dart
View file @
0407aa44
...
...
@@ -416,7 +416,9 @@ class FlutterDevice {
}
devFSWriter
=
device
.
createDevFSWriter
(
package
,
userIdentifier
);
final
Map
<
String
,
dynamic
>
platformArgs
=
<
String
,
dynamic
>{};
final
Map
<
String
,
dynamic
>
platformArgs
=
<
String
,
dynamic
>{
'multidex'
:
hotRunner
.
multidexEnabled
,
};
await
startEchoingDeviceLog
();
...
...
@@ -492,6 +494,7 @@ class FlutterDevice {
if
(
coldRunner
.
traceStartup
!=
null
)
{
platformArgs
[
'trace-startup'
]
=
coldRunner
.
traceStartup
;
}
platformArgs
[
'multidex'
]
=
coldRunner
.
multidexEnabled
;
await
startEchoingDeviceLog
();
...
...
packages/flutter_tools/lib/src/run_cold.dart
View file @
0407aa44
...
...
@@ -28,6 +28,7 @@ class ColdRunner extends ResidentRunner {
this
.
traceStartup
=
false
,
this
.
awaitFirstFrameWhenTracing
=
true
,
this
.
applicationBinary
,
this
.
multidexEnabled
=
false
,
bool
ipv6
=
false
,
bool
stayResident
=
true
,
bool
machine
=
false
,
...
...
@@ -46,6 +47,7 @@ class ColdRunner extends ResidentRunner {
final
bool
traceStartup
;
final
bool
awaitFirstFrameWhenTracing
;
final
File
applicationBinary
;
final
bool
multidexEnabled
;
bool
_didAttach
=
false
;
@override
...
...
packages/flutter_tools/lib/src/run_hot.dart
View file @
0407aa44
...
...
@@ -93,6 +93,7 @@ class HotRunner extends ResidentRunner {
bool
stayResident
=
true
,
bool
ipv6
=
false
,
bool
machine
=
false
,
this
.
multidexEnabled
=
false
,
ResidentDevtoolsHandlerFactory
devtoolsHandler
=
createDefaultHandler
,
StopwatchFactory
stopwatchFactory
=
const
StopwatchFactory
(),
ReloadSourcesHelper
reloadSourcesHelper
=
_defaultReloadSourcesHelper
,
...
...
@@ -120,6 +121,7 @@ class HotRunner extends ResidentRunner {
final
bool
benchmarkMode
;
final
File
applicationBinary
;
final
bool
hostIsIde
;
final
bool
multidexEnabled
;
/// When performing a hot restart, the tool needs to upload a new main.dart.dill to
/// each attached device's devfs. Replacing the existing file is not safe and does
...
...
packages/flutter_tools/lib/src/runner/flutter_command.dart
View file @
0407aa44
...
...
@@ -818,6 +818,16 @@ abstract class FlutterCommand extends Command<void> {
);
}
void
addMultidexOption
({
bool
hide
=
false
})
{
argParser
.
addFlag
(
'multidex'
,
negatable:
true
,
defaultsTo:
true
,
help:
'When enabled, indicates that the app should be built with multidex support. This '
'flag adds the dependencies for multidex when the minimum android sdk is 20 or '
'below. For android sdk versions 21 and above, multidex support is native.'
,
);
}
/// Adds build options common to all of the desktop build commands.
void
addCommonDesktopBuildOptions
({
@required
bool
verboseHelp
})
{
addBuildModeFlags
(
verboseHelp:
verboseHelp
);
...
...
packages/flutter_tools/templates/app_shared/android.tmpl/app/src/main/AndroidManifest.xml.tmpl
View file @
0407aa44
...
...
@@ -2,6 +2,7 @@
package="{{androidIdentifier}}">
<application
android:label="{{projectName}}"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity"
...
...
packages/flutter_tools/test/commands.shard/permeable/build_apk_test.dart
View file @
0407aa44
...
...
@@ -157,6 +157,7 @@ void main() {
'-q'
,
'-Ptarget-platform=android-arm,android-arm64,android-x64'
,
'-Ptarget=
${globals.fs.path.join(tempDir.path, 'flutter_project', 'lib', 'main.dart')}
'
,
'-Pbase-application-name=android.app.Application'
,
'-Pdart-obfuscation=false'
,
'-Ptrack-widget-creation=true'
,
'-Ptree-shake-icons=true'
,
...
...
@@ -186,6 +187,7 @@ void main() {
'-q'
,
'-Ptarget-platform=android-arm,android-arm64,android-x64'
,
'-Ptarget=
${globals.fs.path.join(tempDir.path, 'flutter_project', 'lib', 'main.dart')}
'
,
'-Pbase-application-name=android.app.Application'
,
'-Pdart-obfuscation=false'
,
'-Psplit-debug-info=
${tempDir.path}
'
,
'-Ptrack-widget-creation=true'
,
...
...
@@ -216,6 +218,7 @@ void main() {
'-q'
,
'-Ptarget-platform=android-arm,android-arm64,android-x64'
,
'-Ptarget=
${globals.fs.path.join(tempDir.path, 'flutter_project', 'lib', 'main.dart')}
'
,
'-Pbase-application-name=android.app.Application'
,
'-Pdart-obfuscation=false'
,
'-Pextra-front-end-options=foo,bar'
,
'-Ptrack-widget-creation=true'
,
...
...
@@ -246,6 +249,7 @@ void main() {
'-q'
,
'-Ptarget-platform=android-arm,android-arm64,android-x64'
,
'-Ptarget=
${globals.fs.path.join(tempDir.path, 'flutter_project', 'lib', 'main.dart')}
'
,
'-Pbase-application-name=android.app.Application'
,
'-Pdart-obfuscation=false'
,
'-Ptrack-widget-creation=true'
,
'-Ptree-shake-icons=true'
,
...
...
@@ -281,6 +285,7 @@ void main() {
'-q'
,
'-Ptarget-platform=android-arm,android-arm64,android-x64'
,
'-Ptarget=
${globals.fs.path.join(tempDir.path, 'flutter_project', 'lib', 'main.dart')}
'
,
'-Pbase-application-name=android.app.Application'
,
'-Pdart-obfuscation=false'
,
'-Ptrack-widget-creation=true'
,
'-Ptree-shake-icons=true'
,
...
...
@@ -335,6 +340,7 @@ void main() {
'-q'
,
'-Ptarget-platform=android-arm,android-arm64,android-x64'
,
'-Ptarget=
${globals.fs.path.join(tempDir.path, 'flutter_project', 'lib', 'main.dart')}
'
,
'-Pbase-application-name=android.app.Application'
,
'-Pdart-obfuscation=false'
,
'-Ptrack-widget-creation=true'
,
'-Ptree-shake-icons=true'
,
...
...
@@ -381,6 +387,7 @@ void main() {
'-q'
,
'-Ptarget-platform=android-arm,android-arm64,android-x64'
,
'-Ptarget=
${globals.fs.path.join(tempDir.path, 'flutter_project', 'lib', 'main.dart')}
'
,
'-Pbase-application-name=android.app.Application'
,
'-Pdart-obfuscation=false'
,
'-Ptrack-widget-creation=true'
,
'-Ptree-shake-icons=true'
,
...
...
packages/flutter_tools/test/general.shard/android/android_gradle_builder_test.dart
View file @
0407aa44
...
...
@@ -56,6 +56,7 @@ void main() {
'-q'
,
'-Ptarget-platform=android-arm,android-arm64,android-x64'
,
'-Ptarget=lib/main.dart'
,
'-Pbase-application-name=io.flutter.app.FlutterApplication'
,
'-Pdart-obfuscation=false'
,
'-Ptrack-widget-creation=false'
,
'-Ptree-shake-icons=false'
,
...
...
@@ -101,6 +102,7 @@ void main() {
String
line
,
FlutterProject
project
,
bool
usesAndroidX
,
bool
multidexEnabled
})
async
{
handlerCalled
=
true
;
return
GradleBuildStatus
.
exit
;
...
...
@@ -142,6 +144,7 @@ void main() {
'-q'
,
'-Ptarget-platform=android-arm,android-arm64,android-x64'
,
'-Ptarget=lib/main.dart'
,
'-Pbase-application-name=io.flutter.app.FlutterApplication'
,
'-Pdart-obfuscation=false'
,
'-Ptrack-widget-creation=false'
,
'-Ptree-shake-icons=false'
,
...
...
@@ -156,6 +159,7 @@ void main() {
'-q'
,
'-Ptarget-platform=android-arm,android-arm64,android-x64'
,
'-Ptarget=lib/main.dart'
,
'-Pbase-application-name=io.flutter.app.FlutterApplication'
,
'-Pdart-obfuscation=false'
,
'-Ptrack-widget-creation=false'
,
'-Ptree-shake-icons=false'
,
...
...
@@ -205,6 +209,7 @@ void main() {
String
line
,
FlutterProject
project
,
bool
usesAndroidX
,
bool
multidexEnabled
})
async
{
return
GradleBuildStatus
.
retry
;
},
...
...
@@ -243,6 +248,7 @@ void main() {
'-q'
,
'-Ptarget-platform=android-arm,android-arm64,android-x64'
,
'-Ptarget=lib/main.dart'
,
'-Pbase-application-name=io.flutter.app.FlutterApplication'
,
'-Pdart-obfuscation=false'
,
'-Ptrack-widget-creation=false'
,
'-Ptree-shake-icons=false'
,
...
...
@@ -288,6 +294,7 @@ void main() {
String
line
,
FlutterProject
project
,
bool
usesAndroidX
,
bool
multidexEnabled
})
async
{
handlerCalled
=
true
;
return
GradleBuildStatus
.
exit
;
...
...
@@ -329,6 +336,7 @@ void main() {
'-q'
,
'-Ptarget-platform=android-arm,android-arm64,android-x64'
,
'-Ptarget=lib/main.dart'
,
'-Pbase-application-name=io.flutter.app.FlutterApplication'
,
'-Pdart-obfuscation=false'
,
'-Ptrack-widget-creation=false'
,
'-Ptree-shake-icons=false'
,
...
...
@@ -388,6 +396,7 @@ void main() {
'-q'
,
'-Ptarget-platform=android-arm,android-arm64,android-x64'
,
'-Ptarget=lib/main.dart'
,
'-Pbase-application-name=io.flutter.app.FlutterApplication'
,
'-Pdart-obfuscation=false'
,
'-Ptrack-widget-creation=false'
,
'-Ptree-shake-icons=false'
,
...
...
@@ -402,6 +411,7 @@ void main() {
'-q'
,
'-Ptarget-platform=android-arm,android-arm64,android-x64'
,
'-Ptarget=lib/main.dart'
,
'-Pbase-application-name=io.flutter.app.FlutterApplication'
,
'-Pdart-obfuscation=false'
,
'-Ptrack-widget-creation=false'
,
'-Ptree-shake-icons=false'
,
...
...
@@ -450,6 +460,7 @@ void main() {
String
line
,
FlutterProject
project
,
bool
usesAndroidX
,
bool
multidexEnabled
})
async
{
return
GradleBuildStatus
.
retry
;
},
...
...
@@ -488,6 +499,7 @@ void main() {
'-q'
,
'-Ptarget-platform=android-arm64'
,
'-Ptarget=lib/main.dart'
,
'-Pbase-application-name=io.flutter.app.FlutterApplication'
,
'-Pdart-obfuscation=false'
,
'-Ptrack-widget-creation=false'
,
'-Ptree-shake-icons=false'
,
...
...
@@ -580,6 +592,7 @@ void main() {
'-q'
,
'-Ptarget-platform=android-arm,android-arm64,android-x64'
,
'-Ptarget=lib/main.dart'
,
'-Pbase-application-name=io.flutter.app.FlutterApplication'
,
'-Pdart-obfuscation=false'
,
'-Ptrack-widget-creation=false'
,
'-Ptree-shake-icons=false'
,
...
...
@@ -765,6 +778,7 @@ void main() {
'-Plocal-engine-out=out/android_arm'
,
'-Ptarget-platform=android-arm'
,
'-Ptarget=lib/main.dart'
,
'-Pbase-application-name=io.flutter.app.FlutterApplication'
,
'-Pdart-obfuscation=false'
,
'-Ptrack-widget-creation=false'
,
'-Ptree-shake-icons=false'
,
...
...
@@ -838,6 +852,7 @@ void main() {
'-Plocal-engine-out=out/android_arm64'
,
'-Ptarget-platform=android-arm64'
,
'-Ptarget=lib/main.dart'
,
'-Pbase-application-name=io.flutter.app.FlutterApplication'
,
'-Pdart-obfuscation=false'
,
'-Ptrack-widget-creation=false'
,
'-Ptree-shake-icons=false'
,
...
...
@@ -911,6 +926,7 @@ void main() {
'-Plocal-engine-out=out/android_x86'
,
'-Ptarget-platform=android-x86'
,
'-Ptarget=lib/main.dart'
,
'-Pbase-application-name=io.flutter.app.FlutterApplication'
,
'-Pdart-obfuscation=false'
,
'-Ptrack-widget-creation=false'
,
'-Ptree-shake-icons=false'
,
...
...
@@ -984,6 +1000,7 @@ void main() {
'-Plocal-engine-out=out/android_x64'
,
'-Ptarget-platform=android-x64'
,
'-Ptarget=lib/main.dart'
,
'-Pbase-application-name=io.flutter.app.FlutterApplication'
,
'-Pdart-obfuscation=false'
,
'-Ptrack-widget-creation=false'
,
'-Ptree-shake-icons=false'
,
...
...
@@ -1056,6 +1073,7 @@ void main() {
'--no-daemon'
,
'-Ptarget-platform=android-arm,android-arm64,android-x64'
,
'-Ptarget=lib/main.dart'
,
'-Pbase-application-name=io.flutter.app.FlutterApplication'
,
'-Pdart-obfuscation=false'
,
'-Ptrack-widget-creation=false'
,
'-Ptree-shake-icons=false'
,
...
...
packages/flutter_tools/test/general.shard/android/gradle_errors_test.dart
View file @
0407aa44
This diff is collapsed.
Click to expand it.
packages/flutter_tools/test/general.shard/android/multidex_test.dart
0 → 100644
View file @
0407aa44
// 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/file.dart'
;
import
'package:file/memory.dart'
;
import
'package:flutter_tools/src/android/multidex.dart'
;
import
'package:flutter_tools/src/base/file_system.dart'
;
import
'package:flutter_tools/src/globals_null_migrated.dart'
as
globals
;
import
'../../src/common.dart'
;
import
'../../src/context.dart'
;
void
main
(
)
{
testUsingContext
(
'ensureMultidexUtilsExists returns when exists'
,
()
async
{
final
Directory
directory
=
globals
.
fs
.
currentDirectory
;
final
File
applicationFile
=
directory
.
childDirectory
(
'android'
)
.
childDirectory
(
'app'
)
.
childDirectory
(
'src'
)
.
childDirectory
(
'main'
)
.
childDirectory
(
'java'
)
.
childDirectory
(
'io'
)
.
childDirectory
(
'flutter'
)
.
childDirectory
(
'app'
)
.
childFile
(
'FlutterMultiDexApplication.java'
);
applicationFile
.
createSync
(
recursive:
true
);
applicationFile
.
writeAsStringSync
(
'hello'
,
flush:
true
);
expect
(
applicationFile
.
readAsStringSync
(),
'hello'
);
ensureMultiDexApplicationExists
(
directory
);
// File should remain untouched
expect
(
applicationFile
.
readAsStringSync
(),
'hello'
);
},
overrides:
<
Type
,
Generator
>{
FileSystem:
()
=>
MemoryFileSystem
.
test
(),
ProcessManager:
()
=>
FakeProcessManager
.
any
(),
});
testUsingContext
(
'ensureMultiDexApplicationExists generates when does not exist'
,
()
async
{
final
Directory
directory
=
globals
.
fs
.
currentDirectory
;
final
File
applicationFile
=
directory
.
childDirectory
(
'android'
)
.
childDirectory
(
'app'
)
.
childDirectory
(
'src'
)
.
childDirectory
(
'main'
)
.
childDirectory
(
'java'
)
.
childDirectory
(
'io'
)
.
childDirectory
(
'flutter'
)
.
childDirectory
(
'app'
)
.
childFile
(
'FlutterMultiDexApplication.java'
);
ensureMultiDexApplicationExists
(
directory
);
final
String
contents
=
applicationFile
.
readAsStringSync
();
expect
(
contents
.
contains
(
'FlutterMultiDexApplication'
),
true
);
expect
(
contents
.
contains
(
'MultiDex.install(this);'
),
true
);
},
overrides:
<
Type
,
Generator
>{
FileSystem:
()
=>
MemoryFileSystem
.
test
(),
ProcessManager:
()
=>
FakeProcessManager
.
any
(),
});
testUsingContext
(
'multiDexApplicationExists false when does not exist'
,
()
async
{
final
Directory
directory
=
globals
.
fs
.
currentDirectory
;
expect
(
multiDexApplicationExists
(
directory
),
false
);
},
overrides:
<
Type
,
Generator
>{
FileSystem:
()
=>
MemoryFileSystem
.
test
(),
ProcessManager:
()
=>
FakeProcessManager
.
any
(),
});
testUsingContext
(
'multiDexApplicationExists true when does exist'
,
()
async
{
final
Directory
directory
=
globals
.
fs
.
currentDirectory
;
final
File
utilsFile
=
directory
.
childDirectory
(
'android'
)
.
childDirectory
(
'app'
)
.
childDirectory
(
'src'
)
.
childDirectory
(
'main'
)
.
childDirectory
(
'java'
)
.
childDirectory
(
'io'
)
.
childDirectory
(
'flutter'
)
.
childDirectory
(
'app'
)
.
childFile
(
'FlutterMultiDexApplication.java'
);
utilsFile
.
createSync
(
recursive:
true
);
expect
(
multiDexApplicationExists
(
directory
),
true
);
},
overrides:
<
Type
,
Generator
>{
FileSystem:
()
=>
MemoryFileSystem
.
test
(),
ProcessManager:
()
=>
FakeProcessManager
.
any
(),
});
testUsingContext
(
'androidManifestHasNameVariable true with valid manifest'
,
()
async
{
final
Directory
directory
=
globals
.
fs
.
currentDirectory
;
final
File
applicationFile
=
directory
.
childDirectory
(
'android'
)
.
childDirectory
(
'app'
)
.
childDirectory
(
'src'
)
.
childDirectory
(
'main'
)
.
childFile
(
'AndroidManifest.xml'
);
applicationFile
.
createSync
(
recursive:
true
);
applicationFile
.
writeAsStringSync
(
r''
'
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.multidexapp">
<application
android:label="multidextest2"
android:name="
${applicationName}
"
android:icon="@mipmap/ic_launcher">
</application>
</manifest>
'''
,
flush:
true
);
expect
(
androidManifestHasNameVariable
(
directory
),
true
);
},
overrides:
<
Type
,
Generator
>{
FileSystem:
()
=>
MemoryFileSystem
.
test
(),
ProcessManager:
()
=>
FakeProcessManager
.
any
(),
});
testUsingContext
(
'androidManifestHasNameVariable false with no android:name attribute'
,
()
async
{
final
Directory
directory
=
globals
.
fs
.
currentDirectory
;
final
File
applicationFile
=
directory
.
childDirectory
(
'android'
)
.
childDirectory
(
'app'
)
.
childDirectory
(
'src'
)
.
childDirectory
(
'main'
)
.
childFile
(
'AndroidManifest.xml'
);
applicationFile
.
createSync
(
recursive:
true
);
applicationFile
.
writeAsStringSync
(
r''
'
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.multidexapp">
<application
android:label="multidextest2"
android:icon="@mipmap/ic_launcher">
</application>
'''
,
flush:
true
);
expect
(
androidManifestHasNameVariable
(
directory
),
false
);
},
overrides:
<
Type
,
Generator
>{
FileSystem:
()
=>
MemoryFileSystem
.
test
(),
ProcessManager:
()
=>
FakeProcessManager
.
any
(),
});
testUsingContext
(
'androidManifestHasNameVariable false with incorrect android:name attribute'
,
()
async
{
final
Directory
directory
=
globals
.
fs
.
currentDirectory
;
final
File
applicationFile
=
directory
.
childDirectory
(
'android'
)
.
childDirectory
(
'app'
)
.
childDirectory
(
'src'
)
.
childDirectory
(
'main'
)
.
childFile
(
'AndroidManifest.xml'
);
applicationFile
.
createSync
(
recursive:
true
);
applicationFile
.
writeAsStringSync
(
r''
'
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.multidexapp">
<application
android:label="multidextest2"
android:name="io.flutter.app.FlutterApplication"
android:icon="@mipmap/ic_launcher">
</application>
'''
,
flush:
true
);
expect
(
androidManifestHasNameVariable
(
directory
),
false
);
},
overrides:
<
Type
,
Generator
>{
FileSystem:
()
=>
MemoryFileSystem
.
test
(),
ProcessManager:
()
=>
FakeProcessManager
.
any
(),
});
testUsingContext
(
'androidManifestHasNameVariable false with invalid xml manifest'
,
()
async
{
final
Directory
directory
=
globals
.
fs
.
currentDirectory
;
final
File
applicationFile
=
directory
.
childDirectory
(
'android'
)
.
childDirectory
(
'app'
)
.
childDirectory
(
'src'
)
.
childDirectory
(
'main'
)
.
childFile
(
'AndroidManifest.xml'
);
applicationFile
.
createSync
(
recursive:
true
);
applicationFile
.
writeAsStringSync
(
r''
'
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.multidexapp">
<application
android:label="multidextest2"
android:name="
${applicationName}
"
android:icon="@mipmap/ic_launcher">
</application>
'''
,
flush:
true
);
expect
(
androidManifestHasNameVariable
(
directory
),
false
);
},
overrides:
<
Type
,
Generator
>{
FileSystem:
()
=>
MemoryFileSystem
.
test
(),
ProcessManager:
()
=>
FakeProcessManager
.
any
(),
});
testUsingContext
(
'androidManifestHasNameVariable false with no manifest file'
,
()
async
{
final
Directory
directory
=
globals
.
fs
.
currentDirectory
;
expect
(
androidManifestHasNameVariable
(
directory
),
false
);
},
overrides:
<
Type
,
Generator
>{
FileSystem:
()
=>
MemoryFileSystem
.
test
(),
ProcessManager:
()
=>
FakeProcessManager
.
any
(),
});
}
packages/flutter_tools/test/integration.shard/multidex_build_test.dart
0 → 100644
View file @
0407aa44
// 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/file.dart'
;
import
'package:flutter_tools/src/base/io.dart'
;
import
'../src/common.dart'
;
import
'test_data/multidex_project.dart'
;
import
'test_driver.dart'
;
import
'test_utils.dart'
;
void
main
(
)
{
Directory
tempDir
;
FlutterRunTestDriver
_flutter
;
setUp
(()
async
{
tempDir
=
createResolvedTempDirectorySync
(
'run_test.'
);
_flutter
=
FlutterRunTestDriver
(
tempDir
);
});
tearDown
(()
async
{
await
_flutter
.
stop
();
tryToDelete
(
tempDir
);
});
testWithoutContext
(
'simple build apk succeeds'
,
()
async
{
final
MultidexProject
project
=
MultidexProject
(
true
);
await
project
.
setUpIn
(
tempDir
);
final
String
flutterBin
=
fileSystem
.
path
.
join
(
getFlutterRoot
(),
'bin'
,
'flutter'
);
final
ProcessResult
result
=
await
processManager
.
run
(<
String
>[
flutterBin
,
...
getLocalEngineArguments
(),
'build'
,
'apk'
,
'--debug'
,
],
workingDirectory:
tempDir
.
path
);
expect
(
result
.
exitCode
,
0
);
expect
(
result
.
stdout
.
toString
(),
contains
(
'app-debug.apk'
));
});
testWithoutContext
(
'simple build apk without FlutterMultiDexApplication fails'
,
()
async
{
final
MultidexProject
project
=
MultidexProject
(
false
);
await
project
.
setUpIn
(
tempDir
);
final
String
flutterBin
=
fileSystem
.
path
.
join
(
getFlutterRoot
(),
'bin'
,
'flutter'
);
final
ProcessResult
result
=
await
processManager
.
run
(<
String
>[
flutterBin
,
...
getLocalEngineArguments
(),
'build'
,
'apk'
,
'--debug'
,
],
workingDirectory:
tempDir
.
path
);
expect
(
result
.
stderr
.
toString
(),
contains
(
'Cannot fit requested classes in a single dex file'
));
expect
(
result
.
stderr
.
toString
(),
contains
(
'The number of method references in a .dex file cannot exceed 64K.'
));
expect
(
result
.
exitCode
,
1
);
});
}
packages/flutter_tools/test/integration.shard/test_data/multidex_project.dart
0 → 100644
View file @
0407aa44
This diff is collapsed.
Click to expand it.
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