Unverified Commit 4b8efad9 authored by Dan Field's avatar Dan Field Committed by GitHub

Font subset in the tool (#49737)

parent a4093edb
...@@ -422,6 +422,7 @@ class CompileTest { ...@@ -422,6 +422,7 @@ class CompileTest {
case DeviceOperatingSystem.android: case DeviceOperatingSystem.android:
options.insert(0, 'apk'); options.insert(0, 'apk');
options.add('--target-platform=android-arm'); options.add('--target-platform=android-arm');
options.add('--tree-shake-icons');
watch.start(); watch.start();
await flutter('build', options: options); await flutter('build', options: options);
watch.stop(); watch.stop();
......
...@@ -62,6 +62,11 @@ ephemeral_dir="${SOURCE_ROOT}/Flutter/ephemeral" ...@@ -62,6 +62,11 @@ ephemeral_dir="${SOURCE_ROOT}/Flutter/ephemeral"
build_inputs_path="${ephemeral_dir}/FlutterInputs.xcfilelist" build_inputs_path="${ephemeral_dir}/FlutterInputs.xcfilelist"
build_outputs_path="${ephemeral_dir}/FlutterOutputs.xcfilelist" build_outputs_path="${ephemeral_dir}/FlutterOutputs.xcfilelist"
icon_tree_shaker_flag="false"
if [[ -n "$TREE_SHAKE_ICONS" ]]; then
icon_tree_shaker_flag="true"
fi
RunCommand "${FLUTTER_ROOT}/bin/flutter" --suppress-analytics \ RunCommand "${FLUTTER_ROOT}/bin/flutter" --suppress-analytics \
${verbose_flag} \ ${verbose_flag} \
${flutter_engine_flag} \ ${flutter_engine_flag} \
...@@ -70,6 +75,7 @@ RunCommand "${FLUTTER_ROOT}/bin/flutter" --suppress-analytics \ ...@@ -70,6 +75,7 @@ RunCommand "${FLUTTER_ROOT}/bin/flutter" --suppress-analytics \
-dTargetPlatform=darwin-x64 \ -dTargetPlatform=darwin-x64 \
-dTargetFile="${target_path}" \ -dTargetFile="${target_path}" \
-dBuildMode="${build_mode}" \ -dBuildMode="${build_mode}" \
-dFontSubset="${icon_tree_shaker_flag}" \
--build-inputs="${build_inputs_path}" \ --build-inputs="${build_inputs_path}" \
--build-outputs="${build_outputs_path}" \ --build-outputs="${build_outputs_path}" \
--output="${ephemeral_dir}" \ --output="${ephemeral_dir}" \
......
...@@ -603,6 +603,10 @@ class FlutterPlugin implements Plugin<Project> { ...@@ -603,6 +603,10 @@ class FlutterPlugin implements Plugin<Project> {
if (project.hasProperty('extra-gen-snapshot-options')) { if (project.hasProperty('extra-gen-snapshot-options')) {
extraGenSnapshotOptionsValue = project.property('extra-gen-snapshot-options') extraGenSnapshotOptionsValue = project.property('extra-gen-snapshot-options')
} }
Boolean treeShakeIconsOptionsValue = false
if (project.hasProperty('tree-shake-icons')) {
treeShakeIconsOptionsValue = project.property('tree-shake-icons').toBoolean()
}
def targetPlatforms = getTargetPlatforms() def targetPlatforms = getTargetPlatforms()
def addFlutterDeps = { variant -> def addFlutterDeps = { variant ->
if (shouldSplitPerAbi()) { if (shouldSplitPerAbi()) {
...@@ -637,6 +641,7 @@ class FlutterPlugin implements Plugin<Project> { ...@@ -637,6 +641,7 @@ class FlutterPlugin implements Plugin<Project> {
intermediateDir project.file("${project.buildDir}/${AndroidProject.FD_INTERMEDIATES}/flutter/${variant.name}/") intermediateDir project.file("${project.buildDir}/${AndroidProject.FD_INTERMEDIATES}/flutter/${variant.name}/")
extraFrontEndOptions extraFrontEndOptionsValue extraFrontEndOptions extraFrontEndOptionsValue
extraGenSnapshotOptions extraGenSnapshotOptionsValue extraGenSnapshotOptions extraGenSnapshotOptionsValue
treeShakeIcons treeShakeIconsOptionsValue
} }
File libJar = project.file("${project.buildDir}/${AndroidProject.FD_INTERMEDIATES}/flutter/${variant.name}/libs.jar") File libJar = project.file("${project.buildDir}/${AndroidProject.FD_INTERMEDIATES}/flutter/${variant.name}/libs.jar")
Task packFlutterAppAotTask = project.tasks.create(name: "packLibs${FLUTTER_BUILD_PREFIX}${variant.name.capitalize()}", type: Jar) { Task packFlutterAppAotTask = project.tasks.create(name: "packLibs${FLUTTER_BUILD_PREFIX}${variant.name.capitalize()}", type: Jar) {
...@@ -769,6 +774,8 @@ abstract class BaseFlutterTask extends DefaultTask { ...@@ -769,6 +774,8 @@ abstract class BaseFlutterTask extends DefaultTask {
String extraFrontEndOptions String extraFrontEndOptions
@Optional @Input @Optional @Input
String extraGenSnapshotOptions String extraGenSnapshotOptions
@Optional @Input
Boolean treeShakeIcons
@OutputFiles @OutputFiles
FileCollection getDependenciesFiles() { FileCollection getDependenciesFiles() {
...@@ -825,6 +832,9 @@ abstract class BaseFlutterTask extends DefaultTask { ...@@ -825,6 +832,9 @@ abstract class BaseFlutterTask extends DefaultTask {
if (extraFrontEndOptions != null) { if (extraFrontEndOptions != null) {
args "-dExtraFrontEndOptions=${extraFrontEndOptions}" args "-dExtraFrontEndOptions=${extraFrontEndOptions}"
} }
if (treeShakeIcons == true) {
args "-dTreeShakeIcons=true"
}
if (extraGenSnapshotOptions != null) { if (extraGenSnapshotOptions != null) {
args "--ExtraGenSnapshotOptions=${extraGenSnapshotOptions}" args "--ExtraGenSnapshotOptions=${extraGenSnapshotOptions}"
} }
......
...@@ -338,6 +338,9 @@ Future<void> buildGradleApp({ ...@@ -338,6 +338,9 @@ Future<void> buildGradleApp({
if (androidBuildInfo.fastStart) { if (androidBuildInfo.fastStart) {
command.add('-Pfast-start=true'); command.add('-Pfast-start=true');
} }
if (androidBuildInfo.buildInfo.treeShakeIcons) {
command.add('-Ptree-shake-icons=true');
}
command.add(assembleTask); command.add(assembleTask);
GradleHandledError detectedGradleError; GradleHandledError detectedGradleError;
...@@ -546,6 +549,9 @@ Future<void> buildGradleAar({ ...@@ -546,6 +549,9 @@ Future<void> buildGradleAar({
command.add('-Plocal-engine-repo=${localEngineRepo.path}'); command.add('-Plocal-engine-repo=${localEngineRepo.path}');
command.add('-Plocal-engine-build-mode=${androidBuildInfo.buildInfo.modeName}'); command.add('-Plocal-engine-build-mode=${androidBuildInfo.buildInfo.modeName}');
command.add('-Plocal-engine-out=${localEngineArtifacts.engineOutPath}'); command.add('-Plocal-engine-out=${localEngineArtifacts.engineOutPath}');
if (androidBuildInfo.buildInfo.treeShakeIcons) {
command.add('-Pfont-subset=true');
}
// Copy the local engine repo in the output directory. // Copy the local engine repo in the output directory.
try { try {
...@@ -738,10 +744,11 @@ Future<void> buildPluginsAsAar( ...@@ -738,10 +744,11 @@ Future<void> buildPluginsAsAar(
try { try {
await buildGradleAar( await buildGradleAar(
project: FlutterProject.fromDirectory(pluginDirectory), project: FlutterProject.fromDirectory(pluginDirectory),
androidBuildInfo: const AndroidBuildInfo( androidBuildInfo: AndroidBuildInfo(
BuildInfo( BuildInfo(
BuildMode.release, // Plugins are built as release. BuildMode.release, // Plugins are built as release.
null, // Plugins don't define flavors. null, // Plugins don't define flavors.
treeShakeIcons: androidBuildInfo.buildInfo.treeShakeIcons,
), ),
), ),
target: '', target: '',
......
...@@ -36,6 +36,7 @@ class AotBuilder { ...@@ -36,6 +36,7 @@ class AotBuilder {
List<String> extraFrontEndOptions, List<String> extraFrontEndOptions,
List<String> extraGenSnapshotOptions, List<String> extraGenSnapshotOptions,
@required List<String> dartDefines, @required List<String> dartDefines,
@required bool treeShakeIcons,
}) async { }) async {
if (platform == null) { if (platform == null) {
throwToolExit('No AOT build platform specified'); throwToolExit('No AOT build platform specified');
......
...@@ -367,7 +367,10 @@ class CachedArtifacts extends Artifacts { ...@@ -367,7 +367,10 @@ class CachedArtifacts extends Artifacts {
return _fileSystem.path.join(dartPackageDirectory.path, _artifactToFileName(artifact)); return _fileSystem.path.join(dartPackageDirectory.path, _artifactToFileName(artifact));
case Artifact.fontSubset: case Artifact.fontSubset:
case Artifact.constFinder: case Artifact.constFinder:
return _cache.getArtifactDirectory('font-subset').childFile(_artifactToFileName(artifact, platform, mode)).path; return _cache.getArtifactDirectory('engine')
.childDirectory(getNameForTargetPlatform(platform))
.childFile(_artifactToFileName(artifact, platform, mode))
.path;
default: default:
assert(false, 'Artifact $artifact not available for platform $platform.'); assert(false, 'Artifact $artifact not available for platform $platform.');
return null; return null;
......
...@@ -21,6 +21,8 @@ const AssetBundleFactory _kManifestFactory = _ManifestAssetBundleFactory(); ...@@ -21,6 +21,8 @@ const AssetBundleFactory _kManifestFactory = _ManifestAssetBundleFactory();
const String defaultManifestPath = 'pubspec.yaml'; const String defaultManifestPath = 'pubspec.yaml';
const String kFontManifestJson = 'FontManifest.json';
/// Injected factory class for spawning [AssetBundle] instances. /// Injected factory class for spawning [AssetBundle] instances.
abstract class AssetBundleFactory { abstract class AssetBundleFactory {
/// The singleton instance, pulled from the [AppContext]. /// The singleton instance, pulled from the [AppContext].
...@@ -71,7 +73,6 @@ class _ManifestAssetBundle implements AssetBundle { ...@@ -71,7 +73,6 @@ class _ManifestAssetBundle implements AssetBundle {
DateTime _lastBuildTimestamp; DateTime _lastBuildTimestamp;
static const String _assetManifestJson = 'AssetManifest.json'; static const String _assetManifestJson = 'AssetManifest.json';
static const String _fontManifestJson = 'FontManifest.json';
static const String _fontSetMaterial = 'material'; static const String _fontSetMaterial = 'material';
static const String _license = 'LICENSE'; static const String _license = 'LICENSE';
...@@ -242,7 +243,7 @@ class _ManifestAssetBundle implements AssetBundle { ...@@ -242,7 +243,7 @@ class _ManifestAssetBundle implements AssetBundle {
entries[_assetManifestJson] = _createAssetManifest(assetVariants); entries[_assetManifestJson] = _createAssetManifest(assetVariants);
entries[_fontManifestJson] = DevFSStringContent(json.encode(fonts)); entries[kFontManifestJson] = DevFSStringContent(json.encode(fonts));
// TODO(ianh): Only do the following line if we've changed packages or if our LICENSE file changed // TODO(ianh): Only do the following line if we've changed packages or if our LICENSE file changed
entries[_license] = _obtainLicenses(packageMap, assetBasePath, reportPackages: reportLicensedPackages); entries[_license] = _obtainLicenses(packageMap, assetBasePath, reportPackages: reportLicensedPackages);
......
...@@ -2,8 +2,11 @@ ...@@ -2,8 +2,11 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'package:meta/meta.dart';
import 'base/context.dart'; import 'base/context.dart';
import 'base/utils.dart'; import 'base/utils.dart';
import 'build_system/targets/icon_tree_shaker.dart';
import 'globals.dart' as globals; import 'globals.dart' as globals;
/// Information about a build to be performed or used. /// Information about a build to be performed or used.
...@@ -18,10 +21,14 @@ class BuildInfo { ...@@ -18,10 +21,14 @@ class BuildInfo {
this.fileSystemScheme, this.fileSystemScheme,
this.buildNumber, this.buildNumber,
this.buildName, this.buildName,
@required this.treeShakeIcons,
}); });
final BuildMode mode; final BuildMode mode;
/// Whether the build should subdset icon fonts.
final bool treeShakeIcons;
/// Represents a custom Android product flavor or an Xcode scheme, null for /// Represents a custom Android product flavor or an Xcode scheme, null for
/// using the default. /// using the default.
/// ///
...@@ -55,10 +62,10 @@ class BuildInfo { ...@@ -55,10 +62,10 @@ class BuildInfo {
/// On Xcode builds it is used as CFBundleShortVersionString, /// On Xcode builds it is used as CFBundleShortVersionString,
final String buildName; final String buildName;
static const BuildInfo debug = BuildInfo(BuildMode.debug, null); static const BuildInfo debug = BuildInfo(BuildMode.debug, null, treeShakeIcons: false);
static const BuildInfo profile = BuildInfo(BuildMode.profile, null); static const BuildInfo profile = BuildInfo(BuildMode.profile, null, treeShakeIcons: kIconTreeShakerEnabledDefault);
static const BuildInfo jitRelease = BuildInfo(BuildMode.jitRelease, null); static const BuildInfo jitRelease = BuildInfo(BuildMode.jitRelease, null, treeShakeIcons: kIconTreeShakerEnabledDefault);
static const BuildInfo release = BuildInfo(BuildMode.release, null); static const BuildInfo release = BuildInfo(BuildMode.release, null, treeShakeIcons: kIconTreeShakerEnabledDefault);
/// Returns whether a debug build is requested. /// Returns whether a debug build is requested.
/// ///
......
...@@ -12,6 +12,7 @@ import '../depfile.dart'; ...@@ -12,6 +12,7 @@ import '../depfile.dart';
import '../exceptions.dart'; import '../exceptions.dart';
import 'assets.dart'; import 'assets.dart';
import 'dart.dart'; import 'dart.dart';
import 'icon_tree_shaker.dart';
/// Prepares the asset bundle in the format expected by flutter.gradle. /// Prepares the asset bundle in the format expected by flutter.gradle.
/// ///
...@@ -25,6 +26,7 @@ abstract class AndroidAssetBundle extends Target { ...@@ -25,6 +26,7 @@ abstract class AndroidAssetBundle extends Target {
@override @override
List<Source> get inputs => const <Source>[ List<Source> get inputs => const <Source>[
Source.pattern('{BUILD_DIR}/app.dill'), Source.pattern('{BUILD_DIR}/app.dill'),
...IconTreeShaker.inputs,
]; ];
@override @override
......
...@@ -12,6 +12,8 @@ import '../../plugins.dart'; ...@@ -12,6 +12,8 @@ import '../../plugins.dart';
import '../../project.dart'; import '../../project.dart';
import '../build_system.dart'; import '../build_system.dart';
import '../depfile.dart'; import '../depfile.dart';
import 'dart.dart';
import 'icon_tree_shaker.dart';
/// A helper function to copy an asset bundle into an [environment]'s output /// A helper function to copy an asset bundle into an [environment]'s output
/// directory. /// directory.
...@@ -31,6 +33,16 @@ Future<Depfile> copyAssets(Environment environment, Directory outputDirectory) a ...@@ -31,6 +33,16 @@ Future<Depfile> copyAssets(Environment environment, Directory outputDirectory) a
pubspecFile, pubspecFile,
]; ];
final List<File> outputs = <File>[]; final List<File> outputs = <File>[];
final IconTreeShaker iconTreeShaker = IconTreeShaker(
environment,
assetBundle.entries[kFontManifestJson] as DevFSStringContent,
processManager: globals.processManager,
logger: globals.logger,
fileSystem: globals.fs,
artifacts: globals.artifacts,
);
await Future.wait<void>( await Future.wait<void>(
assetBundle.entries.entries.map<Future<void>>((MapEntry<String, DevFSContent> entry) async { assetBundle.entries.entries.map<Future<void>>((MapEntry<String, DevFSContent> entry) async {
final PoolResource resource = await pool.request(); final PoolResource resource = await pool.request();
...@@ -46,7 +58,13 @@ Future<Depfile> copyAssets(Environment environment, Directory outputDirectory) a ...@@ -46,7 +58,13 @@ Future<Depfile> copyAssets(Environment environment, Directory outputDirectory) a
final DevFSContent content = entry.value; final DevFSContent content = entry.value;
if (content is DevFSFileContent && content.file is File) { if (content is DevFSFileContent && content.file is File) {
inputs.add(globals.fs.file(content.file.path)); inputs.add(globals.fs.file(content.file.path));
await (content.file as File).copy(file.path); if (!await iconTreeShaker.subsetFont(
inputPath: content.file.path,
outputPath: file.path,
relativePath: entry.key,
)) {
await (content.file as File).copy(file.path);
}
} else { } else {
await file.writeAsBytes(await entry.value.contentsAsBytes()); await file.writeAsBytes(await entry.value.contentsAsBytes());
} }
...@@ -65,11 +83,14 @@ class CopyAssets extends Target { ...@@ -65,11 +83,14 @@ class CopyAssets extends Target {
String get name => 'copy_assets'; String get name => 'copy_assets';
@override @override
List<Target> get dependencies => const <Target>[]; List<Target> get dependencies => const <Target>[
KernelSnapshot(),
];
@override @override
List<Source> get inputs => const <Source>[ List<Source> get inputs => const <Source>[
Source.pattern('{FLUTTER_ROOT}/packages/flutter_tools/lib/src/build_system/targets/assets.dart'), Source.pattern('{FLUTTER_ROOT}/packages/flutter_tools/lib/src/build_system/targets/assets.dart'),
...IconTreeShaker.inputs,
]; ];
@override @override
......
...@@ -14,6 +14,7 @@ import '../build_system.dart'; ...@@ -14,6 +14,7 @@ import '../build_system.dart';
import '../depfile.dart'; import '../depfile.dart';
import '../exceptions.dart'; import '../exceptions.dart';
import 'assets.dart'; import 'assets.dart';
import 'icon_tree_shaker.dart';
/// The define to pass a [BuildMode]. /// The define to pass a [BuildMode].
const String kBuildMode= 'BuildMode'; const String kBuildMode= 'BuildMode';
...@@ -75,6 +76,7 @@ class CopyFlutterBundle extends Target { ...@@ -75,6 +76,7 @@ class CopyFlutterBundle extends Target {
Source.artifact(Artifact.vmSnapshotData, mode: BuildMode.debug), Source.artifact(Artifact.vmSnapshotData, mode: BuildMode.debug),
Source.artifact(Artifact.isolateSnapshotData, mode: BuildMode.debug), Source.artifact(Artifact.isolateSnapshotData, mode: BuildMode.debug),
Source.pattern('{BUILD_DIR}/app.dill'), Source.pattern('{BUILD_DIR}/app.dill'),
...IconTreeShaker.inputs,
]; ];
@override @override
......
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:meta/meta.dart';
import 'package:process/process.dart';
import '../../artifacts.dart';
import '../../base/common.dart';
import '../../base/file_system.dart';
import '../../base/io.dart';
import '../../base/logger.dart';
import '../../convert.dart';
import '../../devfs.dart';
import '../build_system.dart';
import 'dart.dart';
/// The build define controlling whether icon fonts should be stripped down to
/// only the glyphs used by the application.
const String kIconTreeShakerFlag = 'TreeShakeIcons';
/// Whether icon font subsetting is enabled by default.
const bool kIconTreeShakerEnabledDefault = false;
List<Map<String, dynamic>> _getList(dynamic object, String errorMessage) {
try {
return (object as List<dynamic>).cast<Map<String, dynamic>>();
} on CastError catch (_) {
throw IconTreeShakerException._(errorMessage);
}
}
/// A class that wraps the functionality of the const finder package and the
/// font subset utility to tree shake unused icons from fonts.
class IconTreeShaker {
/// Creates a wrapper for icon font subsetting.
///
/// The environment parameter must not be null.
///
/// If the `fontManifest` parameter is null, [enabled] will return false since
/// there are no fonts to shake.
///
/// The constructor will validate the environment and print a warning if
/// font subsetting has been requested in a debug build mode.
IconTreeShaker(
this._environment,
DevFSStringContent fontManifest, {
@required ProcessManager processManager,
@required Logger logger,
@required FileSystem fileSystem,
@required Artifacts artifacts,
}) : assert(_environment != null),
assert(processManager != null),
assert(logger != null),
assert(fileSystem != null),
assert(artifacts != null),
_processManager = processManager,
_logger = logger,
_fs = fileSystem,
_artifacts = artifacts,
_fontManifest = fontManifest?.string {
if (_environment.defines[kIconTreeShakerFlag] == 'true' &&
_environment.defines[kBuildMode] == 'debug') {
logger.printError('Font subetting is not supported in debug mode. The '
'--tree-shake-icons flag will be ignored.');
}
}
/// The [Source] inputs that targets using this should depend on.
///
/// See [Target.inputs].
static const List<Source> inputs = <Source>[
Source.pattern('{FLUTTER_ROOT}/packages/flutter_tools/lib/src/build_system/targets/icon_tree_shaker.dart'),
Source.artifact(Artifact.constFinder),
Source.artifact(Artifact.fontSubset),
];
final Environment _environment;
final String _fontManifest;
Map<String, _IconTreeShakerData> _iconData;
final ProcessManager _processManager;
final Logger _logger;
final FileSystem _fs;
final Artifacts _artifacts;
/// Whether font subsetting should be used for this [Environment].
bool get enabled => _fontManifest != null
&& _environment.defines[kIconTreeShakerFlag] == 'true'
&& _environment.defines[kBuildMode] != 'debug';
/// Fills the [_iconData] map.
Future<Map<String, _IconTreeShakerData>> _getIconData(Environment environment) async {
if (!enabled) {
return null;
}
final File appDill = environment.buildDir.childFile('app.dill');
if (!appDill.existsSync()) {
throw IconTreeShakerException._('Expected to find kernel file at ${appDill.path}, but no file found.');
}
final File constFinder = _fs.file(
_artifacts.getArtifactPath(Artifact.constFinder),
);
final File dart = _fs.file(
_artifacts.getArtifactPath(Artifact.engineDartBinary),
);
final Map<String, List<int>> iconData = await _findConstants(
dart,
constFinder,
appDill,
);
final Set<String> familyKeys = iconData.keys.toSet();
final Map<String, String> fonts = await _parseFontJson(
_fontManifest,
familyKeys,
);
if (fonts.length != iconData.length) {
throwToolExit('Expected to find fonts for ${iconData.keys}, but found '
'${fonts.keys}. This usually means you are refering to '
'font families in an IconData class but not including them '
'in the assets section of your pubspec.yaml, are missing '
'the package that would include them, or are missing '
'"uses-material-design: true".');
}
final Map<String, _IconTreeShakerData> result = <String, _IconTreeShakerData>{};
for (final MapEntry<String, String> entry in fonts.entries) {
result[entry.value] = _IconTreeShakerData(
family: entry.key,
relativePath: entry.value,
codePoints: iconData[entry.key],
);
}
return result;
}
/// Calls font-subset, which transforms the `inputPath` font file to a
/// subsetted version at `outputPath`.
///
/// The `relativePath` parameter
///
/// All parameters are required.
///
/// If [enabled] is false, or the relative path is not recognized as an icon
/// font used in the Flutter application, this returns false.
/// If the font-subset subprocess fails, it will [throwToolExit].
/// Otherwise, it will return true.
Future<bool> subsetFont({
@required String inputPath,
@required String outputPath,
@required String relativePath,
}) async {
if (!enabled) {
return false;
}
_iconData ??= await _getIconData(_environment);
assert(_iconData != null);
final _IconTreeShakerData iconTreeShakerData = _iconData[relativePath];
if (iconTreeShakerData == null) {
return false;
}
final File fontSubset = _fs.file(
_artifacts.getArtifactPath(Artifact.fontSubset),
);
if (!fontSubset.existsSync()) {
throw IconTreeShakerException._('The font-subset utility is missing. Run "flutter doctor".');
}
final List<String> cmd = <String>[
fontSubset.path,
outputPath,
inputPath,
];
final String codePoints = iconTreeShakerData.codePoints.join(' ');
_logger.printTrace('Running font-subset: ${cmd.join(' ')}, '
'using codepoints $codePoints');
final Process fontSubsetProcess = await _processManager.start(cmd);
try {
fontSubsetProcess.stdin.writeln(codePoints);
await fontSubsetProcess.stdin.flush();
await fontSubsetProcess.stdin.close();
} on Exception catch (_) {
// handled by checking the exit code.
} on OSError catch (_) {
// handled by checking the exit code.
}
final int code = await fontSubsetProcess.exitCode;
if (code != 0) {
_logger.printTrace(await utf8.decodeStream(fontSubsetProcess.stdout));
_logger.printError(await utf8.decodeStream(fontSubsetProcess.stderr));
throw IconTreeShakerException._('Font subsetting failed with exit code $code.');
}
return true;
}
/// Returns a map of { fontFamly: relativePath } pairs.
Future<Map<String, String>> _parseFontJson(
String fontManifestData,
Set<String> families,
) async {
final Map<String, String> result = <String, String>{};
final List<Map<String, dynamic>> fontList = _getList(
json.decode(fontManifestData),
'FontManifest.json invalid: expected top level to be a list of objects.',
);
for (final Map<String, dynamic> map in fontList) {
if (map['family'] is! String) {
throw IconTreeShakerException._(
'FontManifest.json invalid: expected the family value to be a string, '
'got: ${map['family']}.');
}
final String familyKey = map['family'] as String;
if (!families.contains(familyKey)) {
continue;
}
final List<Map<String, dynamic>> fonts = _getList(
map['fonts'],
'FontManifest.json invalid: expected "fonts" to be a list of objects.',
);
if (fonts.length != 1) {
throw IconTreeShakerException._(
'This tool cannot process icon fonts with multiple fonts in a '
'single family.');
}
if (fonts.first['asset'] is! String) {
throw IconTreeShakerException._(
'FontManifest.json invalid: expected "asset" value to be a string, '
'got: ${map['assets']}.');
}
result[familyKey] = fonts.first['asset'] as String;
}
return result;
}
Future<Map<String, List<int>>> _findConstants(
File dart,
File constFinder,
File appDill,
) async {
final List<String> cmd = <String>[
dart.path,
constFinder.path,
'--kernel-file', appDill.path,
'--class-library-uri', 'package:flutter/src/widgets/icon_data.dart',
'--class-name', 'IconData',
];
_logger.printTrace('Running command: ${cmd.join(' ')}');
final ProcessResult constFinderProcessResult = await _processManager.run(cmd);
if (constFinderProcessResult.exitCode != 0) {
throw IconTreeShakerException._('ConstFinder failure: ${constFinderProcessResult.stderr}');
}
final dynamic jsonDecode = json.decode(constFinderProcessResult.stdout as String);
if (jsonDecode is! Map<String, dynamic>) {
throw IconTreeShakerException._(
'Invalid ConstFinder output: expected a top level JSON object, '
'got $jsonDecode.');
}
final Map<String, dynamic> constFinderMap = jsonDecode as Map<String, dynamic>;
final _ConstFinderResult constFinderResult = _ConstFinderResult(constFinderMap);
if (constFinderResult.hasNonConstantLocations) {
_logger.printError('This application cannot tree shake icons fonts. '
'It has non-constant instances of IconData at the '
'following locations:', emphasis: true);
for (final Map<String, dynamic> location in constFinderResult.nonConstantLocations) {
_logger.printError(
'- ${location['file']}:${location['line']}:${location['column']}',
indent: 2,
hangingIndent: 4,
);
}
throwToolExit('Avoid non-constant invocations of IconData or try to '
'build again with --no-tree-shake-icons.');
}
return _parseConstFinderResult(constFinderResult);
}
Map<String, List<int>> _parseConstFinderResult(_ConstFinderResult consts) {
final Map<String, List<int>> result = <String, List<int>>{};
for (final Map<String, dynamic> iconDataMap in consts.constantInstances) {
if ((iconDataMap['fontPackage'] ?? '') is! String || // Null is ok here.
iconDataMap['fontFamily'] is! String ||
iconDataMap['codePoint'] is! int) {
throw IconTreeShakerException._(
'Invalid ConstFinder result. Expected "fontPackage" to be a String, '
'"fontFamily" to be a String, and "codePoint" to be an int, '
'got: $iconDataMap.');
}
final String package = iconDataMap['fontPackage'] as String;
final String family = iconDataMap['fontFamily'] as String;
final String key = package == null
? family
: 'packages/$package/$family';
result[key] ??= <int>[];
result[key].add(iconDataMap['codePoint'] as int);
}
return result;
}
}
class _ConstFinderResult {
_ConstFinderResult(this.result);
final Map<String, dynamic> result;
List<Map<String, dynamic>> _constantInstances;
List<Map<String, dynamic>> get constantInstances {
_constantInstances ??= _getList(
result['constantInstances'],
'Invalid ConstFinder output: Expected "constInstances" to be a list of objects.',
);
return _constantInstances;
}
List<Map<String, dynamic>> _nonConstantLocations;
List<Map<String, dynamic>> get nonConstantLocations {
_nonConstantLocations ??= _getList(
result['nonConstantLocations'],
'Invalid ConstFinder output: Expected "nonConstLocations" to be a list ofobjects',
);
return _nonConstantLocations;
}
bool get hasNonConstantLocations => nonConstantLocations.isNotEmpty;
}
/// The font family name, relative path to font file, and list of code points
/// the application is using.
class _IconTreeShakerData {
/// All parameters are required.
const _IconTreeShakerData({
@required this.family,
@required this.relativePath,
@required this.codePoints,
}) : assert(family != null),
assert(relativePath != null),
assert(codePoints != null);
/// The font family name, e.g. "MaterialIcons".
final String family;
/// The relative path to the font file.
final String relativePath;
/// The list of code points for the font.
final List<int> codePoints;
@override
String toString() => 'FontSubsetData($family, $relativePath, $codePoints)';
}
class IconTreeShakerException implements Exception {
IconTreeShakerException._(this.message);
final String message;
@override
String toString() => 'FontSubset error: $message';
}
...@@ -11,6 +11,7 @@ import '../depfile.dart'; ...@@ -11,6 +11,7 @@ import '../depfile.dart';
import '../exceptions.dart'; import '../exceptions.dart';
import 'assets.dart'; import 'assets.dart';
import 'dart.dart'; import 'dart.dart';
import 'icon_tree_shaker.dart';
/// The only files/subdirectories we care out. /// The only files/subdirectories we care out.
const List<String> _kLinuxArtifacts = <String>[ const List<String> _kLinuxArtifacts = <String>[
...@@ -119,6 +120,7 @@ class DebugBundleLinuxAssets extends Target { ...@@ -119,6 +120,7 @@ class DebugBundleLinuxAssets extends Target {
Source.pattern('{BUILD_DIR}/app.dill'), Source.pattern('{BUILD_DIR}/app.dill'),
Source.pattern('{FLUTTER_ROOT}/packages/flutter_tools/lib/src/build_system/targets/linux.dart'), Source.pattern('{FLUTTER_ROOT}/packages/flutter_tools/lib/src/build_system/targets/linux.dart'),
Source.pattern('{PROJECT_DIR}/pubspec.yaml'), Source.pattern('{PROJECT_DIR}/pubspec.yaml'),
...IconTreeShaker.inputs,
]; ];
@override @override
......
...@@ -14,6 +14,7 @@ import '../depfile.dart'; ...@@ -14,6 +14,7 @@ import '../depfile.dart';
import '../exceptions.dart'; import '../exceptions.dart';
import 'assets.dart'; import 'assets.dart';
import 'dart.dart'; import 'dart.dart';
import 'icon_tree_shaker.dart';
const String _kOutputPrefix = '{OUTPUT_DIR}/FlutterMacOS.framework'; const String _kOutputPrefix = '{OUTPUT_DIR}/FlutterMacOS.framework';
...@@ -243,6 +244,7 @@ abstract class MacOSBundleFlutterAssets extends Target { ...@@ -243,6 +244,7 @@ abstract class MacOSBundleFlutterAssets extends Target {
@override @override
List<Source> get inputs => const <Source>[ List<Source> get inputs => const <Source>[
Source.pattern('{BUILD_DIR}/App.framework/App'), Source.pattern('{BUILD_DIR}/App.framework/App'),
...IconTreeShaker.inputs,
]; ];
@override @override
......
...@@ -15,6 +15,7 @@ import 'build_info.dart'; ...@@ -15,6 +15,7 @@ import 'build_info.dart';
import 'build_system/build_system.dart'; import 'build_system/build_system.dart';
import 'build_system/depfile.dart'; import 'build_system/depfile.dart';
import 'build_system/targets/dart.dart'; import 'build_system/targets/dart.dart';
import 'build_system/targets/icon_tree_shaker.dart';
import 'cache.dart'; import 'cache.dart';
import 'dart/package_map.dart'; import 'dart/package_map.dart';
import 'devfs.dart'; import 'devfs.dart';
...@@ -68,6 +69,7 @@ class BundleBuilder { ...@@ -68,6 +69,7 @@ class BundleBuilder {
List<String> extraGenSnapshotOptions = const <String>[], List<String> extraGenSnapshotOptions = const <String>[],
List<String> fileSystemRoots, List<String> fileSystemRoots,
String fileSystemScheme, String fileSystemScheme,
@required bool treeShakeIcons,
}) async { }) async {
mainPath ??= defaultMainPath; mainPath ??= defaultMainPath;
depfilePath ??= defaultDepfilePath; depfilePath ??= defaultDepfilePath;
...@@ -83,6 +85,7 @@ class BundleBuilder { ...@@ -83,6 +85,7 @@ class BundleBuilder {
depfilePath: depfilePath, depfilePath: depfilePath,
precompiled: precompiledSnapshot, precompiled: precompiledSnapshot,
trackWidgetCreation: trackWidgetCreation, trackWidgetCreation: trackWidgetCreation,
treeShakeIcons: treeShakeIcons,
); );
// Work around for flutter_tester placing kernel artifacts in odd places. // Work around for flutter_tester placing kernel artifacts in odd places.
if (applicationKernelFilePath != null) { if (applicationKernelFilePath != null) {
...@@ -107,6 +110,7 @@ Future<void> buildWithAssemble({ ...@@ -107,6 +110,7 @@ Future<void> buildWithAssemble({
@required String depfilePath, @required String depfilePath,
@required bool precompiled, @required bool precompiled,
bool trackWidgetCreation, bool trackWidgetCreation,
@required bool treeShakeIcons,
}) async { }) async {
// If the precompiled flag was not passed, force us into debug mode. // If the precompiled flag was not passed, force us into debug mode.
buildMode = precompiled ? buildMode : BuildMode.debug; buildMode = precompiled ? buildMode : BuildMode.debug;
...@@ -121,6 +125,7 @@ Future<void> buildWithAssemble({ ...@@ -121,6 +125,7 @@ Future<void> buildWithAssemble({
kBuildMode: getNameForBuildMode(buildMode), kBuildMode: getNameForBuildMode(buildMode),
kTargetPlatform: getNameForTargetPlatform(targetPlatform), kTargetPlatform: getNameForTargetPlatform(targetPlatform),
kTrackWidgetCreation: trackWidgetCreation?.toString(), kTrackWidgetCreation: trackWidgetCreation?.toString(),
kIconTreeShakerFlag: treeShakeIcons ? 'true' : null,
}, },
); );
final Target target = buildMode == BuildMode.debug final Target target = buildMode == BuildMode.debug
......
...@@ -34,6 +34,7 @@ class BuildAarCommand extends BuildSubCommand { ...@@ -34,6 +34,7 @@ class BuildAarCommand extends BuildSubCommand {
defaultsTo: true, defaultsTo: true,
help: 'Build a release version of the current project.', help: 'Build a release version of the current project.',
); );
addTreeShakeIconsFlag();
usesFlavorOption(); usesFlavorOption();
usesBuildNumberOption(); usesBuildNumberOption();
usesPubOption(); usesPubOption();
...@@ -104,7 +105,7 @@ class BuildAarCommand extends BuildSubCommand { ...@@ -104,7 +105,7 @@ class BuildAarCommand extends BuildSubCommand {
for (final String buildMode in const <String>['debug', 'profile', 'release']) { for (final String buildMode in const <String>['debug', 'profile', 'release']) {
if (boolArg(buildMode)) { if (boolArg(buildMode)) {
androidBuildInfo.add(AndroidBuildInfo( androidBuildInfo.add(AndroidBuildInfo(
BuildInfo(BuildMode.fromName(buildMode), stringArg('flavor')), BuildInfo(BuildMode.fromName(buildMode), stringArg('flavor'), treeShakeIcons: boolArg('tree-shake-icons')),
targetArchs: targetArchitectures, targetArchs: targetArchitectures,
)); ));
} }
......
...@@ -15,6 +15,7 @@ import 'build.dart'; ...@@ -15,6 +15,7 @@ import 'build.dart';
/// Builds AOT snapshots into platform specific library containers. /// Builds AOT snapshots into platform specific library containers.
class BuildAotCommand extends BuildSubCommand with TargetPlatformBasedDevelopmentArtifacts { class BuildAotCommand extends BuildSubCommand with TargetPlatformBasedDevelopmentArtifacts {
BuildAotCommand({bool verboseHelp = false, this.aotBuilder}) { BuildAotCommand({bool verboseHelp = false, this.aotBuilder}) {
addTreeShakeIconsFlag();
usesTargetOption(); usesTargetOption();
addBuildModeFlags(); addBuildModeFlags();
usesPubOption(); usesPubOption();
...@@ -88,6 +89,7 @@ class BuildAotCommand extends BuildSubCommand with TargetPlatformBasedDevelopmen ...@@ -88,6 +89,7 @@ class BuildAotCommand extends BuildSubCommand with TargetPlatformBasedDevelopmen
extraFrontEndOptions: stringsArg(FlutterOptions.kExtraFrontEndOptions), extraFrontEndOptions: stringsArg(FlutterOptions.kExtraFrontEndOptions),
extraGenSnapshotOptions: stringsArg(FlutterOptions.kExtraGenSnapshotOptions), extraGenSnapshotOptions: stringsArg(FlutterOptions.kExtraGenSnapshotOptions),
dartDefines: dartDefines, dartDefines: dartDefines,
treeShakeIcons: boolArg('tree-shake-icons'),
); );
return FlutterCommandResult.success(); return FlutterCommandResult.success();
} }
......
...@@ -18,6 +18,7 @@ import 'build.dart'; ...@@ -18,6 +18,7 @@ import 'build.dart';
class BuildApkCommand extends BuildSubCommand { class BuildApkCommand extends BuildSubCommand {
BuildApkCommand({bool verboseHelp = false}) { BuildApkCommand({bool verboseHelp = false}) {
addTreeShakeIconsFlag();
usesTargetOption(); usesTargetOption();
addBuildModeFlags(verboseHelp: verboseHelp); addBuildModeFlags(verboseHelp: verboseHelp);
usesFlavorOption(); usesFlavorOption();
...@@ -25,7 +26,6 @@ class BuildApkCommand extends BuildSubCommand { ...@@ -25,7 +26,6 @@ class BuildApkCommand extends BuildSubCommand {
usesBuildNumberOption(); usesBuildNumberOption();
usesBuildNameOption(); usesBuildNameOption();
addShrinkingFlag(); addShrinkingFlag();
argParser argParser
..addFlag('split-per-abi', ..addFlag('split-per-abi',
negatable: false, negatable: false,
......
...@@ -16,6 +16,7 @@ import 'build.dart'; ...@@ -16,6 +16,7 @@ import 'build.dart';
class BuildAppBundleCommand extends BuildSubCommand { class BuildAppBundleCommand extends BuildSubCommand {
BuildAppBundleCommand({bool verboseHelp = false}) { BuildAppBundleCommand({bool verboseHelp = false}) {
addTreeShakeIconsFlag();
usesTargetOption(); usesTargetOption();
addBuildModeFlags(); addBuildModeFlags();
usesFlavorOption(); usesFlavorOption();
......
...@@ -16,6 +16,7 @@ import 'build.dart'; ...@@ -16,6 +16,7 @@ import 'build.dart';
class BuildBundleCommand extends BuildSubCommand { class BuildBundleCommand extends BuildSubCommand {
BuildBundleCommand({bool verboseHelp = false, this.bundleBuilder}) { BuildBundleCommand({bool verboseHelp = false, this.bundleBuilder}) {
addTreeShakeIconsFlag();
usesTargetOption(); usesTargetOption();
usesFilesystemOptions(hide: !verboseHelp); usesFilesystemOptions(hide: !verboseHelp);
usesBuildNumberOption(); usesBuildNumberOption();
...@@ -138,6 +139,7 @@ class BuildBundleCommand extends BuildSubCommand { ...@@ -138,6 +139,7 @@ class BuildBundleCommand extends BuildSubCommand {
extraGenSnapshotOptions: stringsArg(FlutterOptions.kExtraGenSnapshotOptions), extraGenSnapshotOptions: stringsArg(FlutterOptions.kExtraGenSnapshotOptions),
fileSystemScheme: stringArg('filesystem-scheme'), fileSystemScheme: stringArg('filesystem-scheme'),
fileSystemRoots: stringsArg('filesystem-root'), fileSystemRoots: stringsArg('filesystem-root'),
treeShakeIcons: boolArg('tree-shake-icons'),
); );
return FlutterCommandResult.success(); return FlutterCommandResult.success();
} }
......
...@@ -17,6 +17,7 @@ import 'build.dart'; ...@@ -17,6 +17,7 @@ import 'build.dart';
/// A command to build a Fuchsia target. /// A command to build a Fuchsia target.
class BuildFuchsiaCommand extends BuildSubCommand { class BuildFuchsiaCommand extends BuildSubCommand {
BuildFuchsiaCommand({bool verboseHelp = false}) { BuildFuchsiaCommand({bool verboseHelp = false}) {
addTreeShakeIconsFlag();
usesTargetOption(); usesTargetOption();
addBuildModeFlags(verboseHelp: verboseHelp); addBuildModeFlags(verboseHelp: verboseHelp);
argParser.addOption( argParser.addOption(
......
...@@ -18,6 +18,7 @@ import 'build.dart'; ...@@ -18,6 +18,7 @@ import 'build.dart';
/// .ipas, see https://flutter.dev/docs/deployment/ios. /// .ipas, see https://flutter.dev/docs/deployment/ios.
class BuildIOSCommand extends BuildSubCommand { class BuildIOSCommand extends BuildSubCommand {
BuildIOSCommand() { BuildIOSCommand() {
addTreeShakeIconsFlag();
addBuildModeFlags(defaultToRelease: false); addBuildModeFlags(defaultToRelease: false);
usesTargetOption(); usesTargetOption();
usesFlavorOption(); usesFlavorOption();
......
...@@ -44,6 +44,7 @@ class BuildIOSFrameworkCommand extends BuildSubCommand { ...@@ -44,6 +44,7 @@ class BuildIOSFrameworkCommand extends BuildSubCommand {
_bundleBuilder = bundleBuilder, _bundleBuilder = bundleBuilder,
_cache = cache, _cache = cache,
_platform = platform { _platform = platform {
addTreeShakeIconsFlag();
usesTargetOption(); usesTargetOption();
usesFlavorOption(); usesFlavorOption();
usesPubOption(); usesPubOption();
...@@ -361,6 +362,7 @@ end ...@@ -361,6 +362,7 @@ end
mainPath: globals.fs.path.absolute(targetFile), mainPath: globals.fs.path.absolute(targetFile),
assetDirPath: destinationAppFrameworkDirectory.childDirectory('flutter_assets').path, assetDirPath: destinationAppFrameworkDirectory.childDirectory('flutter_assets').path,
precompiledSnapshot: mode != BuildMode.debug, precompiledSnapshot: mode != BuildMode.debug,
treeShakeIcons: boolArg('tree-shake-icons')
); );
} finally { } finally {
status.stop(); status.stop();
...@@ -426,6 +428,7 @@ end ...@@ -426,6 +428,7 @@ end
reportTimings: false, reportTimings: false,
iosBuildArchs: <DarwinArch>[DarwinArch.armv7, DarwinArch.arm64], iosBuildArchs: <DarwinArch>[DarwinArch.armv7, DarwinArch.arm64],
dartDefines: dartDefines, dartDefines: dartDefines,
treeShakeIcons: boolArg('tree-shake-icons'),
); );
} finally { } finally {
status.stop(); status.stop();
......
...@@ -17,6 +17,7 @@ import 'build.dart'; ...@@ -17,6 +17,7 @@ import 'build.dart';
/// A command to build a linux desktop target through a build shell script. /// A command to build a linux desktop target through a build shell script.
class BuildLinuxCommand extends BuildSubCommand { class BuildLinuxCommand extends BuildSubCommand {
BuildLinuxCommand() { BuildLinuxCommand() {
addTreeShakeIconsFlag();
addBuildModeFlags(); addBuildModeFlags();
usesTargetOption(); usesTargetOption();
} }
......
...@@ -17,6 +17,7 @@ import 'build.dart'; ...@@ -17,6 +17,7 @@ import 'build.dart';
/// A command to build a macOS desktop target through a build shell script. /// A command to build a macOS desktop target through a build shell script.
class BuildMacosCommand extends BuildSubCommand { class BuildMacosCommand extends BuildSubCommand {
BuildMacosCommand() { BuildMacosCommand() {
addTreeShakeIconsFlag();
usesTargetOption(); usesTargetOption();
addBuildModeFlags(); addBuildModeFlags();
} }
......
...@@ -15,6 +15,7 @@ import 'build.dart'; ...@@ -15,6 +15,7 @@ import 'build.dart';
class BuildWebCommand extends BuildSubCommand { class BuildWebCommand extends BuildSubCommand {
BuildWebCommand() { BuildWebCommand() {
addTreeShakeIconsFlag();
usesTargetOption(); usesTargetOption();
usesPubOption(); usesPubOption();
addBuildModeFlags(excludeDebug: true); addBuildModeFlags(excludeDebug: true);
......
...@@ -17,6 +17,7 @@ import 'build.dart'; ...@@ -17,6 +17,7 @@ import 'build.dart';
/// A command to build a windows desktop target through a build shell script. /// A command to build a windows desktop target through a build shell script.
class BuildWindowsCommand extends BuildSubCommand { class BuildWindowsCommand extends BuildSubCommand {
BuildWindowsCommand() { BuildWindowsCommand() {
addTreeShakeIconsFlag();
addBuildModeFlags(); addBuildModeFlags();
usesTargetOption(); usesTargetOption();
} }
......
...@@ -209,7 +209,6 @@ Future<XcodeBuildResult> buildXcodeProject({ ...@@ -209,7 +209,6 @@ Future<XcodeBuildResult> buildXcodeProject({
bool buildForDevice, bool buildForDevice,
DarwinArch activeArch, DarwinArch activeArch,
bool codesign = true, bool codesign = true,
}) async { }) async {
if (!upgradePbxProjWithFlutterAssets(app.project)) { if (!upgradePbxProjWithFlutterAssets(app.project)) {
return XcodeBuildResult(success: false); return XcodeBuildResult(success: false);
......
...@@ -428,7 +428,8 @@ class IOSSimulator extends Device { ...@@ -428,7 +428,8 @@ class IOSSimulator extends Device {
final BuildInfo debugBuildInfo = BuildInfo(BuildMode.debug, buildInfo.flavor, final BuildInfo debugBuildInfo = BuildInfo(BuildMode.debug, buildInfo.flavor,
trackWidgetCreation: buildInfo.trackWidgetCreation, trackWidgetCreation: buildInfo.trackWidgetCreation,
extraFrontEndOptions: buildInfo.extraFrontEndOptions, extraFrontEndOptions: buildInfo.extraFrontEndOptions,
extraGenSnapshotOptions: buildInfo.extraGenSnapshotOptions); extraGenSnapshotOptions: buildInfo.extraGenSnapshotOptions,
treeShakeIcons: buildInfo.treeShakeIcons);
final XcodeBuildResult buildResult = await buildXcodeProject( final XcodeBuildResult buildResult = await buildXcodeProject(
app: app, app: app,
...@@ -460,6 +461,7 @@ class IOSSimulator extends Device { ...@@ -460,6 +461,7 @@ class IOSSimulator extends Device {
precompiledSnapshot: false, precompiledSnapshot: false,
buildMode: buildInfo.mode, buildMode: buildInfo.mode,
trackWidgetCreation: buildInfo.trackWidgetCreation, trackWidgetCreation: buildInfo.trackWidgetCreation,
treeShakeIcons: false,
); );
} }
......
...@@ -216,6 +216,10 @@ List<String> _xcodeBuildSettingsLines({ ...@@ -216,6 +216,10 @@ List<String> _xcodeBuildSettingsLines({
xcodeBuildSettings.add('TRACK_WIDGET_CREATION=true'); xcodeBuildSettings.add('TRACK_WIDGET_CREATION=true');
} }
if (buildInfo.treeShakeIcons) {
xcodeBuildSettings.add('TREE_SHAKE_ICONS=true');
}
return xcodeBuildSettings; return xcodeBuildSettings;
} }
......
...@@ -85,6 +85,8 @@ Future<void> buildMacOS({ ...@@ -85,6 +85,8 @@ Future<void> buildMacOS({
'OBJROOT=${globals.fs.path.join(flutterBuildDir.absolute.path, 'Build', 'Intermediates.noindex')}', 'OBJROOT=${globals.fs.path.join(flutterBuildDir.absolute.path, 'Build', 'Intermediates.noindex')}',
'SYMROOT=${globals.fs.path.join(flutterBuildDir.absolute.path, 'Build', 'Products')}', 'SYMROOT=${globals.fs.path.join(flutterBuildDir.absolute.path, 'Build', 'Products')}',
'COMPILER_INDEX_STORE_ENABLE=NO', 'COMPILER_INDEX_STORE_ENABLE=NO',
if (buildInfo.treeShakeIcons)
'TREE_SHAKE_ICONS=true',
...environmentVariablesAsXcodeBuildSettings(globals.platform) ...environmentVariablesAsXcodeBuildSettings(globals.platform)
], trace: true); ], trace: true);
} finally { } finally {
......
...@@ -20,6 +20,7 @@ import '../base/time.dart'; ...@@ -20,6 +20,7 @@ import '../base/time.dart';
import '../base/user_messages.dart'; import '../base/user_messages.dart';
import '../base/utils.dart'; import '../base/utils.dart';
import '../build_info.dart'; import '../build_info.dart';
import '../build_system/targets/icon_tree_shaker.dart' show kIconTreeShakerEnabledDefault;
import '../bundle.dart' as bundle; import '../bundle.dart' as bundle;
import '../cache.dart'; import '../cache.dart';
import '../dart/package_map.dart'; import '../dart/package_map.dart';
...@@ -365,6 +366,14 @@ abstract class FlutterCommand extends Command<void> { ...@@ -365,6 +366,14 @@ abstract class FlutterCommand extends Command<void> {
help: 'Build a JIT release version of your app${defaultToRelease ? ' (default mode)' : ''}.'); help: 'Build a JIT release version of your app${defaultToRelease ? ' (default mode)' : ''}.');
} }
void addTreeShakeIconsFlag() {
argParser.addFlag('tree-shake-icons',
negatable: true,
defaultsTo: kIconTreeShakerEnabledDefault,
help: 'Tree shake icon fonts so that only glyphs used by the application remain.',
);
}
void addShrinkingFlag() { void addShrinkingFlag() {
argParser.addFlag('shrink', argParser.addFlag('shrink',
negatable: true, negatable: true,
...@@ -490,6 +499,9 @@ abstract class FlutterCommand extends Command<void> { ...@@ -490,6 +499,9 @@ abstract class FlutterCommand extends Command<void> {
buildName: argParser.options.containsKey('build-name') buildName: argParser.options.containsKey('build-name')
? stringArg('build-name') ? stringArg('build-name')
: null, : null,
treeShakeIcons: argParser.options.containsKey('tree-shake-icons')
? boolArg('tree-shake-icons')
: kIconTreeShakerEnabledDefault,
); );
} }
......
...@@ -151,6 +151,7 @@ class FlutterTesterDevice extends Device { ...@@ -151,6 +151,7 @@ class FlutterTesterDevice extends Device {
precompiledSnapshot: false, precompiledSnapshot: false,
trackWidgetCreation: buildInfo.trackWidgetCreation, trackWidgetCreation: buildInfo.trackWidgetCreation,
platform: getTargetPlatformForName(getNameForHostPlatform(getCurrentHostPlatform())), platform: getTargetPlatformForName(getNameForHostPlatform(getCurrentHostPlatform())),
treeShakeIcons: buildInfo.treeShakeIcons,
); );
command.add('--flutter-assets-dir=$assetDirPath'); command.add('--flutter-assets-dir=$assetDirPath');
......
...@@ -52,6 +52,8 @@ Future<void> buildWeb( ...@@ -52,6 +52,8 @@ Future<void> buildWeb(
kHasWebPlugins: hasWebPlugins.toString(), kHasWebPlugins: hasWebPlugins.toString(),
kDartDefines: jsonEncode(dartDefines), kDartDefines: jsonEncode(dartDefines),
kCspMode: csp.toString(), kCspMode: csp.toString(),
// TODO(dnfield): Enable font subset. We need to get a kernel file to do
// that. https://github.com/flutter/flutter/issues/49730
}, },
)); ));
if (!result.success) { if (!result.success) {
......
...@@ -5,7 +5,7 @@ author: Flutter Authors <flutter-dev@googlegroups.com> ...@@ -5,7 +5,7 @@ author: Flutter Authors <flutter-dev@googlegroups.com>
environment: environment:
# The pub client defaults to an <2.0.0 sdk constraint which we need to explicitly overwrite. # The pub client defaults to an <2.0.0 sdk constraint which we need to explicitly overwrite.
sdk: ">=2.2.2 <3.0.0" sdk: ">=2.7.0 <3.0.0"
dependencies: dependencies:
# To update these, use "flutter update-packages --force-upgrade". # To update these, use "flutter update-packages --force-upgrade".
......
...@@ -7,6 +7,7 @@ import 'package:file/memory.dart'; ...@@ -7,6 +7,7 @@ import 'package:file/memory.dart';
import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/build_system/build_system.dart'; import 'package:flutter_tools/src/build_system/build_system.dart';
import 'package:flutter_tools/src/build_system/targets/dart.dart'; import 'package:flutter_tools/src/build_system/targets/dart.dart';
import 'package:flutter_tools/src/build_system/targets/icon_tree_shaker.dart';
import 'package:flutter_tools/src/bundle.dart'; import 'package:flutter_tools/src/bundle.dart';
import 'package:flutter_tools/src/cache.dart'; import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/commands/build_bundle.dart'; import 'package:flutter_tools/src/commands/build_bundle.dart';
...@@ -47,6 +48,7 @@ void main() { ...@@ -47,6 +48,7 @@ void main() {
extraGenSnapshotOptions: anyNamed('extraGenSnapshotOptions'), extraGenSnapshotOptions: anyNamed('extraGenSnapshotOptions'),
fileSystemRoots: anyNamed('fileSystemRoots'), fileSystemRoots: anyNamed('fileSystemRoots'),
fileSystemScheme: anyNamed('fileSystemScheme'), fileSystemScheme: anyNamed('fileSystemScheme'),
treeShakeIcons: anyNamed('treeShakeIcons'),
), ),
).thenAnswer((_) => Future<void>.value()); ).thenAnswer((_) => Future<void>.value());
}); });
...@@ -217,6 +219,7 @@ void main() { ...@@ -217,6 +219,7 @@ void main() {
kBuildMode: 'debug', kBuildMode: 'debug',
kTargetPlatform: 'android-arm', kTargetPlatform: 'android-arm',
kTrackWidgetCreation: 'true', kTrackWidgetCreation: 'true',
kIconTreeShakerFlag: null,
}); });
return BuildResult(success: true); return BuildResult(success: true);
......
...@@ -101,7 +101,7 @@ void main() { ...@@ -101,7 +101,7 @@ void main() {
mockApk, mockApk,
prebuiltApplication: true, prebuiltApplication: true,
debuggingOptions: DebuggingOptions.disabled( debuggingOptions: DebuggingOptions.disabled(
const BuildInfo(BuildMode.release, null), const BuildInfo(BuildMode.release, null, treeShakeIcons: false),
cacheSkSL: true, cacheSkSL: true,
), ),
platformArgs: <String, dynamic>{}, platformArgs: <String, dynamic>{},
...@@ -153,7 +153,7 @@ void main() { ...@@ -153,7 +153,7 @@ void main() {
mockApk, mockApk,
prebuiltApplication: true, prebuiltApplication: true,
debuggingOptions: DebuggingOptions.disabled( debuggingOptions: DebuggingOptions.disabled(
const BuildInfo(BuildMode.release, null), const BuildInfo(BuildMode.release, null, treeShakeIcons: false),
), ),
platformArgs: <String, dynamic>{}, platformArgs: <String, dynamic>{},
); );
......
...@@ -98,33 +98,33 @@ void main() { ...@@ -98,33 +98,33 @@ void main() {
group('gradle tasks', () { group('gradle tasks', () {
test('assemble release', () { test('assemble release', () {
expect( expect(
getAssembleTaskFor(const BuildInfo(BuildMode.release, null)), getAssembleTaskFor(const BuildInfo(BuildMode.release, null, treeShakeIcons: false)),
equals('assembleRelease'), equals('assembleRelease'),
); );
expect( expect(
getAssembleTaskFor(const BuildInfo(BuildMode.release, 'flavorFoo')), getAssembleTaskFor(const BuildInfo(BuildMode.release, 'flavorFoo', treeShakeIcons: false)),
equals('assembleFlavorFooRelease'), equals('assembleFlavorFooRelease'),
); );
}); });
test('assemble debug', () { test('assemble debug', () {
expect( expect(
getAssembleTaskFor(const BuildInfo(BuildMode.debug, null)), getAssembleTaskFor(const BuildInfo(BuildMode.debug, null, treeShakeIcons: false)),
equals('assembleDebug'), equals('assembleDebug'),
); );
expect( expect(
getAssembleTaskFor(const BuildInfo(BuildMode.debug, 'flavorFoo')), getAssembleTaskFor(const BuildInfo(BuildMode.debug, 'flavorFoo', treeShakeIcons: false)),
equals('assembleFlavorFooDebug'), equals('assembleFlavorFooDebug'),
); );
}); });
test('assemble profile', () { test('assemble profile', () {
expect( expect(
getAssembleTaskFor(const BuildInfo(BuildMode.profile, null)), getAssembleTaskFor(const BuildInfo(BuildMode.profile, null, treeShakeIcons: false)),
equals('assembleProfile'), equals('assembleProfile'),
); );
expect( expect(
getAssembleTaskFor(const BuildInfo(BuildMode.profile, 'flavorFoo')), getAssembleTaskFor(const BuildInfo(BuildMode.profile, 'flavorFoo', treeShakeIcons: false)),
equals('assembleFlavorFooProfile'), equals('assembleFlavorFooProfile'),
); );
}); });
...@@ -135,7 +135,7 @@ void main() { ...@@ -135,7 +135,7 @@ void main() {
testUsingContext('Finds app bundle when flavor contains underscores in release mode', () { testUsingContext('Finds app bundle when flavor contains underscores in release mode', () {
final FlutterProject project = generateFakeAppBundle('foo_barRelease', 'app.aab'); final FlutterProject project = generateFakeAppBundle('foo_barRelease', 'app.aab');
final File bundle = findBundleFile(project, const BuildInfo(BuildMode.release, 'foo_bar')); final File bundle = findBundleFile(project, const BuildInfo(BuildMode.release, 'foo_bar', treeShakeIcons: false));
expect(bundle, isNotNull); expect(bundle, isNotNull);
expect(bundle.path, globals.fs.path.join('irrelevant', 'app', 'outputs', 'bundle', 'foo_barRelease', 'app.aab')); expect(bundle.path, globals.fs.path.join('irrelevant', 'app', 'outputs', 'bundle', 'foo_barRelease', 'app.aab'));
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
...@@ -145,7 +145,7 @@ void main() { ...@@ -145,7 +145,7 @@ void main() {
testUsingContext('Finds app bundle when flavor doesn\'t contain underscores in release mode', () { testUsingContext('Finds app bundle when flavor doesn\'t contain underscores in release mode', () {
final FlutterProject project = generateFakeAppBundle('fooRelease', 'app.aab'); final FlutterProject project = generateFakeAppBundle('fooRelease', 'app.aab');
final File bundle = findBundleFile(project, const BuildInfo(BuildMode.release, 'foo')); final File bundle = findBundleFile(project, const BuildInfo(BuildMode.release, 'foo', treeShakeIcons: false));
expect(bundle, isNotNull); expect(bundle, isNotNull);
expect(bundle.path, globals.fs.path.join('irrelevant', 'app', 'outputs', 'bundle', 'fooRelease', 'app.aab')); expect(bundle.path, globals.fs.path.join('irrelevant', 'app', 'outputs', 'bundle', 'fooRelease', 'app.aab'));
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
...@@ -155,7 +155,7 @@ void main() { ...@@ -155,7 +155,7 @@ void main() {
testUsingContext('Finds app bundle when no flavor is used in release mode', () { testUsingContext('Finds app bundle when no flavor is used in release mode', () {
final FlutterProject project = generateFakeAppBundle('release', 'app.aab'); final FlutterProject project = generateFakeAppBundle('release', 'app.aab');
final File bundle = findBundleFile(project, const BuildInfo(BuildMode.release, null)); final File bundle = findBundleFile(project, const BuildInfo(BuildMode.release, null, treeShakeIcons: false));
expect(bundle, isNotNull); expect(bundle, isNotNull);
expect(bundle.path, globals.fs.path.join('irrelevant', 'app', 'outputs', 'bundle', 'release', 'app.aab')); expect(bundle.path, globals.fs.path.join('irrelevant', 'app', 'outputs', 'bundle', 'release', 'app.aab'));
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
...@@ -165,7 +165,7 @@ void main() { ...@@ -165,7 +165,7 @@ void main() {
testUsingContext('Finds app bundle when flavor contains underscores in debug mode', () { testUsingContext('Finds app bundle when flavor contains underscores in debug mode', () {
final FlutterProject project = generateFakeAppBundle('foo_barDebug', 'app.aab'); final FlutterProject project = generateFakeAppBundle('foo_barDebug', 'app.aab');
final File bundle = findBundleFile(project, const BuildInfo(BuildMode.debug, 'foo_bar')); final File bundle = findBundleFile(project, const BuildInfo(BuildMode.debug, 'foo_bar', treeShakeIcons: false));
expect(bundle, isNotNull); expect(bundle, isNotNull);
expect(bundle.path, globals.fs.path.join('irrelevant', 'app', 'outputs', 'bundle', 'foo_barDebug', 'app.aab')); expect(bundle.path, globals.fs.path.join('irrelevant', 'app', 'outputs', 'bundle', 'foo_barDebug', 'app.aab'));
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
...@@ -175,7 +175,7 @@ void main() { ...@@ -175,7 +175,7 @@ void main() {
testUsingContext('Finds app bundle when flavor doesn\'t contain underscores in debug mode', () { testUsingContext('Finds app bundle when flavor doesn\'t contain underscores in debug mode', () {
final FlutterProject project = generateFakeAppBundle('fooDebug', 'app.aab'); final FlutterProject project = generateFakeAppBundle('fooDebug', 'app.aab');
final File bundle = findBundleFile(project, const BuildInfo(BuildMode.debug, 'foo')); final File bundle = findBundleFile(project, const BuildInfo(BuildMode.debug, 'foo', treeShakeIcons: false));
expect(bundle, isNotNull); expect(bundle, isNotNull);
expect(bundle.path, globals.fs.path.join('irrelevant', 'app', 'outputs', 'bundle', 'fooDebug', 'app.aab')); expect(bundle.path, globals.fs.path.join('irrelevant', 'app', 'outputs', 'bundle', 'fooDebug', 'app.aab'));
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
...@@ -185,7 +185,7 @@ void main() { ...@@ -185,7 +185,7 @@ void main() {
testUsingContext('Finds app bundle when no flavor is used in debug mode', () { testUsingContext('Finds app bundle when no flavor is used in debug mode', () {
final FlutterProject project = generateFakeAppBundle('debug', 'app.aab'); final FlutterProject project = generateFakeAppBundle('debug', 'app.aab');
final File bundle = findBundleFile(project, const BuildInfo(BuildMode.debug, null)); final File bundle = findBundleFile(project, const BuildInfo(BuildMode.debug, null, treeShakeIcons: false));
expect(bundle, isNotNull); expect(bundle, isNotNull);
expect(bundle.path, globals.fs.path.join('irrelevant', 'app', 'outputs', 'bundle', 'debug', 'app.aab')); expect(bundle.path, globals.fs.path.join('irrelevant', 'app', 'outputs', 'bundle', 'debug', 'app.aab'));
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
...@@ -195,7 +195,7 @@ void main() { ...@@ -195,7 +195,7 @@ void main() {
testUsingContext('Finds app bundle when flavor contains underscores in profile mode', () { testUsingContext('Finds app bundle when flavor contains underscores in profile mode', () {
final FlutterProject project = generateFakeAppBundle('foo_barProfile', 'app.aab'); final FlutterProject project = generateFakeAppBundle('foo_barProfile', 'app.aab');
final File bundle = findBundleFile(project, const BuildInfo(BuildMode.profile, 'foo_bar')); final File bundle = findBundleFile(project, const BuildInfo(BuildMode.profile, 'foo_bar', treeShakeIcons: false));
expect(bundle, isNotNull); expect(bundle, isNotNull);
expect(bundle.path, globals.fs.path.join('irrelevant', 'app', 'outputs', 'bundle', 'foo_barProfile', 'app.aab')); expect(bundle.path, globals.fs.path.join('irrelevant', 'app', 'outputs', 'bundle', 'foo_barProfile', 'app.aab'));
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
...@@ -205,7 +205,7 @@ void main() { ...@@ -205,7 +205,7 @@ void main() {
testUsingContext('Finds app bundle when flavor doesn\'t contain underscores in profile mode', () { testUsingContext('Finds app bundle when flavor doesn\'t contain underscores in profile mode', () {
final FlutterProject project = generateFakeAppBundle('fooProfile', 'app.aab'); final FlutterProject project = generateFakeAppBundle('fooProfile', 'app.aab');
final File bundle = findBundleFile(project, const BuildInfo(BuildMode.profile, 'foo')); final File bundle = findBundleFile(project, const BuildInfo(BuildMode.profile, 'foo', treeShakeIcons: false));
expect(bundle, isNotNull); expect(bundle, isNotNull);
expect(bundle.path, globals.fs.path.join('irrelevant', 'app', 'outputs', 'bundle', 'fooProfile', 'app.aab')); expect(bundle.path, globals.fs.path.join('irrelevant', 'app', 'outputs', 'bundle', 'fooProfile', 'app.aab'));
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
...@@ -215,7 +215,7 @@ void main() { ...@@ -215,7 +215,7 @@ void main() {
testUsingContext('Finds app bundle when no flavor is used in profile mode', () { testUsingContext('Finds app bundle when no flavor is used in profile mode', () {
final FlutterProject project = generateFakeAppBundle('profile', 'app.aab'); final FlutterProject project = generateFakeAppBundle('profile', 'app.aab');
final File bundle = findBundleFile(project, const BuildInfo(BuildMode.profile, null)); final File bundle = findBundleFile(project, const BuildInfo(BuildMode.profile, null, treeShakeIcons: false));
expect(bundle, isNotNull); expect(bundle, isNotNull);
expect(bundle.path, globals.fs.path.join('irrelevant', 'app', 'outputs', 'bundle', 'profile', 'app.aab')); expect(bundle.path, globals.fs.path.join('irrelevant', 'app', 'outputs', 'bundle', 'profile', 'app.aab'));
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
...@@ -225,7 +225,7 @@ void main() { ...@@ -225,7 +225,7 @@ void main() {
testUsingContext('Finds app bundle in release mode - Gradle 3.5', () { testUsingContext('Finds app bundle in release mode - Gradle 3.5', () {
final FlutterProject project = generateFakeAppBundle('release', 'app-release.aab'); final FlutterProject project = generateFakeAppBundle('release', 'app-release.aab');
final File bundle = findBundleFile(project, const BuildInfo(BuildMode.release, null)); final File bundle = findBundleFile(project, const BuildInfo(BuildMode.release, null, treeShakeIcons: false));
expect(bundle, isNotNull); expect(bundle, isNotNull);
expect(bundle.path, globals.fs.path.join('irrelevant', 'app', 'outputs', 'bundle', 'release', 'app-release.aab')); expect(bundle.path, globals.fs.path.join('irrelevant', 'app', 'outputs', 'bundle', 'release', 'app-release.aab'));
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
...@@ -235,7 +235,7 @@ void main() { ...@@ -235,7 +235,7 @@ void main() {
testUsingContext('Finds app bundle in profile mode - Gradle 3.5', () { testUsingContext('Finds app bundle in profile mode - Gradle 3.5', () {
final FlutterProject project = generateFakeAppBundle('profile', 'app-profile.aab'); final FlutterProject project = generateFakeAppBundle('profile', 'app-profile.aab');
final File bundle = findBundleFile(project, const BuildInfo(BuildMode.profile, null)); final File bundle = findBundleFile(project, const BuildInfo(BuildMode.profile, null, treeShakeIcons: false));
expect(bundle, isNotNull); expect(bundle, isNotNull);
expect(bundle.path, globals.fs.path.join('irrelevant', 'app', 'outputs', 'bundle', 'profile', 'app-profile.aab')); expect(bundle.path, globals.fs.path.join('irrelevant', 'app', 'outputs', 'bundle', 'profile', 'app-profile.aab'));
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
...@@ -245,7 +245,7 @@ void main() { ...@@ -245,7 +245,7 @@ void main() {
testUsingContext('Finds app bundle in debug mode - Gradle 3.5', () { testUsingContext('Finds app bundle in debug mode - Gradle 3.5', () {
final FlutterProject project = generateFakeAppBundle('debug', 'app-debug.aab'); final FlutterProject project = generateFakeAppBundle('debug', 'app-debug.aab');
final File bundle = findBundleFile(project, const BuildInfo(BuildMode.debug, null)); final File bundle = findBundleFile(project, const BuildInfo(BuildMode.debug, null, treeShakeIcons: false));
expect(bundle, isNotNull); expect(bundle, isNotNull);
expect(bundle.path, globals.fs.path.join('irrelevant', 'app', 'outputs', 'bundle', 'debug', 'app-debug.aab')); expect(bundle.path, globals.fs.path.join('irrelevant', 'app', 'outputs', 'bundle', 'debug', 'app-debug.aab'));
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
...@@ -255,7 +255,7 @@ void main() { ...@@ -255,7 +255,7 @@ void main() {
testUsingContext('Finds app bundle when flavor contains underscores in release mode - Gradle 3.5', () { testUsingContext('Finds app bundle when flavor contains underscores in release mode - Gradle 3.5', () {
final FlutterProject project = generateFakeAppBundle('foo_barRelease', 'app-foo_bar-release.aab'); final FlutterProject project = generateFakeAppBundle('foo_barRelease', 'app-foo_bar-release.aab');
final File bundle = findBundleFile(project, const BuildInfo(BuildMode.release, 'foo_bar')); final File bundle = findBundleFile(project, const BuildInfo(BuildMode.release, 'foo_bar', treeShakeIcons: false));
expect(bundle, isNotNull); expect(bundle, isNotNull);
expect(bundle.path, globals.fs.path.join('irrelevant', 'app', 'outputs', 'bundle', 'foo_barRelease', 'app-foo_bar-release.aab')); expect(bundle.path, globals.fs.path.join('irrelevant', 'app', 'outputs', 'bundle', 'foo_barRelease', 'app-foo_bar-release.aab'));
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
...@@ -265,7 +265,7 @@ void main() { ...@@ -265,7 +265,7 @@ void main() {
testUsingContext('Finds app bundle when flavor contains underscores in profile mode - Gradle 3.5', () { testUsingContext('Finds app bundle when flavor contains underscores in profile mode - Gradle 3.5', () {
final FlutterProject project = generateFakeAppBundle('foo_barProfile', 'app-foo_bar-profile.aab'); final FlutterProject project = generateFakeAppBundle('foo_barProfile', 'app-foo_bar-profile.aab');
final File bundle = findBundleFile(project, const BuildInfo(BuildMode.profile, 'foo_bar')); final File bundle = findBundleFile(project, const BuildInfo(BuildMode.profile, 'foo_bar', treeShakeIcons: false));
expect(bundle, isNotNull); expect(bundle, isNotNull);
expect(bundle.path, globals.fs.path.join('irrelevant', 'app', 'outputs', 'bundle', 'foo_barProfile', 'app-foo_bar-profile.aab')); expect(bundle.path, globals.fs.path.join('irrelevant', 'app', 'outputs', 'bundle', 'foo_barProfile', 'app-foo_bar-profile.aab'));
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
...@@ -275,7 +275,7 @@ void main() { ...@@ -275,7 +275,7 @@ void main() {
testUsingContext('Finds app bundle when flavor contains underscores in debug mode - Gradle 3.5', () { testUsingContext('Finds app bundle when flavor contains underscores in debug mode - Gradle 3.5', () {
final FlutterProject project = generateFakeAppBundle('foo_barDebug', 'app-foo_bar-debug.aab'); final FlutterProject project = generateFakeAppBundle('foo_barDebug', 'app-foo_bar-debug.aab');
final File bundle = findBundleFile(project, const BuildInfo(BuildMode.debug, 'foo_bar')); final File bundle = findBundleFile(project, const BuildInfo(BuildMode.debug, 'foo_bar', treeShakeIcons: false));
expect(bundle, isNotNull); expect(bundle, isNotNull);
expect(bundle.path, globals.fs.path.join('irrelevant','app', 'outputs', 'bundle', 'foo_barDebug', 'app-foo_bar-debug.aab')); expect(bundle.path, globals.fs.path.join('irrelevant','app', 'outputs', 'bundle', 'foo_barDebug', 'app-foo_bar-debug.aab'));
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
...@@ -287,7 +287,7 @@ void main() { ...@@ -287,7 +287,7 @@ void main() {
final FlutterProject project = FlutterProject.current(); final FlutterProject project = FlutterProject.current();
expect( expect(
() { () {
findBundleFile(project, const BuildInfo(BuildMode.debug, 'foo_bar')); findBundleFile(project, const BuildInfo(BuildMode.debug, 'foo_bar', treeShakeIcons: false));
}, },
throwsToolExit( throwsToolExit(
message: message:
...@@ -329,7 +329,7 @@ void main() { ...@@ -329,7 +329,7 @@ void main() {
final Iterable<File> apks = findApkFiles( final Iterable<File> apks = findApkFiles(
project, project,
const AndroidBuildInfo(BuildInfo(BuildMode.release, '')), const AndroidBuildInfo(BuildInfo(BuildMode.release, '', treeShakeIcons: false)),
); );
expect(apks.isNotEmpty, isTrue); expect(apks.isNotEmpty, isTrue);
expect(apks.first.path, equals(globals.fs.path.join('irrelevant', 'app', 'outputs', 'apk', 'release', 'app-release.apk'))); expect(apks.first.path, equals(globals.fs.path.join('irrelevant', 'app', 'outputs', 'apk', 'release', 'app-release.apk')));
...@@ -352,7 +352,7 @@ void main() { ...@@ -352,7 +352,7 @@ void main() {
final Iterable<File> apks = findApkFiles( final Iterable<File> apks = findApkFiles(
project, project,
const AndroidBuildInfo(BuildInfo(BuildMode.release, 'flavor1')), const AndroidBuildInfo(BuildInfo(BuildMode.release, 'flavor1', treeShakeIcons: false)),
); );
expect(apks.isNotEmpty, isTrue); expect(apks.isNotEmpty, isTrue);
expect(apks.first.path, equals(globals.fs.path.join('irrelevant', 'app', 'outputs', 'apk', 'release', 'app-flavor1-release.apk'))); expect(apks.first.path, equals(globals.fs.path.join('irrelevant', 'app', 'outputs', 'apk', 'release', 'app-flavor1-release.apk')));
...@@ -375,7 +375,7 @@ void main() { ...@@ -375,7 +375,7 @@ void main() {
final Iterable<File> apks = findApkFiles( final Iterable<File> apks = findApkFiles(
project, project,
const AndroidBuildInfo(BuildInfo(BuildMode.release, 'flavor1')), const AndroidBuildInfo(BuildInfo(BuildMode.release, 'flavor1', treeShakeIcons: false)),
); );
expect(apks.isNotEmpty, isTrue); expect(apks.isNotEmpty, isTrue);
expect(apks.first.path, equals(globals.fs.path.join('irrelevant', 'app', 'outputs', 'apk', 'flavor1', 'release', 'app-flavor1-release.apk'))); expect(apks.first.path, equals(globals.fs.path.join('irrelevant', 'app', 'outputs', 'apk', 'flavor1', 'release', 'app-flavor1-release.apk')));
...@@ -390,7 +390,7 @@ void main() { ...@@ -390,7 +390,7 @@ void main() {
() { () {
findApkFiles( findApkFiles(
project, project,
const AndroidBuildInfo(BuildInfo(BuildMode.debug, 'foo_bar')), const AndroidBuildInfo(BuildInfo(BuildMode.debug, 'foo_bar', treeShakeIcons: false)),
); );
}, },
throwsToolExit( throwsToolExit(
...@@ -604,7 +604,7 @@ dependencies: ...@@ -604,7 +604,7 @@ dependencies:
flutter: flutter:
'''; ''';
const BuildInfo buildInfo = BuildInfo(BuildMode.release, null); const BuildInfo buildInfo = BuildInfo(BuildMode.release, null, treeShakeIcons: false);
await checkBuildVersion( await checkBuildVersion(
manifest: manifest, manifest: manifest,
buildInfo: buildInfo, buildInfo: buildInfo,
...@@ -622,7 +622,7 @@ dependencies: ...@@ -622,7 +622,7 @@ dependencies:
sdk: flutter sdk: flutter
flutter: flutter:
'''; ''';
const BuildInfo buildInfo = BuildInfo(BuildMode.release, null); const BuildInfo buildInfo = BuildInfo(BuildMode.release, null, treeShakeIcons: false);
await checkBuildVersion( await checkBuildVersion(
manifest: manifest, manifest: manifest,
buildInfo: buildInfo, buildInfo: buildInfo,
...@@ -640,7 +640,7 @@ dependencies: ...@@ -640,7 +640,7 @@ dependencies:
sdk: flutter sdk: flutter
flutter: flutter:
'''; ''';
const BuildInfo buildInfo = BuildInfo(BuildMode.release, null, buildName: '1.0.2'); const BuildInfo buildInfo = BuildInfo(BuildMode.release, null, buildName: '1.0.2', treeShakeIcons: false);
await checkBuildVersion( await checkBuildVersion(
manifest: manifest, manifest: manifest,
buildInfo: buildInfo, buildInfo: buildInfo,
...@@ -658,7 +658,7 @@ dependencies: ...@@ -658,7 +658,7 @@ dependencies:
sdk: flutter sdk: flutter
flutter: flutter:
'''; ''';
const BuildInfo buildInfo = BuildInfo(BuildMode.release, null, buildNumber: '3'); const BuildInfo buildInfo = BuildInfo(BuildMode.release, null, buildNumber: '3', treeShakeIcons: false);
await checkBuildVersion( await checkBuildVersion(
manifest: manifest, manifest: manifest,
buildInfo: buildInfo, buildInfo: buildInfo,
...@@ -676,7 +676,7 @@ dependencies: ...@@ -676,7 +676,7 @@ dependencies:
sdk: flutter sdk: flutter
flutter: flutter:
'''; ''';
const BuildInfo buildInfo = BuildInfo(BuildMode.release, null, buildName: '1.0.2', buildNumber: '3'); const BuildInfo buildInfo = BuildInfo(BuildMode.release, null, buildName: '1.0.2', buildNumber: '3', treeShakeIcons: false);
await checkBuildVersion( await checkBuildVersion(
manifest: manifest, manifest: manifest,
buildInfo: buildInfo, buildInfo: buildInfo,
...@@ -694,7 +694,7 @@ dependencies: ...@@ -694,7 +694,7 @@ dependencies:
sdk: flutter sdk: flutter
flutter: flutter:
'''; ''';
const BuildInfo buildInfo = BuildInfo(BuildMode.release, null, buildName: '1.0.2', buildNumber: '3'); const BuildInfo buildInfo = BuildInfo(BuildMode.release, null, buildName: '1.0.2', buildNumber: '3', treeShakeIcons: false);
await checkBuildVersion( await checkBuildVersion(
manifest: manifest, manifest: manifest,
buildInfo: buildInfo, buildInfo: buildInfo,
...@@ -711,7 +711,7 @@ dependencies: ...@@ -711,7 +711,7 @@ dependencies:
sdk: flutter sdk: flutter
flutter: flutter:
'''; ''';
const BuildInfo buildInfo = BuildInfo(BuildMode.release, null, buildName: '1.0.2', buildNumber: '3'); const BuildInfo buildInfo = BuildInfo(BuildMode.release, null, buildName: '1.0.2', buildNumber: '3', treeShakeIcons: false);
await checkBuildVersion( await checkBuildVersion(
manifest: manifest, manifest: manifest,
buildInfo: buildInfo, buildInfo: buildInfo,
...@@ -730,19 +730,19 @@ flutter: ...@@ -730,19 +730,19 @@ flutter:
'''; ''';
await checkBuildVersion( await checkBuildVersion(
manifest: manifest, manifest: manifest,
buildInfo: const BuildInfo(BuildMode.release, null, buildName: null, buildNumber: null), buildInfo: const BuildInfo(BuildMode.release, null, buildName: null, buildNumber: null, treeShakeIcons: false),
expectedBuildName: null, expectedBuildName: null,
expectedBuildNumber: null, expectedBuildNumber: null,
); );
await checkBuildVersion( await checkBuildVersion(
manifest: manifest, manifest: manifest,
buildInfo: const BuildInfo(BuildMode.release, null, buildName: '1.0.2', buildNumber: '3'), buildInfo: const BuildInfo(BuildMode.release, null, buildName: '1.0.2', buildNumber: '3', treeShakeIcons: false),
expectedBuildName: '1.0.2', expectedBuildName: '1.0.2',
expectedBuildNumber: '3', expectedBuildNumber: '3',
); );
await checkBuildVersion( await checkBuildVersion(
manifest: manifest, manifest: manifest,
buildInfo: const BuildInfo(BuildMode.release, null, buildName: '1.0.3', buildNumber: '4'), buildInfo: const BuildInfo(BuildMode.release, null, buildName: '1.0.3', buildNumber: '4', treeShakeIcons: false),
expectedBuildName: '1.0.3', expectedBuildName: '1.0.3',
expectedBuildNumber: '4', expectedBuildNumber: '4',
); );
...@@ -756,7 +756,7 @@ flutter: ...@@ -756,7 +756,7 @@ flutter:
// Values get unset. // Values get unset.
await checkBuildVersion( await checkBuildVersion(
manifest: manifest, manifest: manifest,
buildInfo: const BuildInfo(BuildMode.release, null, buildName: null, buildNumber: null), buildInfo: const BuildInfo(BuildMode.release, null, buildName: null, buildNumber: null, treeShakeIcons: false),
expectedBuildName: null, expectedBuildName: null,
expectedBuildNumber: null, expectedBuildNumber: null,
); );
...@@ -1125,6 +1125,7 @@ plugin1=${plugin1.path} ...@@ -1125,6 +1125,7 @@ plugin1=${plugin1.path}
BuildInfo( BuildInfo(
BuildMode.release, BuildMode.release,
null, null,
treeShakeIcons: false,
), ),
), ),
target: 'lib/main.dart', target: 'lib/main.dart',
...@@ -1204,6 +1205,7 @@ plugin1=${plugin1.path} ...@@ -1204,6 +1205,7 @@ plugin1=${plugin1.path}
BuildInfo( BuildInfo(
BuildMode.release, BuildMode.release,
null, null,
treeShakeIcons: false,
), ),
), ),
target: 'lib/main.dart', target: 'lib/main.dart',
...@@ -1279,6 +1281,7 @@ plugin1=${plugin1.path} ...@@ -1279,6 +1281,7 @@ plugin1=${plugin1.path}
BuildInfo( BuildInfo(
BuildMode.release, BuildMode.release,
null, null,
treeShakeIcons: false,
), ),
), ),
target: 'lib/main.dart', target: 'lib/main.dart',
...@@ -1351,6 +1354,7 @@ plugin1=${plugin1.path} ...@@ -1351,6 +1354,7 @@ plugin1=${plugin1.path}
BuildInfo( BuildInfo(
BuildMode.release, BuildMode.release,
null, null,
treeShakeIcons: false,
), ),
), ),
target: 'lib/main.dart', target: 'lib/main.dart',
...@@ -1418,6 +1422,7 @@ plugin1=${plugin1.path} ...@@ -1418,6 +1422,7 @@ plugin1=${plugin1.path}
BuildInfo( BuildInfo(
BuildMode.release, BuildMode.release,
null, null,
treeShakeIcons: false,
), ),
), ),
target: 'lib/main.dart', target: 'lib/main.dart',
...@@ -1491,6 +1496,7 @@ plugin1=${plugin1.path} ...@@ -1491,6 +1496,7 @@ plugin1=${plugin1.path}
BuildInfo( BuildInfo(
BuildMode.release, BuildMode.release,
null, null,
treeShakeIcons: false,
), ),
), ),
target: 'lib/main.dart', target: 'lib/main.dart',
...@@ -1582,6 +1588,7 @@ plugin1=${plugin1.path} ...@@ -1582,6 +1588,7 @@ plugin1=${plugin1.path}
BuildInfo( BuildInfo(
BuildMode.release, BuildMode.release,
null, null,
treeShakeIcons: false,
), ),
), ),
target: 'lib/main.dart', target: 'lib/main.dart',
...@@ -1630,7 +1637,7 @@ plugin1=${plugin1.path} ...@@ -1630,7 +1637,7 @@ plugin1=${plugin1.path}
fileSystem.directory('build/outputs/repo').createSync(recursive: true); fileSystem.directory('build/outputs/repo').createSync(recursive: true);
await buildGradleAar( await buildGradleAar(
androidBuildInfo: const AndroidBuildInfo(BuildInfo(BuildMode.release, null)), androidBuildInfo: const AndroidBuildInfo(BuildInfo(BuildMode.release, null, treeShakeIcons: false)),
project: FlutterProject.current(), project: FlutterProject.current(),
outputDirectory: fileSystem.directory('build/'), outputDirectory: fileSystem.directory('build/'),
target: '', target: '',
...@@ -1715,6 +1722,7 @@ plugin1=${plugin1.path} ...@@ -1715,6 +1722,7 @@ plugin1=${plugin1.path}
BuildInfo( BuildInfo(
BuildMode.release, BuildMode.release,
null, null,
treeShakeIcons: false,
), ),
), ),
target: 'lib/main.dart', target: 'lib/main.dart',
...@@ -1801,7 +1809,7 @@ plugin1=${plugin1.path} ...@@ -1801,7 +1809,7 @@ plugin1=${plugin1.path}
when(fileSystemUtils.copyDirectorySync(any, any)).thenReturn(null); when(fileSystemUtils.copyDirectorySync(any, any)).thenReturn(null);
await buildGradleAar( await buildGradleAar(
androidBuildInfo: const AndroidBuildInfo(BuildInfo(BuildMode.release, null)), androidBuildInfo: const AndroidBuildInfo(BuildInfo(BuildMode.release, null, treeShakeIcons: false)),
project: FlutterProject.current(), project: FlutterProject.current(),
outputDirectory: fileSystem.directory('build/'), outputDirectory: fileSystem.directory('build/'),
target: '', target: '',
......
...@@ -2,8 +2,6 @@ ...@@ -2,8 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:io';
import 'package:flutter_tools/src/build_system/build_system.dart'; import 'package:flutter_tools/src/build_system/build_system.dart';
import 'package:flutter_tools/src/build_system/targets/assets.dart'; import 'package:flutter_tools/src/build_system/targets/assets.dart';
import 'package:flutter_tools/src/globals.dart' as globals; import 'package:flutter_tools/src/globals.dart' as globals;
...@@ -12,7 +10,6 @@ import '../../../src/common.dart'; ...@@ -12,7 +10,6 @@ import '../../../src/common.dart';
import '../../../src/testbed.dart'; import '../../../src/testbed.dart';
void main() { void main() {
const BuildSystem buildSystem = BuildSystem();
Environment environment; Environment environment;
Testbed testbed; Testbed testbed;
...@@ -21,6 +18,7 @@ void main() { ...@@ -21,6 +18,7 @@ void main() {
environment = Environment.test( environment = Environment.test(
globals.fs.currentDirectory, globals.fs.currentDirectory,
); );
globals.fs.file(environment.buildDir.childFile('app.dill')).createSync(recursive: true);
globals.fs.file(globals.fs.path.join('packages', 'flutter_tools', 'lib', 'src', globals.fs.file(globals.fs.path.join('packages', 'flutter_tools', 'lib', 'src',
'build_system', 'targets', 'assets.dart')) 'build_system', 'targets', 'assets.dart'))
..createSync(recursive: true); ..createSync(recursive: true);
...@@ -44,7 +42,7 @@ flutter: ...@@ -44,7 +42,7 @@ flutter:
}); });
test('Copies files to correct asset directory', () => testbed.run(() async { test('Copies files to correct asset directory', () => testbed.run(() async {
await buildSystem.build(const CopyAssets(), environment); await const CopyAssets().build(environment);
expect(globals.fs.file(globals.fs.path.join(environment.buildDir.path, 'flutter_assets', 'AssetManifest.json')).existsSync(), true); expect(globals.fs.file(globals.fs.path.join(environment.buildDir.path, 'flutter_assets', 'AssetManifest.json')).existsSync(), true);
expect(globals.fs.file(globals.fs.path.join(environment.buildDir.path, 'flutter_assets', 'FontManifest.json')).existsSync(), true); expect(globals.fs.file(globals.fs.path.join(environment.buildDir.path, 'flutter_assets', 'FontManifest.json')).existsSync(), true);
...@@ -55,24 +53,6 @@ flutter: ...@@ -55,24 +53,6 @@ flutter:
expect(globals.fs.file(globals.fs.path.join(environment.buildDir.path, 'flutter_assets', 'assets/wildcard/%23bar.png')).existsSync(), true); expect(globals.fs.file(globals.fs.path.join(environment.buildDir.path, 'flutter_assets', 'assets/wildcard/%23bar.png')).existsSync(), true);
})); }));
test('Does not leave stale files in build directory', () => testbed.run(() async {
await buildSystem.build(const CopyAssets(), environment);
expect(globals.fs.file(globals.fs.path.join(environment.buildDir.path, 'flutter_assets', 'assets/foo/bar.png')).existsSync(), true);
// Modify manifest to remove asset.
globals.fs.file('pubspec.yaml')
..createSync()
..writeAsStringSync('''
name: example
flutter:
''');
await buildSystem.build(const CopyAssets(), environment);
// See https://github.com/flutter/flutter/issues/35293
expect(globals.fs.file(globals.fs.path.join(environment.buildDir.path, 'flutter_assets', 'assets/foo/bar.png')).existsSync(), false);
}), skip: Platform.isWindows); // See https://github.com/google/file.dart/issues/131
test('FlutterPlugins updates required files as needed', () => testbed.run(() async { test('FlutterPlugins updates required files as needed', () => testbed.run(() async {
globals.fs.file('pubspec.yaml') globals.fs.file('pubspec.yaml')
..writeAsStringSync('name: foo\ndependencies:\n foo: any\n'); ..writeAsStringSync('name: foo\ndependencies:\n foo: any\n');
......
// 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:convert';
import 'dart:io';
import 'package:file/memory.dart';
import 'package:flutter_tools/src/artifacts.dart';
import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/base/terminal.dart';
import 'package:flutter_tools/src/build_system/build_system.dart';
import 'package:flutter_tools/src/build_system/targets/dart.dart';
import 'package:flutter_tools/src/build_system/targets/icon_tree_shaker.dart';
import 'package:flutter_tools/src/devfs.dart';
import 'package:meta/meta.dart';
import 'package:mockito/mockito.dart';
import 'package:platform/platform.dart';
import '../../../src/common.dart';
import '../../../src/context.dart';
import '../../../src/mocks.dart' as mocks;
final Platform _kNoAnsiPlatform =
FakePlatform.fromPlatform(const LocalPlatform())
..stdoutSupportsAnsi = false;
void main() {
BufferLogger logger;
MemoryFileSystem fs;
MockProcessManager mockProcessManager;
MockProcess fontSubsetProcess;
MockArtifacts mockArtifacts;
DevFSStringContent fontManifestContent;
const String dartPath = '/flutter/dart';
const String constFinderPath = '/flutter/const_finder.snapshot.dart';
const String fontSubsetPath = '/flutter/font-subset';
const String inputPath = '/input/fonts/MaterialIcons-Regular.ttf';
const String outputPath = '/output/fonts/MaterialIcons-Regular.ttf';
const String relativePath = 'fonts/MaterialIcons-Regular.ttf';
List<String> getConstFinderArgs(String appDillPath) => <String>[
dartPath,
constFinderPath,
'--kernel-file', appDillPath,
'--class-library-uri', 'package:flutter/src/widgets/icon_data.dart',
'--class-name', 'IconData',
];
const List<String> fontSubsetArgs = <String>[
fontSubsetPath,
outputPath,
inputPath,
];
void _addConstFinderInvocation(
String appDillPath, {
int exitCode = 0,
String stdout = '',
String stderr = '',
}) {
when(mockProcessManager.run(getConstFinderArgs(appDillPath))).thenAnswer((_) async {
return ProcessResult(0, exitCode, stdout, stderr);
});
}
void _resetFontSubsetInvocation({
int exitCode = 0,
String stdout = '',
String stderr = '',
@required mocks.CompleterIOSink stdinSink,
}) {
assert(stdinSink != null);
stdinSink.writes.clear();
when(fontSubsetProcess.exitCode).thenAnswer((_) async => exitCode);
when(fontSubsetProcess.stdout).thenAnswer((_) => Stream<List<int>>.fromIterable(<List<int>>[utf8.encode(stdout)]));
when(fontSubsetProcess.stderr).thenAnswer((_) => Stream<List<int>>.fromIterable(<List<int>>[utf8.encode(stderr)]));
when(fontSubsetProcess.stdin).thenReturn(stdinSink);
when(mockProcessManager.start(fontSubsetArgs)).thenAnswer((_) async {
return fontSubsetProcess;
});
}
setUp(() {
fontManifestContent = DevFSStringContent(validFontManifestJson);
mockProcessManager = MockProcessManager();
fontSubsetProcess = MockProcess();
mockArtifacts = MockArtifacts();
fs = MemoryFileSystem();
logger = BufferLogger(
terminal: AnsiTerminal(
stdio: mocks.MockStdio(),
platform: _kNoAnsiPlatform,
),
outputPreferences: OutputPreferences.test(showColor: false),
);
fs.file(constFinderPath).createSync(recursive: true);
fs.file(dartPath).createSync(recursive: true);
fs.file(fontSubsetPath).createSync(recursive: true);
when(mockArtifacts.getArtifactPath(Artifact.constFinder)).thenReturn(constFinderPath);
when(mockArtifacts.getArtifactPath(Artifact.fontSubset)).thenReturn(fontSubsetPath);
when(mockArtifacts.getArtifactPath(Artifact.engineDartBinary)).thenReturn(dartPath);
});
Environment _createEnvironment(Map<String, String> defines) {
return Environment.test(
fs.directory('/icon_test')..createSync(recursive: true),
defines: defines,
);
}
testWithoutContext('Prints error in debug mode environment', () async {
final Environment environment = _createEnvironment(<String, String>{
kIconTreeShakerFlag: 'true',
kBuildMode: 'debug',
});
final IconTreeShaker iconTreeShaker = IconTreeShaker(
environment,
fontManifestContent,
logger: logger,
processManager: mockProcessManager,
fileSystem: fs,
artifacts: mockArtifacts,
);
expect(
logger.errorText,
'Font subetting is not supported in debug mode. The --tree-shake-icons flag will be ignored.\n',
);
expect(iconTreeShaker.enabled, false);
final bool subsets = await iconTreeShaker.subsetFont(
inputPath: inputPath,
outputPath: outputPath,
relativePath: relativePath,
);
expect(subsets, false);
verifyNever(mockProcessManager.run(any));
verifyNever(mockProcessManager.start(any));
});
testWithoutContext('Does not get enabled without font manifest', () {
final Environment environment = _createEnvironment(<String, String>{
kIconTreeShakerFlag: 'true',
kBuildMode: 'release',
});
final IconTreeShaker iconTreeShaker = IconTreeShaker(
environment,
null,
logger: logger,
processManager: mockProcessManager,
fileSystem: fs,
artifacts: mockArtifacts,
);
expect(
logger.errorText,
isEmpty,
);
expect(iconTreeShaker.enabled, false);
verifyNever(mockProcessManager.run(any));
verifyNever(mockProcessManager.start(any));
});
testWithoutContext('Gets enabled', () {
final Environment environment = _createEnvironment(<String, String>{
kIconTreeShakerFlag: 'true',
kBuildMode: 'release',
});
final IconTreeShaker iconTreeShaker = IconTreeShaker(
environment,
fontManifestContent,
logger: logger,
processManager: mockProcessManager,
fileSystem: fs,
artifacts: mockArtifacts,
);
expect(
logger.errorText,
isEmpty,
);
expect(iconTreeShaker.enabled, true);
verifyNever(mockProcessManager.run(any));
verifyNever(mockProcessManager.start(any));
});
test('No app.dill throws exception', () async {
final Environment environment = _createEnvironment(<String, String>{
kIconTreeShakerFlag: 'true',
kBuildMode: 'release',
});
final IconTreeShaker iconTreeShaker = IconTreeShaker(
environment,
fontManifestContent,
logger: logger,
processManager: mockProcessManager,
fileSystem: fs,
artifacts: mockArtifacts,
);
expect(
() => iconTreeShaker.subsetFont(
inputPath: inputPath,
outputPath: outputPath,
relativePath: relativePath,
),
throwsA(isA<IconTreeShakerException>()),
);
});
testWithoutContext('The happy path', () async {
final Environment environment = _createEnvironment(<String, String>{
kIconTreeShakerFlag: 'true',
kBuildMode: 'release',
});
final File appDill = environment.buildDir.childFile('app.dill')..createSync(recursive: true);
fs.file(inputPath).createSync(recursive: true);
final IconTreeShaker iconTreeShaker = IconTreeShaker(
environment,
fontManifestContent,
logger: logger,
processManager: mockProcessManager,
fileSystem: fs,
artifacts: mockArtifacts,
);
final mocks.CompleterIOSink stdinSink = mocks.CompleterIOSink();
_addConstFinderInvocation(appDill.path, stdout: validConstFinderResult);
_resetFontSubsetInvocation(stdinSink: stdinSink);
bool subsetted = await iconTreeShaker.subsetFont(
inputPath: inputPath,
outputPath: outputPath,
relativePath: relativePath,
);
expect(stdinSink.writes, <List<int>>[utf8.encode('59470\n')]);
_resetFontSubsetInvocation(stdinSink: stdinSink);
expect(subsetted, true);
subsetted = await iconTreeShaker.subsetFont(
inputPath: inputPath,
outputPath: outputPath,
relativePath: relativePath,
);
expect(subsetted, true);
expect(stdinSink.writes, <List<int>>[utf8.encode('59470\n')]);
verify(mockProcessManager.run(getConstFinderArgs(appDill.path))).called(1);
verify(mockProcessManager.start(fontSubsetArgs)).called(2);
});
testWithoutContext('Non-constant instances', () async {
final Environment environment = _createEnvironment(<String, String>{
kIconTreeShakerFlag: 'true',
kBuildMode: 'release',
});
final File appDill = environment.buildDir.childFile('app.dill')..createSync(recursive: true);
fs.file(inputPath).createSync(recursive: true);
final IconTreeShaker iconTreeShaker = IconTreeShaker(
environment,
fontManifestContent,
logger: logger,
processManager: mockProcessManager,
fileSystem: fs,
artifacts: mockArtifacts,
);
_addConstFinderInvocation(appDill.path, stdout: constFinderResultWithInvalid);
expect(
iconTreeShaker.subsetFont(
inputPath: inputPath,
outputPath: outputPath,
relativePath: relativePath,
),
throwsToolExit(
message: 'Avoid non-constant invocations of IconData or try to build again with --no-tree-shake-icons.',
),
);
verify(mockProcessManager.run(getConstFinderArgs(appDill.path))).called(1);
verifyNever(mockProcessManager.start(fontSubsetArgs));
});
testWithoutContext('Non-zero font-subset exit code', () async {
final Environment environment = _createEnvironment(<String, String>{
kIconTreeShakerFlag: 'true',
kBuildMode: 'release',
});
final File appDill = environment.buildDir.childFile('app.dill')..createSync(recursive: true);
fs.file(inputPath).createSync(recursive: true);
final IconTreeShaker iconTreeShaker = IconTreeShaker(
environment,
fontManifestContent,
logger: logger,
processManager: mockProcessManager,
fileSystem: fs,
artifacts: mockArtifacts,
);
final mocks.CompleterIOSink stdinSink = mocks.CompleterIOSink();
_addConstFinderInvocation(appDill.path, stdout: validConstFinderResult);
_resetFontSubsetInvocation(exitCode: -1, stdinSink: stdinSink);
expect(
iconTreeShaker.subsetFont(
inputPath: inputPath,
outputPath: outputPath,
relativePath: relativePath,
),
throwsA(isA<IconTreeShakerException>()),
);
verify(mockProcessManager.run(getConstFinderArgs(appDill.path))).called(1);
verifyNever(mockProcessManager.start(fontSubsetArgs));
});
testWithoutContext('font-subset throws on write to sdtin', () async {
final Environment environment = _createEnvironment(<String, String>{
kIconTreeShakerFlag: 'true',
kBuildMode: 'release',
});
final File appDill = environment.buildDir.childFile('app.dill')..createSync(recursive: true);
fs.file(inputPath).createSync(recursive: true);
final IconTreeShaker iconTreeShaker = IconTreeShaker(
environment,
fontManifestContent,
logger: logger,
processManager: mockProcessManager,
fileSystem: fs,
artifacts: mockArtifacts,
);
final mocks.CompleterIOSink stdinSink = mocks.CompleterIOSink(throwOnAdd: true);
_addConstFinderInvocation(appDill.path, stdout: validConstFinderResult);
_resetFontSubsetInvocation(exitCode: -1, stdinSink: stdinSink);
expect(
iconTreeShaker.subsetFont(
inputPath: inputPath,
outputPath: outputPath,
relativePath: relativePath,
),
throwsA(isA<IconTreeShakerException>()),
);
verify(mockProcessManager.run(getConstFinderArgs(appDill.path))).called(1);
verifyNever(mockProcessManager.start(fontSubsetArgs));
});
testWithoutContext('Invalid font manifest', () async {
final Environment environment = _createEnvironment(<String, String>{
kIconTreeShakerFlag: 'true',
kBuildMode: 'release',
});
final File appDill = environment.buildDir.childFile('app.dill')..createSync(recursive: true);
fs.file(inputPath).createSync(recursive: true);
fontManifestContent = DevFSStringContent(invalidFontManifestJson);
final IconTreeShaker iconTreeShaker = IconTreeShaker(
environment,
fontManifestContent,
logger: logger,
processManager: mockProcessManager,
fileSystem: fs,
artifacts: mockArtifacts,
);
_addConstFinderInvocation(appDill.path, stdout: validConstFinderResult);
expect(
iconTreeShaker.subsetFont(
inputPath: inputPath,
outputPath: outputPath,
relativePath: relativePath,
),
throwsA(isA<IconTreeShakerException>()),
);
verify(mockProcessManager.run(getConstFinderArgs(appDill.path))).called(1);
verifyNever(mockProcessManager.start(fontSubsetArgs));
});
testWithoutContext('ConstFinder non-zero exit', () async {
final Environment environment = _createEnvironment(<String, String>{
kIconTreeShakerFlag: 'true',
kBuildMode: 'release',
});
final File appDill = environment.buildDir.childFile('app.dill')..createSync(recursive: true);
fs.file(inputPath).createSync(recursive: true);
fontManifestContent = DevFSStringContent(invalidFontManifestJson);
final IconTreeShaker iconTreeShaker = IconTreeShaker(
environment,
fontManifestContent,
logger: logger,
processManager: mockProcessManager,
fileSystem: fs,
artifacts: mockArtifacts,
);
_addConstFinderInvocation(appDill.path, exitCode: -1);
expect(
iconTreeShaker.subsetFont(
inputPath: inputPath,
outputPath: outputPath,
relativePath: relativePath,
),
throwsA(isA<IconTreeShakerException>()),
);
verify(mockProcessManager.run(getConstFinderArgs(appDill.path))).called(1);
verifyNever(mockProcessManager.start(fontSubsetArgs));
});
}
const String validConstFinderResult = '''
{
"constantInstances": [
{
"codePoint": 59470,
"fontFamily": "MaterialIcons",
"fontPackage": null,
"matchTextDirection": false
}
],
"nonConstantLocations": []
}
''';
const String constFinderResultWithInvalid = '''
{
"constantInstances": [
{
"codePoint": 59470,
"fontFamily": "MaterialIcons",
"fontPackage": null,
"matchTextDirection": false
}
],
"nonConstantLocations": [
{
"file": "file:///Path/to/hello_world/lib/file.dart",
"line": 19,
"column": 11
}
]
}
''';
const String validFontManifestJson = '''
[
{
"family": "MaterialIcons",
"fonts": [
{
"asset": "fonts/MaterialIcons-Regular.ttf"
}
]
},
{
"family": "GalleryIcons",
"fonts": [
{
"asset": "packages/flutter_gallery_assets/fonts/private/gallery_icons/GalleryIcons.ttf"
}
]
},
{
"family": "packages/cupertino_icons/CupertinoIcons",
"fonts": [
{
"asset": "packages/cupertino_icons/assets/CupertinoIcons.ttf"
}
]
}
]
''';
const String invalidFontManifestJson = '''
{
"famly": "MaterialIcons",
"fonts": [
{
"asset": "fonts/MaterialIcons-Regular.ttf"
}
]
}
''';
class MockProcessManager extends Mock implements ProcessManager {}
class MockProcess extends Mock implements Process {}
class MockArtifacts extends Mock implements Artifacts {}
...@@ -39,6 +39,7 @@ void main() { ...@@ -39,6 +39,7 @@ void main() {
targetPlatform: TargetPlatform.ios, targetPlatform: TargetPlatform.ios,
depfilePath: 'example.d', depfilePath: 'example.d',
precompiled: false, precompiled: false,
treeShakeIcons: false,
); );
expect(globals.fs.file(globals.fs.path.join('example', 'kernel_blob.bin')).existsSync(), true); expect(globals.fs.file(globals.fs.path.join('example', 'kernel_blob.bin')).existsSync(), true);
expect(globals.fs.file(globals.fs.path.join('example', 'LICENSE')).existsSync(), true); expect(globals.fs.file(globals.fs.path.join('example', 'LICENSE')).existsSync(), true);
...@@ -61,6 +62,7 @@ void main() { ...@@ -61,6 +62,7 @@ void main() {
targetPlatform: TargetPlatform.linux_x64, targetPlatform: TargetPlatform.linux_x64,
depfilePath: 'example.d', depfilePath: 'example.d',
precompiled: false, precompiled: false,
treeShakeIcons: false,
), throwsToolExit()); ), throwsToolExit());
})); }));
} }
......
...@@ -720,7 +720,7 @@ void main() { ...@@ -720,7 +720,7 @@ void main() {
app = BuildableFuchsiaApp(project: FlutterProject.current().fuchsia); app = BuildableFuchsiaApp(project: FlutterProject.current().fuchsia);
} }
final DebuggingOptions debuggingOptions = DebuggingOptions.disabled(BuildInfo(mode, null)); final DebuggingOptions debuggingOptions = DebuggingOptions.disabled(BuildInfo(mode, null, treeShakeIcons: false));
return await device.startApp( return await device.startApp(
app, app,
prebuiltApplication: prebuilt, prebuiltApplication: prebuilt,
...@@ -753,7 +753,7 @@ void main() { ...@@ -753,7 +753,7 @@ void main() {
final FuchsiaApp app = FuchsiaApp.fromPrebuiltApp(far); final FuchsiaApp app = FuchsiaApp.fromPrebuiltApp(far);
final DebuggingOptions debuggingOptions = final DebuggingOptions debuggingOptions =
DebuggingOptions.disabled(const BuildInfo(BuildMode.release, null)); DebuggingOptions.disabled(const BuildInfo(BuildMode.release, null, treeShakeIcons: false));
final LaunchResult launchResult = await device.startApp(app, final LaunchResult launchResult = await device.startApp(app,
prebuiltApplication: true, prebuiltApplication: true,
debuggingOptions: debuggingOptions); debuggingOptions: debuggingOptions);
......
...@@ -292,7 +292,7 @@ void main() { ...@@ -292,7 +292,7 @@ void main() {
final LaunchResult launchResult = await device.startApp(mockApp, final LaunchResult launchResult = await device.startApp(mockApp,
prebuiltApplication: true, prebuiltApplication: true,
debuggingOptions: DebuggingOptions.enabled(const BuildInfo(BuildMode.debug, null)), debuggingOptions: DebuggingOptions.enabled(const BuildInfo(BuildMode.debug, null, treeShakeIcons: false)),
platformArgs: <String, dynamic>{}, platformArgs: <String, dynamic>{},
); );
verify(mockUsage.sendEvent('ios-mdns', 'success')).called(1); verify(mockUsage.sendEvent('ios-mdns', 'success')).called(1);
...@@ -362,7 +362,7 @@ void main() { ...@@ -362,7 +362,7 @@ void main() {
final LaunchResult launchResult = await device.startApp(mockApp, final LaunchResult launchResult = await device.startApp(mockApp,
prebuiltApplication: true, prebuiltApplication: true,
debuggingOptions: DebuggingOptions.enabled(const BuildInfo(BuildMode.debug, null)), debuggingOptions: DebuggingOptions.enabled(const BuildInfo(BuildMode.debug, null, treeShakeIcons: false)),
platformArgs: <String, dynamic>{}, platformArgs: <String, dynamic>{},
); );
verify(mockUsage.sendEvent('ios-mdns', 'failure')).called(1); verify(mockUsage.sendEvent('ios-mdns', 'failure')).called(1);
...@@ -395,7 +395,7 @@ void main() { ...@@ -395,7 +395,7 @@ void main() {
final LaunchResult launchResult = await device.startApp(mockApp, final LaunchResult launchResult = await device.startApp(mockApp,
prebuiltApplication: true, prebuiltApplication: true,
debuggingOptions: DebuggingOptions.enabled(const BuildInfo(BuildMode.debug, null)), debuggingOptions: DebuggingOptions.enabled(const BuildInfo(BuildMode.debug, null, treeShakeIcons: false)),
platformArgs: <String, dynamic>{}, platformArgs: <String, dynamic>{},
); );
verify(mockUsage.sendEvent('ios-mdns', 'failure')).called(1); verify(mockUsage.sendEvent('ios-mdns', 'failure')).called(1);
...@@ -416,7 +416,7 @@ void main() { ...@@ -416,7 +416,7 @@ void main() {
final IOSDevice device = IOSDevice('123'); final IOSDevice device = IOSDevice('123');
final LaunchResult launchResult = await device.startApp(mockApp, final LaunchResult launchResult = await device.startApp(mockApp,
prebuiltApplication: true, prebuiltApplication: true,
debuggingOptions: DebuggingOptions.disabled(const BuildInfo(BuildMode.release, null)), debuggingOptions: DebuggingOptions.disabled(const BuildInfo(BuildMode.release, null, treeShakeIcons: false)),
platformArgs: <String, dynamic>{}, platformArgs: <String, dynamic>{},
); );
expect(launchResult.started, isTrue); expect(launchResult.started, isTrue);
...@@ -455,7 +455,7 @@ void main() { ...@@ -455,7 +455,7 @@ void main() {
final LaunchResult launchResult = await device.startApp(mockApp, final LaunchResult launchResult = await device.startApp(mockApp,
prebuiltApplication: true, prebuiltApplication: true,
debuggingOptions: DebuggingOptions.enabled( debuggingOptions: DebuggingOptions.enabled(
const BuildInfo(BuildMode.debug, null), const BuildInfo(BuildMode.debug, null, treeShakeIcons: false),
cacheSkSL: true, cacheSkSL: true,
), ),
platformArgs: <String, dynamic>{}, platformArgs: <String, dynamic>{},
...@@ -499,7 +499,7 @@ void main() { ...@@ -499,7 +499,7 @@ void main() {
final LaunchResult launchResult = await device.startApp(mockApp, final LaunchResult launchResult = await device.startApp(mockApp,
prebuiltApplication: true, prebuiltApplication: true,
debuggingOptions: DebuggingOptions.enabled( debuggingOptions: DebuggingOptions.enabled(
const BuildInfo(BuildMode.debug, null), const BuildInfo(BuildMode.debug, null, treeShakeIcons: false),
deviceVmServicePort: 8181, deviceVmServicePort: 8181,
), ),
platformArgs: <String, dynamic>{}, platformArgs: <String, dynamic>{},
...@@ -600,7 +600,7 @@ void main() { ...@@ -600,7 +600,7 @@ void main() {
device.startApp( device.startApp(
app, app,
prebuiltApplication: false, prebuiltApplication: false,
debuggingOptions: DebuggingOptions.disabled(const BuildInfo(BuildMode.debug, null)), debuggingOptions: DebuggingOptions.disabled(const BuildInfo(BuildMode.debug, null, treeShakeIcons: false)),
platformArgs: <String, dynamic>{}, platformArgs: <String, dynamic>{},
).then((LaunchResult result) { ).then((LaunchResult result) {
completer.complete(result); completer.complete(result);
......
...@@ -10,6 +10,7 @@ import 'package:flutter_tools/src/build_info.dart'; ...@@ -10,6 +10,7 @@ import 'package:flutter_tools/src/build_info.dart';
import 'package:file/memory.dart'; import 'package:file/memory.dart';
import 'package:flutter_tools/src/build_system/build_system.dart'; import 'package:flutter_tools/src/build_system/build_system.dart';
import 'package:flutter_tools/src/build_system/targets/dart.dart'; import 'package:flutter_tools/src/build_system/targets/dart.dart';
import 'package:flutter_tools/src/build_system/targets/icon_tree_shaker.dart';
import 'package:flutter_tools/src/device.dart'; import 'package:flutter_tools/src/device.dart';
import 'package:flutter_tools/src/application_package.dart'; import 'package:flutter_tools/src/application_package.dart';
import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/file_system.dart';
...@@ -219,6 +220,7 @@ void main() { ...@@ -219,6 +220,7 @@ void main() {
kTargetPlatform: 'ios', kTargetPlatform: 'ios',
kBuildMode: 'debug', kBuildMode: 'debug',
kTrackWidgetCreation: 'false', kTrackWidgetCreation: 'false',
kIconTreeShakerFlag: null,
}); });
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
BuildSystem: () => MockBuildSystem(), BuildSystem: () => MockBuildSystem(),
...@@ -501,7 +503,7 @@ void main() { ...@@ -501,7 +503,7 @@ void main() {
final Directory mockDir = globals.fs.currentDirectory; final Directory mockDir = globals.fs.currentDirectory;
final IOSApp package = PrebuiltIOSApp(projectBundleId: 'incorrect', bundleName: 'name', bundleDir: mockDir); final IOSApp package = PrebuiltIOSApp(projectBundleId: 'incorrect', bundleName: 'name', bundleDir: mockDir);
const BuildInfo mockInfo = BuildInfo(BuildMode.debug, 'flavor'); const BuildInfo mockInfo = BuildInfo(BuildMode.debug, 'flavor', treeShakeIcons: false);
final DebuggingOptions mockOptions = DebuggingOptions.disabled(mockInfo); final DebuggingOptions mockOptions = DebuggingOptions.disabled(mockInfo);
await device.startApp(package, prebuiltApplication: true, debuggingOptions: mockOptions); await device.startApp(package, prebuiltApplication: true, debuggingOptions: mockOptions);
......
...@@ -350,14 +350,14 @@ Information about project "Runner": ...@@ -350,14 +350,14 @@ Information about project "Runner":
}); });
testWithoutContext('expected scheme for flavored build is the title-cased flavor', () { testWithoutContext('expected scheme for flavored build is the title-cased flavor', () {
expect(XcodeProjectInfo.expectedSchemeFor(const BuildInfo(BuildMode.debug, 'hello')), 'Hello'); expect(XcodeProjectInfo.expectedSchemeFor(const BuildInfo(BuildMode.debug, 'hello', treeShakeIcons: false)), 'Hello');
expect(XcodeProjectInfo.expectedSchemeFor(const BuildInfo(BuildMode.profile, 'HELLO')), 'HELLO'); expect(XcodeProjectInfo.expectedSchemeFor(const BuildInfo(BuildMode.profile, 'HELLO', treeShakeIcons: false)), 'HELLO');
expect(XcodeProjectInfo.expectedSchemeFor(const BuildInfo(BuildMode.release, 'Hello')), 'Hello'); expect(XcodeProjectInfo.expectedSchemeFor(const BuildInfo(BuildMode.release, 'Hello', treeShakeIcons: false)), 'Hello');
}); });
testWithoutContext('expected build configuration for flavored build is Mode-Flavor', () { testWithoutContext('expected build configuration for flavored build is Mode-Flavor', () {
expect(XcodeProjectInfo.expectedBuildConfigurationFor(const BuildInfo(BuildMode.debug, 'hello'), 'Hello'), 'Debug-Hello'); expect(XcodeProjectInfo.expectedBuildConfigurationFor(const BuildInfo(BuildMode.debug, 'hello', treeShakeIcons: false), 'Hello'), 'Debug-Hello');
expect(XcodeProjectInfo.expectedBuildConfigurationFor(const BuildInfo(BuildMode.profile, 'HELLO'), 'Hello'), 'Profile-Hello'); expect(XcodeProjectInfo.expectedBuildConfigurationFor(const BuildInfo(BuildMode.profile, 'HELLO', treeShakeIcons: false), 'Hello'), 'Profile-Hello');
expect(XcodeProjectInfo.expectedBuildConfigurationFor(const BuildInfo(BuildMode.release, 'Hello'), 'Hello'), 'Release-Hello'); expect(XcodeProjectInfo.expectedBuildConfigurationFor(const BuildInfo(BuildMode.release, 'Hello', treeShakeIcons: false), 'Hello'), 'Release-Hello');
}); });
testWithoutContext('scheme for default project is Runner', () { testWithoutContext('scheme for default project is Runner', () {
...@@ -366,7 +366,7 @@ Information about project "Runner": ...@@ -366,7 +366,7 @@ Information about project "Runner":
expect(info.schemeFor(BuildInfo.debug), 'Runner'); expect(info.schemeFor(BuildInfo.debug), 'Runner');
expect(info.schemeFor(BuildInfo.profile), 'Runner'); expect(info.schemeFor(BuildInfo.profile), 'Runner');
expect(info.schemeFor(BuildInfo.release), 'Runner'); expect(info.schemeFor(BuildInfo.release), 'Runner');
expect(info.schemeFor(const BuildInfo(BuildMode.debug, 'unknown')), isNull); expect(info.schemeFor(const BuildInfo(BuildMode.debug, 'unknown', treeShakeIcons: false)), isNull);
}); });
testWithoutContext('build configuration for default project is matched against BuildMode', () { testWithoutContext('build configuration for default project is matched against BuildMode', () {
...@@ -384,11 +384,11 @@ Information about project "Runner": ...@@ -384,11 +384,11 @@ Information about project "Runner":
<String>['Free', 'Paid'], <String>['Free', 'Paid'],
); );
expect(info.schemeFor(const BuildInfo(BuildMode.debug, 'free')), 'Free'); expect(info.schemeFor(const BuildInfo(BuildMode.debug, 'free', treeShakeIcons: false)), 'Free');
expect(info.schemeFor(const BuildInfo(BuildMode.profile, 'Free')), 'Free'); expect(info.schemeFor(const BuildInfo(BuildMode.profile, 'Free', treeShakeIcons: false)), 'Free');
expect(info.schemeFor(const BuildInfo(BuildMode.release, 'paid')), 'Paid'); expect(info.schemeFor(const BuildInfo(BuildMode.release, 'paid', treeShakeIcons: false)), 'Paid');
expect(info.schemeFor(const BuildInfo(BuildMode.debug, null)), isNull); expect(info.schemeFor(const BuildInfo(BuildMode.debug, null, treeShakeIcons: false)), isNull);
expect(info.schemeFor(const BuildInfo(BuildMode.debug, 'unknown')), isNull); expect(info.schemeFor(const BuildInfo(BuildMode.debug, 'unknown', treeShakeIcons: false)), isNull);
}); });
testWithoutContext('build configuration for project with custom schemes is matched against BuildMode and flavor', () { testWithoutContext('build configuration for project with custom schemes is matched against BuildMode and flavor', () {
...@@ -398,10 +398,10 @@ Information about project "Runner": ...@@ -398,10 +398,10 @@ Information about project "Runner":
<String>['Free', 'Paid'], <String>['Free', 'Paid'],
); );
expect(info.buildConfigurationFor(const BuildInfo(BuildMode.debug, 'free'), 'Free'), 'debug (free)'); expect(info.buildConfigurationFor(const BuildInfo(BuildMode.debug, 'free', treeShakeIcons: false), 'Free'), 'debug (free)');
expect(info.buildConfigurationFor(const BuildInfo(BuildMode.debug, 'Paid'), 'Paid'), 'Debug paid'); expect(info.buildConfigurationFor(const BuildInfo(BuildMode.debug, 'Paid', treeShakeIcons: false), 'Paid'), 'Debug paid');
expect(info.buildConfigurationFor(const BuildInfo(BuildMode.profile, 'FREE'), 'Free'), 'profile - Free'); expect(info.buildConfigurationFor(const BuildInfo(BuildMode.profile, 'FREE', treeShakeIcons: false), 'Free'), 'profile - Free');
expect(info.buildConfigurationFor(const BuildInfo(BuildMode.release, 'paid'), 'Paid'), 'Release-Paid'); expect(info.buildConfigurationFor(const BuildInfo(BuildMode.release, 'paid', treeShakeIcons: false), 'Paid'), 'Release-Paid');
}); });
testWithoutContext('build configuration for project with inconsistent naming is null', () { testWithoutContext('build configuration for project with inconsistent naming is null', () {
...@@ -410,9 +410,9 @@ Information about project "Runner": ...@@ -410,9 +410,9 @@ Information about project "Runner":
<String>['Debug-F', 'Dbg Paid', 'Rel Free', 'Release Full'], <String>['Debug-F', 'Dbg Paid', 'Rel Free', 'Release Full'],
<String>['Free', 'Paid'], <String>['Free', 'Paid'],
); );
expect(info.buildConfigurationFor(const BuildInfo(BuildMode.debug, 'Free'), 'Free'), null); expect(info.buildConfigurationFor(const BuildInfo(BuildMode.debug, 'Free', treeShakeIcons: false), 'Free'), null);
expect(info.buildConfigurationFor(const BuildInfo(BuildMode.profile, 'Free'), 'Free'), null); expect(info.buildConfigurationFor(const BuildInfo(BuildMode.profile, 'Free', treeShakeIcons: false), 'Free'), null);
expect(info.buildConfigurationFor(const BuildInfo(BuildMode.release, 'Paid'), 'Paid'), null); expect(info.buildConfigurationFor(const BuildInfo(BuildMode.release, 'Paid', treeShakeIcons: false), 'Paid'), null);
}); });
group('environmentVariablesAsXcodeBuildSettings', () { group('environmentVariablesAsXcodeBuildSettings', () {
FakePlatform platform; FakePlatform platform;
...@@ -461,7 +461,7 @@ Information about project "Runner": ...@@ -461,7 +461,7 @@ Information about project "Runner":
platform: TargetPlatform.ios, mode: anyNamed('mode'))).thenReturn('engine'); platform: TargetPlatform.ios, mode: anyNamed('mode'))).thenReturn('engine');
when(mockArtifacts.engineOutPath).thenReturn(fs.path.join('out', 'ios_profile_arm')); when(mockArtifacts.engineOutPath).thenReturn(fs.path.join('out', 'ios_profile_arm'));
const BuildInfo buildInfo = BuildInfo(BuildMode.debug, null); const BuildInfo buildInfo = BuildInfo(BuildMode.debug, null, treeShakeIcons: false);
final FlutterProject project = FlutterProject.fromPath('path/to/project'); final FlutterProject project = FlutterProject.fromPath('path/to/project');
await updateGeneratedXcodeProperties( await updateGeneratedXcodeProperties(
project: project, project: project,
...@@ -485,7 +485,7 @@ Information about project "Runner": ...@@ -485,7 +485,7 @@ Information about project "Runner":
when(mockArtifacts.getArtifactPath(Artifact.flutterFramework, when(mockArtifacts.getArtifactPath(Artifact.flutterFramework,
platform: TargetPlatform.ios, mode: anyNamed('mode'))).thenReturn('engine'); platform: TargetPlatform.ios, mode: anyNamed('mode'))).thenReturn('engine');
when(mockArtifacts.engineOutPath).thenReturn(fs.path.join('out', 'ios_profile_arm')); when(mockArtifacts.engineOutPath).thenReturn(fs.path.join('out', 'ios_profile_arm'));
const BuildInfo buildInfo = BuildInfo(BuildMode.debug, null, trackWidgetCreation: true); const BuildInfo buildInfo = BuildInfo(BuildMode.debug, null, trackWidgetCreation: true, treeShakeIcons: false);
final FlutterProject project = FlutterProject.fromPath('path/to/project'); final FlutterProject project = FlutterProject.fromPath('path/to/project');
await updateGeneratedXcodeProperties( await updateGeneratedXcodeProperties(
project: project, project: project,
...@@ -509,7 +509,7 @@ Information about project "Runner": ...@@ -509,7 +509,7 @@ Information about project "Runner":
when(mockArtifacts.getArtifactPath(Artifact.flutterFramework, when(mockArtifacts.getArtifactPath(Artifact.flutterFramework,
platform: TargetPlatform.ios, mode: anyNamed('mode'))).thenReturn('engine'); platform: TargetPlatform.ios, mode: anyNamed('mode'))).thenReturn('engine');
when(mockArtifacts.engineOutPath).thenReturn(fs.path.join('out', 'ios_profile_arm')); when(mockArtifacts.engineOutPath).thenReturn(fs.path.join('out', 'ios_profile_arm'));
const BuildInfo buildInfo = BuildInfo(BuildMode.debug, null); const BuildInfo buildInfo = BuildInfo(BuildMode.debug, null, treeShakeIcons: false);
final FlutterProject project = FlutterProject.fromPath('path/to/project'); final FlutterProject project = FlutterProject.fromPath('path/to/project');
await updateGeneratedXcodeProperties( await updateGeneratedXcodeProperties(
project: project, project: project,
...@@ -533,7 +533,7 @@ Information about project "Runner": ...@@ -533,7 +533,7 @@ Information about project "Runner":
when(mockArtifacts.getArtifactPath(Artifact.flutterFramework, when(mockArtifacts.getArtifactPath(Artifact.flutterFramework,
platform: TargetPlatform.ios, mode: anyNamed('mode'))).thenReturn('engine'); platform: TargetPlatform.ios, mode: anyNamed('mode'))).thenReturn('engine');
when(mockArtifacts.engineOutPath).thenReturn(fs.path.join('out', 'ios_profile')); when(mockArtifacts.engineOutPath).thenReturn(fs.path.join('out', 'ios_profile'));
const BuildInfo buildInfo = BuildInfo(BuildMode.debug, null); const BuildInfo buildInfo = BuildInfo(BuildMode.debug, null, treeShakeIcons: false);
final FlutterProject project = FlutterProject.fromPath('path/to/project'); final FlutterProject project = FlutterProject.fromPath('path/to/project');
await updateGeneratedXcodeProperties( await updateGeneratedXcodeProperties(
...@@ -595,7 +595,7 @@ dependencies: ...@@ -595,7 +595,7 @@ dependencies:
flutter: flutter:
'''; ''';
const BuildInfo buildInfo = BuildInfo(BuildMode.release, null); const BuildInfo buildInfo = BuildInfo(BuildMode.release, null, treeShakeIcons: false);
await checkBuildVersion( await checkBuildVersion(
manifestString: manifest, manifestString: manifest,
buildInfo: buildInfo, buildInfo: buildInfo,
...@@ -613,7 +613,7 @@ dependencies: ...@@ -613,7 +613,7 @@ dependencies:
sdk: flutter sdk: flutter
flutter: flutter:
'''; ''';
const BuildInfo buildInfo = BuildInfo(BuildMode.release, null); const BuildInfo buildInfo = BuildInfo(BuildMode.release, null, treeShakeIcons: false);
await checkBuildVersion( await checkBuildVersion(
manifestString: manifest, manifestString: manifest,
buildInfo: buildInfo, buildInfo: buildInfo,
...@@ -631,7 +631,7 @@ dependencies: ...@@ -631,7 +631,7 @@ dependencies:
sdk: flutter sdk: flutter
flutter: flutter:
'''; ''';
const BuildInfo buildInfo = BuildInfo(BuildMode.release, null, buildName: '1.0.2'); const BuildInfo buildInfo = BuildInfo(BuildMode.release, null, buildName: '1.0.2', treeShakeIcons: false);
await checkBuildVersion( await checkBuildVersion(
manifestString: manifest, manifestString: manifest,
buildInfo: buildInfo, buildInfo: buildInfo,
...@@ -649,7 +649,7 @@ dependencies: ...@@ -649,7 +649,7 @@ dependencies:
sdk: flutter sdk: flutter
flutter: flutter:
'''; ''';
const BuildInfo buildInfo = BuildInfo(BuildMode.release, null, buildName: '1.0.2'); const BuildInfo buildInfo = BuildInfo(BuildMode.release, null, buildName: '1.0.2', treeShakeIcons: false);
await checkBuildVersion( await checkBuildVersion(
manifestString: manifest, manifestString: manifest,
buildInfo: buildInfo, buildInfo: buildInfo,
...@@ -667,7 +667,7 @@ dependencies: ...@@ -667,7 +667,7 @@ dependencies:
sdk: flutter sdk: flutter
flutter: flutter:
'''; ''';
const BuildInfo buildInfo = BuildInfo(BuildMode.release, null, buildNumber: '3'); const BuildInfo buildInfo = BuildInfo(BuildMode.release, null, buildNumber: '3', treeShakeIcons: false);
await checkBuildVersion( await checkBuildVersion(
manifestString: manifest, manifestString: manifest,
buildInfo: buildInfo, buildInfo: buildInfo,
...@@ -685,7 +685,7 @@ dependencies: ...@@ -685,7 +685,7 @@ dependencies:
sdk: flutter sdk: flutter
flutter: flutter:
'''; ''';
const BuildInfo buildInfo = BuildInfo(BuildMode.release, null, buildName: '1.0.2', buildNumber: '3'); const BuildInfo buildInfo = BuildInfo(BuildMode.release, null, buildName: '1.0.2', buildNumber: '3', treeShakeIcons: false);
await checkBuildVersion( await checkBuildVersion(
manifestString: manifest, manifestString: manifest,
buildInfo: buildInfo, buildInfo: buildInfo,
...@@ -703,7 +703,7 @@ dependencies: ...@@ -703,7 +703,7 @@ dependencies:
sdk: flutter sdk: flutter
flutter: flutter:
'''; ''';
const BuildInfo buildInfo = BuildInfo(BuildMode.release, null, buildName: '1.0.2', buildNumber: '3'); const BuildInfo buildInfo = BuildInfo(BuildMode.release, null, buildName: '1.0.2', buildNumber: '3', treeShakeIcons: false);
await checkBuildVersion( await checkBuildVersion(
manifestString: manifest, manifestString: manifest,
buildInfo: buildInfo, buildInfo: buildInfo,
...@@ -720,7 +720,7 @@ dependencies: ...@@ -720,7 +720,7 @@ dependencies:
sdk: flutter sdk: flutter
flutter: flutter:
'''; ''';
const BuildInfo buildInfo = BuildInfo(BuildMode.release, null, buildName: '1.0.2', buildNumber: '3'); const BuildInfo buildInfo = BuildInfo(BuildMode.release, null, buildName: '1.0.2', buildNumber: '3', treeShakeIcons: false);
await checkBuildVersion( await checkBuildVersion(
manifestString: manifest, manifestString: manifest,
buildInfo: buildInfo, buildInfo: buildInfo,
...@@ -737,7 +737,7 @@ dependencies: ...@@ -737,7 +737,7 @@ dependencies:
sdk: flutter sdk: flutter
flutter: flutter:
'''; ''';
const BuildInfo buildInfo = BuildInfo(BuildMode.release, null); const BuildInfo buildInfo = BuildInfo(BuildMode.release, null, treeShakeIcons: false);
await checkBuildVersion( await checkBuildVersion(
manifestString: manifest, manifestString: manifest,
buildInfo: buildInfo, buildInfo: buildInfo,
......
...@@ -148,7 +148,7 @@ void main() { ...@@ -148,7 +148,7 @@ void main() {
testUsingContext('not debug', () async { testUsingContext('not debug', () async {
final LaunchResult result = await device.startApp(null, final LaunchResult result = await device.startApp(null,
mainPath: mainPath, mainPath: mainPath,
debuggingOptions: DebuggingOptions.disabled(const BuildInfo(BuildMode.release, null))); debuggingOptions: DebuggingOptions.disabled(const BuildInfo(BuildMode.release, null, treeShakeIcons: false)));
expect(result.started, isFalse); expect(result.started, isFalse);
}, overrides: startOverrides); }, overrides: startOverrides);
...@@ -166,7 +166,7 @@ Hello! ...@@ -166,7 +166,7 @@ Hello!
final LaunchResult result = await device.startApp(null, final LaunchResult result = await device.startApp(null,
mainPath: mainPath, mainPath: mainPath,
debuggingOptions: DebuggingOptions.enabled(const BuildInfo(BuildMode.debug, null))); debuggingOptions: DebuggingOptions.enabled(const BuildInfo(BuildMode.debug, null, treeShakeIcons: false)));
expect(result.started, isTrue); expect(result.started, isTrue);
expect(result.observatoryUri, observatoryUri); expect(result.observatoryUri, observatoryUri);
expect(logLines.last, 'Hello!'); expect(logLines.last, 'Hello!');
......
...@@ -372,7 +372,7 @@ class CompleterIOSink extends MemoryIOSink { ...@@ -372,7 +372,7 @@ class CompleterIOSink extends MemoryIOSink {
_completer.complete(throwOnAdd ? <int>[] : data); _completer.complete(throwOnAdd ? <int>[] : data);
} }
if (throwOnAdd) { if (throwOnAdd) {
throw 'CompleterIOSink Error'; throw Exception('CompleterIOSink Error');
} }
super.add(data); super.add(data);
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment