mirror of
https://github.com/flutter/flutter
synced 2024-08-25 02:46:31 +00:00
Enable asset transformation for flutter build
for iOS, Android, Windows, MacOS, Linux, and web (also flutter run
without hot reload support) (#143815)
See title. These are are the platforms that use the `CopyAssets` `Target` as part of their build target. Partial implementation of https://github.com/flutter/flutter/issues/143348.
This commit is contained in:
parent
15f8ef71d9
commit
4e814a5f3c
|
@ -88,10 +88,12 @@ enum AssetKind {
|
||||||
final class AssetBundleEntry {
|
final class AssetBundleEntry {
|
||||||
const AssetBundleEntry(this.content, {
|
const AssetBundleEntry(this.content, {
|
||||||
required this.kind,
|
required this.kind,
|
||||||
|
required this.transformers,
|
||||||
});
|
});
|
||||||
|
|
||||||
final DevFSContent content;
|
final DevFSContent content;
|
||||||
final AssetKind kind;
|
final AssetKind kind;
|
||||||
|
final List<AssetTransformerEntry> transformers;
|
||||||
|
|
||||||
Future<List<int>> contentsAsBytes() => content.contentsAsBytes();
|
Future<List<int>> contentsAsBytes() => content.contentsAsBytes();
|
||||||
}
|
}
|
||||||
|
@ -264,6 +266,7 @@ class ManifestAssetBundle implements AssetBundle {
|
||||||
entries[_kAssetManifestJsonFilename] = AssetBundleEntry(
|
entries[_kAssetManifestJsonFilename] = AssetBundleEntry(
|
||||||
DevFSStringContent('{}'),
|
DevFSStringContent('{}'),
|
||||||
kind: AssetKind.regular,
|
kind: AssetKind.regular,
|
||||||
|
transformers: const <AssetTransformerEntry>[],
|
||||||
);
|
);
|
||||||
final ByteData emptyAssetManifest =
|
final ByteData emptyAssetManifest =
|
||||||
const StandardMessageCodec().encodeMessage(<dynamic, dynamic>{})!;
|
const StandardMessageCodec().encodeMessage(<dynamic, dynamic>{})!;
|
||||||
|
@ -272,12 +275,14 @@ class ManifestAssetBundle implements AssetBundle {
|
||||||
emptyAssetManifest.buffer.asUint8List(0, emptyAssetManifest.lengthInBytes),
|
emptyAssetManifest.buffer.asUint8List(0, emptyAssetManifest.lengthInBytes),
|
||||||
),
|
),
|
||||||
kind: AssetKind.regular,
|
kind: AssetKind.regular,
|
||||||
|
transformers: const <AssetTransformerEntry>[],
|
||||||
);
|
);
|
||||||
// Create .bin.json on web builds.
|
// Create .bin.json on web builds.
|
||||||
if (targetPlatform == TargetPlatform.web_javascript) {
|
if (targetPlatform == TargetPlatform.web_javascript) {
|
||||||
entries[_kAssetManifestBinJsonFilename] = AssetBundleEntry(
|
entries[_kAssetManifestBinJsonFilename] = AssetBundleEntry(
|
||||||
DevFSStringContent('""'),
|
DevFSStringContent('""'),
|
||||||
kind: AssetKind.regular,
|
kind: AssetKind.regular,
|
||||||
|
transformers: const <AssetTransformerEntry>[],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -423,6 +428,7 @@ class ManifestAssetBundle implements AssetBundle {
|
||||||
entries[variant.entryUri.path] ??= AssetBundleEntry(
|
entries[variant.entryUri.path] ??= AssetBundleEntry(
|
||||||
DevFSFileContent(variantFile),
|
DevFSFileContent(variantFile),
|
||||||
kind: variant.kind,
|
kind: variant.kind,
|
||||||
|
transformers: variant.transformers,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -456,6 +462,7 @@ class ManifestAssetBundle implements AssetBundle {
|
||||||
deferredComponentsEntries[componentName]![variant.entryUri.path] ??= AssetBundleEntry(
|
deferredComponentsEntries[componentName]![variant.entryUri.path] ??= AssetBundleEntry(
|
||||||
DevFSFileContent(variantFile),
|
DevFSFileContent(variantFile),
|
||||||
kind: AssetKind.regular,
|
kind: AssetKind.regular,
|
||||||
|
transformers: variant.transformers,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -471,7 +478,11 @@ class ManifestAssetBundle implements AssetBundle {
|
||||||
for (final _Asset asset in materialAssets) {
|
for (final _Asset asset in materialAssets) {
|
||||||
final File assetFile = asset.lookupAssetFile(_fileSystem);
|
final File assetFile = asset.lookupAssetFile(_fileSystem);
|
||||||
assert(assetFile.existsSync(), 'Missing ${assetFile.path}');
|
assert(assetFile.existsSync(), 'Missing ${assetFile.path}');
|
||||||
entries[asset.entryUri.path] ??= AssetBundleEntry(DevFSFileContent(assetFile), kind: asset.kind);
|
entries[asset.entryUri.path] ??= AssetBundleEntry(
|
||||||
|
DevFSFileContent(assetFile),
|
||||||
|
kind: asset.kind,
|
||||||
|
transformers: const <AssetTransformerEntry>[],
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update wildcard directories we can detect changes in them.
|
// Update wildcard directories we can detect changes in them.
|
||||||
|
@ -534,6 +545,7 @@ class ManifestAssetBundle implements AssetBundle {
|
||||||
entries[key] = AssetBundleEntry(
|
entries[key] = AssetBundleEntry(
|
||||||
content,
|
content,
|
||||||
kind: assetKind,
|
kind: assetKind,
|
||||||
|
transformers: const <AssetTransformerEntry>[],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -579,6 +591,7 @@ class ManifestAssetBundle implements AssetBundle {
|
||||||
hintString: 'copyrightsoftwaretothisinandorofthe',
|
hintString: 'copyrightsoftwaretothisinandorofthe',
|
||||||
),
|
),
|
||||||
kind: AssetKind.regular,
|
kind: AssetKind.regular,
|
||||||
|
transformers: const<AssetTransformerEntry>[],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -684,6 +697,8 @@ class ManifestAssetBundle implements AssetBundle {
|
||||||
cache,
|
cache,
|
||||||
componentAssets,
|
componentAssets,
|
||||||
assetsEntry.uri,
|
assetsEntry.uri,
|
||||||
|
flavors: assetsEntry.flavors,
|
||||||
|
transformers: assetsEntry.transformers,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
_parseAssetFromFile(
|
_parseAssetFromFile(
|
||||||
|
@ -693,6 +708,8 @@ class ManifestAssetBundle implements AssetBundle {
|
||||||
cache,
|
cache,
|
||||||
componentAssets,
|
componentAssets,
|
||||||
assetsEntry.uri,
|
assetsEntry.uri,
|
||||||
|
flavors: assetsEntry.flavors,
|
||||||
|
transformers: assetsEntry.transformers,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -863,6 +880,7 @@ class ManifestAssetBundle implements AssetBundle {
|
||||||
packageName: packageName,
|
packageName: packageName,
|
||||||
attributedPackage: attributedPackage,
|
attributedPackage: attributedPackage,
|
||||||
flavors: assetsEntry.flavors,
|
flavors: assetsEntry.flavors,
|
||||||
|
transformers: assetsEntry.transformers,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
_parseAssetFromFile(
|
_parseAssetFromFile(
|
||||||
|
@ -875,6 +893,7 @@ class ManifestAssetBundle implements AssetBundle {
|
||||||
packageName: packageName,
|
packageName: packageName,
|
||||||
attributedPackage: attributedPackage,
|
attributedPackage: attributedPackage,
|
||||||
flavors: assetsEntry.flavors,
|
flavors: assetsEntry.flavors,
|
||||||
|
transformers: assetsEntry.transformers,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -900,6 +919,8 @@ class ManifestAssetBundle implements AssetBundle {
|
||||||
packageName: packageName,
|
packageName: packageName,
|
||||||
attributedPackage: attributedPackage,
|
attributedPackage: attributedPackage,
|
||||||
assetKind: AssetKind.shader,
|
assetKind: AssetKind.shader,
|
||||||
|
flavors: <String>{},
|
||||||
|
transformers: <AssetTransformerEntry>[],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -914,6 +935,8 @@ class ManifestAssetBundle implements AssetBundle {
|
||||||
packageName: packageName,
|
packageName: packageName,
|
||||||
attributedPackage: attributedPackage,
|
attributedPackage: attributedPackage,
|
||||||
assetKind: AssetKind.model,
|
assetKind: AssetKind.model,
|
||||||
|
flavors: <String>{},
|
||||||
|
transformers: <AssetTransformerEntry>[],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -927,6 +950,8 @@ class ManifestAssetBundle implements AssetBundle {
|
||||||
packageName,
|
packageName,
|
||||||
attributedPackage,
|
attributedPackage,
|
||||||
assetKind: AssetKind.font,
|
assetKind: AssetKind.font,
|
||||||
|
flavors: <String>{},
|
||||||
|
transformers: <AssetTransformerEntry>[],
|
||||||
);
|
);
|
||||||
final File baseAssetFile = baseAsset.lookupAssetFile(_fileSystem);
|
final File baseAssetFile = baseAsset.lookupAssetFile(_fileSystem);
|
||||||
if (!baseAssetFile.existsSync()) {
|
if (!baseAssetFile.existsSync()) {
|
||||||
|
@ -949,7 +974,8 @@ class ManifestAssetBundle implements AssetBundle {
|
||||||
Uri assetUri, {
|
Uri assetUri, {
|
||||||
String? packageName,
|
String? packageName,
|
||||||
Package? attributedPackage,
|
Package? attributedPackage,
|
||||||
Set<String>? flavors,
|
required Set<String> flavors,
|
||||||
|
required List<AssetTransformerEntry> transformers,
|
||||||
}) {
|
}) {
|
||||||
final String directoryPath;
|
final String directoryPath;
|
||||||
try {
|
try {
|
||||||
|
@ -985,6 +1011,7 @@ class ManifestAssetBundle implements AssetBundle {
|
||||||
attributedPackage: attributedPackage,
|
attributedPackage: attributedPackage,
|
||||||
originUri: assetUri,
|
originUri: assetUri,
|
||||||
flavors: flavors,
|
flavors: flavors,
|
||||||
|
transformers: transformers,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1000,7 +1027,8 @@ class ManifestAssetBundle implements AssetBundle {
|
||||||
String? packageName,
|
String? packageName,
|
||||||
Package? attributedPackage,
|
Package? attributedPackage,
|
||||||
AssetKind assetKind = AssetKind.regular,
|
AssetKind assetKind = AssetKind.regular,
|
||||||
Set<String>? flavors,
|
required Set<String> flavors,
|
||||||
|
required List<AssetTransformerEntry> transformers,
|
||||||
}) {
|
}) {
|
||||||
final _Asset asset = _resolveAsset(
|
final _Asset asset = _resolveAsset(
|
||||||
packageConfig,
|
packageConfig,
|
||||||
|
@ -1011,6 +1039,7 @@ class ManifestAssetBundle implements AssetBundle {
|
||||||
assetKind: assetKind,
|
assetKind: assetKind,
|
||||||
originUri: originUri,
|
originUri: originUri,
|
||||||
flavors: flavors,
|
flavors: flavors,
|
||||||
|
transformers: transformers,
|
||||||
);
|
);
|
||||||
|
|
||||||
_checkForFlavorConflicts(asset, result.keys.toList());
|
_checkForFlavorConflicts(asset, result.keys.toList());
|
||||||
|
@ -1032,6 +1061,8 @@ class ManifestAssetBundle implements AssetBundle {
|
||||||
relativeUri: relativeUri,
|
relativeUri: relativeUri,
|
||||||
package: attributedPackage,
|
package: attributedPackage,
|
||||||
kind: assetKind,
|
kind: assetKind,
|
||||||
|
flavors: flavors,
|
||||||
|
transformers: transformers,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1116,7 +1147,8 @@ class ManifestAssetBundle implements AssetBundle {
|
||||||
Package? attributedPackage, {
|
Package? attributedPackage, {
|
||||||
Uri? originUri,
|
Uri? originUri,
|
||||||
AssetKind assetKind = AssetKind.regular,
|
AssetKind assetKind = AssetKind.regular,
|
||||||
Set<String>? flavors,
|
required Set<String> flavors,
|
||||||
|
required List<AssetTransformerEntry> transformers,
|
||||||
}) {
|
}) {
|
||||||
final String assetPath = _fileSystem.path.fromUri(assetUri);
|
final String assetPath = _fileSystem.path.fromUri(assetUri);
|
||||||
if (assetUri.pathSegments.first == 'packages'
|
if (assetUri.pathSegments.first == 'packages'
|
||||||
|
@ -1130,6 +1162,7 @@ class ManifestAssetBundle implements AssetBundle {
|
||||||
assetKind: assetKind,
|
assetKind: assetKind,
|
||||||
originUri: originUri,
|
originUri: originUri,
|
||||||
flavors: flavors,
|
flavors: flavors,
|
||||||
|
transformers: transformers,
|
||||||
);
|
);
|
||||||
if (packageAsset != null) {
|
if (packageAsset != null) {
|
||||||
return packageAsset;
|
return packageAsset;
|
||||||
|
@ -1146,6 +1179,7 @@ class ManifestAssetBundle implements AssetBundle {
|
||||||
originUri: originUri,
|
originUri: originUri,
|
||||||
kind: assetKind,
|
kind: assetKind,
|
||||||
flavors: flavors,
|
flavors: flavors,
|
||||||
|
transformers: transformers,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1156,6 +1190,7 @@ class ManifestAssetBundle implements AssetBundle {
|
||||||
AssetKind assetKind = AssetKind.regular,
|
AssetKind assetKind = AssetKind.regular,
|
||||||
Uri? originUri,
|
Uri? originUri,
|
||||||
Set<String>? flavors,
|
Set<String>? flavors,
|
||||||
|
List<AssetTransformerEntry>? transformers,
|
||||||
}) {
|
}) {
|
||||||
assert(assetUri.pathSegments.first == 'packages');
|
assert(assetUri.pathSegments.first == 'packages');
|
||||||
if (assetUri.pathSegments.length > 1) {
|
if (assetUri.pathSegments.length > 1) {
|
||||||
|
@ -1171,6 +1206,7 @@ class ManifestAssetBundle implements AssetBundle {
|
||||||
kind: assetKind,
|
kind: assetKind,
|
||||||
originUri: originUri,
|
originUri: originUri,
|
||||||
flavors: flavors,
|
flavors: flavors,
|
||||||
|
transformers: transformers,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1193,7 +1229,10 @@ class _Asset {
|
||||||
required this.package,
|
required this.package,
|
||||||
this.kind = AssetKind.regular,
|
this.kind = AssetKind.regular,
|
||||||
Set<String>? flavors,
|
Set<String>? flavors,
|
||||||
}): originUri = originUri ?? entryUri, flavors = flavors ?? const <String>{};
|
List<AssetTransformerEntry>? transformers,
|
||||||
|
}) : originUri = originUri ?? entryUri,
|
||||||
|
flavors = flavors ?? const <String>{},
|
||||||
|
transformers = transformers ?? const <AssetTransformerEntry>[];
|
||||||
|
|
||||||
final String baseDir;
|
final String baseDir;
|
||||||
|
|
||||||
|
@ -1214,6 +1253,8 @@ class _Asset {
|
||||||
|
|
||||||
final Set<String> flavors;
|
final Set<String> flavors;
|
||||||
|
|
||||||
|
final List<AssetTransformerEntry> transformers;
|
||||||
|
|
||||||
File lookupAssetFile(FileSystem fileSystem) {
|
File lookupAssetFile(FileSystem fileSystem) {
|
||||||
return fileSystem.file(fileSystem.path.join(baseDir, fileSystem.path.fromUri(relativeUri)));
|
return fileSystem.file(fileSystem.path.join(baseDir, fileSystem.path.fromUri(relativeUri)));
|
||||||
}
|
}
|
||||||
|
|
|
@ -498,3 +498,19 @@ bool setEquals<T>(Set<T>? a, Set<T>? b) {
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Tests for shallow equality on two lists.
|
||||||
|
bool listEquals<T>(List<T> a, List<T> b) {
|
||||||
|
if (identical(a, b)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (a.length != b.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (int index = 0; index < a.length; index++) {
|
||||||
|
if (a[index] != b[index]) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
|
@ -4,14 +4,18 @@
|
||||||
|
|
||||||
import 'package:pool/pool.dart';
|
import 'package:pool/pool.dart';
|
||||||
|
|
||||||
|
import '../../artifacts.dart';
|
||||||
import '../../asset.dart';
|
import '../../asset.dart';
|
||||||
|
import '../../base/common.dart';
|
||||||
import '../../base/file_system.dart';
|
import '../../base/file_system.dart';
|
||||||
import '../../base/logger.dart';
|
import '../../base/logger.dart';
|
||||||
import '../../build_info.dart';
|
import '../../build_info.dart';
|
||||||
import '../../convert.dart';
|
import '../../convert.dart';
|
||||||
import '../../devfs.dart';
|
import '../../devfs.dart';
|
||||||
|
import '../../flutter_manifest.dart';
|
||||||
import '../build_system.dart';
|
import '../build_system.dart';
|
||||||
import '../depfile.dart';
|
import '../depfile.dart';
|
||||||
|
import '../tools/asset_transformer.dart';
|
||||||
import '../tools/scene_importer.dart';
|
import '../tools/scene_importer.dart';
|
||||||
import '../tools/shader_compiler.dart';
|
import '../tools/shader_compiler.dart';
|
||||||
import 'common.dart';
|
import 'common.dart';
|
||||||
|
@ -93,19 +97,29 @@ Future<Depfile> copyAssets(
|
||||||
fileSystem: environment.fileSystem,
|
fileSystem: environment.fileSystem,
|
||||||
artifacts: environment.artifacts,
|
artifacts: environment.artifacts,
|
||||||
);
|
);
|
||||||
|
final AssetTransformer assetTransformer = AssetTransformer(
|
||||||
|
processManager: environment.processManager,
|
||||||
|
fileSystem: environment.fileSystem,
|
||||||
|
dartBinaryPath: environment.artifacts.getArtifactPath(Artifact.engineDartBinary),
|
||||||
|
);
|
||||||
|
|
||||||
final Map<String, AssetBundleEntry> assetEntries = <String, AssetBundleEntry>{
|
final Map<String, AssetBundleEntry> assetEntries = <String, AssetBundleEntry>{
|
||||||
...assetBundle.entries,
|
...assetBundle.entries,
|
||||||
...additionalContent.map((String key, DevFSContent value) {
|
...additionalContent.map((String key, DevFSContent value) {
|
||||||
return MapEntry<String, AssetBundleEntry>(
|
return MapEntry<String, AssetBundleEntry>(
|
||||||
key,
|
key,
|
||||||
AssetBundleEntry(value, kind: AssetKind.regular),
|
AssetBundleEntry(
|
||||||
|
value,
|
||||||
|
kind: AssetKind.regular,
|
||||||
|
transformers: const <AssetTransformerEntry>[],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
if (skslBundle != null)
|
if (skslBundle != null)
|
||||||
kSkSLShaderBundlePath: AssetBundleEntry(
|
kSkSLShaderBundlePath: AssetBundleEntry(
|
||||||
skslBundle,
|
skslBundle,
|
||||||
kind: AssetKind.regular,
|
kind: AssetKind.regular,
|
||||||
|
transformers: const <AssetTransformerEntry>[],
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -128,7 +142,18 @@ Future<Depfile> copyAssets(
|
||||||
bool doCopy = true;
|
bool doCopy = true;
|
||||||
switch (entry.value.kind) {
|
switch (entry.value.kind) {
|
||||||
case AssetKind.regular:
|
case AssetKind.regular:
|
||||||
break;
|
if (entry.value.transformers.isNotEmpty) {
|
||||||
|
final AssetTransformationFailure? failure = await assetTransformer.transformAsset(
|
||||||
|
asset: content.file as File,
|
||||||
|
outputPath: file.path,
|
||||||
|
workingDirectory: environment.projectDir.path,
|
||||||
|
transformerEntries: entry.value.transformers,
|
||||||
|
);
|
||||||
|
doCopy = false;
|
||||||
|
if (failure != null) {
|
||||||
|
throwToolExit(failure.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
case AssetKind.font:
|
case AssetKind.font:
|
||||||
doCopy = !await iconTreeShaker.subsetFont(
|
doCopy = !await iconTreeShaker.subsetFont(
|
||||||
input: content.file as File,
|
input: content.file as File,
|
||||||
|
|
|
@ -0,0 +1,143 @@
|
||||||
|
// 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:process/process.dart';
|
||||||
|
|
||||||
|
import '../../base/error_handling_io.dart';
|
||||||
|
import '../../base/file_system.dart';
|
||||||
|
import '../../base/io.dart';
|
||||||
|
import '../../flutter_manifest.dart';
|
||||||
|
import '../build_system.dart';
|
||||||
|
|
||||||
|
/// Applies a series of user-specified asset-transforming packages to an asset file.
|
||||||
|
final class AssetTransformer {
|
||||||
|
AssetTransformer({
|
||||||
|
required ProcessManager processManager,
|
||||||
|
required FileSystem fileSystem,
|
||||||
|
required String dartBinaryPath,
|
||||||
|
}) : _processManager = processManager,
|
||||||
|
_fileSystem = fileSystem,
|
||||||
|
_dartBinaryPath = dartBinaryPath;
|
||||||
|
|
||||||
|
final ProcessManager _processManager;
|
||||||
|
final FileSystem _fileSystem;
|
||||||
|
final String _dartBinaryPath;
|
||||||
|
|
||||||
|
/// 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/asset_transformer.dart',
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
/// Applies, in sequence, a list of transformers to an [asset] and then copies
|
||||||
|
/// the output to [outputPath].
|
||||||
|
Future<AssetTransformationFailure?> transformAsset({
|
||||||
|
required File asset,
|
||||||
|
required String outputPath,
|
||||||
|
required String workingDirectory,
|
||||||
|
required List<AssetTransformerEntry> transformerEntries,
|
||||||
|
}) async {
|
||||||
|
|
||||||
|
String getTempFilePath(int transformStep) {
|
||||||
|
final String basename = _fileSystem.path.basename(asset.path);
|
||||||
|
final String ext = _fileSystem.path.extension(asset.path);
|
||||||
|
return '$basename-transformOutput$transformStep$ext';
|
||||||
|
}
|
||||||
|
|
||||||
|
File tempInputFile = _fileSystem.systemTempDirectory.childFile(getTempFilePath(0));
|
||||||
|
await asset.copy(tempInputFile.path);
|
||||||
|
File tempOutputFile = _fileSystem.systemTempDirectory.childFile(getTempFilePath(1));
|
||||||
|
|
||||||
|
try {
|
||||||
|
for (final (int i, AssetTransformerEntry transformer) in transformerEntries.indexed) {
|
||||||
|
final AssetTransformationFailure? transformerFailure = await _applyTransformer(
|
||||||
|
asset: tempInputFile,
|
||||||
|
output: tempOutputFile,
|
||||||
|
transformer: transformer,
|
||||||
|
workingDirectory: workingDirectory,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (transformerFailure != null) {
|
||||||
|
return AssetTransformationFailure(
|
||||||
|
'User-defined transformation of asset "${asset.path}" failed.\n'
|
||||||
|
'${transformerFailure.message}',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
ErrorHandlingFileSystem.deleteIfExists(tempInputFile);
|
||||||
|
if (i == transformerEntries.length - 1) {
|
||||||
|
await tempOutputFile.copy(outputPath);
|
||||||
|
} else {
|
||||||
|
tempInputFile = tempOutputFile;
|
||||||
|
tempOutputFile = _fileSystem.systemTempDirectory.childFile(getTempFilePath(i+2));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
ErrorHandlingFileSystem.deleteIfExists(tempInputFile);
|
||||||
|
ErrorHandlingFileSystem.deleteIfExists(tempOutputFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<AssetTransformationFailure?> _applyTransformer({
|
||||||
|
required File asset,
|
||||||
|
required File output,
|
||||||
|
required AssetTransformerEntry transformer,
|
||||||
|
required String workingDirectory,
|
||||||
|
}) async {
|
||||||
|
final List<String> transformerArguments = <String>[
|
||||||
|
'--input=${asset.absolute.path}',
|
||||||
|
'--output=${output.absolute.path}',
|
||||||
|
...?transformer.args,
|
||||||
|
];
|
||||||
|
|
||||||
|
final List<String> command = <String>[
|
||||||
|
_dartBinaryPath,
|
||||||
|
'run',
|
||||||
|
transformer.package,
|
||||||
|
...transformerArguments,
|
||||||
|
];
|
||||||
|
|
||||||
|
final ProcessResult result = await _processManager.run(
|
||||||
|
command,
|
||||||
|
workingDirectory: workingDirectory,
|
||||||
|
);
|
||||||
|
final String stdout = result.stdout as String;
|
||||||
|
final String stderr = result.stderr as String;
|
||||||
|
|
||||||
|
if (result.exitCode != 0) {
|
||||||
|
return AssetTransformationFailure(
|
||||||
|
'Transformer process terminated with non-zero exit code: ${result.exitCode}\n'
|
||||||
|
'Transformer package: ${transformer.package}\n'
|
||||||
|
'Full command: ${command.join(' ')}\n'
|
||||||
|
'stdout:\n$stdout\n'
|
||||||
|
'stderr:\n$stderr'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_fileSystem.file(output).existsSync()) {
|
||||||
|
return AssetTransformationFailure(
|
||||||
|
'Asset transformer ${transformer.package} did not produce an output file.\n'
|
||||||
|
'Input file provided to transformer: "${asset.path}"\n'
|
||||||
|
'Expected output file at: "${output.absolute.path}"\n'
|
||||||
|
'Full command: ${command.join(' ')}\n'
|
||||||
|
'stdout:\n$stdout\n'
|
||||||
|
'stderr:\n$stderr',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final class AssetTransformationFailure {
|
||||||
|
const AssetTransformationFailure(this.message);
|
||||||
|
|
||||||
|
final String message;
|
||||||
|
}
|
|
@ -835,10 +835,11 @@ class AssetsEntry {
|
||||||
int get hashCode => Object.hashAll(<Object?>[
|
int get hashCode => Object.hashAll(<Object?>[
|
||||||
uri.hashCode,
|
uri.hashCode,
|
||||||
Object.hashAllUnordered(flavors),
|
Object.hashAllUnordered(flavors),
|
||||||
|
Object.hashAll(transformers),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() => 'AssetsEntry(uri: $uri, flavors: $flavors)';
|
String toString() => 'AssetsEntry(uri: $uri, flavors: $flavors, transformers: $transformers)';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,373 @@
|
||||||
|
// Copyright 2014 The Flutter Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
import 'package:args/args.dart';
|
||||||
|
import 'package:file/memory.dart';
|
||||||
|
import 'package:file_testing/file_testing.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/build_system/tools/asset_transformer.dart';
|
||||||
|
import 'package:flutter_tools/src/flutter_manifest.dart';
|
||||||
|
|
||||||
|
import '../../../src/common.dart';
|
||||||
|
import '../../../src/fake_process_manager.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
testWithoutContext('Invokes dart properly', () async {
|
||||||
|
final FileSystem fileSystem = MemoryFileSystem.test();
|
||||||
|
final BufferLogger logger = BufferLogger.test();
|
||||||
|
final Artifacts artifacts = Artifacts.test();
|
||||||
|
|
||||||
|
final File asset = fileSystem.file('asset.txt')..createSync()..writeAsStringSync('hello world');
|
||||||
|
const String outputPath = 'output.txt';
|
||||||
|
|
||||||
|
final FakeProcessManager processManager =
|
||||||
|
FakeProcessManager.list(<FakeCommand>[
|
||||||
|
FakeCommand(
|
||||||
|
command: <String>[
|
||||||
|
artifacts.getArtifactPath(Artifact.engineDartBinary),
|
||||||
|
'run',
|
||||||
|
'my_copy_transformer',
|
||||||
|
'--input=/.tmp_rand0/asset.txt-transformOutput0.txt',
|
||||||
|
'--output=/.tmp_rand0/asset.txt-transformOutput1.txt',
|
||||||
|
'-f',
|
||||||
|
'--my_option',
|
||||||
|
'my_option_value',
|
||||||
|
],
|
||||||
|
onRun: (List<String> args) {
|
||||||
|
final ArgResults parsedArgs = (ArgParser()
|
||||||
|
..addOption('input')
|
||||||
|
..addOption('output')
|
||||||
|
..addFlag('foo', abbr: 'f')
|
||||||
|
..addOption('my_option'))
|
||||||
|
.parse(args);
|
||||||
|
|
||||||
|
fileSystem.file(parsedArgs['input']).copySync(parsedArgs['output'] as String);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
|
||||||
|
final AssetTransformer transformer = AssetTransformer(
|
||||||
|
processManager: processManager,
|
||||||
|
fileSystem: fileSystem,
|
||||||
|
dartBinaryPath: artifacts.getArtifactPath(Artifact.engineDartBinary),
|
||||||
|
);
|
||||||
|
|
||||||
|
final AssetTransformationFailure? transformationFailure = await transformer.transformAsset(
|
||||||
|
asset: asset,
|
||||||
|
outputPath: outputPath,
|
||||||
|
workingDirectory: fileSystem.currentDirectory.path,
|
||||||
|
transformerEntries: <AssetTransformerEntry>[
|
||||||
|
const AssetTransformerEntry(
|
||||||
|
package: 'my_copy_transformer',
|
||||||
|
args: <String>[
|
||||||
|
'-f',
|
||||||
|
'--my_option',
|
||||||
|
'my_option_value',
|
||||||
|
],
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(transformationFailure, isNull, reason: logger.errorText);
|
||||||
|
expect(processManager, hasNoRemainingExpectations);
|
||||||
|
expect(fileSystem.file(outputPath).readAsStringSync(), 'hello world');
|
||||||
|
});
|
||||||
|
|
||||||
|
testWithoutContext('logs useful error information when transformation process returns a nonzero exit code', () async {
|
||||||
|
final FileSystem fileSystem = MemoryFileSystem.test();
|
||||||
|
final Artifacts artifacts = Artifacts.test();
|
||||||
|
|
||||||
|
final File asset = fileSystem.file('asset.txt')..createSync();
|
||||||
|
const String outputPath = 'output.txt';
|
||||||
|
|
||||||
|
final String dartBinaryPath = artifacts.getArtifactPath(Artifact.engineDartBinary);
|
||||||
|
final FakeProcessManager processManager =
|
||||||
|
FakeProcessManager.list(<FakeCommand>[
|
||||||
|
FakeCommand(
|
||||||
|
command: <String>[
|
||||||
|
dartBinaryPath,
|
||||||
|
'run',
|
||||||
|
'my_copy_transformer',
|
||||||
|
'--input=/.tmp_rand0/asset.txt-transformOutput0.txt',
|
||||||
|
'--output=/.tmp_rand0/asset.txt-transformOutput1.txt',
|
||||||
|
],
|
||||||
|
onRun: (List<String> args) {
|
||||||
|
final ArgResults parsedArgs = (ArgParser()
|
||||||
|
..addOption('input')
|
||||||
|
..addOption('output'))
|
||||||
|
.parse(args);
|
||||||
|
fileSystem.file(parsedArgs['input']).copySync(parsedArgs['output'] as String);
|
||||||
|
},
|
||||||
|
exitCode: 1,
|
||||||
|
stdout: 'Beginning transformation',
|
||||||
|
stderr: 'Something went wrong',
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
|
||||||
|
final AssetTransformer transformer = AssetTransformer(
|
||||||
|
processManager: processManager,
|
||||||
|
fileSystem: fileSystem,
|
||||||
|
dartBinaryPath: dartBinaryPath,
|
||||||
|
);
|
||||||
|
|
||||||
|
final AssetTransformationFailure? failure = await transformer.transformAsset(
|
||||||
|
asset: asset,
|
||||||
|
outputPath: outputPath,
|
||||||
|
workingDirectory: fileSystem.currentDirectory.path,
|
||||||
|
transformerEntries: <AssetTransformerEntry>[
|
||||||
|
const AssetTransformerEntry(
|
||||||
|
package: 'my_copy_transformer',
|
||||||
|
args: <String>[],
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(asset, exists);
|
||||||
|
expect(processManager, hasNoRemainingExpectations);
|
||||||
|
expect(failure, isNotNull);
|
||||||
|
expect(failure!.message,
|
||||||
|
'''
|
||||||
|
User-defined transformation of asset "asset.txt" failed.
|
||||||
|
Transformer process terminated with non-zero exit code: 1
|
||||||
|
Transformer package: my_copy_transformer
|
||||||
|
Full command: $dartBinaryPath run my_copy_transformer --input=/.tmp_rand0/asset.txt-transformOutput0.txt --output=/.tmp_rand0/asset.txt-transformOutput1.txt
|
||||||
|
stdout:
|
||||||
|
Beginning transformation
|
||||||
|
stderr:
|
||||||
|
Something went wrong''');
|
||||||
|
});
|
||||||
|
|
||||||
|
testWithoutContext('prints error message when the transformer does not produce an output file', () async {
|
||||||
|
final FileSystem fileSystem = MemoryFileSystem.test();
|
||||||
|
final Artifacts artifacts = Artifacts.test();
|
||||||
|
|
||||||
|
final File asset = fileSystem.file('asset.txt')..createSync();
|
||||||
|
const String outputPath = 'output.txt';
|
||||||
|
|
||||||
|
final String dartBinaryPath = artifacts.getArtifactPath(Artifact.engineDartBinary);
|
||||||
|
final FakeProcessManager processManager =
|
||||||
|
FakeProcessManager.list(<FakeCommand>[
|
||||||
|
FakeCommand(
|
||||||
|
command: <String>[
|
||||||
|
dartBinaryPath,
|
||||||
|
'run',
|
||||||
|
'my_transformer',
|
||||||
|
'--input=/.tmp_rand0/asset.txt-transformOutput0.txt',
|
||||||
|
'--output=/.tmp_rand0/asset.txt-transformOutput1.txt',
|
||||||
|
],
|
||||||
|
onRun: (_) {
|
||||||
|
// Do nothing.
|
||||||
|
},
|
||||||
|
stderr: 'Transformation failed, but I forgot to exit with a non-zero code.'
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
|
||||||
|
final AssetTransformer transformer = AssetTransformer(
|
||||||
|
processManager: processManager,
|
||||||
|
fileSystem: fileSystem,
|
||||||
|
dartBinaryPath: dartBinaryPath,
|
||||||
|
);
|
||||||
|
|
||||||
|
final AssetTransformationFailure? failure = await transformer.transformAsset(
|
||||||
|
asset: asset,
|
||||||
|
outputPath: outputPath,
|
||||||
|
workingDirectory: fileSystem.currentDirectory.path,
|
||||||
|
transformerEntries: <AssetTransformerEntry>[
|
||||||
|
const AssetTransformerEntry(
|
||||||
|
package: 'my_transformer',
|
||||||
|
args: <String>[],
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(processManager, hasNoRemainingExpectations);
|
||||||
|
expect(failure, isNotNull);
|
||||||
|
expect(failure!.message,
|
||||||
|
'''
|
||||||
|
User-defined transformation of asset "asset.txt" failed.
|
||||||
|
Asset transformer my_transformer did not produce an output file.
|
||||||
|
Input file provided to transformer: "/.tmp_rand0/asset.txt-transformOutput0.txt"
|
||||||
|
Expected output file at: "/.tmp_rand0/asset.txt-transformOutput1.txt"
|
||||||
|
Full command: $dartBinaryPath run my_transformer --input=/.tmp_rand0/asset.txt-transformOutput0.txt --output=/.tmp_rand0/asset.txt-transformOutput1.txt
|
||||||
|
stdout:
|
||||||
|
|
||||||
|
stderr:
|
||||||
|
Transformation failed, but I forgot to exit with a non-zero code.'''
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWithoutContext('correctly chains transformations when there are multiple of them', () async {
|
||||||
|
final FileSystem fileSystem = MemoryFileSystem.test();
|
||||||
|
final Artifacts artifacts = Artifacts.test();
|
||||||
|
|
||||||
|
final File asset = fileSystem.file('asset.txt')
|
||||||
|
..createSync()
|
||||||
|
..writeAsStringSync('ABC');
|
||||||
|
const String outputPath = 'output.txt';
|
||||||
|
|
||||||
|
final String dartBinaryPath = artifacts.getArtifactPath(Artifact.engineDartBinary);
|
||||||
|
final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
|
||||||
|
FakeCommand(
|
||||||
|
command: <String>[
|
||||||
|
dartBinaryPath,
|
||||||
|
'run',
|
||||||
|
'my_lowercase_transformer',
|
||||||
|
'--input=/.tmp_rand0/asset.txt-transformOutput0.txt',
|
||||||
|
'--output=/.tmp_rand0/asset.txt-transformOutput1.txt',
|
||||||
|
],
|
||||||
|
onRun: (List<String> args) {
|
||||||
|
final ArgResults parsedArgs = (ArgParser()
|
||||||
|
..addOption('input')
|
||||||
|
..addOption('output'))
|
||||||
|
.parse(args);
|
||||||
|
|
||||||
|
final String inputFileContents = fileSystem.file(parsedArgs['input']).readAsStringSync();
|
||||||
|
fileSystem.file(parsedArgs['output'])
|
||||||
|
..createSync()
|
||||||
|
..writeAsStringSync(inputFileContents.toLowerCase());
|
||||||
|
},
|
||||||
|
),
|
||||||
|
FakeCommand(
|
||||||
|
command: <String>[
|
||||||
|
dartBinaryPath,
|
||||||
|
'run',
|
||||||
|
'my_distance_from_ascii_a_transformer',
|
||||||
|
'--input=/.tmp_rand0/asset.txt-transformOutput1.txt',
|
||||||
|
'--output=/.tmp_rand0/asset.txt-transformOutput2.txt',
|
||||||
|
],
|
||||||
|
onRun: (List<String> args) {
|
||||||
|
final ArgResults parsedArgs = (ArgParser()
|
||||||
|
..addOption('input')
|
||||||
|
..addOption('output'))
|
||||||
|
.parse(args);
|
||||||
|
|
||||||
|
final String inputFileContents = fileSystem.file(parsedArgs['input']).readAsStringSync();
|
||||||
|
final StringBuffer outputContents = StringBuffer();
|
||||||
|
|
||||||
|
for (int i = 0; i < inputFileContents.length; i++) {
|
||||||
|
outputContents.write(inputFileContents.codeUnitAt(i) - 'a'.codeUnits.first);
|
||||||
|
}
|
||||||
|
|
||||||
|
fileSystem.file(parsedArgs['output'])
|
||||||
|
..createSync()
|
||||||
|
..writeAsStringSync(outputContents.toString());
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
|
||||||
|
final AssetTransformer transformer = AssetTransformer(
|
||||||
|
processManager: processManager,
|
||||||
|
fileSystem: fileSystem,
|
||||||
|
dartBinaryPath: dartBinaryPath,
|
||||||
|
);
|
||||||
|
|
||||||
|
final AssetTransformationFailure? failure = await transformer.transformAsset(
|
||||||
|
asset: asset,
|
||||||
|
outputPath: outputPath,
|
||||||
|
workingDirectory: fileSystem.currentDirectory.path,
|
||||||
|
transformerEntries: <AssetTransformerEntry>[
|
||||||
|
const AssetTransformerEntry(
|
||||||
|
package: 'my_lowercase_transformer',
|
||||||
|
args: <String>[],
|
||||||
|
),
|
||||||
|
const AssetTransformerEntry(
|
||||||
|
package: 'my_distance_from_ascii_a_transformer',
|
||||||
|
args: <String>[],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(processManager, hasNoRemainingExpectations);
|
||||||
|
expect(failure, isNull);
|
||||||
|
expect(fileSystem.file(outputPath).readAsStringSync(), '012');
|
||||||
|
});
|
||||||
|
|
||||||
|
testWithoutContext('prints an error when a transformer in a chain (thats not the first) does not produce an output', () async {
|
||||||
|
final FileSystem fileSystem = MemoryFileSystem();
|
||||||
|
final Artifacts artifacts = Artifacts.test();
|
||||||
|
|
||||||
|
final File asset = fileSystem.file('asset.txt')
|
||||||
|
..createSync()
|
||||||
|
..writeAsStringSync('ABC');
|
||||||
|
const String outputPath = 'output.txt';
|
||||||
|
|
||||||
|
final String dartBinaryPath = artifacts.getArtifactPath(Artifact.engineDartBinary);
|
||||||
|
final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
|
||||||
|
FakeCommand(
|
||||||
|
command: <String>[
|
||||||
|
dartBinaryPath,
|
||||||
|
'run',
|
||||||
|
'my_lowercase_transformer',
|
||||||
|
'--input=/.tmp_rand0/asset.txt-transformOutput0.txt',
|
||||||
|
'--output=/.tmp_rand0/asset.txt-transformOutput1.txt',
|
||||||
|
],
|
||||||
|
onRun: (List<String> args) {
|
||||||
|
final ArgResults parsedArgs = (ArgParser()
|
||||||
|
..addOption('input')
|
||||||
|
..addOption('output'))
|
||||||
|
.parse(args);
|
||||||
|
|
||||||
|
final String inputFileContents = fileSystem.file(parsedArgs['input']).readAsStringSync();
|
||||||
|
fileSystem.file(parsedArgs['output'])
|
||||||
|
..createSync()
|
||||||
|
..writeAsStringSync(inputFileContents.toLowerCase());
|
||||||
|
},
|
||||||
|
),
|
||||||
|
FakeCommand(
|
||||||
|
command: <String>[
|
||||||
|
dartBinaryPath,
|
||||||
|
'run',
|
||||||
|
'my_distance_from_ascii_a_transformer',
|
||||||
|
'--input=/.tmp_rand0/asset.txt-transformOutput1.txt',
|
||||||
|
'--output=/.tmp_rand0/asset.txt-transformOutput2.txt',
|
||||||
|
],
|
||||||
|
onRun: (List<String> args) {
|
||||||
|
// Do nothing.
|
||||||
|
},
|
||||||
|
stderr: 'Transformation failed, but I forgot to exit with a non-zero code.'
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
|
||||||
|
final AssetTransformer transformer = AssetTransformer(
|
||||||
|
processManager: processManager,
|
||||||
|
fileSystem: fileSystem,
|
||||||
|
dartBinaryPath: dartBinaryPath,
|
||||||
|
);
|
||||||
|
|
||||||
|
final AssetTransformationFailure? failure = await transformer.transformAsset(
|
||||||
|
asset: asset,
|
||||||
|
outputPath: outputPath,
|
||||||
|
workingDirectory: fileSystem.currentDirectory.path,
|
||||||
|
transformerEntries: <AssetTransformerEntry>[
|
||||||
|
const AssetTransformerEntry(
|
||||||
|
package: 'my_lowercase_transformer',
|
||||||
|
args: <String>[],
|
||||||
|
),
|
||||||
|
const AssetTransformerEntry(
|
||||||
|
package: 'my_distance_from_ascii_a_transformer',
|
||||||
|
args: <String>[],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(failure, isNotNull);
|
||||||
|
expect(failure!.message,
|
||||||
|
'''
|
||||||
|
User-defined transformation of asset "asset.txt" failed.
|
||||||
|
Asset transformer my_distance_from_ascii_a_transformer did not produce an output file.
|
||||||
|
Input file provided to transformer: "/.tmp_rand0/asset.txt-transformOutput1.txt"
|
||||||
|
Expected output file at: "/.tmp_rand0/asset.txt-transformOutput2.txt"
|
||||||
|
Full command: Artifact.engineDartBinary run my_distance_from_ascii_a_transformer --input=/.tmp_rand0/asset.txt-transformOutput1.txt --output=/.tmp_rand0/asset.txt-transformOutput2.txt
|
||||||
|
stdout:
|
||||||
|
|
||||||
|
stderr:
|
||||||
|
Transformation failed, but I forgot to exit with a non-zero code.'''
|
||||||
|
);
|
||||||
|
expect(processManager, hasNoRemainingExpectations);
|
||||||
|
expect(fileSystem.file(outputPath), isNot(exists));
|
||||||
|
expect(fileSystem.systemTempDirectory.listSync(), isEmpty);
|
||||||
|
});
|
||||||
|
}
|
|
@ -2,6 +2,7 @@
|
||||||
// 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:args/args.dart';
|
||||||
import 'package:collection/collection.dart' show IterableExtension;
|
import 'package:collection/collection.dart' show IterableExtension;
|
||||||
import 'package:file/memory.dart';
|
import 'package:file/memory.dart';
|
||||||
import 'package:file_testing/file_testing.dart';
|
import 'package:file_testing/file_testing.dart';
|
||||||
|
@ -9,19 +10,24 @@ 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/base/platform.dart';
|
import 'package:flutter_tools/src/base/platform.dart';
|
||||||
|
import 'package:flutter_tools/src/base/user_messages.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/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/cache.dart';
|
||||||
import 'package:flutter_tools/src/convert.dart';
|
import 'package:flutter_tools/src/convert.dart';
|
||||||
import 'package:flutter_tools/src/devfs.dart';
|
import 'package:flutter_tools/src/devfs.dart';
|
||||||
|
import 'package:flutter_tools/src/globals.dart' as globals;
|
||||||
|
|
||||||
import '../../../src/common.dart';
|
import '../../../src/common.dart';
|
||||||
import '../../../src/context.dart';
|
import '../../../src/context.dart';
|
||||||
|
import '../../../src/fake_process_manager.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
late Environment environment;
|
late Environment environment;
|
||||||
late FileSystem fileSystem;
|
late FileSystem fileSystem;
|
||||||
|
late BufferLogger logger;
|
||||||
|
|
||||||
setUp(() {
|
setUp(() {
|
||||||
fileSystem = MemoryFileSystem.test();
|
fileSystem = MemoryFileSystem.test();
|
||||||
|
@ -53,6 +59,7 @@ flutter:
|
||||||
- assets/foo/bar.png
|
- assets/foo/bar.png
|
||||||
- assets/wildcard/
|
- assets/wildcard/
|
||||||
''');
|
''');
|
||||||
|
logger = BufferLogger.test();
|
||||||
});
|
});
|
||||||
|
|
||||||
testUsingContext('includes LICENSE file inputs in dependencies', () async {
|
testUsingContext('includes LICENSE file inputs in dependencies', () async {
|
||||||
|
@ -157,6 +164,153 @@ flutter:
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testUsingContext('transforms assets declared with transformers', () async {
|
||||||
|
Cache.flutterRoot = Cache.defaultFlutterRoot(
|
||||||
|
platform: globals.platform,
|
||||||
|
fileSystem: fileSystem,
|
||||||
|
userMessages: UserMessages(),
|
||||||
|
);
|
||||||
|
|
||||||
|
final Environment environment = Environment.test(
|
||||||
|
fileSystem.currentDirectory,
|
||||||
|
processManager: globals.processManager,
|
||||||
|
artifacts: Artifacts.test(),
|
||||||
|
fileSystem: fileSystem,
|
||||||
|
logger: logger,
|
||||||
|
platform: globals.platform,
|
||||||
|
defines: <String, String>{},
|
||||||
|
);
|
||||||
|
|
||||||
|
await fileSystem.file('.packages').create();
|
||||||
|
|
||||||
|
fileSystem.file('pubspec.yaml')
|
||||||
|
..createSync()
|
||||||
|
..writeAsStringSync('''
|
||||||
|
name: example
|
||||||
|
flutter:
|
||||||
|
assets:
|
||||||
|
- path: input.txt
|
||||||
|
transformers:
|
||||||
|
- package: my_capitalizer_transformer
|
||||||
|
args: ["-a", "-b", "--color", "green"]
|
||||||
|
''');
|
||||||
|
|
||||||
|
fileSystem.file('input.txt')
|
||||||
|
..createSync(recursive: true)
|
||||||
|
..writeAsStringSync('abc');
|
||||||
|
|
||||||
|
await const CopyAssets().build(environment);
|
||||||
|
|
||||||
|
expect(logger.errorText, isEmpty);
|
||||||
|
expect(globals.processManager, hasNoRemainingExpectations);
|
||||||
|
expect(fileSystem.file('${environment.buildDir.path}/flutter_assets/input.txt'), exists);
|
||||||
|
}, overrides: <Type, Generator> {
|
||||||
|
Logger: () => logger,
|
||||||
|
FileSystem: () => fileSystem,
|
||||||
|
Platform: () => FakePlatform(),
|
||||||
|
ProcessManager: () => FakeProcessManager.list(
|
||||||
|
<FakeCommand>[
|
||||||
|
FakeCommand(
|
||||||
|
command: <Pattern>[
|
||||||
|
Artifacts.test().getArtifactPath(Artifact.engineDartBinary),
|
||||||
|
'run',
|
||||||
|
'my_capitalizer_transformer',
|
||||||
|
RegExp('--input=.*'),
|
||||||
|
RegExp('--output=.*'),
|
||||||
|
'-a',
|
||||||
|
'-b',
|
||||||
|
'--color',
|
||||||
|
'green',
|
||||||
|
],
|
||||||
|
onRun: (List<String> args) {
|
||||||
|
final ArgResults parsedArgs = (ArgParser()
|
||||||
|
..addOption('input')
|
||||||
|
..addOption('output')
|
||||||
|
..addOption('color')
|
||||||
|
..addFlag('aaa', abbr: 'a')
|
||||||
|
..addFlag('bbb', abbr: 'b'))
|
||||||
|
.parse(args);
|
||||||
|
|
||||||
|
expect(parsedArgs['aaa'], true);
|
||||||
|
expect(parsedArgs['bbb'], true);
|
||||||
|
expect(parsedArgs['color'], 'green');
|
||||||
|
|
||||||
|
final File input = fileSystem.file(parsedArgs['input'] as String);
|
||||||
|
expect(input, exists);
|
||||||
|
final String inputContents = input.readAsStringSync();
|
||||||
|
expect(inputContents, 'abc');
|
||||||
|
fileSystem.file(parsedArgs['output'])
|
||||||
|
..createSync()
|
||||||
|
..writeAsStringSync(inputContents.toUpperCase());
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
|
testUsingContext('exits tool if an asset transformation fails', () async {
|
||||||
|
Cache.flutterRoot = Cache.defaultFlutterRoot(
|
||||||
|
platform: globals.platform,
|
||||||
|
fileSystem: fileSystem,
|
||||||
|
userMessages: UserMessages(),
|
||||||
|
);
|
||||||
|
|
||||||
|
final Environment environment = Environment.test(
|
||||||
|
fileSystem.currentDirectory,
|
||||||
|
processManager: globals.processManager,
|
||||||
|
artifacts: Artifacts.test(),
|
||||||
|
fileSystem: fileSystem,
|
||||||
|
logger: logger,
|
||||||
|
platform: globals.platform,
|
||||||
|
defines: <String, String>{},
|
||||||
|
);
|
||||||
|
|
||||||
|
await fileSystem.file('.packages').create();
|
||||||
|
|
||||||
|
fileSystem.file('pubspec.yaml')
|
||||||
|
..createSync()
|
||||||
|
..writeAsStringSync('''
|
||||||
|
name: example
|
||||||
|
flutter:
|
||||||
|
assets:
|
||||||
|
- path: input.txt
|
||||||
|
transformers:
|
||||||
|
- package: my_transformer
|
||||||
|
args: ["-a", "-b", "--color", "green"]
|
||||||
|
''');
|
||||||
|
|
||||||
|
await fileSystem.file('input.txt').create(recursive: true);
|
||||||
|
|
||||||
|
await expectToolExitLater(
|
||||||
|
const CopyAssets().build(environment),
|
||||||
|
startsWith('User-defined transformation of asset "/input.txt" failed.\n'),
|
||||||
|
);
|
||||||
|
expect(globals.processManager, hasNoRemainingExpectations);
|
||||||
|
}, overrides: <Type, Generator> {
|
||||||
|
Logger: () => logger,
|
||||||
|
FileSystem: () => fileSystem,
|
||||||
|
Platform: () => FakePlatform(),
|
||||||
|
ProcessManager: () => FakeProcessManager.list(
|
||||||
|
<FakeCommand>[
|
||||||
|
FakeCommand(
|
||||||
|
command: <Pattern>[
|
||||||
|
Artifacts.test().getArtifactPath(Artifact.engineDartBinary),
|
||||||
|
'run',
|
||||||
|
'my_transformer',
|
||||||
|
RegExp('--input=.*'),
|
||||||
|
RegExp('--output=.*'),
|
||||||
|
'-a',
|
||||||
|
'-b',
|
||||||
|
'--color',
|
||||||
|
'green',
|
||||||
|
],
|
||||||
|
exitCode: 1,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
testUsingContext('Throws exception if pubspec contains missing files', () async {
|
testUsingContext('Throws exception if pubspec contains missing files', () async {
|
||||||
fileSystem.file('pubspec.yaml')
|
fileSystem.file('pubspec.yaml')
|
||||||
..createSync()
|
..createSync()
|
||||||
|
@ -182,7 +336,7 @@ flutter:
|
||||||
null,
|
null,
|
||||||
targetPlatform: TargetPlatform.android,
|
targetPlatform: TargetPlatform.android,
|
||||||
fileSystem: MemoryFileSystem.test(),
|
fileSystem: MemoryFileSystem.test(),
|
||||||
logger: BufferLogger.test(),
|
logger: logger,
|
||||||
), isNull);
|
), isNull);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -193,7 +347,7 @@ flutter:
|
||||||
'does_not_exist.sksl',
|
'does_not_exist.sksl',
|
||||||
targetPlatform: TargetPlatform.android,
|
targetPlatform: TargetPlatform.android,
|
||||||
fileSystem: MemoryFileSystem.test(),
|
fileSystem: MemoryFileSystem.test(),
|
||||||
logger: BufferLogger.test(),
|
logger: logger,
|
||||||
), throwsException);
|
), throwsException);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,7 @@ import 'package:flutter_tools/src/build_info.dart';
|
||||||
import 'package:flutter_tools/src/build_system/tools/shader_compiler.dart';
|
import 'package:flutter_tools/src/build_system/tools/shader_compiler.dart';
|
||||||
import 'package:flutter_tools/src/compile.dart';
|
import 'package:flutter_tools/src/compile.dart';
|
||||||
import 'package:flutter_tools/src/devfs.dart';
|
import 'package:flutter_tools/src/devfs.dart';
|
||||||
|
import 'package:flutter_tools/src/flutter_manifest.dart';
|
||||||
import 'package:flutter_tools/src/vmservice.dart';
|
import 'package:flutter_tools/src/vmservice.dart';
|
||||||
import 'package:package_config/package_config.dart';
|
import 'package:package_config/package_config.dart';
|
||||||
import 'package:test/fake.dart';
|
import 'package:test/fake.dart';
|
||||||
|
@ -623,10 +624,12 @@ void main() {
|
||||||
..entries['foo.frag'] = AssetBundleEntry(
|
..entries['foo.frag'] = AssetBundleEntry(
|
||||||
DevFSByteContent(<int>[1, 2, 3, 4]),
|
DevFSByteContent(<int>[1, 2, 3, 4]),
|
||||||
kind: AssetKind.shader,
|
kind: AssetKind.shader,
|
||||||
|
transformers: const <AssetTransformerEntry>[],
|
||||||
)
|
)
|
||||||
..entries['not.frag'] = AssetBundleEntry(
|
..entries['not.frag'] = AssetBundleEntry(
|
||||||
DevFSByteContent(<int>[1, 2, 3, 4]),
|
DevFSByteContent(<int>[1, 2, 3, 4]),
|
||||||
kind: AssetKind.regular,
|
kind: AssetKind.regular,
|
||||||
|
transformers: const <AssetTransformerEntry>[],
|
||||||
);
|
);
|
||||||
|
|
||||||
final UpdateFSReport report = await devFS.update(
|
final UpdateFSReport report = await devFS.update(
|
||||||
|
@ -680,6 +683,7 @@ void main() {
|
||||||
..entries['FontManifest.json'] = AssetBundleEntry(
|
..entries['FontManifest.json'] = AssetBundleEntry(
|
||||||
DevFSByteContent(<int>[1, 2, 3, 4]),
|
DevFSByteContent(<int>[1, 2, 3, 4]),
|
||||||
kind: AssetKind.regular,
|
kind: AssetKind.regular,
|
||||||
|
transformers: const <AssetTransformerEntry>[],
|
||||||
);
|
);
|
||||||
|
|
||||||
final UpdateFSReport report = await devFS.update(
|
final UpdateFSReport report = await devFS.update(
|
||||||
|
|
Loading…
Reference in a new issue