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 {
case DeviceOperatingSystem.android:
options.insert(0, 'apk');
options.add('--target-platform=android-arm');
options.add('--tree-shake-icons');
watch.start();
await flutter('build', options: options);
watch.stop();
......
......@@ -62,6 +62,11 @@ ephemeral_dir="${SOURCE_ROOT}/Flutter/ephemeral"
build_inputs_path="${ephemeral_dir}/FlutterInputs.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 \
${verbose_flag} \
${flutter_engine_flag} \
......@@ -70,6 +75,7 @@ RunCommand "${FLUTTER_ROOT}/bin/flutter" --suppress-analytics \
-dTargetPlatform=darwin-x64 \
-dTargetFile="${target_path}" \
-dBuildMode="${build_mode}" \
-dFontSubset="${icon_tree_shaker_flag}" \
--build-inputs="${build_inputs_path}" \
--build-outputs="${build_outputs_path}" \
--output="${ephemeral_dir}" \
......
......@@ -603,6 +603,10 @@ class FlutterPlugin implements Plugin<Project> {
if (project.hasProperty('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 addFlutterDeps = { variant ->
if (shouldSplitPerAbi()) {
......@@ -637,6 +641,7 @@ class FlutterPlugin implements Plugin<Project> {
intermediateDir project.file("${project.buildDir}/${AndroidProject.FD_INTERMEDIATES}/flutter/${variant.name}/")
extraFrontEndOptions extraFrontEndOptionsValue
extraGenSnapshotOptions extraGenSnapshotOptionsValue
treeShakeIcons treeShakeIconsOptionsValue
}
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) {
......@@ -769,6 +774,8 @@ abstract class BaseFlutterTask extends DefaultTask {
String extraFrontEndOptions
@Optional @Input
String extraGenSnapshotOptions
@Optional @Input
Boolean treeShakeIcons
@OutputFiles
FileCollection getDependenciesFiles() {
......@@ -825,6 +832,9 @@ abstract class BaseFlutterTask extends DefaultTask {
if (extraFrontEndOptions != null) {
args "-dExtraFrontEndOptions=${extraFrontEndOptions}"
}
if (treeShakeIcons == true) {
args "-dTreeShakeIcons=true"
}
if (extraGenSnapshotOptions != null) {
args "--ExtraGenSnapshotOptions=${extraGenSnapshotOptions}"
}
......
......@@ -338,6 +338,9 @@ Future<void> buildGradleApp({
if (androidBuildInfo.fastStart) {
command.add('-Pfast-start=true');
}
if (androidBuildInfo.buildInfo.treeShakeIcons) {
command.add('-Ptree-shake-icons=true');
}
command.add(assembleTask);
GradleHandledError detectedGradleError;
......@@ -546,6 +549,9 @@ Future<void> buildGradleAar({
command.add('-Plocal-engine-repo=${localEngineRepo.path}');
command.add('-Plocal-engine-build-mode=${androidBuildInfo.buildInfo.modeName}');
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.
try {
......@@ -738,10 +744,11 @@ Future<void> buildPluginsAsAar(
try {
await buildGradleAar(
project: FlutterProject.fromDirectory(pluginDirectory),
androidBuildInfo: const AndroidBuildInfo(
androidBuildInfo: AndroidBuildInfo(
BuildInfo(
BuildMode.release, // Plugins are built as release.
null, // Plugins don't define flavors.
treeShakeIcons: androidBuildInfo.buildInfo.treeShakeIcons,
),
),
target: '',
......
......@@ -36,6 +36,7 @@ class AotBuilder {
List<String> extraFrontEndOptions,
List<String> extraGenSnapshotOptions,
@required List<String> dartDefines,
@required bool treeShakeIcons,
}) async {
if (platform == null) {
throwToolExit('No AOT build platform specified');
......
......@@ -367,7 +367,10 @@ class CachedArtifacts extends Artifacts {
return _fileSystem.path.join(dartPackageDirectory.path, _artifactToFileName(artifact));
case Artifact.fontSubset:
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:
assert(false, 'Artifact $artifact not available for platform $platform.');
return null;
......
......@@ -21,6 +21,8 @@ const AssetBundleFactory _kManifestFactory = _ManifestAssetBundleFactory();
const String defaultManifestPath = 'pubspec.yaml';
const String kFontManifestJson = 'FontManifest.json';
/// Injected factory class for spawning [AssetBundle] instances.
abstract class AssetBundleFactory {
/// The singleton instance, pulled from the [AppContext].
......@@ -71,7 +73,6 @@ class _ManifestAssetBundle implements AssetBundle {
DateTime _lastBuildTimestamp;
static const String _assetManifestJson = 'AssetManifest.json';
static const String _fontManifestJson = 'FontManifest.json';
static const String _fontSetMaterial = 'material';
static const String _license = 'LICENSE';
......@@ -242,7 +243,7 @@ class _ManifestAssetBundle implements AssetBundle {
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
entries[_license] = _obtainLicenses(packageMap, assetBasePath, reportPackages: reportLicensedPackages);
......
......@@ -2,8 +2,11 @@
// 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 'base/context.dart';
import 'base/utils.dart';
import 'build_system/targets/icon_tree_shaker.dart';
import 'globals.dart' as globals;
/// Information about a build to be performed or used.
......@@ -18,10 +21,14 @@ class BuildInfo {
this.fileSystemScheme,
this.buildNumber,
this.buildName,
@required this.treeShakeIcons,
});
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
/// using the default.
///
......@@ -55,10 +62,10 @@ class BuildInfo {
/// On Xcode builds it is used as CFBundleShortVersionString,
final String buildName;
static const BuildInfo debug = BuildInfo(BuildMode.debug, null);
static const BuildInfo profile = BuildInfo(BuildMode.profile, null);
static const BuildInfo jitRelease = BuildInfo(BuildMode.jitRelease, null);
static const BuildInfo release = BuildInfo(BuildMode.release, null);
static const BuildInfo debug = BuildInfo(BuildMode.debug, null, treeShakeIcons: false);
static const BuildInfo profile = BuildInfo(BuildMode.profile, null, treeShakeIcons: kIconTreeShakerEnabledDefault);
static const BuildInfo jitRelease = BuildInfo(BuildMode.jitRelease, null, treeShakeIcons: kIconTreeShakerEnabledDefault);
static const BuildInfo release = BuildInfo(BuildMode.release, null, treeShakeIcons: kIconTreeShakerEnabledDefault);
/// Returns whether a debug build is requested.
///
......
......@@ -12,6 +12,7 @@ import '../depfile.dart';
import '../exceptions.dart';
import 'assets.dart';
import 'dart.dart';
import 'icon_tree_shaker.dart';
/// Prepares the asset bundle in the format expected by flutter.gradle.
///
......@@ -25,6 +26,7 @@ abstract class AndroidAssetBundle extends Target {
@override
List<Source> get inputs => const <Source>[
Source.pattern('{BUILD_DIR}/app.dill'),
...IconTreeShaker.inputs,
];
@override
......
......@@ -12,6 +12,8 @@ import '../../plugins.dart';
import '../../project.dart';
import '../build_system.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
/// directory.
......@@ -31,6 +33,16 @@ Future<Depfile> copyAssets(Environment environment, Directory outputDirectory) a
pubspecFile,
];
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>(
assetBundle.entries.entries.map<Future<void>>((MapEntry<String, DevFSContent> entry) async {
final PoolResource resource = await pool.request();
......@@ -46,7 +58,13 @@ Future<Depfile> copyAssets(Environment environment, Directory outputDirectory) a
final DevFSContent content = entry.value;
if (content is DevFSFileContent && content.file is File) {
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 {
await file.writeAsBytes(await entry.value.contentsAsBytes());
}
......@@ -65,11 +83,14 @@ class CopyAssets extends Target {
String get name => 'copy_assets';
@override
List<Target> get dependencies => const <Target>[];
List<Target> get dependencies => const <Target>[
KernelSnapshot(),
];
@override
List<Source> get inputs => const <Source>[
Source.pattern('{FLUTTER_ROOT}/packages/flutter_tools/lib/src/build_system/targets/assets.dart'),
...IconTreeShaker.inputs,
];
@override
......
......@@ -14,6 +14,7 @@ import '../build_system.dart';
import '../depfile.dart';
import '../exceptions.dart';
import 'assets.dart';
import 'icon_tree_shaker.dart';
/// The define to pass a [BuildMode].
const String kBuildMode= 'BuildMode';
......@@ -75,6 +76,7 @@ class CopyFlutterBundle extends Target {
Source.artifact(Artifact.vmSnapshotData, mode: BuildMode.debug),
Source.artifact(Artifact.isolateSnapshotData, mode: BuildMode.debug),
Source.pattern('{BUILD_DIR}/app.dill'),
...IconTreeShaker.inputs,
];
@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';
import '../exceptions.dart';
import 'assets.dart';
import 'dart.dart';
import 'icon_tree_shaker.dart';
/// The only files/subdirectories we care out.
const List<String> _kLinuxArtifacts = <String>[
......@@ -119,6 +120,7 @@ class DebugBundleLinuxAssets extends Target {
Source.pattern('{BUILD_DIR}/app.dill'),
Source.pattern('{FLUTTER_ROOT}/packages/flutter_tools/lib/src/build_system/targets/linux.dart'),
Source.pattern('{PROJECT_DIR}/pubspec.yaml'),
...IconTreeShaker.inputs,
];
@override
......
......@@ -14,6 +14,7 @@ import '../depfile.dart';
import '../exceptions.dart';
import 'assets.dart';
import 'dart.dart';
import 'icon_tree_shaker.dart';
const String _kOutputPrefix = '{OUTPUT_DIR}/FlutterMacOS.framework';
......@@ -243,6 +244,7 @@ abstract class MacOSBundleFlutterAssets extends Target {
@override
List<Source> get inputs => const <Source>[
Source.pattern('{BUILD_DIR}/App.framework/App'),
...IconTreeShaker.inputs,
];
@override
......
......@@ -15,6 +15,7 @@ import 'build_info.dart';
import 'build_system/build_system.dart';
import 'build_system/depfile.dart';
import 'build_system/targets/dart.dart';
import 'build_system/targets/icon_tree_shaker.dart';
import 'cache.dart';
import 'dart/package_map.dart';
import 'devfs.dart';
......@@ -68,6 +69,7 @@ class BundleBuilder {
List<String> extraGenSnapshotOptions = const <String>[],
List<String> fileSystemRoots,
String fileSystemScheme,
@required bool treeShakeIcons,
}) async {
mainPath ??= defaultMainPath;
depfilePath ??= defaultDepfilePath;
......@@ -83,6 +85,7 @@ class BundleBuilder {
depfilePath: depfilePath,
precompiled: precompiledSnapshot,
trackWidgetCreation: trackWidgetCreation,
treeShakeIcons: treeShakeIcons,
);
// Work around for flutter_tester placing kernel artifacts in odd places.
if (applicationKernelFilePath != null) {
......@@ -107,6 +110,7 @@ Future<void> buildWithAssemble({
@required String depfilePath,
@required bool precompiled,
bool trackWidgetCreation,
@required bool treeShakeIcons,
}) async {
// If the precompiled flag was not passed, force us into debug mode.
buildMode = precompiled ? buildMode : BuildMode.debug;
......@@ -121,6 +125,7 @@ Future<void> buildWithAssemble({
kBuildMode: getNameForBuildMode(buildMode),
kTargetPlatform: getNameForTargetPlatform(targetPlatform),
kTrackWidgetCreation: trackWidgetCreation?.toString(),
kIconTreeShakerFlag: treeShakeIcons ? 'true' : null,
},
);
final Target target = buildMode == BuildMode.debug
......
......@@ -34,6 +34,7 @@ class BuildAarCommand extends BuildSubCommand {
defaultsTo: true,
help: 'Build a release version of the current project.',
);
addTreeShakeIconsFlag();
usesFlavorOption();
usesBuildNumberOption();
usesPubOption();
......@@ -104,7 +105,7 @@ class BuildAarCommand extends BuildSubCommand {
for (final String buildMode in const <String>['debug', 'profile', 'release']) {
if (boolArg(buildMode)) {
androidBuildInfo.add(AndroidBuildInfo(
BuildInfo(BuildMode.fromName(buildMode), stringArg('flavor')),
BuildInfo(BuildMode.fromName(buildMode), stringArg('flavor'), treeShakeIcons: boolArg('tree-shake-icons')),
targetArchs: targetArchitectures,
));
}
......
......@@ -15,6 +15,7 @@ import 'build.dart';
/// Builds AOT snapshots into platform specific library containers.
class BuildAotCommand extends BuildSubCommand with TargetPlatformBasedDevelopmentArtifacts {
BuildAotCommand({bool verboseHelp = false, this.aotBuilder}) {
addTreeShakeIconsFlag();
usesTargetOption();
addBuildModeFlags();
usesPubOption();
......@@ -88,6 +89,7 @@ class BuildAotCommand extends BuildSubCommand with TargetPlatformBasedDevelopmen
extraFrontEndOptions: stringsArg(FlutterOptions.kExtraFrontEndOptions),
extraGenSnapshotOptions: stringsArg(FlutterOptions.kExtraGenSnapshotOptions),
dartDefines: dartDefines,
treeShakeIcons: boolArg('tree-shake-icons'),
);
return FlutterCommandResult.success();
}
......
......@@ -18,6 +18,7 @@ import 'build.dart';
class BuildApkCommand extends BuildSubCommand {
BuildApkCommand({bool verboseHelp = false}) {
addTreeShakeIconsFlag();
usesTargetOption();
addBuildModeFlags(verboseHelp: verboseHelp);
usesFlavorOption();
......@@ -25,7 +26,6 @@ class BuildApkCommand extends BuildSubCommand {
usesBuildNumberOption();
usesBuildNameOption();
addShrinkingFlag();
argParser
..addFlag('split-per-abi',
negatable: false,
......
......@@ -16,6 +16,7 @@ import 'build.dart';
class BuildAppBundleCommand extends BuildSubCommand {
BuildAppBundleCommand({bool verboseHelp = false}) {
addTreeShakeIconsFlag();
usesTargetOption();
addBuildModeFlags();
usesFlavorOption();
......
......@@ -16,6 +16,7 @@ import 'build.dart';
class BuildBundleCommand extends BuildSubCommand {
BuildBundleCommand({bool verboseHelp = false, this.bundleBuilder}) {
addTreeShakeIconsFlag();
usesTargetOption();
usesFilesystemOptions(hide: !verboseHelp);
usesBuildNumberOption();
......@@ -138,6 +139,7 @@ class BuildBundleCommand extends BuildSubCommand {
extraGenSnapshotOptions: stringsArg(FlutterOptions.kExtraGenSnapshotOptions),
fileSystemScheme: stringArg('filesystem-scheme'),
fileSystemRoots: stringsArg('filesystem-root'),
treeShakeIcons: boolArg('tree-shake-icons'),
);
return FlutterCommandResult.success();
}
......
......@@ -17,6 +17,7 @@ import 'build.dart';
/// A command to build a Fuchsia target.
class BuildFuchsiaCommand extends BuildSubCommand {
BuildFuchsiaCommand({bool verboseHelp = false}) {
addTreeShakeIconsFlag();
usesTargetOption();
addBuildModeFlags(verboseHelp: verboseHelp);
argParser.addOption(
......
......@@ -18,6 +18,7 @@ import 'build.dart';
/// .ipas, see https://flutter.dev/docs/deployment/ios.
class BuildIOSCommand extends BuildSubCommand {
BuildIOSCommand() {
addTreeShakeIconsFlag();
addBuildModeFlags(defaultToRelease: false);
usesTargetOption();
usesFlavorOption();
......
......@@ -44,6 +44,7 @@ class BuildIOSFrameworkCommand extends BuildSubCommand {
_bundleBuilder = bundleBuilder,
_cache = cache,
_platform = platform {
addTreeShakeIconsFlag();
usesTargetOption();
usesFlavorOption();
usesPubOption();
......@@ -361,6 +362,7 @@ end
mainPath: globals.fs.path.absolute(targetFile),
assetDirPath: destinationAppFrameworkDirectory.childDirectory('flutter_assets').path,
precompiledSnapshot: mode != BuildMode.debug,
treeShakeIcons: boolArg('tree-shake-icons')
);
} finally {
status.stop();
......@@ -426,6 +428,7 @@ end
reportTimings: false,
iosBuildArchs: <DarwinArch>[DarwinArch.armv7, DarwinArch.arm64],
dartDefines: dartDefines,
treeShakeIcons: boolArg('tree-shake-icons'),
);
} finally {
status.stop();
......
......@@ -17,6 +17,7 @@ import 'build.dart';
/// A command to build a linux desktop target through a build shell script.
class BuildLinuxCommand extends BuildSubCommand {
BuildLinuxCommand() {
addTreeShakeIconsFlag();
addBuildModeFlags();
usesTargetOption();
}
......
......@@ -17,6 +17,7 @@ import 'build.dart';
/// A command to build a macOS desktop target through a build shell script.
class BuildMacosCommand extends BuildSubCommand {
BuildMacosCommand() {
addTreeShakeIconsFlag();
usesTargetOption();
addBuildModeFlags();
}
......
......@@ -15,6 +15,7 @@ import 'build.dart';
class BuildWebCommand extends BuildSubCommand {
BuildWebCommand() {
addTreeShakeIconsFlag();
usesTargetOption();
usesPubOption();
addBuildModeFlags(excludeDebug: true);
......
......@@ -17,6 +17,7 @@ import 'build.dart';
/// A command to build a windows desktop target through a build shell script.
class BuildWindowsCommand extends BuildSubCommand {
BuildWindowsCommand() {
addTreeShakeIconsFlag();
addBuildModeFlags();
usesTargetOption();
}
......
......@@ -209,7 +209,6 @@ Future<XcodeBuildResult> buildXcodeProject({
bool buildForDevice,
DarwinArch activeArch,
bool codesign = true,
}) async {
if (!upgradePbxProjWithFlutterAssets(app.project)) {
return XcodeBuildResult(success: false);
......
......@@ -428,7 +428,8 @@ class IOSSimulator extends Device {
final BuildInfo debugBuildInfo = BuildInfo(BuildMode.debug, buildInfo.flavor,
trackWidgetCreation: buildInfo.trackWidgetCreation,
extraFrontEndOptions: buildInfo.extraFrontEndOptions,
extraGenSnapshotOptions: buildInfo.extraGenSnapshotOptions);
extraGenSnapshotOptions: buildInfo.extraGenSnapshotOptions,
treeShakeIcons: buildInfo.treeShakeIcons);
final XcodeBuildResult buildResult = await buildXcodeProject(
app: app,
......@@ -460,6 +461,7 @@ class IOSSimulator extends Device {
precompiledSnapshot: false,
buildMode: buildInfo.mode,
trackWidgetCreation: buildInfo.trackWidgetCreation,
treeShakeIcons: false,
);
}
......
......@@ -216,6 +216,10 @@ List<String> _xcodeBuildSettingsLines({
xcodeBuildSettings.add('TRACK_WIDGET_CREATION=true');
}
if (buildInfo.treeShakeIcons) {
xcodeBuildSettings.add('TREE_SHAKE_ICONS=true');
}
return xcodeBuildSettings;
}
......
......@@ -85,6 +85,8 @@ Future<void> buildMacOS({
'OBJROOT=${globals.fs.path.join(flutterBuildDir.absolute.path, 'Build', 'Intermediates.noindex')}',
'SYMROOT=${globals.fs.path.join(flutterBuildDir.absolute.path, 'Build', 'Products')}',
'COMPILER_INDEX_STORE_ENABLE=NO',
if (buildInfo.treeShakeIcons)
'TREE_SHAKE_ICONS=true',
...environmentVariablesAsXcodeBuildSettings(globals.platform)
], trace: true);
} finally {
......
......@@ -20,6 +20,7 @@ import '../base/time.dart';
import '../base/user_messages.dart';
import '../base/utils.dart';
import '../build_info.dart';
import '../build_system/targets/icon_tree_shaker.dart' show kIconTreeShakerEnabledDefault;
import '../bundle.dart' as bundle;
import '../cache.dart';
import '../dart/package_map.dart';
......@@ -365,6 +366,14 @@ abstract class FlutterCommand extends Command<void> {
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() {
argParser.addFlag('shrink',
negatable: true,
......@@ -490,6 +499,9 @@ abstract class FlutterCommand extends Command<void> {
buildName: argParser.options.containsKey('build-name')
? stringArg('build-name')
: null,
treeShakeIcons: argParser.options.containsKey('tree-shake-icons')
? boolArg('tree-shake-icons')
: kIconTreeShakerEnabledDefault,
);
}
......
......@@ -151,6 +151,7 @@ class FlutterTesterDevice extends Device {
precompiledSnapshot: false,
trackWidgetCreation: buildInfo.trackWidgetCreation,
platform: getTargetPlatformForName(getNameForHostPlatform(getCurrentHostPlatform())),
treeShakeIcons: buildInfo.treeShakeIcons,
);
command.add('--flutter-assets-dir=$assetDirPath');
......
......@@ -52,6 +52,8 @@ Future<void> buildWeb(
kHasWebPlugins: hasWebPlugins.toString(),
kDartDefines: jsonEncode(dartDefines),
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) {
......
......@@ -5,7 +5,7 @@ author: Flutter Authors <flutter-dev@googlegroups.com>
environment:
# 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:
# To update these, use "flutter update-packages --force-upgrade".
......
......@@ -7,6 +7,7 @@ import 'package:file/memory.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/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/cache.dart';
import 'package:flutter_tools/src/commands/build_bundle.dart';
......@@ -47,6 +48,7 @@ void main() {
extraGenSnapshotOptions: anyNamed('extraGenSnapshotOptions'),
fileSystemRoots: anyNamed('fileSystemRoots'),
fileSystemScheme: anyNamed('fileSystemScheme'),
treeShakeIcons: anyNamed('treeShakeIcons'),
),
).thenAnswer((_) => Future<void>.value());
});
......@@ -217,6 +219,7 @@ void main() {
kBuildMode: 'debug',
kTargetPlatform: 'android-arm',
kTrackWidgetCreation: 'true',
kIconTreeShakerFlag: null,
});
return BuildResult(success: true);
......
......@@ -101,7 +101,7 @@ void main() {
mockApk,
prebuiltApplication: true,
debuggingOptions: DebuggingOptions.disabled(
const BuildInfo(BuildMode.release, null),
const BuildInfo(BuildMode.release, null, treeShakeIcons: false),
cacheSkSL: true,
),
platformArgs: <String, dynamic>{},
......@@ -153,7 +153,7 @@ void main() {
mockApk,
prebuiltApplication: true,
debuggingOptions: DebuggingOptions.disabled(
const BuildInfo(BuildMode.release, null),
const BuildInfo(BuildMode.release, null, treeShakeIcons: false),
),
platformArgs: <String, dynamic>{},
);
......
......@@ -98,33 +98,33 @@ void main() {
group('gradle tasks', () {
test('assemble release', () {
expect(
getAssembleTaskFor(const BuildInfo(BuildMode.release, null)),
getAssembleTaskFor(const BuildInfo(BuildMode.release, null, treeShakeIcons: false)),
equals('assembleRelease'),
);
expect(
getAssembleTaskFor(const BuildInfo(BuildMode.release, 'flavorFoo')),
getAssembleTaskFor(const BuildInfo(BuildMode.release, 'flavorFoo', treeShakeIcons: false)),
equals('assembleFlavorFooRelease'),
);
});
test('assemble debug', () {
expect(
getAssembleTaskFor(const BuildInfo(BuildMode.debug, null)),
getAssembleTaskFor(const BuildInfo(BuildMode.debug, null, treeShakeIcons: false)),
equals('assembleDebug'),
);
expect(
getAssembleTaskFor(const BuildInfo(BuildMode.debug, 'flavorFoo')),
getAssembleTaskFor(const BuildInfo(BuildMode.debug, 'flavorFoo', treeShakeIcons: false)),
equals('assembleFlavorFooDebug'),
);
});
test('assemble profile', () {
expect(
getAssembleTaskFor(const BuildInfo(BuildMode.profile, null)),
getAssembleTaskFor(const BuildInfo(BuildMode.profile, null, treeShakeIcons: false)),
equals('assembleProfile'),
);
expect(
getAssembleTaskFor(const BuildInfo(BuildMode.profile, 'flavorFoo')),
getAssembleTaskFor(const BuildInfo(BuildMode.profile, 'flavorFoo', treeShakeIcons: false)),
equals('assembleFlavorFooProfile'),
);
});
......@@ -135,7 +135,7 @@ void main() {
testUsingContext('Finds app bundle when flavor contains underscores in release mode', () {
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.path, globals.fs.path.join('irrelevant', 'app', 'outputs', 'bundle', 'foo_barRelease', 'app.aab'));
}, overrides: <Type, Generator>{
......@@ -145,7 +145,7 @@ void main() {
testUsingContext('Finds app bundle when flavor doesn\'t contain underscores in release mode', () {
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.path, globals.fs.path.join('irrelevant', 'app', 'outputs', 'bundle', 'fooRelease', 'app.aab'));
}, overrides: <Type, Generator>{
......@@ -155,7 +155,7 @@ void main() {
testUsingContext('Finds app bundle when no flavor is used in release mode', () {
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.path, globals.fs.path.join('irrelevant', 'app', 'outputs', 'bundle', 'release', 'app.aab'));
}, overrides: <Type, Generator>{
......@@ -165,7 +165,7 @@ void main() {
testUsingContext('Finds app bundle when flavor contains underscores in debug mode', () {
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.path, globals.fs.path.join('irrelevant', 'app', 'outputs', 'bundle', 'foo_barDebug', 'app.aab'));
}, overrides: <Type, Generator>{
......@@ -175,7 +175,7 @@ void main() {
testUsingContext('Finds app bundle when flavor doesn\'t contain underscores in debug mode', () {
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.path, globals.fs.path.join('irrelevant', 'app', 'outputs', 'bundle', 'fooDebug', 'app.aab'));
}, overrides: <Type, Generator>{
......@@ -185,7 +185,7 @@ void main() {
testUsingContext('Finds app bundle when no flavor is used in debug mode', () {
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.path, globals.fs.path.join('irrelevant', 'app', 'outputs', 'bundle', 'debug', 'app.aab'));
}, overrides: <Type, Generator>{
......@@ -195,7 +195,7 @@ void main() {
testUsingContext('Finds app bundle when flavor contains underscores in profile mode', () {
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.path, globals.fs.path.join('irrelevant', 'app', 'outputs', 'bundle', 'foo_barProfile', 'app.aab'));
}, overrides: <Type, Generator>{
......@@ -205,7 +205,7 @@ void main() {
testUsingContext('Finds app bundle when flavor doesn\'t contain underscores in profile mode', () {
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.path, globals.fs.path.join('irrelevant', 'app', 'outputs', 'bundle', 'fooProfile', 'app.aab'));
}, overrides: <Type, Generator>{
......@@ -215,7 +215,7 @@ void main() {
testUsingContext('Finds app bundle when no flavor is used in profile mode', () {
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.path, globals.fs.path.join('irrelevant', 'app', 'outputs', 'bundle', 'profile', 'app.aab'));
}, overrides: <Type, Generator>{
......@@ -225,7 +225,7 @@ void main() {
testUsingContext('Finds app bundle in release mode - Gradle 3.5', () {
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.path, globals.fs.path.join('irrelevant', 'app', 'outputs', 'bundle', 'release', 'app-release.aab'));
}, overrides: <Type, Generator>{
......@@ -235,7 +235,7 @@ void main() {
testUsingContext('Finds app bundle in profile mode - Gradle 3.5', () {
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.path, globals.fs.path.join('irrelevant', 'app', 'outputs', 'bundle', 'profile', 'app-profile.aab'));
}, overrides: <Type, Generator>{
......@@ -245,7 +245,7 @@ void main() {
testUsingContext('Finds app bundle in debug mode - Gradle 3.5', () {
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.path, globals.fs.path.join('irrelevant', 'app', 'outputs', 'bundle', 'debug', 'app-debug.aab'));
}, overrides: <Type, Generator>{
......@@ -255,7 +255,7 @@ void main() {
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 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.path, globals.fs.path.join('irrelevant', 'app', 'outputs', 'bundle', 'foo_barRelease', 'app-foo_bar-release.aab'));
}, overrides: <Type, Generator>{
......@@ -265,7 +265,7 @@ void main() {
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 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.path, globals.fs.path.join('irrelevant', 'app', 'outputs', 'bundle', 'foo_barProfile', 'app-foo_bar-profile.aab'));
}, overrides: <Type, Generator>{
......@@ -275,7 +275,7 @@ void main() {
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 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.path, globals.fs.path.join('irrelevant','app', 'outputs', 'bundle', 'foo_barDebug', 'app-foo_bar-debug.aab'));
}, overrides: <Type, Generator>{
......@@ -287,7 +287,7 @@ void main() {
final FlutterProject project = FlutterProject.current();
expect(
() {
findBundleFile(project, const BuildInfo(BuildMode.debug, 'foo_bar'));
findBundleFile(project, const BuildInfo(BuildMode.debug, 'foo_bar', treeShakeIcons: false));
},
throwsToolExit(
message:
......@@ -329,7 +329,7 @@ void main() {
final Iterable<File> apks = findApkFiles(
project,
const AndroidBuildInfo(BuildInfo(BuildMode.release, '')),
const AndroidBuildInfo(BuildInfo(BuildMode.release, '', treeShakeIcons: false)),
);
expect(apks.isNotEmpty, isTrue);
expect(apks.first.path, equals(globals.fs.path.join('irrelevant', 'app', 'outputs', 'apk', 'release', 'app-release.apk')));
......@@ -352,7 +352,7 @@ void main() {
final Iterable<File> apks = findApkFiles(
project,
const AndroidBuildInfo(BuildInfo(BuildMode.release, 'flavor1')),
const AndroidBuildInfo(BuildInfo(BuildMode.release, 'flavor1', treeShakeIcons: false)),
);
expect(apks.isNotEmpty, isTrue);
expect(apks.first.path, equals(globals.fs.path.join('irrelevant', 'app', 'outputs', 'apk', 'release', 'app-flavor1-release.apk')));
......@@ -375,7 +375,7 @@ void main() {
final Iterable<File> apks = findApkFiles(
project,
const AndroidBuildInfo(BuildInfo(BuildMode.release, 'flavor1')),
const AndroidBuildInfo(BuildInfo(BuildMode.release, 'flavor1', treeShakeIcons: false)),
);
expect(apks.isNotEmpty, isTrue);
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() {
() {
findApkFiles(
project,
const AndroidBuildInfo(BuildInfo(BuildMode.debug, 'foo_bar')),
const AndroidBuildInfo(BuildInfo(BuildMode.debug, 'foo_bar', treeShakeIcons: false)),
);
},
throwsToolExit(
......@@ -604,7 +604,7 @@ dependencies:
flutter:
''';
const BuildInfo buildInfo = BuildInfo(BuildMode.release, null);
const BuildInfo buildInfo = BuildInfo(BuildMode.release, null, treeShakeIcons: false);
await checkBuildVersion(
manifest: manifest,
buildInfo: buildInfo,
......@@ -622,7 +622,7 @@ dependencies:
sdk: flutter
flutter:
''';
const BuildInfo buildInfo = BuildInfo(BuildMode.release, null);
const BuildInfo buildInfo = BuildInfo(BuildMode.release, null, treeShakeIcons: false);
await checkBuildVersion(
manifest: manifest,
buildInfo: buildInfo,
......@@ -640,7 +640,7 @@ dependencies:
sdk: 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(
manifest: manifest,
buildInfo: buildInfo,
......@@ -658,7 +658,7 @@ dependencies:
sdk: flutter
flutter:
''';
const BuildInfo buildInfo = BuildInfo(BuildMode.release, null, buildNumber: '3');
const BuildInfo buildInfo = BuildInfo(BuildMode.release, null, buildNumber: '3', treeShakeIcons: false);
await checkBuildVersion(
manifest: manifest,
buildInfo: buildInfo,
......@@ -676,7 +676,7 @@ dependencies:
sdk: 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(
manifest: manifest,
buildInfo: buildInfo,
......@@ -694,7 +694,7 @@ dependencies:
sdk: 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(
manifest: manifest,
buildInfo: buildInfo,
......@@ -711,7 +711,7 @@ dependencies:
sdk: 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(
manifest: manifest,
buildInfo: buildInfo,
......@@ -730,19 +730,19 @@ flutter:
''';
await checkBuildVersion(
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,
expectedBuildNumber: null,
);
await checkBuildVersion(
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',
expectedBuildNumber: '3',
);
await checkBuildVersion(
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',
expectedBuildNumber: '4',
);
......@@ -756,7 +756,7 @@ flutter:
// Values get unset.
await checkBuildVersion(
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,
expectedBuildNumber: null,
);
......@@ -1125,6 +1125,7 @@ plugin1=${plugin1.path}
BuildInfo(
BuildMode.release,
null,
treeShakeIcons: false,
),
),
target: 'lib/main.dart',
......@@ -1204,6 +1205,7 @@ plugin1=${plugin1.path}
BuildInfo(
BuildMode.release,
null,
treeShakeIcons: false,
),
),
target: 'lib/main.dart',
......@@ -1279,6 +1281,7 @@ plugin1=${plugin1.path}
BuildInfo(
BuildMode.release,
null,
treeShakeIcons: false,
),
),
target: 'lib/main.dart',
......@@ -1351,6 +1354,7 @@ plugin1=${plugin1.path}
BuildInfo(
BuildMode.release,
null,
treeShakeIcons: false,
),
),
target: 'lib/main.dart',
......@@ -1418,6 +1422,7 @@ plugin1=${plugin1.path}
BuildInfo(
BuildMode.release,
null,
treeShakeIcons: false,
),
),
target: 'lib/main.dart',
......@@ -1491,6 +1496,7 @@ plugin1=${plugin1.path}
BuildInfo(
BuildMode.release,
null,
treeShakeIcons: false,
),
),
target: 'lib/main.dart',
......@@ -1582,6 +1588,7 @@ plugin1=${plugin1.path}
BuildInfo(
BuildMode.release,
null,
treeShakeIcons: false,
),
),
target: 'lib/main.dart',
......@@ -1630,7 +1637,7 @@ plugin1=${plugin1.path}
fileSystem.directory('build/outputs/repo').createSync(recursive: true);
await buildGradleAar(
androidBuildInfo: const AndroidBuildInfo(BuildInfo(BuildMode.release, null)),
androidBuildInfo: const AndroidBuildInfo(BuildInfo(BuildMode.release, null, treeShakeIcons: false)),
project: FlutterProject.current(),
outputDirectory: fileSystem.directory('build/'),
target: '',
......@@ -1715,6 +1722,7 @@ plugin1=${plugin1.path}
BuildInfo(
BuildMode.release,
null,
treeShakeIcons: false,
),
),
target: 'lib/main.dart',
......@@ -1801,7 +1809,7 @@ plugin1=${plugin1.path}
when(fileSystemUtils.copyDirectorySync(any, any)).thenReturn(null);
await buildGradleAar(
androidBuildInfo: const AndroidBuildInfo(BuildInfo(BuildMode.release, null)),
androidBuildInfo: const AndroidBuildInfo(BuildInfo(BuildMode.release, null, treeShakeIcons: false)),
project: FlutterProject.current(),
outputDirectory: fileSystem.directory('build/'),
target: '',
......
......@@ -2,8 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be
// 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/targets/assets.dart';
import 'package:flutter_tools/src/globals.dart' as globals;
......@@ -12,7 +10,6 @@ import '../../../src/common.dart';
import '../../../src/testbed.dart';
void main() {
const BuildSystem buildSystem = BuildSystem();
Environment environment;
Testbed testbed;
......@@ -21,6 +18,7 @@ void main() {
environment = Environment.test(
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',
'build_system', 'targets', 'assets.dart'))
..createSync(recursive: true);
......@@ -44,7 +42,7 @@ flutter:
});
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', 'FontManifest.json')).existsSync(), true);
......@@ -55,24 +53,6 @@ flutter:
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 {
globals.fs.file('pubspec.yaml')
..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() {
targetPlatform: TargetPlatform.ios,
depfilePath: 'example.d',
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', 'LICENSE')).existsSync(), true);
......@@ -61,6 +62,7 @@ void main() {
targetPlatform: TargetPlatform.linux_x64,
depfilePath: 'example.d',
precompiled: false,
treeShakeIcons: false,
), throwsToolExit());
}));
}
......
......@@ -720,7 +720,7 @@ void main() {
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(
app,
prebuiltApplication: prebuilt,
......@@ -753,7 +753,7 @@ void main() {
final FuchsiaApp app = FuchsiaApp.fromPrebuiltApp(far);
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,
prebuiltApplication: true,
debuggingOptions: debuggingOptions);
......
......@@ -292,7 +292,7 @@ void main() {
final LaunchResult launchResult = await device.startApp(mockApp,
prebuiltApplication: true,
debuggingOptions: DebuggingOptions.enabled(const BuildInfo(BuildMode.debug, null)),
debuggingOptions: DebuggingOptions.enabled(const BuildInfo(BuildMode.debug, null, treeShakeIcons: false)),
platformArgs: <String, dynamic>{},
);
verify(mockUsage.sendEvent('ios-mdns', 'success')).called(1);
......@@ -362,7 +362,7 @@ void main() {
final LaunchResult launchResult = await device.startApp(mockApp,
prebuiltApplication: true,
debuggingOptions: DebuggingOptions.enabled(const BuildInfo(BuildMode.debug, null)),
debuggingOptions: DebuggingOptions.enabled(const BuildInfo(BuildMode.debug, null, treeShakeIcons: false)),
platformArgs: <String, dynamic>{},
);
verify(mockUsage.sendEvent('ios-mdns', 'failure')).called(1);
......@@ -395,7 +395,7 @@ void main() {
final LaunchResult launchResult = await device.startApp(mockApp,
prebuiltApplication: true,
debuggingOptions: DebuggingOptions.enabled(const BuildInfo(BuildMode.debug, null)),
debuggingOptions: DebuggingOptions.enabled(const BuildInfo(BuildMode.debug, null, treeShakeIcons: false)),
platformArgs: <String, dynamic>{},
);
verify(mockUsage.sendEvent('ios-mdns', 'failure')).called(1);
......@@ -416,7 +416,7 @@ void main() {
final IOSDevice device = IOSDevice('123');
final LaunchResult launchResult = await device.startApp(mockApp,
prebuiltApplication: true,
debuggingOptions: DebuggingOptions.disabled(const BuildInfo(BuildMode.release, null)),
debuggingOptions: DebuggingOptions.disabled(const BuildInfo(BuildMode.release, null, treeShakeIcons: false)),
platformArgs: <String, dynamic>{},
);
expect(launchResult.started, isTrue);
......@@ -455,7 +455,7 @@ void main() {
final LaunchResult launchResult = await device.startApp(mockApp,
prebuiltApplication: true,
debuggingOptions: DebuggingOptions.enabled(
const BuildInfo(BuildMode.debug, null),
const BuildInfo(BuildMode.debug, null, treeShakeIcons: false),
cacheSkSL: true,
),
platformArgs: <String, dynamic>{},
......@@ -499,7 +499,7 @@ void main() {
final LaunchResult launchResult = await device.startApp(mockApp,
prebuiltApplication: true,
debuggingOptions: DebuggingOptions.enabled(
const BuildInfo(BuildMode.debug, null),
const BuildInfo(BuildMode.debug, null, treeShakeIcons: false),
deviceVmServicePort: 8181,
),
platformArgs: <String, dynamic>{},
......@@ -600,7 +600,7 @@ void main() {
device.startApp(
app,
prebuiltApplication: false,
debuggingOptions: DebuggingOptions.disabled(const BuildInfo(BuildMode.debug, null)),
debuggingOptions: DebuggingOptions.disabled(const BuildInfo(BuildMode.debug, null, treeShakeIcons: false)),
platformArgs: <String, dynamic>{},
).then((LaunchResult result) {
completer.complete(result);
......
......@@ -10,6 +10,7 @@ import 'package:flutter_tools/src/build_info.dart';
import 'package:file/memory.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/device.dart';
import 'package:flutter_tools/src/application_package.dart';
import 'package:flutter_tools/src/base/file_system.dart';
......@@ -219,6 +220,7 @@ void main() {
kTargetPlatform: 'ios',
kBuildMode: 'debug',
kTrackWidgetCreation: 'false',
kIconTreeShakerFlag: null,
});
}, overrides: <Type, Generator>{
BuildSystem: () => MockBuildSystem(),
......@@ -501,7 +503,7 @@ void main() {
final Directory mockDir = globals.fs.currentDirectory;
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);
await device.startApp(package, prebuiltApplication: true, debuggingOptions: mockOptions);
......
......@@ -350,14 +350,14 @@ Information about project "Runner":
});
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.profile, 'HELLO')), 'HELLO');
expect(XcodeProjectInfo.expectedSchemeFor(const BuildInfo(BuildMode.release, 'Hello')), 'Hello');
expect(XcodeProjectInfo.expectedSchemeFor(const BuildInfo(BuildMode.debug, 'hello', treeShakeIcons: false)), 'Hello');
expect(XcodeProjectInfo.expectedSchemeFor(const BuildInfo(BuildMode.profile, 'HELLO', treeShakeIcons: false)), 'HELLO');
expect(XcodeProjectInfo.expectedSchemeFor(const BuildInfo(BuildMode.release, 'Hello', treeShakeIcons: false)), 'Hello');
});
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.profile, 'HELLO'), 'Hello'), 'Profile-Hello');
expect(XcodeProjectInfo.expectedBuildConfigurationFor(const BuildInfo(BuildMode.release, 'Hello'), 'Hello'), 'Release-Hello');
expect(XcodeProjectInfo.expectedBuildConfigurationFor(const BuildInfo(BuildMode.debug, 'hello', treeShakeIcons: false), 'Hello'), 'Debug-Hello');
expect(XcodeProjectInfo.expectedBuildConfigurationFor(const BuildInfo(BuildMode.profile, 'HELLO', treeShakeIcons: false), 'Hello'), 'Profile-Hello');
expect(XcodeProjectInfo.expectedBuildConfigurationFor(const BuildInfo(BuildMode.release, 'Hello', treeShakeIcons: false), 'Hello'), 'Release-Hello');
});
testWithoutContext('scheme for default project is Runner', () {
......@@ -366,7 +366,7 @@ Information about project "Runner":
expect(info.schemeFor(BuildInfo.debug), 'Runner');
expect(info.schemeFor(BuildInfo.profile), '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', () {
......@@ -384,11 +384,11 @@ Information about project "Runner":
<String>['Free', 'Paid'],
);
expect(info.schemeFor(const BuildInfo(BuildMode.debug, 'free')), 'Free');
expect(info.schemeFor(const BuildInfo(BuildMode.profile, 'Free')), 'Free');
expect(info.schemeFor(const BuildInfo(BuildMode.release, 'paid')), 'Paid');
expect(info.schemeFor(const BuildInfo(BuildMode.debug, null)), isNull);
expect(info.schemeFor(const BuildInfo(BuildMode.debug, 'unknown')), isNull);
expect(info.schemeFor(const BuildInfo(BuildMode.debug, 'free', treeShakeIcons: false)), 'Free');
expect(info.schemeFor(const BuildInfo(BuildMode.profile, 'Free', treeShakeIcons: false)), 'Free');
expect(info.schemeFor(const BuildInfo(BuildMode.release, 'paid', treeShakeIcons: false)), 'Paid');
expect(info.schemeFor(const BuildInfo(BuildMode.debug, null, treeShakeIcons: false)), 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', () {
......@@ -398,10 +398,10 @@ Information about project "Runner":
<String>['Free', 'Paid'],
);
expect(info.buildConfigurationFor(const BuildInfo(BuildMode.debug, 'free'), 'Free'), 'debug (free)');
expect(info.buildConfigurationFor(const BuildInfo(BuildMode.debug, 'Paid'), 'Paid'), 'Debug paid');
expect(info.buildConfigurationFor(const BuildInfo(BuildMode.profile, 'FREE'), 'Free'), 'profile - Free');
expect(info.buildConfigurationFor(const BuildInfo(BuildMode.release, 'paid'), 'Paid'), 'Release-Paid');
expect(info.buildConfigurationFor(const BuildInfo(BuildMode.debug, 'free', treeShakeIcons: false), 'Free'), 'debug (free)');
expect(info.buildConfigurationFor(const BuildInfo(BuildMode.debug, 'Paid', treeShakeIcons: false), 'Paid'), 'Debug paid');
expect(info.buildConfigurationFor(const BuildInfo(BuildMode.profile, 'FREE', treeShakeIcons: false), 'Free'), 'profile - Free');
expect(info.buildConfigurationFor(const BuildInfo(BuildMode.release, 'paid', treeShakeIcons: false), 'Paid'), 'Release-Paid');
});
testWithoutContext('build configuration for project with inconsistent naming is null', () {
......@@ -410,9 +410,9 @@ Information about project "Runner":
<String>['Debug-F', 'Dbg Paid', 'Rel Free', 'Release Full'],
<String>['Free', 'Paid'],
);
expect(info.buildConfigurationFor(const BuildInfo(BuildMode.debug, 'Free'), 'Free'), null);
expect(info.buildConfigurationFor(const BuildInfo(BuildMode.profile, 'Free'), 'Free'), null);
expect(info.buildConfigurationFor(const BuildInfo(BuildMode.release, 'Paid'), 'Paid'), null);
expect(info.buildConfigurationFor(const BuildInfo(BuildMode.debug, 'Free', treeShakeIcons: false), 'Free'), null);
expect(info.buildConfigurationFor(const BuildInfo(BuildMode.profile, 'Free', treeShakeIcons: false), 'Free'), null);
expect(info.buildConfigurationFor(const BuildInfo(BuildMode.release, 'Paid', treeShakeIcons: false), 'Paid'), null);
});
group('environmentVariablesAsXcodeBuildSettings', () {
FakePlatform platform;
......@@ -461,7 +461,7 @@ Information about project "Runner":
platform: TargetPlatform.ios, mode: anyNamed('mode'))).thenReturn('engine');
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');
await updateGeneratedXcodeProperties(
project: project,
......@@ -485,7 +485,7 @@ Information about project "Runner":
when(mockArtifacts.getArtifactPath(Artifact.flutterFramework,
platform: TargetPlatform.ios, mode: anyNamed('mode'))).thenReturn('engine');
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');
await updateGeneratedXcodeProperties(
project: project,
......@@ -509,7 +509,7 @@ Information about project "Runner":
when(mockArtifacts.getArtifactPath(Artifact.flutterFramework,
platform: TargetPlatform.ios, mode: anyNamed('mode'))).thenReturn('engine');
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');
await updateGeneratedXcodeProperties(
project: project,
......@@ -533,7 +533,7 @@ Information about project "Runner":
when(mockArtifacts.getArtifactPath(Artifact.flutterFramework,
platform: TargetPlatform.ios, mode: anyNamed('mode'))).thenReturn('engine');
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');
await updateGeneratedXcodeProperties(
......@@ -595,7 +595,7 @@ dependencies:
flutter:
''';
const BuildInfo buildInfo = BuildInfo(BuildMode.release, null);
const BuildInfo buildInfo = BuildInfo(BuildMode.release, null, treeShakeIcons: false);
await checkBuildVersion(
manifestString: manifest,
buildInfo: buildInfo,
......@@ -613,7 +613,7 @@ dependencies:
sdk: flutter
flutter:
''';
const BuildInfo buildInfo = BuildInfo(BuildMode.release, null);
const BuildInfo buildInfo = BuildInfo(BuildMode.release, null, treeShakeIcons: false);
await checkBuildVersion(
manifestString: manifest,
buildInfo: buildInfo,
......@@ -631,7 +631,7 @@ dependencies:
sdk: 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(
manifestString: manifest,
buildInfo: buildInfo,
......@@ -649,7 +649,7 @@ dependencies:
sdk: 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(
manifestString: manifest,
buildInfo: buildInfo,
......@@ -667,7 +667,7 @@ dependencies:
sdk: flutter
flutter:
''';
const BuildInfo buildInfo = BuildInfo(BuildMode.release, null, buildNumber: '3');
const BuildInfo buildInfo = BuildInfo(BuildMode.release, null, buildNumber: '3', treeShakeIcons: false);
await checkBuildVersion(
manifestString: manifest,
buildInfo: buildInfo,
......@@ -685,7 +685,7 @@ dependencies:
sdk: 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(
manifestString: manifest,
buildInfo: buildInfo,
......@@ -703,7 +703,7 @@ dependencies:
sdk: 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(
manifestString: manifest,
buildInfo: buildInfo,
......@@ -720,7 +720,7 @@ dependencies:
sdk: 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(
manifestString: manifest,
buildInfo: buildInfo,
......@@ -737,7 +737,7 @@ dependencies:
sdk: flutter
flutter:
''';
const BuildInfo buildInfo = BuildInfo(BuildMode.release, null);
const BuildInfo buildInfo = BuildInfo(BuildMode.release, null, treeShakeIcons: false);
await checkBuildVersion(
manifestString: manifest,
buildInfo: buildInfo,
......
......@@ -148,7 +148,7 @@ void main() {
testUsingContext('not debug', () async {
final LaunchResult result = await device.startApp(null,
mainPath: mainPath,
debuggingOptions: DebuggingOptions.disabled(const BuildInfo(BuildMode.release, null)));
debuggingOptions: DebuggingOptions.disabled(const BuildInfo(BuildMode.release, null, treeShakeIcons: false)));
expect(result.started, isFalse);
}, overrides: startOverrides);
......@@ -166,7 +166,7 @@ Hello!
final LaunchResult result = await device.startApp(null,
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.observatoryUri, observatoryUri);
expect(logLines.last, 'Hello!');
......
......@@ -372,7 +372,7 @@ class CompleterIOSink extends MemoryIOSink {
_completer.complete(throwOnAdd ? <int>[] : data);
}
if (throwOnAdd) {
throw 'CompleterIOSink Error';
throw Exception('CompleterIOSink Error');
}
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