Unverified Commit 368da5bb authored by Jonah Williams's avatar Jonah Williams Committed by GitHub

[flutter_tools] support bundling SkSL shaders in flutter build apk/appbundle (#56059)

Support bundling SkSL shaders into an android APK or appbundle via the --bundle-sksl-path command line options. If provided, these are validated for platform engine revision and then placed in flutter_assets/io.flutter.shaders.json
parent 0d452b83
...@@ -619,6 +619,10 @@ class FlutterPlugin implements Plugin<Project> { ...@@ -619,6 +619,10 @@ class FlutterPlugin implements Plugin<Project> {
if (project.hasProperty('dart-defines')) { if (project.hasProperty('dart-defines')) {
dartDefinesValue = project.property('dart-defines') dartDefinesValue = project.property('dart-defines')
} }
String bundleSkSLPathValue;
if (project.hasProperty('bundle-sksl-path')) {
bundleSkSLPathValue = project.property('bundle-sksl-path')
}
def targetPlatforms = getTargetPlatforms() def targetPlatforms = getTargetPlatforms()
def addFlutterDeps = { variant -> def addFlutterDeps = { variant ->
if (shouldSplitPerAbi()) { if (shouldSplitPerAbi()) {
...@@ -657,6 +661,7 @@ class FlutterPlugin implements Plugin<Project> { ...@@ -657,6 +661,7 @@ class FlutterPlugin implements Plugin<Project> {
treeShakeIcons treeShakeIconsOptionsValue treeShakeIcons treeShakeIconsOptionsValue
dartObfuscation dartObfuscationValue dartObfuscation dartObfuscationValue
dartDefines dartDefinesValue dartDefines dartDefinesValue
bundleSkSLPath bundleSkSLPathValue
doLast { doLast {
project.exec { project.exec {
if (Os.isFamily(Os.FAMILY_WINDOWS)) { if (Os.isFamily(Os.FAMILY_WINDOWS)) {
...@@ -849,6 +854,8 @@ abstract class BaseFlutterTask extends DefaultTask { ...@@ -849,6 +854,8 @@ abstract class BaseFlutterTask extends DefaultTask {
Boolean dartObfuscation Boolean dartObfuscation
@Optional @Input @Optional @Input
String dartDefines String dartDefines
@Optional @Input
String bundleSkSLPath
@OutputFiles @OutputFiles
FileCollection getDependenciesFiles() { FileCollection getDependenciesFiles() {
...@@ -918,6 +925,9 @@ abstract class BaseFlutterTask extends DefaultTask { ...@@ -918,6 +925,9 @@ abstract class BaseFlutterTask extends DefaultTask {
if (dartDefines != null) { if (dartDefines != null) {
args "--DartDefines=${dartDefines}" args "--DartDefines=${dartDefines}"
} }
if (bundleSkSLPath != null) {
args "-iBundleSkSLPath=${bundleSkSLPath}"
}
if (extraGenSnapshotOptions != null) { if (extraGenSnapshotOptions != null) {
args "--ExtraGenSnapshotOptions=${extraGenSnapshotOptions}" args "--ExtraGenSnapshotOptions=${extraGenSnapshotOptions}"
} }
......
...@@ -351,6 +351,9 @@ Future<void> buildGradleApp({ ...@@ -351,6 +351,9 @@ Future<void> buildGradleApp({
if (androidBuildInfo.buildInfo.dartObfuscation) { if (androidBuildInfo.buildInfo.dartObfuscation) {
command.add('-Pdart-obfuscation=true'); command.add('-Pdart-obfuscation=true');
} }
if (androidBuildInfo.buildInfo.bundleSkSLPath != null) {
command.add('-Pbundle-sksl-path=${androidBuildInfo.buildInfo.bundleSkSLPath}');
}
command.add(assembleTask); command.add(assembleTask);
GradleHandledError detectedGradleError; GradleHandledError detectedGradleError;
......
...@@ -61,13 +61,14 @@ class _ManifestAssetBundleFactory implements AssetBundleFactory { ...@@ -61,13 +61,14 @@ class _ManifestAssetBundleFactory implements AssetBundleFactory {
const _ManifestAssetBundleFactory(); const _ManifestAssetBundleFactory();
@override @override
AssetBundle createBundle() => _ManifestAssetBundle(); AssetBundle createBundle() => ManifestAssetBundle();
} }
class _ManifestAssetBundle implements AssetBundle { /// An asset bundle based on a pubspec.yaml
/// Constructs an [_ManifestAssetBundle] that gathers the set of assets from the class ManifestAssetBundle implements AssetBundle {
/// Constructs an [ManifestAssetBundle] that gathers the set of assets from the
/// pubspec.yaml manifest. /// pubspec.yaml manifest.
_ManifestAssetBundle(); ManifestAssetBundle();
@override @override
final Map<String, DevFSContent> entries = <String, DevFSContent>{}; final Map<String, DevFSContent> entries = <String, DevFSContent>{};
...@@ -493,7 +494,7 @@ List<Map<String, dynamic>> _parseFonts( ...@@ -493,7 +494,7 @@ List<Map<String, dynamic>> _parseFonts(
}) { }) {
return <Map<String, dynamic>>[ return <Map<String, dynamic>>[
if (manifest.usesMaterialDesign && includeDefaultFonts) if (manifest.usesMaterialDesign && includeDefaultFonts)
..._getMaterialFonts(_ManifestAssetBundle._fontSetMaterial), ..._getMaterialFonts(ManifestAssetBundle._fontSetMaterial),
if (packageName == null) if (packageName == null)
...manifest.fontsDescriptor ...manifest.fontsDescriptor
else else
......
...@@ -24,6 +24,7 @@ class BuildInfo { ...@@ -24,6 +24,7 @@ class BuildInfo {
this.splitDebugInfoPath, this.splitDebugInfoPath,
this.dartObfuscation = false, this.dartObfuscation = false,
this.dartDefines = const <String>[], this.dartDefines = const <String>[],
this.bundleSkSLPath,
this.dartExperiments = const <String>[], this.dartExperiments = const <String>[],
@required this.treeShakeIcons, @required this.treeShakeIcons,
}); });
...@@ -74,6 +75,11 @@ class BuildInfo { ...@@ -74,6 +75,11 @@ class BuildInfo {
/// Whether to apply dart source code obfuscation. /// Whether to apply dart source code obfuscation.
final bool dartObfuscation; final bool dartObfuscation;
/// An optional path to a JSON containing object SkSL shaders
///
/// Currently this is only supported for Android builds.
final String bundleSkSLPath;
/// Additional constant values to be made available in the Dart program. /// Additional constant values to be made available in the Dart program.
/// ///
/// These values can be used with the const `fromEnvironment` constructors of /// These values can be used with the const `fromEnvironment` constructors of
......
...@@ -6,6 +6,7 @@ import '../../artifacts.dart'; ...@@ -6,6 +6,7 @@ import '../../artifacts.dart';
import '../../base/build.dart'; import '../../base/build.dart';
import '../../base/file_system.dart'; import '../../base/file_system.dart';
import '../../build_info.dart'; import '../../build_info.dart';
import '../../devfs.dart';
import '../../globals.dart' as globals; import '../../globals.dart' as globals;
import '../build_system.dart'; import '../build_system.dart';
import '../depfile.dart'; import '../depfile.dart';
...@@ -62,7 +63,27 @@ abstract class AndroidAssetBundle extends Target { ...@@ -62,7 +63,27 @@ abstract class AndroidAssetBundle extends Target {
.copySync(outputDirectory.childFile('isolate_snapshot_data').path); .copySync(outputDirectory.childFile('isolate_snapshot_data').path);
} }
if (_copyAssets) { if (_copyAssets) {
final Depfile assetDepfile = await copyAssets(environment, outputDirectory); final String shaderBundlePath = environment.inputs[kBundleSkSLPath];
final DevFSContent skslBundle = processSkSLBundle(
shaderBundlePath,
engineVersion: environment.engineVersion,
fileSystem: environment.fileSystem,
logger: environment.logger,
targetPlatform: TargetPlatform.android,
);
final Depfile assetDepfile = await copyAssets(
environment,
outputDirectory,
additionalContent: <String, DevFSContent>{
if (skslBundle != null)
kSkSLShaderBundlePath: skslBundle,
}
);
if (shaderBundlePath != null) {
final File skSLBundleFile = environment.fileSystem
.file(shaderBundlePath).absolute;
assetDepfile.inputs.add(skSLBundleFile);
}
final DepfileService depfileService = DepfileService( final DepfileService depfileService = DepfileService(
fileSystem: globals.fs, fileSystem: globals.fs,
logger: globals.logger, logger: globals.logger,
......
...@@ -2,10 +2,14 @@ ...@@ -2,10 +2,14 @@
// 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 'package:pool/pool.dart'; import 'package:pool/pool.dart';
import '../../asset.dart'; import '../../asset.dart';
import '../../base/file_system.dart'; import '../../base/file_system.dart';
import '../../base/logger.dart';
import '../../build_info.dart';
import '../../convert.dart';
import '../../devfs.dart'; import '../../devfs.dart';
import '../../globals.dart' as globals; import '../../globals.dart' as globals;
import '../build_system.dart'; import '../build_system.dart';
...@@ -13,13 +17,21 @@ import '../depfile.dart'; ...@@ -13,13 +17,21 @@ import '../depfile.dart';
import 'dart.dart'; import 'dart.dart';
import 'icon_tree_shaker.dart'; import 'icon_tree_shaker.dart';
/// The input key for an SkSL bundle path.
const String kBundleSkSLPath = 'BundleSkSLPath';
/// 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.
/// ///
/// Throws [Exception] if [AssetBundle.build] returns a non-zero exit code. /// Throws [Exception] if [AssetBundle.build] returns a non-zero exit code.
/// ///
/// [additionalContent] may contain additional DevFS entries that will be
/// included in the final bundle, but not the AssetManifest.json file.
///
/// Returns a [Depfile] containing all assets used in the build. /// Returns a [Depfile] containing all assets used in the build.
Future<Depfile> copyAssets(Environment environment, Directory outputDirectory) async { Future<Depfile> copyAssets(Environment environment, Directory outputDirectory, {
Map<String, DevFSContent> additionalContent,
}) async {
final File pubspecFile = environment.projectDir.childFile('pubspec.yaml'); final File pubspecFile = environment.projectDir.childFile('pubspec.yaml');
final AssetBundle assetBundle = AssetBundleFactory.instance.createBundle(); final AssetBundle assetBundle = AssetBundleFactory.instance.createBundle();
final int resultCode = await assetBundle.build( final int resultCode = await assetBundle.build(
...@@ -46,8 +58,13 @@ Future<Depfile> copyAssets(Environment environment, Directory outputDirectory) a ...@@ -46,8 +58,13 @@ Future<Depfile> copyAssets(Environment environment, Directory outputDirectory) a
artifacts: globals.artifacts, artifacts: globals.artifacts,
); );
final Map<String, DevFSContent> assetEntries = <String, DevFSContent>{
...assetBundle.entries,
...?additionalContent,
};
await Future.wait<void>( await Future.wait<void>(
assetBundle.entries.entries.map<Future<void>>((MapEntry<String, DevFSContent> entry) async { assetEntries.entries.map<Future<void>>((MapEntry<String, DevFSContent> entry) async {
final PoolResource resource = await pool.request(); final PoolResource resource = await pool.request();
try { try {
// This will result in strange looking files, for example files with `/` // This will result in strange looking files, for example files with `/`
...@@ -78,6 +95,76 @@ Future<Depfile> copyAssets(Environment environment, Directory outputDirectory) a ...@@ -78,6 +95,76 @@ Future<Depfile> copyAssets(Environment environment, Directory outputDirectory) a
return Depfile(inputs + assetBundle.additionalDependencies, outputs); return Depfile(inputs + assetBundle.additionalDependencies, outputs);
} }
/// The path of the SkSL JSON bundle included in flutter_assets.
const String kSkSLShaderBundlePath = 'io.flutter.shaders.json';
/// Validate and process an SkSL asset bundle in a [DevFSContent].
///
/// Returns `null` if the bundle was not provided, otherwise attempts to
/// validate the bundle.
///
/// Throws [Exception] if the bundle is invalid due to formatting issues.
///
/// If the current target platform is different than the platform constructed
/// for the bundle, a warning will be printed.
DevFSContent processSkSLBundle(String bundlePath, {
@required TargetPlatform targetPlatform,
@required FileSystem fileSystem,
@required Logger logger,
@required String engineVersion,
}) {
if (bundlePath == null) {
return null;
}
// Step 1: check that file exists.
final File skSLBundleFile = fileSystem.file(bundlePath);
if (!skSLBundleFile.existsSync()) {
logger.printError('$bundlePath does not exist.');
throw Exception('SkSL bundle was invalid.');
}
// Step 2: validate top level bundle structure.
Map<String, Object> bundle;
try {
final Object rawBundle = json.decode(skSLBundleFile.readAsStringSync());
if (rawBundle is Map<String, Object>) {
bundle = rawBundle;
} else {
logger.printError('"$bundle" was not a JSON object: $rawBundle');
throw Exception('SkSL bundle was invalid.');
}
} on FormatException catch (err) {
logger.printError('"$bundle" was not a JSON object: $err');
throw Exception('SkSL bundle was invalid.');
}
// Step 3: Validate that:
// * The engine revision the bundle was compiled with
// is the same as the current revision.
// * The target platform is the same (this one is a warning only).
final String bundleEngineRevision = bundle['engineRevision'] as String;
if (bundleEngineRevision != engineVersion) {
logger.printError(
'Expected Flutter $bundleEngineRevision, but found $engineVersion\n'
'The SkSL bundle was produced with a different engine version. It must '
'be recreated for the current Flutter version.'
);
throw Exception('SkSL bundle was invalid');
}
final TargetPlatform bundleTargetPlatform = getTargetPlatformForName(
bundle['platform'] as String);
if (bundleTargetPlatform != targetPlatform) {
logger.printError(
'The SkSL bundle was created for $bundleTargetPlatform, but the curent '
'platform is $targetPlatform. This may lead to less efficient shader '
'caching.'
);
}
return DevFSStringContent(json.encode(<String, Object>{
'data': bundle['data'],
}));
}
/// Copy the assets defined in the flutter manifest into a build directory. /// Copy the assets defined in the flutter manifest into a build directory.
class CopyAssets extends Target { class CopyAssets extends Target {
const CopyAssets(); const CopyAssets();
......
...@@ -19,7 +19,7 @@ import 'assets.dart'; ...@@ -19,7 +19,7 @@ import 'assets.dart';
import 'icon_tree_shaker.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';
/// The define to pass whether we compile 64-bit android-arm code. /// The define to pass whether we compile 64-bit android-arm code.
const String kTargetPlatform = 'TargetPlatform'; const String kTargetPlatform = 'TargetPlatform';
......
...@@ -293,13 +293,14 @@ abstract class MacOSBundleFlutterAssets extends Target { ...@@ -293,13 +293,14 @@ abstract class MacOSBundleFlutterAssets extends Target {
.childDirectory('Resources') .childDirectory('Resources')
.childDirectory('flutter_assets'); .childDirectory('flutter_assets');
assetDirectory.createSync(recursive: true); assetDirectory.createSync(recursive: true);
final Depfile depfile = await copyAssets(environment, assetDirectory);
final Depfile assetDepfile = await copyAssets(environment, assetDirectory);
final DepfileService depfileService = DepfileService( final DepfileService depfileService = DepfileService(
fileSystem: globals.fs, fileSystem: globals.fs,
logger: globals.logger, logger: globals.logger,
); );
depfileService.writeToFile( depfileService.writeToFile(
depfile, assetDepfile,
environment.buildDir.childFile('flutter_assets.d'), environment.buildDir.childFile('flutter_assets.d'),
); );
......
...@@ -30,6 +30,7 @@ class BuildApkCommand extends BuildSubCommand { ...@@ -30,6 +30,7 @@ class BuildApkCommand extends BuildSubCommand {
addDartObfuscationOption(); addDartObfuscationOption();
usesDartDefineOption(); usesDartDefineOption();
usesExtraFrontendOptions(); usesExtraFrontendOptions();
addBundleSkSLPathOption(hide: !verboseHelp);
addEnableExperimentation(hide: !verboseHelp); addEnableExperimentation(hide: !verboseHelp);
argParser argParser
..addFlag('split-per-abi', ..addFlag('split-per-abi',
......
...@@ -28,6 +28,7 @@ class BuildAppBundleCommand extends BuildSubCommand { ...@@ -28,6 +28,7 @@ class BuildAppBundleCommand extends BuildSubCommand {
addDartObfuscationOption(); addDartObfuscationOption();
usesDartDefineOption(); usesDartDefineOption();
usesExtraFrontendOptions(); usesExtraFrontendOptions();
addBundleSkSLPathOption(hide: !verboseHelp);
argParser argParser
..addFlag('track-widget-creation', negatable: false, hide: !verboseHelp) ..addFlag('track-widget-creation', negatable: false, hide: !verboseHelp)
..addMultiOption('target-platform', ..addMultiOption('target-platform',
......
...@@ -834,8 +834,21 @@ abstract class ResidentRunner { ...@@ -834,8 +834,21 @@ abstract class ResidentRunner {
'sksl', 'sksl',
); );
final Device device = flutterDevices.first.device; final Device device = flutterDevices.first.device;
// Convert android sub-platforms to single target platform.
TargetPlatform targetPlatform = await flutterDevices.first.device.targetPlatform;
switch (targetPlatform) {
case TargetPlatform.android_arm:
case TargetPlatform.android_arm64:
case TargetPlatform.android_x64:
case TargetPlatform.android_x86:
targetPlatform = TargetPlatform.android;
break;
default:
break;
}
final Map<String, Object> manifest = <String, Object>{ final Map<String, Object> manifest = <String, Object>{
'platform': getNameForTargetPlatform(await flutterDevices.first.device.targetPlatform), 'platform': getNameForTargetPlatform(targetPlatform),
'name': device.name, 'name': device.name,
'engineRevision': globals.flutterVersion.engineRevision, 'engineRevision': globals.flutterVersion.engineRevision,
'data': data, 'data': data,
......
...@@ -108,6 +108,7 @@ class FlutterOptions { ...@@ -108,6 +108,7 @@ class FlutterOptions {
static const String kSplitDebugInfoOption = 'split-debug-info'; static const String kSplitDebugInfoOption = 'split-debug-info';
static const String kDartObfuscationOption = 'obfuscate'; static const String kDartObfuscationOption = 'obfuscate';
static const String kDartDefinesOption = 'dart-define'; static const String kDartDefinesOption = 'dart-define';
static const String kBundleSkSLPathOption = 'bundle-sksl-path';
} }
abstract class FlutterCommand extends Command<void> { abstract class FlutterCommand extends Command<void> {
...@@ -425,6 +426,16 @@ abstract class FlutterCommand extends Command<void> { ...@@ -425,6 +426,16 @@ abstract class FlutterCommand extends Command<void> {
); );
} }
void addBundleSkSLPathOption({ @required bool hide }) {
argParser.addOption(FlutterOptions.kBundleSkSLPathOption,
help: 'A path to a file containing precompiled SkSL shaders generated '
'during "flutter run". These can be included in an application to '
'improve the first frame render times.',
hide: hide,
valueHelp: '/project-name/flutter_1.sksl'
);
}
void addTreeShakeIconsFlag({ void addTreeShakeIconsFlag({
bool enabledByDefault bool enabledByDefault
}) { }) {
...@@ -587,6 +598,10 @@ abstract class FlutterCommand extends Command<void> { ...@@ -587,6 +598,10 @@ abstract class FlutterCommand extends Command<void> {
&& buildMode.isPrecompiled && buildMode.isPrecompiled
&& boolArg('tree-shake-icons'); && boolArg('tree-shake-icons');
final String bundleSkSLPath = argParser.options.containsKey(FlutterOptions.kBundleSkSLPathOption)
? stringArg(FlutterOptions.kBundleSkSLPathOption)
: null;
return BuildInfo(buildMode, return BuildInfo(buildMode,
argParser.options.containsKey('flavor') argParser.options.containsKey('flavor')
? stringArg('flavor') ? stringArg('flavor')
...@@ -614,6 +629,7 @@ abstract class FlutterCommand extends Command<void> { ...@@ -614,6 +629,7 @@ abstract class FlutterCommand extends Command<void> {
dartDefines: argParser.options.containsKey(FlutterOptions.kDartDefinesOption) dartDefines: argParser.options.containsKey(FlutterOptions.kDartDefinesOption)
? stringsArg(FlutterOptions.kDartDefinesOption) ? stringsArg(FlutterOptions.kDartDefinesOption)
: const <String>[], : const <String>[],
bundleSkSLPath: bundleSkSLPath,
dartExperiments: experiments, dartExperiments: experiments,
); );
} }
......
...@@ -2,12 +2,15 @@ ...@@ -2,12 +2,15 @@
// 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:file_testing/file_testing.dart';
import 'package:flutter_tools/src/artifacts.dart'; import 'package:flutter_tools/src/artifacts.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_info.dart'; import 'package:flutter_tools/src/build_info.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/android.dart'; import 'package:flutter_tools/src/build_system/targets/android.dart';
import 'package:flutter_tools/src/build_system/targets/assets.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/convert.dart';
import 'package:flutter_tools/src/globals.dart' as globals; import 'package:flutter_tools/src/globals.dart' as globals;
import 'package:flutter_tools/src/cache.dart'; import 'package:flutter_tools/src/cache.dart';
import 'package:mockito/mockito.dart'; import 'package:mockito/mockito.dart';
...@@ -58,6 +61,50 @@ void main() { ...@@ -58,6 +61,50 @@ void main() {
expect(globals.fs.file(globals.fs.path.join('out', 'flutter_assets', 'kernel_blob.bin')).existsSync(), true); expect(globals.fs.file(globals.fs.path.join('out', 'flutter_assets', 'kernel_blob.bin')).existsSync(), true);
}); });
testbed.test('debug bundle contains expected resources with bundle SkSL', () async {
final Environment environment = Environment.test(
globals.fs.currentDirectory,
outputDir: globals.fs.directory('out')..createSync(),
defines: <String, String>{
kBuildMode: 'debug',
},
inputs: <String, String>{
kBundleSkSLPath: 'bundle.sksl'
},
processManager: fakeProcessManager,
artifacts: MockArtifacts(),
fileSystem: globals.fs,
logger: globals.logger,
engineVersion: '2',
);
environment.buildDir.createSync(recursive: true);
globals.fs.file('bundle.sksl').writeAsStringSync(json.encode(
<String, Object>{
'engineRevision': '2',
'platform': 'android',
'data': <String, Object>{
'A': 'B',
}
}
));
// create pre-requisites.
environment.buildDir.childFile('app.dill')
.writeAsStringSync('abcd');
final Directory hostDirectory = globals.fs.currentDirectory
.childDirectory(getNameForHostPlatform(getCurrentHostPlatform()))
..createSync(recursive: true);
hostDirectory.childFile('vm_isolate_snapshot.bin').createSync();
hostDirectory.childFile('isolate_snapshot.bin').createSync();
await const DebugAndroidApplication().build(environment);
expect(globals.fs.file(globals.fs.path.join('out', 'flutter_assets', 'isolate_snapshot_data')), exists);
expect(globals.fs.file(globals.fs.path.join('out', 'flutter_assets', 'vm_snapshot_data')), exists);
expect(globals.fs.file(globals.fs.path.join('out', 'flutter_assets', 'kernel_blob.bin')), exists);
expect(globals.fs.file(globals.fs.path.join('out', 'flutter_assets', 'io.flutter.shaders.json')), exists);
});
testbed.test('profile bundle contains expected resources', () async { testbed.test('profile bundle contains expected resources', () async {
final Environment environment = Environment.test( final Environment environment = Environment.test(
globals.fs.currentDirectory, globals.fs.currentDirectory,
......
...@@ -7,9 +7,12 @@ import 'package:file_testing/file_testing.dart'; ...@@ -7,9 +7,12 @@ import 'package:file_testing/file_testing.dart';
import 'package:flutter_tools/src/artifacts.dart'; import 'package:flutter_tools/src/artifacts.dart';
import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/logger.dart'; import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/build_info.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/depfile.dart'; import 'package:flutter_tools/src/build_system/depfile.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/convert.dart';
import 'package:flutter_tools/src/devfs.dart';
import 'package:mockito/mockito.dart'; import 'package:mockito/mockito.dart';
import 'package:platform/platform.dart'; import 'package:platform/platform.dart';
...@@ -116,6 +119,134 @@ flutter: ...@@ -116,6 +119,134 @@ flutter:
ProcessManager: () => FakeProcessManager.any(), ProcessManager: () => FakeProcessManager.any(),
Platform: () => platform, Platform: () => platform,
}); });
testWithoutContext('processSkSLBundle returns null if there is no path '
'to the bundle', () {
expect(processSkSLBundle(
null,
targetPlatform: TargetPlatform.android,
fileSystem: MemoryFileSystem.test(),
logger: BufferLogger.test(),
engineVersion: null,
), isNull);
});
testWithoutContext('processSkSLBundle throws exception if bundle file is '
'missing', () {
expect(() => processSkSLBundle(
'does_not_exist.sksl',
targetPlatform: TargetPlatform.android,
fileSystem: MemoryFileSystem.test(),
logger: BufferLogger.test(),
engineVersion: null,
), throwsA(isA<Exception>()));
});
testWithoutContext('processSkSLBundle throws exception if the bundle is not '
'valid JSON', () {
final FileSystem fileSystem = MemoryFileSystem.test();
final BufferLogger logger = BufferLogger.test();
fileSystem.file('bundle.sksl').writeAsStringSync('{');
expect(() => processSkSLBundle(
'bundle.sksl',
targetPlatform: TargetPlatform.android,
fileSystem: fileSystem,
logger: logger,
engineVersion: null,
), throwsA(isA<Exception>()));
expect(logger.errorText, contains('was not a JSON object'));
});
testWithoutContext('processSkSLBundle throws exception if the bundle is not '
'a JSON object', () {
final FileSystem fileSystem = MemoryFileSystem.test();
final BufferLogger logger = BufferLogger.test();
fileSystem.file('bundle.sksl').writeAsStringSync('[]');
expect(() => processSkSLBundle(
'bundle.sksl',
targetPlatform: TargetPlatform.android,
fileSystem: fileSystem,
logger: logger,
engineVersion: null,
), throwsA(isA<Exception>()));
expect(logger.errorText, contains('was not a JSON object'));
});
testWithoutContext('processSkSLBundle throws an exception if the engine '
'revision is different', () {
final FileSystem fileSystem = MemoryFileSystem.test();
final BufferLogger logger = BufferLogger.test();
fileSystem.file('bundle.sksl').writeAsStringSync(json.encode(
<String, String>{
'engineRevision': '1'
}
));
expect(() => processSkSLBundle(
'bundle.sksl',
targetPlatform: TargetPlatform.android,
fileSystem: fileSystem,
logger: logger,
engineVersion: '2',
), throwsA(isA<Exception>()));
expect(logger.errorText, contains('Expected Flutter 1, but found 2'));
});
testWithoutContext('processSkSLBundle warns if the bundle target platform is '
'different from the current target', () async {
final FileSystem fileSystem = MemoryFileSystem.test();
final BufferLogger logger = BufferLogger.test();
fileSystem.file('bundle.sksl').writeAsStringSync(json.encode(
<String, Object>{
'engineRevision': '2',
'platform': 'fuchsia',
'data': <String, Object>{}
}
));
final DevFSContent content = processSkSLBundle(
'bundle.sksl',
targetPlatform: TargetPlatform.android,
fileSystem: fileSystem,
logger: logger,
engineVersion: '2',
);
expect(await content.contentsAsBytes(), utf8.encode('{"data":{}}'));
expect(logger.errorText, contains('This may lead to less efficient shader caching'));
});
testWithoutContext('processSkSLBundle does not warn and produces bundle', () async {
final FileSystem fileSystem = MemoryFileSystem.test();
final BufferLogger logger = BufferLogger.test();
fileSystem.file('bundle.sksl').writeAsStringSync(json.encode(
<String, Object>{
'engineRevision': '2',
'platform': 'android',
'data': <String, Object>{}
}
));
final DevFSContent content = processSkSLBundle(
'bundle.sksl',
targetPlatform: TargetPlatform.android,
fileSystem: fileSystem,
logger: logger,
engineVersion: '2',
);
expect(await content.contentsAsBytes(), utf8.encode('{"data":{}}'));
expect(logger.errorText, isEmpty);
});
} }
class MockArtifacts extends Mock implements Artifacts {} class MockArtifacts extends Mock implements Artifacts {}
...@@ -627,7 +627,7 @@ void main() { ...@@ -627,7 +627,7 @@ void main() {
expect(testLogger.statusText, contains('flutter_01.sksl')); expect(testLogger.statusText, contains('flutter_01.sksl'));
expect(globals.fs.file('flutter_01.sksl'), exists); expect(globals.fs.file('flutter_01.sksl'), exists);
expect(json.decode(globals.fs.file('flutter_01.sksl').readAsStringSync()), <String, Object>{ expect(json.decode(globals.fs.file('flutter_01.sksl').readAsStringSync()), <String, Object>{
'platform': 'android-arm', 'platform': 'android',
'name': 'test device', 'name': 'test device',
'engineRevision': '42.2', // From FakeFlutterVersion 'engineRevision': '42.2', // From FakeFlutterVersion
'data': <String, Object>{'A': 'B'} 'data': <String, Object>{'A': 'B'}
......
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