[flutter_tools] wire up complete support for Dart obfuscation (#50509)
This commit is contained in:
parent
baa4b78333
commit
08d079f6c2
62
dev/devicelab/bin/tasks/android_obfuscate_test.dart
Normal file
62
dev/devicelab/bin/tasks/android_obfuscate_test.dart
Normal file
@ -0,0 +1,62 @@
|
||||
// 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 'dart:async';
|
||||
|
||||
import 'package:flutter_devicelab/framework/apk_utils.dart';
|
||||
import 'package:flutter_devicelab/framework/framework.dart';
|
||||
import 'package:flutter_devicelab/framework/utils.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
|
||||
Future<void> main() async {
|
||||
await task(() async {
|
||||
try {
|
||||
bool foundProjectName = false;
|
||||
await runProjectTest((FlutterProject flutterProject) async {
|
||||
section('APK content for task assembleRelease with --obfuscate');
|
||||
await inDirectory(flutterProject.rootPath, () async {
|
||||
await flutter('build', options: <String>[
|
||||
'apk',
|
||||
'--target-platform=android-arm',
|
||||
'--obfuscate',
|
||||
'--split-debug-info=foo/',
|
||||
]);
|
||||
});
|
||||
final String outputDirectory = path.join(
|
||||
flutterProject.rootPath,
|
||||
'build/app/outputs/apk/release/app-release.apk',
|
||||
);
|
||||
final Iterable<String> apkFiles = await getFilesInApk(outputDirectory);
|
||||
|
||||
checkCollectionContains<String>(<String>[
|
||||
...flutterAssets,
|
||||
...baseApkFiles,
|
||||
'lib/armeabi-v7a/libapp.so',
|
||||
], apkFiles);
|
||||
|
||||
// Verify that an identifier from the Dart project code is not present
|
||||
// in the compiled binary.
|
||||
await inDirectory(flutterProject.rootPath, () async {
|
||||
await exec('unzip', <String>[outputDirectory]);
|
||||
final String response = await eval(
|
||||
'grep',
|
||||
<String>[flutterProject.name, 'lib/armeabi-v7a/libapp.so'],
|
||||
canFail: true,
|
||||
);
|
||||
if (response.trim().contains('matches')) {
|
||||
foundProjectName = true;
|
||||
}
|
||||
});
|
||||
});
|
||||
if (foundProjectName) {
|
||||
return TaskResult.failure('Found project name in obfuscated dart library');
|
||||
}
|
||||
return TaskResult.success(null);
|
||||
} on TaskResult catch (taskResult) {
|
||||
return taskResult;
|
||||
} catch (e) {
|
||||
return TaskResult.failure(e.toString());
|
||||
}
|
||||
});
|
||||
}
|
58
dev/devicelab/bin/tasks/ios_obfuscate_test.dart
Normal file
58
dev/devicelab/bin/tasks/ios_obfuscate_test.dart
Normal file
@ -0,0 +1,58 @@
|
||||
// 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 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter_devicelab/framework/apk_utils.dart';
|
||||
import 'package:flutter_devicelab/framework/framework.dart';
|
||||
import 'package:flutter_devicelab/framework/utils.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
|
||||
Future<void> main() async {
|
||||
await task(() async {
|
||||
try {
|
||||
bool foundProjectName = false;
|
||||
await runProjectTest((FlutterProject flutterProject) async {
|
||||
section('iOS Framework content with --obfuscate');
|
||||
await inDirectory(flutterProject.rootPath, () async {
|
||||
await flutter('build', options: <String>[
|
||||
'ios',
|
||||
'--release',
|
||||
'--obfuscate',
|
||||
'--split-debug-info=foo/',
|
||||
]);
|
||||
});
|
||||
final String outputFramework = path.join(
|
||||
flutterProject.rootPath,
|
||||
'build/ios/iphoneos/Runner.app/Frameworks/App.framework/App',
|
||||
);
|
||||
if (!File(outputFramework).existsSync()) {
|
||||
fail('Failed to produce expected output at $outputFramework');
|
||||
}
|
||||
|
||||
// Verify that an identifier from the Dart project code is not present
|
||||
// in the compiled binary.
|
||||
await inDirectory(flutterProject.rootPath, () async {
|
||||
final String response = await eval(
|
||||
'grep',
|
||||
<String>[flutterProject.name, outputFramework],
|
||||
canFail: true,
|
||||
);
|
||||
if (response.trim().contains('matches')) {
|
||||
foundProjectName = true;
|
||||
}
|
||||
});
|
||||
});
|
||||
if (foundProjectName) {
|
||||
return TaskResult.failure('Found project name in obfuscated dart library');
|
||||
}
|
||||
return TaskResult.success(null);
|
||||
} on TaskResult catch (taskResult) {
|
||||
return taskResult;
|
||||
} catch (e) {
|
||||
return TaskResult.failure(e.toString());
|
||||
}
|
||||
});
|
||||
}
|
@ -319,6 +319,13 @@ tasks:
|
||||
stage: devicelab
|
||||
required_agent_capabilities: ["mac/android"]
|
||||
|
||||
android_obfuscate_test:
|
||||
description: >
|
||||
Builds an obfuscated APK and verifies a dart identifier cannot be found
|
||||
stage: devicelab
|
||||
flaky: true
|
||||
required_agent_capabilities: ["linux/android"]
|
||||
|
||||
complex_layout_semantics_perf:
|
||||
description: >
|
||||
Measures duration of building the initial semantics tree.
|
||||
@ -390,6 +397,13 @@ tasks:
|
||||
|
||||
# iOS on-device tests
|
||||
|
||||
ios_obfuscate_test:
|
||||
description: >
|
||||
Builds an obfuscated APK and verifies a dart identifier cannot be found
|
||||
stage: devicelab
|
||||
flaky: true
|
||||
required_agent_capabilities: ["mac/ios"]
|
||||
|
||||
tiles_scroll_perf_ios__timeline_summary:
|
||||
description: >
|
||||
Measures the runtime performance of the tiles tab in the Complex Layout sample app on iPhone 6.
|
||||
|
@ -67,6 +67,11 @@ if [[ -n "$TREE_SHAKE_ICONS" ]]; then
|
||||
icon_tree_shaker_flag="true"
|
||||
fi
|
||||
|
||||
dart_obfuscation_flag="false"
|
||||
if [[ -n "$DART_OBFUSCATION" ]]; then
|
||||
dart_obfuscation_flag="true"
|
||||
fi
|
||||
|
||||
RunCommand "${FLUTTER_ROOT}/bin/flutter" --suppress-analytics \
|
||||
${verbose_flag} \
|
||||
${flutter_engine_flag} \
|
||||
@ -76,6 +81,7 @@ RunCommand "${FLUTTER_ROOT}/bin/flutter" --suppress-analytics \
|
||||
-dTargetFile="${target_path}" \
|
||||
-dBuildMode="${build_mode}" \
|
||||
-dTreeShakeIcons="${icon_tree_shaker_flag}" \
|
||||
-dDartObfuscation="${dart_obfuscation_flag}" \
|
||||
-dSplitDebugInfo="${SPLIT_DEBUG_INFO}" \
|
||||
--build-inputs="${build_inputs_path}" \
|
||||
--build-outputs="${build_outputs_path}" \
|
||||
|
@ -166,6 +166,11 @@ BuildApp() {
|
||||
icon_tree_shaker_flag="true"
|
||||
fi
|
||||
|
||||
dart_obfuscation_flag="false"
|
||||
if [[ -n "$DART_OBFUSCATION" ]]; then
|
||||
dart_obfuscation_flag="true"
|
||||
fi
|
||||
|
||||
RunCommand "${FLUTTER_ROOT}/bin/flutter" \
|
||||
${verbose_flag} \
|
||||
${flutter_engine_flag} \
|
||||
@ -179,6 +184,7 @@ BuildApp() {
|
||||
-dSplitDebugInfo="${SPLIT_DEBUG_INFO}" \
|
||||
-dTreeShakeIcons="${icon_tree_shaker_flag}" \
|
||||
-dTrackWidgetCreation="${track_widget_creation_flag}" \
|
||||
-dDartObfuscation="${dart_obfuscation_flag}" \
|
||||
-dEnableBitcode="${bitcode_flag}" \
|
||||
"${build_mode}_ios_bundle_flutter_assets"
|
||||
|
||||
|
@ -607,6 +607,10 @@ class FlutterPlugin implements Plugin<Project> {
|
||||
if (project.hasProperty('split-debug-info')) {
|
||||
splitDebugInfoValue = project.property('split-debug-info')
|
||||
}
|
||||
Boolean dartObfuscationValue = false
|
||||
if (project.hasProperty('dart-obfuscation')) {
|
||||
dartObfuscationValue = project.property('dart-obfuscation').toBoolean();
|
||||
}
|
||||
Boolean treeShakeIconsOptionsValue = false
|
||||
if (project.hasProperty('tree-shake-icons')) {
|
||||
treeShakeIconsOptionsValue = project.property('tree-shake-icons').toBoolean()
|
||||
@ -647,6 +651,7 @@ class FlutterPlugin implements Plugin<Project> {
|
||||
extraGenSnapshotOptions extraGenSnapshotOptionsValue
|
||||
splitDebugInfo splitDebugInfoValue
|
||||
treeShakeIcons treeShakeIconsOptionsValue
|
||||
dartObfuscation dartObfuscationValue
|
||||
doLast {
|
||||
project.exec {
|
||||
if (Os.isFamily(Os.FAMILY_WINDOWS)) {
|
||||
@ -792,6 +797,8 @@ abstract class BaseFlutterTask extends DefaultTask {
|
||||
String splitDebugInfo
|
||||
@Optional @Input
|
||||
Boolean treeShakeIcons
|
||||
@Optional @Input
|
||||
Boolean dartObfuscation
|
||||
|
||||
@OutputFiles
|
||||
FileCollection getDependenciesFiles() {
|
||||
@ -854,6 +861,9 @@ abstract class BaseFlutterTask extends DefaultTask {
|
||||
if (treeShakeIcons == true) {
|
||||
args "-dTreeShakeIcons=true"
|
||||
}
|
||||
if (dartObfuscation == true) {
|
||||
args "-dDartObfuscation=true"
|
||||
}
|
||||
if (extraGenSnapshotOptions != null) {
|
||||
args "--ExtraGenSnapshotOptions=${extraGenSnapshotOptions}"
|
||||
}
|
||||
|
@ -344,6 +344,9 @@ Future<void> buildGradleApp({
|
||||
if (androidBuildInfo.buildInfo.treeShakeIcons) {
|
||||
command.add('-Ptree-shake-icons=true');
|
||||
}
|
||||
if (androidBuildInfo.buildInfo.dartObfuscation) {
|
||||
command.add('-Pdart-obfuscation=true');
|
||||
}
|
||||
command.add(assembleTask);
|
||||
|
||||
GradleHandledError detectedGradleError;
|
||||
|
@ -93,6 +93,7 @@ class AotBuilder {
|
||||
bitcode: bitcode,
|
||||
quiet: quiet,
|
||||
splitDebugInfo: null,
|
||||
dartObfuscation: false,
|
||||
).then<int>((int buildExitCode) {
|
||||
return buildExitCode;
|
||||
});
|
||||
@ -130,6 +131,7 @@ class AotBuilder {
|
||||
extraGenSnapshotOptions: extraGenSnapshotOptions,
|
||||
bitcode: false,
|
||||
splitDebugInfo: null,
|
||||
dartObfuscation: false,
|
||||
);
|
||||
if (snapshotExitCode != 0) {
|
||||
status?.cancel();
|
||||
|
@ -42,6 +42,16 @@ class GenSnapshot {
|
||||
Artifact.genSnapshot, platform: snapshotType.platform, mode: snapshotType.mode);
|
||||
}
|
||||
|
||||
/// Ignored warning messages from gen_snapshot.
|
||||
static const Set<String> kIgnoredWarnings = <String>{
|
||||
// --strip on elf snapshot.
|
||||
'Warning: Generating ELF library without DWARF debugging information.',
|
||||
// --strip on ios-assembly snapshot.
|
||||
'Warning: Generating assembly code without DWARF debugging information.',
|
||||
// A fun two-part message with spaces for obfuscation.
|
||||
'Warning: This VM has been configured to obfuscate symbol information which violates the Dart standard.',
|
||||
' See dartbug.com/30524 for more information.',
|
||||
};
|
||||
Future<int> run({
|
||||
@required SnapshotType snapshotType,
|
||||
DarwinArch darwinArch,
|
||||
@ -59,18 +69,9 @@ class GenSnapshot {
|
||||
snapshotterPath += '_' + getNameForDarwinArch(darwinArch);
|
||||
}
|
||||
|
||||
StringConverter outputFilter;
|
||||
if (additionalArgs.contains('--strip')) {
|
||||
// Filter out gen_snapshot's warning message about stripping debug symbols
|
||||
// from ELF library snapshots.
|
||||
const String kStripWarning = 'Warning: Generating ELF library without DWARF debugging information.';
|
||||
const String kAssemblyStripWarning = 'Warning: Generating assembly code without DWARF debugging information.';
|
||||
outputFilter = (String line) => line != kStripWarning && line != kAssemblyStripWarning ? line : null;
|
||||
}
|
||||
|
||||
return processUtils.stream(
|
||||
<String>[snapshotterPath, ...args],
|
||||
mapFunction: outputFilter,
|
||||
mapFunction: (String line) => kIgnoredWarnings.contains(line) ? null : line,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -94,6 +95,7 @@ class AOTSnapshotter {
|
||||
List<String> extraGenSnapshotOptions = const <String>[],
|
||||
@required bool bitcode,
|
||||
@required String splitDebugInfo,
|
||||
@required bool dartObfuscation,
|
||||
bool quiet = false,
|
||||
}) async {
|
||||
if (bitcode && platform != TargetPlatform.ios) {
|
||||
@ -147,7 +149,8 @@ class AOTSnapshotter {
|
||||
// multiple debug files.
|
||||
final String archName = getNameForTargetPlatform(platform, darwinArch: darwinArch);
|
||||
final String debugFilename = 'app.$archName.symbols';
|
||||
if (splitDebugInfo?.isNotEmpty ?? false) {
|
||||
final bool shouldSplitDebugInfo = splitDebugInfo?.isNotEmpty ?? false;
|
||||
if (shouldSplitDebugInfo) {
|
||||
globals.fs.directory(splitDebugInfo)
|
||||
.createSync(recursive: true);
|
||||
}
|
||||
@ -157,10 +160,12 @@ class AOTSnapshotter {
|
||||
// Faster async/await
|
||||
'--no-causal-async-stacks',
|
||||
'--lazy-async-stacks',
|
||||
if (splitDebugInfo?.isNotEmpty ?? false) ...<String>[
|
||||
if (shouldSplitDebugInfo) ...<String>[
|
||||
'--dwarf-stack-traces',
|
||||
'--save-debugging-info=${globals.fs.path.join(splitDebugInfo, debugFilename)}'
|
||||
]
|
||||
],
|
||||
if (dartObfuscation)
|
||||
'--obfuscate',
|
||||
]);
|
||||
|
||||
genSnapshotArgs.add(mainPath);
|
||||
|
@ -22,6 +22,7 @@ class BuildInfo {
|
||||
this.buildNumber,
|
||||
this.buildName,
|
||||
this.splitDebugInfoPath,
|
||||
this.dartObfuscation = false,
|
||||
@required this.treeShakeIcons,
|
||||
});
|
||||
|
||||
@ -68,6 +69,9 @@ class BuildInfo {
|
||||
/// executable.
|
||||
final String splitDebugInfoPath;
|
||||
|
||||
/// Whether to apply dart source code obfuscation.
|
||||
final bool dartObfuscation;
|
||||
|
||||
static const BuildInfo debug = BuildInfo(BuildMode.debug, null, treeShakeIcons: false);
|
||||
static const BuildInfo profile = BuildInfo(BuildMode.profile, null, treeShakeIcons: kIconTreeShakerEnabledDefault);
|
||||
static const BuildInfo jitRelease = BuildInfo(BuildMode.jitRelease, null, treeShakeIcons: kIconTreeShakerEnabledDefault);
|
||||
|
@ -222,6 +222,7 @@ class AndroidAot extends AotElfBase {
|
||||
final List<String> extraGenSnapshotOptions = environment.defines[kExtraGenSnapshotOptions]?.split(',')
|
||||
?? const <String>[];
|
||||
final BuildMode buildMode = getBuildModeForName(environment.defines[kBuildMode]);
|
||||
final bool dartObfuscation = environment.defines[kDartObfuscation] == 'true';
|
||||
final int snapshotExitCode = await snapshotter.build(
|
||||
platform: targetPlatform,
|
||||
buildMode: buildMode,
|
||||
@ -231,6 +232,7 @@ class AndroidAot extends AotElfBase {
|
||||
bitcode: false,
|
||||
extraGenSnapshotOptions: extraGenSnapshotOptions,
|
||||
splitDebugInfo: splitDebugInfo,
|
||||
dartObfuscation: dartObfuscation,
|
||||
);
|
||||
if (snapshotExitCode != 0) {
|
||||
throw Exception('AOT snapshotter exited with code $snapshotExitCode');
|
||||
|
@ -66,6 +66,9 @@ const String kDartDefines = 'DartDefines';
|
||||
/// The other supported value is armv7, the 32-bit iOS architecture.
|
||||
const String kIosArchs = 'IosArchs';
|
||||
|
||||
/// Whether to enable Dart obfuscation and where to save the symbol map.
|
||||
const String kDartObfuscation = 'DartObfuscation';
|
||||
|
||||
/// Copies the pre-built flutter bundle.
|
||||
// This is a one-off rule for implementing build bundle in terms of assemble.
|
||||
class CopyFlutterBundle extends Target {
|
||||
@ -270,7 +273,8 @@ abstract class AotElfBase extends Target {
|
||||
?? const <String>[];
|
||||
final BuildMode buildMode = getBuildModeForName(environment.defines[kBuildMode]);
|
||||
final TargetPlatform targetPlatform = getTargetPlatformForName(environment.defines[kTargetPlatform]);
|
||||
final String saveDebuggingInformation = environment.defines[kSplitDebugInfo];
|
||||
final String splitDebugInfo = environment.defines[kSplitDebugInfo];
|
||||
final bool dartObfuscation = environment.defines[kDartObfuscation] == 'true';
|
||||
final int snapshotExitCode = await snapshotter.build(
|
||||
platform: targetPlatform,
|
||||
buildMode: buildMode,
|
||||
@ -279,7 +283,8 @@ abstract class AotElfBase extends Target {
|
||||
outputPath: outputPath,
|
||||
bitcode: false,
|
||||
extraGenSnapshotOptions: extraGenSnapshotOptions,
|
||||
splitDebugInfo: saveDebuggingInformation
|
||||
splitDebugInfo: splitDebugInfo,
|
||||
dartObfuscation: dartObfuscation,
|
||||
);
|
||||
if (snapshotExitCode != 0) {
|
||||
throw Exception('AOT snapshotter exited with code $snapshotExitCode');
|
||||
|
@ -40,6 +40,7 @@ abstract class AotAssemblyBase extends Target {
|
||||
final BuildMode buildMode = getBuildModeForName(environment.defines[kBuildMode]);
|
||||
final TargetPlatform targetPlatform = getTargetPlatformForName(environment.defines[kTargetPlatform]);
|
||||
final String splitDebugInfo = environment.defines[kSplitDebugInfo];
|
||||
final bool dartObfuscation = environment.defines[kDartObfuscation] == 'true';
|
||||
final List<DarwinArch> iosArchs = environment.defines[kIosArchs]
|
||||
?.split(' ')
|
||||
?.map(getIOSArchForName)
|
||||
@ -63,6 +64,7 @@ abstract class AotAssemblyBase extends Target {
|
||||
bitcode: bitcode,
|
||||
quiet: true,
|
||||
splitDebugInfo: splitDebugInfo,
|
||||
dartObfuscation: dartObfuscation,
|
||||
));
|
||||
}
|
||||
final List<int> results = await Future.wait(pending);
|
||||
|
@ -201,6 +201,7 @@ class CompileMacOSFramework extends Target {
|
||||
throw Exception('precompiled macOS framework only supported in release/profile builds.');
|
||||
}
|
||||
final String splitDebugInfo = environment.defines[kSplitDebugInfo];
|
||||
final bool dartObfuscation = environment.defines[kDartObfuscation] == 'true';
|
||||
final int result = await AOTSnapshotter(reportTimings: false).build(
|
||||
bitcode: false,
|
||||
buildMode: buildMode,
|
||||
@ -210,6 +211,7 @@ class CompileMacOSFramework extends Target {
|
||||
darwinArch: DarwinArch.x86_64,
|
||||
packagesPath: environment.projectDir.childFile('.packages').path,
|
||||
splitDebugInfo: splitDebugInfo,
|
||||
dartObfuscation: dartObfuscation,
|
||||
);
|
||||
if (result != 0) {
|
||||
throw Exception('gen shapshot failed.');
|
||||
|
@ -27,6 +27,7 @@ class BuildApkCommand extends BuildSubCommand {
|
||||
usesBuildNameOption();
|
||||
addShrinkingFlag();
|
||||
addSplitDebugInfoOption();
|
||||
addDartObfuscationOption();
|
||||
argParser
|
||||
..addFlag('split-per-abi',
|
||||
negatable: false,
|
||||
|
@ -24,6 +24,8 @@ class BuildAppBundleCommand extends BuildSubCommand {
|
||||
usesBuildNumberOption();
|
||||
usesBuildNameOption();
|
||||
addShrinkingFlag();
|
||||
addSplitDebugInfoOption();
|
||||
addDartObfuscationOption();
|
||||
|
||||
argParser
|
||||
..addFlag('track-widget-creation', negatable: false, hide: !verboseHelp)
|
||||
|
@ -26,6 +26,7 @@ class BuildIOSCommand extends BuildSubCommand {
|
||||
usesPubOption();
|
||||
usesBuildNumberOption();
|
||||
usesBuildNameOption();
|
||||
addDartObfuscationOption();
|
||||
argParser
|
||||
..addFlag('simulator',
|
||||
help: 'Build for the iOS simulator instead of the device.',
|
||||
|
@ -49,6 +49,8 @@ class BuildIOSFrameworkCommand extends BuildSubCommand {
|
||||
usesFlavorOption();
|
||||
usesPubOption();
|
||||
usesDartDefines();
|
||||
addSplitDebugInfoOption();
|
||||
addDartObfuscationOption();
|
||||
argParser
|
||||
..addFlag('debug',
|
||||
negatable: true,
|
||||
|
@ -21,6 +21,7 @@ class BuildMacosCommand extends BuildSubCommand {
|
||||
addSplitDebugInfoOption();
|
||||
usesTargetOption();
|
||||
addBuildModeFlags();
|
||||
addDartObfuscationOption();
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -172,6 +172,11 @@ List<String> _xcodeBuildSettingsLines({
|
||||
xcodeBuildSettings.add('SPLIT_DEBUG_INFO=${buildInfo.splitDebugInfoPath}');
|
||||
}
|
||||
|
||||
// This is an optional path to obfuscate and output a mapping.
|
||||
if (buildInfo.dartObfuscation) {
|
||||
xcodeBuildSettings.add('DART_OBFUSCATION=true');
|
||||
}
|
||||
|
||||
// The build outputs directory, relative to FLUTTER_APPLICATION_PATH.
|
||||
xcodeBuildSettings.add('FLUTTER_BUILD_DIR=${buildDirOverride ?? getBuildDirectory()}');
|
||||
|
||||
|
@ -108,6 +108,7 @@ class FlutterOptions {
|
||||
static const String kFileSystemRoot = 'filesystem-root';
|
||||
static const String kFileSystemScheme = 'filesystem-scheme';
|
||||
static const String kSplitDebugInfoOption = 'split-debug-info';
|
||||
static const String kDartObfuscationOption = 'obfuscate';
|
||||
}
|
||||
|
||||
abstract class FlutterCommand extends Command<void> {
|
||||
@ -393,6 +394,18 @@ abstract class FlutterCommand extends Command<void> {
|
||||
);
|
||||
}
|
||||
|
||||
void addDartObfuscationOption() {
|
||||
argParser.addFlag(FlutterOptions.kDartObfuscationOption,
|
||||
help: 'In a release build, this flag removes identifiers and replaces them '
|
||||
'with randomized values for the purposes of source code obfuscation. This '
|
||||
'flag must always be combined with "--split-debug-info" option, the '
|
||||
'mapping between the values and the original identifiers is stored in the '
|
||||
'symbol map created in the specified directory. For an app built with this '
|
||||
'flag, the \'flutter symbolize\' command with the right program '
|
||||
'symbol file is required to obtain a human readable stack trace.',
|
||||
);
|
||||
}
|
||||
|
||||
void addTreeShakeIconsFlag() {
|
||||
argParser.addFlag('tree-shake-icons',
|
||||
negatable: true,
|
||||
@ -483,6 +496,10 @@ abstract class FlutterCommand extends Command<void> {
|
||||
);
|
||||
}
|
||||
|
||||
/// Compute the [BuildInfo] for the current flutter command.
|
||||
///
|
||||
/// Throws a [ToolExit] if the current set of options is not compatible with
|
||||
/// eachother.
|
||||
BuildInfo getBuildInfo() {
|
||||
final bool trackWidgetCreation = argParser.options.containsKey('track-widget-creation') &&
|
||||
boolArg('track-widget-creation');
|
||||
@ -507,6 +524,20 @@ abstract class FlutterCommand extends Command<void> {
|
||||
}
|
||||
}
|
||||
|
||||
final bool dartObfuscation = argParser.options.containsKey(FlutterOptions.kDartObfuscationOption)
|
||||
&& boolArg(FlutterOptions.kDartObfuscationOption);
|
||||
|
||||
final String splitDebugInfoPath = argParser.options.containsKey(FlutterOptions.kSplitDebugInfoOption)
|
||||
? stringArg(FlutterOptions.kSplitDebugInfoOption)
|
||||
: null;
|
||||
|
||||
if (dartObfuscation && (splitDebugInfoPath == null || splitDebugInfoPath.isEmpty)) {
|
||||
throwToolExit(
|
||||
'"--${FlutterOptions.kDartObfuscationOption}" can only be used in '
|
||||
'combination with "--${FlutterOptions.kSplitDebugInfoOption}"',
|
||||
);
|
||||
}
|
||||
|
||||
return BuildInfo(getBuildMode(),
|
||||
argParser.options.containsKey('flavor')
|
||||
? stringArg('flavor')
|
||||
@ -526,12 +557,11 @@ abstract class FlutterCommand extends Command<void> {
|
||||
buildName: argParser.options.containsKey('build-name')
|
||||
? stringArg('build-name')
|
||||
: null,
|
||||
splitDebugInfoPath: argParser.options.containsKey(FlutterOptions.kSplitDebugInfoOption)
|
||||
? stringArg(FlutterOptions.kSplitDebugInfoOption)
|
||||
: null,
|
||||
treeShakeIcons: argParser.options.containsKey('tree-shake-icons')
|
||||
? boolArg('tree-shake-icons')
|
||||
: kIconTreeShakerEnabledDefault,
|
||||
splitDebugInfoPath: splitDebugInfoPath,
|
||||
dartObfuscation: dartObfuscation,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -36,5 +36,8 @@ lib/generated_plugin_registrant.dart
|
||||
# Symbolication related
|
||||
app.*.symbols
|
||||
|
||||
# Obfuscation related
|
||||
app.*.map.json
|
||||
|
||||
# Exceptions to above rules.
|
||||
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
|
||||
|
@ -43,3 +43,6 @@ build/
|
||||
|
||||
# Symbolication related
|
||||
app.*.symbols
|
||||
|
||||
# Obfuscation related
|
||||
app.*.map.json
|
||||
|
@ -0,0 +1,41 @@
|
||||
// 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:args/command_runner.dart';
|
||||
import 'package:flutter_tools/src/base/common.dart';
|
||||
import 'package:flutter_tools/src/runner/flutter_command.dart';
|
||||
|
||||
import '../../src/common.dart';
|
||||
import '../../src/context.dart';
|
||||
|
||||
void main() {
|
||||
testUsingContext('obfuscate requires split-debug-info', () {
|
||||
final FakeBuildCommand command = FakeBuildCommand();
|
||||
final CommandRunner<void> commandRunner = createTestCommandRunner(command);
|
||||
|
||||
expect(() => commandRunner.run(<String>[
|
||||
'build',
|
||||
'--obfuscate',
|
||||
]), throwsA(isA<ToolExit>()));
|
||||
});
|
||||
}
|
||||
|
||||
class FakeBuildCommand extends FlutterCommand {
|
||||
FakeBuildCommand() {
|
||||
addSplitDebugInfoOption();
|
||||
addDartObfuscationOption();
|
||||
}
|
||||
|
||||
@override
|
||||
String get description => throw UnimplementedError();
|
||||
|
||||
@override
|
||||
String get name => 'build';
|
||||
|
||||
@override
|
||||
Future<FlutterCommandResult> runCommand() async {
|
||||
getBuildInfo();
|
||||
return FlutterCommandResult.success();
|
||||
}
|
||||
}
|
@ -267,6 +267,7 @@ void main() {
|
||||
outputPath: outputPath,
|
||||
bitcode: false,
|
||||
splitDebugInfo: null,
|
||||
dartObfuscation: false,
|
||||
), isNot(equals(0)));
|
||||
}, overrides: contextOverrides);
|
||||
|
||||
@ -280,6 +281,7 @@ void main() {
|
||||
outputPath: outputPath,
|
||||
bitcode: false,
|
||||
splitDebugInfo: null,
|
||||
dartObfuscation: false,
|
||||
), isNot(0));
|
||||
}, overrides: contextOverrides);
|
||||
|
||||
@ -293,6 +295,7 @@ void main() {
|
||||
outputPath: outputPath,
|
||||
bitcode: false,
|
||||
splitDebugInfo: null,
|
||||
dartObfuscation: false,
|
||||
), isNot(0));
|
||||
}, overrides: contextOverrides);
|
||||
|
||||
@ -320,6 +323,7 @@ void main() {
|
||||
darwinArch: DarwinArch.armv7,
|
||||
bitcode: true,
|
||||
splitDebugInfo: null,
|
||||
dartObfuscation: false,
|
||||
);
|
||||
|
||||
expect(genSnapshotExitCode, 0);
|
||||
@ -381,6 +385,7 @@ void main() {
|
||||
darwinArch: DarwinArch.armv7,
|
||||
bitcode: true,
|
||||
splitDebugInfo: null,
|
||||
dartObfuscation: false,
|
||||
);
|
||||
|
||||
expect(genSnapshotExitCode, 0);
|
||||
@ -441,6 +446,7 @@ void main() {
|
||||
darwinArch: DarwinArch.armv7,
|
||||
bitcode: false,
|
||||
splitDebugInfo: null,
|
||||
dartObfuscation: false,
|
||||
);
|
||||
|
||||
expect(genSnapshotExitCode, 0);
|
||||
@ -469,7 +475,7 @@ void main() {
|
||||
expect(assemblyFile.readAsStringSync().contains('.section __DWARF'), true);
|
||||
}, overrides: contextOverrides);
|
||||
|
||||
testUsingContext('builds iOS armv7 profile AOT snapshot with dwarf stack traces', () async {
|
||||
testUsingContext('builds iOS armv7 profile AOT snapshot with dwarStackTraces', () async {
|
||||
globals.fs.file('main.dill').writeAsStringSync('binary magic');
|
||||
|
||||
final String outputPath = globals.fs.path.join('build', 'foo');
|
||||
@ -494,6 +500,7 @@ void main() {
|
||||
darwinArch: DarwinArch.armv7,
|
||||
bitcode: false,
|
||||
splitDebugInfo: 'foo',
|
||||
dartObfuscation: false,
|
||||
);
|
||||
|
||||
expect(genSnapshotExitCode, 0);
|
||||
@ -524,6 +531,60 @@ void main() {
|
||||
expect(assemblyFile.readAsStringSync().contains('.section __DWARF'), true);
|
||||
}, overrides: contextOverrides);
|
||||
|
||||
testUsingContext('builds iOS armv7 profile AOT snapshot with obfuscate', () async {
|
||||
globals.fs.file('main.dill').writeAsStringSync('binary magic');
|
||||
|
||||
final String outputPath = globals.fs.path.join('build', 'foo');
|
||||
globals.fs.directory(outputPath).createSync(recursive: true);
|
||||
|
||||
final String assembly = globals.fs.path.join(outputPath, 'snapshot_assembly.S');
|
||||
genSnapshot.outputs = <String, String>{
|
||||
assembly: 'blah blah\n.section __DWARF\nblah blah\n',
|
||||
};
|
||||
|
||||
final RunResult successResult = RunResult(ProcessResult(1, 0, '', ''), <String>['command name', 'arguments...']);
|
||||
when(mockXcode.cc(any)).thenAnswer((_) => Future<RunResult>.value(successResult));
|
||||
when(mockXcode.clang(any)).thenAnswer((_) => Future<RunResult>.value(successResult));
|
||||
|
||||
final int genSnapshotExitCode = await snapshotter.build(
|
||||
platform: TargetPlatform.ios,
|
||||
buildMode: BuildMode.profile,
|
||||
mainPath: 'main.dill',
|
||||
packagesPath: '.packages',
|
||||
outputPath: outputPath,
|
||||
darwinArch: DarwinArch.armv7,
|
||||
bitcode: false,
|
||||
splitDebugInfo: null,
|
||||
dartObfuscation: true,
|
||||
);
|
||||
|
||||
expect(genSnapshotExitCode, 0);
|
||||
expect(genSnapshot.callCount, 1);
|
||||
expect(genSnapshot.snapshotType.platform, TargetPlatform.ios);
|
||||
expect(genSnapshot.snapshotType.mode, BuildMode.profile);
|
||||
expect(genSnapshot.additionalArgs, <String>[
|
||||
'--deterministic',
|
||||
'--snapshot_kind=app-aot-assembly',
|
||||
'--assembly=$assembly',
|
||||
'--strip',
|
||||
'--no-sim-use-hardfp',
|
||||
'--no-use-integer-division',
|
||||
'--no-causal-async-stacks',
|
||||
'--lazy-async-stacks',
|
||||
'--obfuscate',
|
||||
'main.dill',
|
||||
]);
|
||||
verifyNever(mockXcode.cc(argThat(contains('-fembed-bitcode'))));
|
||||
verifyNever(mockXcode.clang(argThat(contains('-fembed-bitcode'))));
|
||||
|
||||
verify(mockXcode.cc(argThat(contains('-isysroot')))).called(1);
|
||||
verify(mockXcode.clang(argThat(contains('-isysroot')))).called(1);
|
||||
|
||||
final File assemblyFile = globals.fs.file(assembly);
|
||||
expect(assemblyFile.existsSync(), true);
|
||||
expect(assemblyFile.readAsStringSync().contains('.section __DWARF'), true);
|
||||
}, overrides: contextOverrides);
|
||||
|
||||
testUsingContext('builds iOS arm64 profile AOT snapshot', () async {
|
||||
globals.fs.file('main.dill').writeAsStringSync('binary magic');
|
||||
|
||||
@ -547,6 +608,7 @@ void main() {
|
||||
darwinArch: DarwinArch.arm64,
|
||||
bitcode: false,
|
||||
splitDebugInfo: null,
|
||||
dartObfuscation: false,
|
||||
);
|
||||
|
||||
expect(genSnapshotExitCode, 0);
|
||||
@ -587,6 +649,7 @@ void main() {
|
||||
darwinArch: DarwinArch.armv7,
|
||||
bitcode: false,
|
||||
splitDebugInfo: null,
|
||||
dartObfuscation: false,
|
||||
);
|
||||
|
||||
expect(genSnapshotExitCode, 0);
|
||||
@ -629,6 +692,7 @@ void main() {
|
||||
darwinArch: DarwinArch.arm64,
|
||||
bitcode: false,
|
||||
splitDebugInfo: null,
|
||||
dartObfuscation: false,
|
||||
);
|
||||
|
||||
expect(genSnapshotExitCode, 0);
|
||||
@ -660,6 +724,7 @@ void main() {
|
||||
outputPath: outputPath,
|
||||
bitcode: false,
|
||||
splitDebugInfo: null,
|
||||
dartObfuscation: false,
|
||||
);
|
||||
|
||||
expect(genSnapshotExitCode, 0);
|
||||
@ -694,6 +759,7 @@ void main() {
|
||||
outputPath: outputPath,
|
||||
bitcode: false,
|
||||
splitDebugInfo: 'foo',
|
||||
dartObfuscation: false,
|
||||
);
|
||||
|
||||
expect(genSnapshotExitCode, 0);
|
||||
@ -715,6 +781,41 @@ void main() {
|
||||
]);
|
||||
}, overrides: contextOverrides);
|
||||
|
||||
testUsingContext('builds shared library for android-arm with obfuscate', () async {
|
||||
globals.fs.file('main.dill').writeAsStringSync('binary magic');
|
||||
|
||||
final String outputPath = globals.fs.path.join('build', 'foo');
|
||||
globals.fs.directory(outputPath).createSync(recursive: true);
|
||||
|
||||
final int genSnapshotExitCode = await snapshotter.build(
|
||||
platform: TargetPlatform.android_arm,
|
||||
buildMode: BuildMode.release,
|
||||
mainPath: 'main.dill',
|
||||
packagesPath: '.packages',
|
||||
outputPath: outputPath,
|
||||
bitcode: false,
|
||||
splitDebugInfo: null,
|
||||
dartObfuscation: true,
|
||||
);
|
||||
|
||||
expect(genSnapshotExitCode, 0);
|
||||
expect(genSnapshot.callCount, 1);
|
||||
expect(genSnapshot.snapshotType.platform, TargetPlatform.android_arm);
|
||||
expect(genSnapshot.snapshotType.mode, BuildMode.release);
|
||||
expect(genSnapshot.additionalArgs, <String>[
|
||||
'--deterministic',
|
||||
'--snapshot_kind=app-aot-elf',
|
||||
'--elf=build/foo/app.so',
|
||||
'--strip',
|
||||
'--no-sim-use-hardfp',
|
||||
'--no-use-integer-division',
|
||||
'--no-causal-async-stacks',
|
||||
'--lazy-async-stacks',
|
||||
'--obfuscate',
|
||||
'main.dill',
|
||||
]);
|
||||
}, overrides: contextOverrides);
|
||||
|
||||
testUsingContext('builds shared library for android-arm without dwarf stack traces due to empty string', () async {
|
||||
globals.fs.file('main.dill').writeAsStringSync('binary magic');
|
||||
|
||||
@ -729,6 +830,7 @@ void main() {
|
||||
outputPath: outputPath,
|
||||
bitcode: false,
|
||||
splitDebugInfo: '',
|
||||
dartObfuscation: false,
|
||||
);
|
||||
|
||||
expect(genSnapshotExitCode, 0);
|
||||
@ -762,6 +864,7 @@ void main() {
|
||||
outputPath: outputPath,
|
||||
bitcode: false,
|
||||
splitDebugInfo: null,
|
||||
dartObfuscation: false,
|
||||
);
|
||||
|
||||
expect(genSnapshotExitCode, 0);
|
||||
@ -801,6 +904,7 @@ void main() {
|
||||
outputPath: outputPath,
|
||||
bitcode: false,
|
||||
splitDebugInfo: null,
|
||||
dartObfuscation: false,
|
||||
);
|
||||
|
||||
expect(genSnapshotExitCode, 0);
|
||||
|
Loading…
x
Reference in New Issue
Block a user