mirror of
https://github.com/flutter/flutter
synced 2024-08-25 11:07:06 +00:00
Enable asset transformation feature in hot reload workflow (excluding Web) (#144161)
Partial implementation of https://github.com/flutter/flutter/issues/143348 This enables asset transformation during hot reload (except for web, because that has its own implementation of `DevFS` ð). Asset transformers will be reapplied after changing any asset and performing a hot reload during `flutter run`.
This commit is contained in:
parent
15e8d324f5
commit
ff3b6dc02c
|
@ -218,8 +218,8 @@ class ManifestAssetBundle implements AssetBundle {
|
|||
return true;
|
||||
}
|
||||
|
||||
final FileStat stat = _fileSystem.file(manifestPath).statSync();
|
||||
if (stat.type == FileSystemEntityType.notFound) {
|
||||
final FileStat manifestStat = _fileSystem.file(manifestPath).statSync();
|
||||
if (manifestStat.type == FileSystemEntityType.notFound) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -235,7 +235,7 @@ class ManifestAssetBundle implements AssetBundle {
|
|||
}
|
||||
}
|
||||
|
||||
return stat.modified.isAfter(lastBuildTimestamp);
|
||||
return manifestStat.modified.isAfter(lastBuildTimestamp);
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
|
@ -3,11 +3,16 @@
|
|||
// found in the LICENSE file.
|
||||
|
||||
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:pool/pool.dart';
|
||||
import 'package:process/process.dart';
|
||||
|
||||
import '../../base/error_handling_io.dart';
|
||||
import '../../base/file_system.dart';
|
||||
import '../../base/io.dart';
|
||||
import '../../base/logger.dart';
|
||||
import '../../devfs.dart';
|
||||
import '../../flutter_manifest.dart';
|
||||
import '../build_system.dart';
|
||||
|
||||
|
@ -52,6 +57,7 @@ final class AssetTransformer {
|
|||
File tempInputFile = _fileSystem.systemTempDirectory.childFile(getTempFilePath(0));
|
||||
await asset.copy(tempInputFile.path);
|
||||
File tempOutputFile = _fileSystem.systemTempDirectory.childFile(getTempFilePath(1));
|
||||
ErrorHandlingFileSystem.deleteIfExists(tempOutputFile);
|
||||
|
||||
try {
|
||||
for (final (int i, AssetTransformerEntry transformer) in transformerEntries.indexed) {
|
||||
|
@ -71,10 +77,12 @@ final class AssetTransformer {
|
|||
|
||||
ErrorHandlingFileSystem.deleteIfExists(tempInputFile);
|
||||
if (i == transformerEntries.length - 1) {
|
||||
await _fileSystem.file(outputPath).create(recursive: true);
|
||||
await tempOutputFile.copy(outputPath);
|
||||
} else {
|
||||
tempInputFile = tempOutputFile;
|
||||
tempOutputFile = _fileSystem.systemTempDirectory.childFile(getTempFilePath(i+2));
|
||||
ErrorHandlingFileSystem.deleteIfExists(tempOutputFile);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
|
@ -136,6 +144,69 @@ final class AssetTransformer {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
// A wrapper around [AssetTransformer] to support hot reload of transformed assets.
|
||||
final class DevelopmentAssetTransformer {
|
||||
DevelopmentAssetTransformer({
|
||||
required FileSystem fileSystem,
|
||||
required AssetTransformer transformer,
|
||||
required Logger logger,
|
||||
}) : _fileSystem = fileSystem,
|
||||
_transformer = transformer,
|
||||
_logger = logger;
|
||||
|
||||
final AssetTransformer _transformer;
|
||||
final FileSystem _fileSystem;
|
||||
final Pool _transformationPool = Pool(4);
|
||||
final Logger _logger;
|
||||
|
||||
/// Re-transforms an asset and returns a [DevFSContent] that should be synced
|
||||
/// to the attached device in its place.
|
||||
///
|
||||
/// Returns `null` if any of the transformation subprocesses failed.
|
||||
Future<DevFSContent?> retransformAsset({
|
||||
required String inputAssetKey,
|
||||
required DevFSContent inputAssetContent,
|
||||
required List<AssetTransformerEntry> transformerEntries,
|
||||
required String workingDirectory,
|
||||
}) async {
|
||||
final File output = _fileSystem.systemTempDirectory.childFile('retransformerInput-$inputAssetKey');
|
||||
ErrorHandlingFileSystem.deleteIfExists(output);
|
||||
File? inputFile;
|
||||
bool cleanupInput = false;
|
||||
Uint8List result;
|
||||
PoolResource? resource;
|
||||
try {
|
||||
resource = await _transformationPool.request();
|
||||
if (inputAssetContent is DevFSFileContent) {
|
||||
inputFile = inputAssetContent.file as File;
|
||||
} else {
|
||||
inputFile = _fileSystem.systemTempDirectory.childFile('retransformerInput-$inputAssetKey');
|
||||
inputFile.writeAsBytesSync(await inputAssetContent.contentsAsBytes());
|
||||
cleanupInput = true;
|
||||
}
|
||||
final AssetTransformationFailure? failure = await _transformer.transformAsset(
|
||||
asset: inputFile,
|
||||
outputPath: output.path,
|
||||
transformerEntries: transformerEntries,
|
||||
workingDirectory: workingDirectory,
|
||||
);
|
||||
if (failure != null) {
|
||||
_logger.printError(failure.message);
|
||||
return null;
|
||||
}
|
||||
result = output.readAsBytesSync();
|
||||
} finally {
|
||||
resource?.release();
|
||||
ErrorHandlingFileSystem.deleteIfExists(output);
|
||||
if (cleanupInput && inputFile != null) {
|
||||
ErrorHandlingFileSystem.deleteIfExists(inputFile);
|
||||
}
|
||||
}
|
||||
return DevFSByteContent(result);
|
||||
}
|
||||
}
|
||||
|
||||
final class AssetTransformationFailure {
|
||||
const AssetTransformationFailure(this.message);
|
||||
|
||||
|
|
|
@ -5,8 +5,10 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:package_config/package_config.dart';
|
||||
import 'package:process/process.dart';
|
||||
import 'package:vm_service/vm_service.dart' as vm_service;
|
||||
|
||||
import 'artifacts.dart';
|
||||
import 'asset.dart';
|
||||
import 'base/config.dart';
|
||||
import 'base/context.dart';
|
||||
|
@ -16,6 +18,7 @@ import 'base/logger.dart';
|
|||
import 'base/net.dart';
|
||||
import 'base/os.dart';
|
||||
import 'build_info.dart';
|
||||
import 'build_system/tools/asset_transformer.dart';
|
||||
import 'build_system/tools/scene_importer.dart';
|
||||
import 'build_system/tools/shader_compiler.dart';
|
||||
import 'compile.dart';
|
||||
|
@ -454,6 +457,8 @@ class DevFS {
|
|||
required OperatingSystemUtils osUtils,
|
||||
required Logger logger,
|
||||
required FileSystem fileSystem,
|
||||
required ProcessManager processManager,
|
||||
required Artifacts artifacts,
|
||||
HttpClient? httpClient,
|
||||
Duration? uploadRetryThrottle,
|
||||
StopwatchFactory stopwatchFactory = const StopwatchFactory(),
|
||||
|
@ -471,7 +476,16 @@ class DevFS {
|
|||
? HttpClient()
|
||||
: context.get<HttpClientFactory>()!())),
|
||||
_stopwatchFactory = stopwatchFactory,
|
||||
_config = config;
|
||||
_config = config,
|
||||
_assetTransformer = DevelopmentAssetTransformer(
|
||||
transformer: AssetTransformer(
|
||||
processManager: processManager,
|
||||
fileSystem: fileSystem,
|
||||
dartBinaryPath: artifacts.getArtifactPath(Artifact.engineDartBinary),
|
||||
),
|
||||
fileSystem: fileSystem,
|
||||
logger: logger,
|
||||
);
|
||||
|
||||
final FlutterVmService _vmService;
|
||||
final _DevFSHttpWriter _httpWriter;
|
||||
|
@ -479,9 +493,10 @@ class DevFS {
|
|||
final FileSystem _fileSystem;
|
||||
final StopwatchFactory _stopwatchFactory;
|
||||
final Config? _config;
|
||||
final DevelopmentAssetTransformer _assetTransformer;
|
||||
|
||||
final String fsName;
|
||||
final Directory? rootDirectory;
|
||||
final Directory rootDirectory;
|
||||
final Set<String> assetPathsToEvict = <String>{};
|
||||
final Set<String> shaderPathsToEvict = <String>{};
|
||||
final Set<String> scenePathsToEvict = <String>{};
|
||||
|
@ -505,7 +520,7 @@ class DevFS {
|
|||
final String baseUriString = baseUri.toString();
|
||||
if (deviceUriString.startsWith(baseUriString)) {
|
||||
final String deviceUriSuffix = deviceUriString.substring(baseUriString.length);
|
||||
return rootDirectory!.uri.resolve(deviceUriSuffix);
|
||||
return rootDirectory.uri.resolve(deviceUriSuffix);
|
||||
}
|
||||
return deviceUri;
|
||||
}
|
||||
|
@ -600,7 +615,7 @@ class DevFS {
|
|||
invalidatedFiles,
|
||||
outputPath: dillOutputPath,
|
||||
fs: _fileSystem,
|
||||
projectRootPath: rootDirectory?.path,
|
||||
projectRootPath: rootDirectory.path,
|
||||
packageConfig: packageConfig,
|
||||
checkDartPluginRegistry: true, // The entry point is assumed not to have changed.
|
||||
dartPluginRegistrant: dartPluginRegistrant,
|
||||
|
@ -636,8 +651,8 @@ class DevFS {
|
|||
if (archivePath == _kFontManifest) {
|
||||
didUpdateFontManifest = true;
|
||||
}
|
||||
|
||||
switch (bundle.entries[archivePath]?.kind) {
|
||||
final AssetKind? kind = bundle.entries[archivePath]?.kind;
|
||||
switch (kind) {
|
||||
case AssetKind.shader:
|
||||
final Future<DevFSContent?> pending = shaderCompiler.recompileShader(entry.content);
|
||||
pendingAssetBuilds.add(pending);
|
||||
|
@ -672,11 +687,30 @@ class DevFS {
|
|||
case AssetKind.regular:
|
||||
case AssetKind.font:
|
||||
case null:
|
||||
dirtyEntries[deviceUri] = entry.content;
|
||||
syncedBytes += entry.content.size;
|
||||
if (!bundleFirstUpload) {
|
||||
assetPathsToEvict.add(archivePath);
|
||||
}
|
||||
final Future<DevFSContent?> pending = (() async {
|
||||
if (entry.transformers.isEmpty || kind != AssetKind.regular) {
|
||||
return entry.content;
|
||||
}
|
||||
return _assetTransformer.retransformAsset(
|
||||
inputAssetKey: archivePath,
|
||||
inputAssetContent: entry.content,
|
||||
transformerEntries: entry.transformers,
|
||||
workingDirectory: rootDirectory.path,
|
||||
);
|
||||
})();
|
||||
|
||||
pendingAssetBuilds.add(pending);
|
||||
pending.then((DevFSContent? content) {
|
||||
if (content == null) {
|
||||
assetBuildFailed = true;
|
||||
return;
|
||||
}
|
||||
dirtyEntries[deviceUri] = content;
|
||||
syncedBytes += content.size;
|
||||
if (!bundleFirstUpload) {
|
||||
assetPathsToEvict.add(archivePath);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -701,6 +701,7 @@ class WebDevFS implements DevFS {
|
|||
required this.nullSafetyMode,
|
||||
required this.ddcModuleSystem,
|
||||
required this.webRenderer,
|
||||
required this.rootDirectory,
|
||||
this.testMode = false,
|
||||
}) : _port = port;
|
||||
|
||||
|
@ -857,7 +858,7 @@ class WebDevFS implements DevFS {
|
|||
String get fsName => 'web_asset';
|
||||
|
||||
@override
|
||||
Directory? get rootDirectory => null;
|
||||
final Directory rootDirectory;
|
||||
|
||||
@override
|
||||
Future<UpdateFSReport> update({
|
||||
|
|
|
@ -311,6 +311,7 @@ Please provide a valid TCP port (an integer between 0 and 65535, inclusive).
|
|||
nativeNullAssertions: debuggingOptions.nativeNullAssertions,
|
||||
ddcModuleSystem: debuggingOptions.buildInfo.ddcModuleFormat == DdcModuleFormat.ddc,
|
||||
webRenderer: debuggingOptions.webRenderer,
|
||||
rootDirectory: fileSystem.directory(projectRootPath),
|
||||
);
|
||||
Uri url = await device!.devFS!.create();
|
||||
if (debuggingOptions.tlsCertKeyPath != null && debuggingOptions.tlsCertPath != null) {
|
||||
|
|
|
@ -385,6 +385,8 @@ class FlutterDevice {
|
|||
osUtils: globals.os,
|
||||
fileSystem: globals.fs,
|
||||
logger: globals.logger,
|
||||
processManager: globals.processManager,
|
||||
artifacts: globals.artifacts!,
|
||||
);
|
||||
return devFS!.create();
|
||||
}
|
||||
|
|
|
@ -74,6 +74,7 @@ void main() {
|
|||
expect(transformationFailure, isNull, reason: logger.errorText);
|
||||
expect(processManager, hasNoRemainingExpectations);
|
||||
expect(fileSystem.file(outputPath).readAsStringSync(), 'hello world');
|
||||
expect(fileSystem.directory('.tmp_rand0').listSync(), isEmpty, reason: 'Transformer did not clean up after itself.');
|
||||
});
|
||||
|
||||
testWithoutContext('logs useful error information when transformation process returns a nonzero exit code', () async {
|
||||
|
@ -138,6 +139,7 @@ stdout:
|
|||
Beginning transformation
|
||||
stderr:
|
||||
Something went wrong''');
|
||||
expect(fileSystem.directory('.tmp_rand0').listSync(), isEmpty, reason: 'Transformer did not clean up after itself.');
|
||||
});
|
||||
|
||||
testWithoutContext('prints error message when the transformer does not produce an output file', () async {
|
||||
|
@ -197,6 +199,7 @@ stdout:
|
|||
stderr:
|
||||
Transformation failed, but I forgot to exit with a non-zero code.'''
|
||||
);
|
||||
expect(fileSystem.directory('.tmp_rand0').listSync(), isEmpty, reason: 'Transformer did not clean up after itself.');
|
||||
});
|
||||
|
||||
testWithoutContext('correctly chains transformations when there are multiple of them', () async {
|
||||
|
@ -283,6 +286,7 @@ Transformation failed, but I forgot to exit with a non-zero code.'''
|
|||
expect(processManager, hasNoRemainingExpectations);
|
||||
expect(failure, isNull);
|
||||
expect(fileSystem.file(outputPath).readAsStringSync(), '012');
|
||||
expect(fileSystem.directory('.tmp_rand0').listSync(), isEmpty, reason: 'Transformer did not clean up after itself.');
|
||||
});
|
||||
|
||||
testWithoutContext('prints an error when a transformer in a chain (thats not the first) does not produce an output', () async {
|
||||
|
@ -368,6 +372,6 @@ 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);
|
||||
expect(fileSystem.directory('.tmp_rand0').listSync(), isEmpty, reason: 'Transformer did not clean up after itself.');
|
||||
});
|
||||
}
|
||||
|
|
|
@ -5,7 +5,9 @@
|
|||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io' as io show Process, ProcessSignal;
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:args/args.dart';
|
||||
import 'package:file/file.dart';
|
||||
import 'package:file/memory.dart';
|
||||
import 'package:file_testing/file_testing.dart';
|
||||
|
@ -29,6 +31,7 @@ import 'package:test/fake.dart';
|
|||
import '../src/common.dart';
|
||||
import '../src/context.dart';
|
||||
import '../src/fake_http_client.dart';
|
||||
import '../src/fake_process_manager.dart';
|
||||
import '../src/fake_vm_services.dart';
|
||||
import '../src/fakes.dart';
|
||||
import '../src/logging_logger.dart';
|
||||
|
@ -144,6 +147,8 @@ void main() {
|
|||
fileSystem: fileSystem,
|
||||
logger: BufferLogger.test(),
|
||||
httpClient: FakeHttpClient.any(),
|
||||
processManager: FakeProcessManager.empty(),
|
||||
artifacts: Artifacts.test(),
|
||||
);
|
||||
expect(() async => devFS.create(), throwsA(isA<DevFSException>()));
|
||||
});
|
||||
|
@ -167,6 +172,8 @@ void main() {
|
|||
fileSystem: fileSystem,
|
||||
logger: BufferLogger.test(),
|
||||
httpClient: FakeHttpClient.any(),
|
||||
processManager: FakeProcessManager.empty(),
|
||||
artifacts: Artifacts.test(),
|
||||
);
|
||||
|
||||
expect(await devFS.create(), isNotNull);
|
||||
|
@ -215,6 +222,8 @@ void main() {
|
|||
FakeRequest(Uri.parse('http://localhost'), method: HttpMethod.put, body: <int>[for (final List<int> chunk in expectedEncoded) ...chunk]),
|
||||
]),
|
||||
uploadRetryThrottle: Duration.zero,
|
||||
processManager: FakeProcessManager.empty(),
|
||||
artifacts: Artifacts.test(),
|
||||
);
|
||||
await devFS.create();
|
||||
|
||||
|
@ -248,6 +257,8 @@ void main() {
|
|||
logger: BufferLogger.test(),
|
||||
osUtils: FakeOperatingSystemUtils(),
|
||||
httpClient: FakeHttpClient.any(),
|
||||
processManager: FakeProcessManager.empty(),
|
||||
artifacts: Artifacts.test(),
|
||||
);
|
||||
|
||||
await devFS.create();
|
||||
|
@ -288,6 +299,8 @@ void main() {
|
|||
logger: BufferLogger.test(),
|
||||
osUtils: FakeOperatingSystemUtils(),
|
||||
httpClient: FakeHttpClient.any(),
|
||||
processManager: FakeProcessManager.empty(),
|
||||
artifacts: Artifacts.test(),
|
||||
);
|
||||
|
||||
await devFS.create();
|
||||
|
@ -330,6 +343,8 @@ void main() {
|
|||
logger: BufferLogger.test(),
|
||||
osUtils: FakeOperatingSystemUtils(),
|
||||
httpClient: HttpClient(),
|
||||
processManager: FakeProcessManager.empty(),
|
||||
artifacts: Artifacts.test(),
|
||||
);
|
||||
|
||||
await devFS.create();
|
||||
|
@ -379,6 +394,8 @@ void main() {
|
|||
logger: BufferLogger.test(),
|
||||
osUtils: FakeOperatingSystemUtils(),
|
||||
httpClient: FakeHttpClient.any(),
|
||||
processManager: FakeProcessManager.empty(),
|
||||
artifacts: Artifacts.test(),
|
||||
);
|
||||
|
||||
await devFS.create();
|
||||
|
@ -456,6 +473,8 @@ void main() {
|
|||
'compile': FakeStopwatch()..elapsed = const Duration(seconds: 3),
|
||||
'transfer': FakeStopwatch()..elapsed = const Duration(seconds: 5),
|
||||
}),
|
||||
processManager: FakeProcessManager.empty(),
|
||||
artifacts: Artifacts.test(),
|
||||
);
|
||||
|
||||
await devFS.create();
|
||||
|
@ -500,6 +519,8 @@ void main() {
|
|||
logger: logger,
|
||||
osUtils: FakeOperatingSystemUtils(),
|
||||
httpClient: FakeHttpClient.any(),
|
||||
processManager: FakeProcessManager.empty(),
|
||||
artifacts: Artifacts.test(),
|
||||
);
|
||||
|
||||
await devFS.create();
|
||||
|
@ -604,6 +625,8 @@ void main() {
|
|||
osUtils: FakeOperatingSystemUtils(),
|
||||
httpClient: FakeHttpClient.any(),
|
||||
config: Config.test(),
|
||||
processManager: FakeProcessManager.empty(),
|
||||
artifacts: Artifacts.test(),
|
||||
);
|
||||
|
||||
await devFS.create();
|
||||
|
@ -660,6 +683,8 @@ void main() {
|
|||
osUtils: FakeOperatingSystemUtils(),
|
||||
httpClient: FakeHttpClient.any(),
|
||||
config: Config.test(),
|
||||
processManager: FakeProcessManager.empty(),
|
||||
artifacts: Artifacts.test(),
|
||||
);
|
||||
|
||||
await devFS.create();
|
||||
|
@ -698,6 +723,191 @@ void main() {
|
|||
expect(devFS.didUpdateFontManifest, true);
|
||||
});
|
||||
});
|
||||
|
||||
group('Asset transformation', () {
|
||||
testWithoutContext('DevFS re-transforms assets with transformers during update', () async {
|
||||
final MemoryFileSystem fileSystem = MemoryFileSystem.test();
|
||||
final Artifacts artifacts = Artifacts.test();
|
||||
final FakeDevFSWriter devFSWriter = FakeDevFSWriter();
|
||||
final FakeProcessManager processManager = FakeProcessManager.list(
|
||||
<FakeCommand>[
|
||||
FakeCommand(
|
||||
command: <Pattern>[
|
||||
artifacts.getArtifactPath(Artifact.engineDartBinary),
|
||||
'run',
|
||||
'increment',
|
||||
'--input=/.tmp_rand0/retransformerInput-asset.txt-transformOutput0.txt',
|
||||
'--output=/.tmp_rand0/retransformerInput-asset.txt-transformOutput1.txt',
|
||||
],
|
||||
onRun: (List<String> command) {
|
||||
final ArgResults argParseResults = (ArgParser()
|
||||
..addOption('input', mandatory: true)
|
||||
..addOption('output', mandatory: true))
|
||||
.parse(command);
|
||||
|
||||
final File inputFile = fileSystem.file(argParseResults['input']);
|
||||
final File outputFile = fileSystem.file(argParseResults['output']);
|
||||
|
||||
expect(inputFile, exists);
|
||||
outputFile
|
||||
..createSync()
|
||||
..writeAsBytesSync(
|
||||
Uint8List.fromList(
|
||||
inputFile.readAsBytesSync().map((int b) => b + 1).toList(),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost(
|
||||
requests: <VmServiceExpectation>[createDevFSRequest],
|
||||
httpAddress: Uri.parse('http://localhost'),
|
||||
);
|
||||
final BufferLogger logger = BufferLogger.test();
|
||||
final DevFS devFS = DevFS(
|
||||
fakeVmServiceHost.vmService,
|
||||
'test',
|
||||
fileSystem.currentDirectory,
|
||||
fileSystem: fileSystem,
|
||||
logger: logger,
|
||||
osUtils: FakeOperatingSystemUtils(),
|
||||
httpClient: FakeHttpClient.any(),
|
||||
config: Config.test(),
|
||||
processManager: processManager,
|
||||
artifacts: artifacts,
|
||||
);
|
||||
|
||||
await devFS.create();
|
||||
|
||||
final FakeResidentCompiler residentCompiler = FakeResidentCompiler()
|
||||
..onRecompile = (Uri mainUri, List<Uri>? invalidatedFiles) async {
|
||||
fileSystem.file('lib/foo.dill')
|
||||
..createSync(recursive: true)
|
||||
..writeAsBytesSync(<int>[1, 2, 3, 4, 5]);
|
||||
return const CompilerOutput('lib/foo.dill', 0, <Uri>[]);
|
||||
};
|
||||
|
||||
final FakeBundle bundle = FakeBundle()
|
||||
..entries['asset.txt'] = AssetBundleEntry(
|
||||
DevFSByteContent(<int>[1, 2, 3, 4]),
|
||||
kind: AssetKind.regular,
|
||||
transformers: const <AssetTransformerEntry>[
|
||||
AssetTransformerEntry(package: 'increment', args: <String>[]),
|
||||
],
|
||||
);
|
||||
|
||||
final UpdateFSReport report = await devFS.update(
|
||||
mainUri: Uri.parse('lib/main.dart'),
|
||||
generator: residentCompiler,
|
||||
dillOutputPath: 'lib/foo.dill',
|
||||
pathToReload: 'lib/foo.txt.dill',
|
||||
trackWidgetCreation: false,
|
||||
invalidatedFiles: <Uri>[],
|
||||
packageConfig: PackageConfig.empty,
|
||||
devFSWriter: devFSWriter,
|
||||
shaderCompiler: const FakeShaderCompiler(),
|
||||
bundle: bundle,
|
||||
);
|
||||
|
||||
expect(processManager, hasNoRemainingExpectations);
|
||||
expect(report.success, true);
|
||||
expect(devFSWriter.entries, isNotNull);
|
||||
final Uri assetUri = Uri(path: 'build/flutter_assets/asset.txt');
|
||||
expect(devFSWriter.entries, contains(assetUri));
|
||||
expect(
|
||||
await devFSWriter.entries![assetUri]!.contentsAsBytes(),
|
||||
containsAllInOrder(<int>[2, 3, 4, 5]),
|
||||
);
|
||||
});
|
||||
|
||||
testWithoutContext('DevFS reports failure when asset transformation fails', () async {
|
||||
final MemoryFileSystem fileSystem = MemoryFileSystem.test();
|
||||
final Artifacts artifacts = Artifacts.test();
|
||||
final FakeDevFSWriter devFSWriter = FakeDevFSWriter();
|
||||
final FakeProcessManager processManager = FakeProcessManager.list(
|
||||
<FakeCommand>[
|
||||
FakeCommand(
|
||||
command: <Pattern>[
|
||||
artifacts.getArtifactPath(Artifact.engineDartBinary),
|
||||
'run',
|
||||
'increment',
|
||||
'--input=/.tmp_rand0/retransformerInput-asset.txt-transformOutput0.txt',
|
||||
'--output=/.tmp_rand0/retransformerInput-asset.txt-transformOutput1.txt',
|
||||
],
|
||||
exitCode: 1,
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost(
|
||||
requests: <VmServiceExpectation>[createDevFSRequest],
|
||||
httpAddress: Uri.parse('http://localhost'),
|
||||
);
|
||||
final BufferLogger logger = BufferLogger.test();
|
||||
final DevFS devFS = DevFS(
|
||||
fakeVmServiceHost.vmService,
|
||||
'test',
|
||||
fileSystem.currentDirectory,
|
||||
fileSystem: fileSystem,
|
||||
logger: logger,
|
||||
osUtils: FakeOperatingSystemUtils(),
|
||||
httpClient: FakeHttpClient.any(),
|
||||
config: Config.test(),
|
||||
processManager: processManager,
|
||||
artifacts: artifacts,
|
||||
);
|
||||
|
||||
await devFS.create();
|
||||
|
||||
final FakeResidentCompiler residentCompiler = FakeResidentCompiler()
|
||||
..onRecompile = (Uri mainUri, List<Uri>? invalidatedFiles) async {
|
||||
fileSystem.file('lib/foo.dill')
|
||||
..createSync(recursive: true)
|
||||
..writeAsBytesSync(<int>[1, 2, 3, 4, 5]);
|
||||
return const CompilerOutput('lib/foo.dill', 0, <Uri>[]);
|
||||
};
|
||||
|
||||
final FakeBundle bundle = FakeBundle()
|
||||
..entries['asset.txt'] = AssetBundleEntry(
|
||||
DevFSByteContent(<int>[1, 2, 3, 4]),
|
||||
kind: AssetKind.regular,
|
||||
transformers: const <AssetTransformerEntry>[
|
||||
AssetTransformerEntry(package: 'increment', args: <String>[]),
|
||||
],
|
||||
);
|
||||
|
||||
final UpdateFSReport report = await devFS.update(
|
||||
mainUri: Uri.parse('lib/main.dart'),
|
||||
generator: residentCompiler,
|
||||
dillOutputPath: 'lib/foo.dill',
|
||||
pathToReload: 'lib/foo.txt.dill',
|
||||
trackWidgetCreation: false,
|
||||
invalidatedFiles: <Uri>[],
|
||||
packageConfig: PackageConfig.empty,
|
||||
devFSWriter: devFSWriter,
|
||||
shaderCompiler: const FakeShaderCompiler(),
|
||||
bundle: bundle,
|
||||
);
|
||||
|
||||
expect(processManager, hasNoRemainingExpectations);
|
||||
expect(report.success, false, reason: 'DevFS update should fail since asset transformation failed.');
|
||||
expect(devFSWriter.entries, isNull, reason: 'DevFS should not have written anything since the update failed.');
|
||||
expect(
|
||||
logger.errorText,
|
||||
'User-defined transformation of asset "/.tmp_rand0/retransformerInput-asset.txt" failed.\n'
|
||||
'Transformer process terminated with non-zero exit code: 1\n'
|
||||
'Transformer package: increment\n'
|
||||
'Full command: Artifact.engineDartBinary run increment --input=/.tmp_rand0/retransformerInput-asset.txt-transformOutput0.txt --output=/.tmp_rand0/retransformerInput-asset.txt-transformOutput1.txt\n'
|
||||
'stdout:\n'
|
||||
'\n'
|
||||
'stderr:\n'
|
||||
'\n',
|
||||
);
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
class FakeResidentCompiler extends Fake implements ResidentCompiler {
|
||||
|
@ -723,10 +933,12 @@ class FakeResidentCompiler extends Fake implements ResidentCompiler {
|
|||
|
||||
class FakeDevFSWriter implements DevFSWriter {
|
||||
bool written = false;
|
||||
Map<Uri, DevFSContent>? entries;
|
||||
|
||||
@override
|
||||
Future<void> write(Map<Uri, DevFSContent> entries, Uri baseUri, DevFSWriter parent) async {
|
||||
written = true;
|
||||
this.entries = entries;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -909,6 +909,7 @@ void main() {
|
|||
nullSafetyMode: NullSafetyMode.unsound,
|
||||
ddcModuleSystem: usesDdcModuleSystem,
|
||||
webRenderer: WebRendererMode.html,
|
||||
rootDirectory: globals.fs.currentDirectory,
|
||||
);
|
||||
webDevFS.ddcModuleLoaderJS.createSync(recursive: true);
|
||||
webDevFS.flutterJs.createSync(recursive: true);
|
||||
|
@ -1044,6 +1045,7 @@ void main() {
|
|||
nullSafetyMode: NullSafetyMode.sound,
|
||||
ddcModuleSystem: usesDdcModuleSystem,
|
||||
webRenderer: WebRendererMode.html,
|
||||
rootDirectory: globals.fs.currentDirectory,
|
||||
);
|
||||
webDevFS.ddcModuleLoaderJS.createSync(recursive: true);
|
||||
webDevFS.flutterJs.createSync(recursive: true);
|
||||
|
@ -1179,6 +1181,7 @@ void main() {
|
|||
nullSafetyMode: NullSafetyMode.sound,
|
||||
ddcModuleSystem: usesDdcModuleSystem,
|
||||
webRenderer: WebRendererMode.canvaskit,
|
||||
rootDirectory: globals.fs.currentDirectory,
|
||||
);
|
||||
webDevFS.ddcModuleLoaderJS.createSync(recursive: true);
|
||||
webDevFS.stackTraceMapper.createSync(recursive: true);
|
||||
|
@ -1251,6 +1254,7 @@ void main() {
|
|||
nullSafetyMode: NullSafetyMode.sound,
|
||||
ddcModuleSystem: usesDdcModuleSystem,
|
||||
webRenderer: WebRendererMode.canvaskit,
|
||||
rootDirectory: globals.fs.currentDirectory,
|
||||
);
|
||||
webDevFS.ddcModuleLoaderJS.createSync(recursive: true);
|
||||
webDevFS.stackTraceMapper.createSync(recursive: true);
|
||||
|
@ -1299,6 +1303,7 @@ void main() {
|
|||
nullSafetyMode: NullSafetyMode.sound,
|
||||
ddcModuleSystem: usesDdcModuleSystem,
|
||||
webRenderer: WebRendererMode.canvaskit,
|
||||
rootDirectory: globals.fs.currentDirectory,
|
||||
);
|
||||
webDevFS.ddcModuleLoaderJS.createSync(recursive: true);
|
||||
webDevFS.stackTraceMapper.createSync(recursive: true);
|
||||
|
@ -1349,6 +1354,7 @@ void main() {
|
|||
nullSafetyMode: NullSafetyMode.sound,
|
||||
ddcModuleSystem: usesDdcModuleSystem,
|
||||
webRenderer: WebRendererMode.auto,
|
||||
rootDirectory: globals.fs.currentDirectory,
|
||||
);
|
||||
webDevFS.ddcModuleLoaderJS.createSync(recursive: true);
|
||||
webDevFS.stackTraceMapper.createSync(recursive: true);
|
||||
|
@ -1401,6 +1407,7 @@ void main() {
|
|||
nullSafetyMode: NullSafetyMode.unsound,
|
||||
ddcModuleSystem: usesDdcModuleSystem,
|
||||
webRenderer: WebRendererMode.canvaskit,
|
||||
rootDirectory: globals.fs.currentDirectory,
|
||||
);
|
||||
webDevFS.ddcModuleLoaderJS.createSync(recursive: true);
|
||||
webDevFS.stackTraceMapper.createSync(recursive: true);
|
||||
|
@ -1571,6 +1578,7 @@ void main() {
|
|||
nullSafetyMode: NullSafetyMode.unsound,
|
||||
ddcModuleSystem: usesDdcModuleSystem,
|
||||
webRenderer: WebRendererMode.canvaskit,
|
||||
rootDirectory: globals.fs.currentDirectory,
|
||||
);
|
||||
webDevFS.ddcModuleLoaderJS.createSync(recursive: true);
|
||||
webDevFS.stackTraceMapper.createSync(recursive: true);
|
||||
|
|
|
@ -697,6 +697,7 @@ void main() {
|
|||
nullSafetyMode: NullSafetyMode.unsound,
|
||||
ddcModuleSystem: usesDdcModuleSystem,
|
||||
webRenderer: WebRendererMode.html,
|
||||
rootDirectory: globals.fs.currentDirectory,
|
||||
);
|
||||
webDevFS.requireJS.createSync(recursive: true);
|
||||
webDevFS.flutterJs.createSync(recursive: true);
|
||||
|
@ -808,6 +809,7 @@ void main() {
|
|||
nullSafetyMode: NullSafetyMode.sound,
|
||||
ddcModuleSystem: usesDdcModuleSystem,
|
||||
webRenderer: WebRendererMode.html,
|
||||
rootDirectory: globals.fs.currentDirectory,
|
||||
);
|
||||
webDevFS.requireJS.createSync(recursive: true);
|
||||
webDevFS.flutterJs.createSync(recursive: true);
|
||||
|
@ -918,6 +920,7 @@ void main() {
|
|||
nullSafetyMode: NullSafetyMode.sound,
|
||||
ddcModuleSystem: usesDdcModuleSystem,
|
||||
webRenderer: WebRendererMode.canvaskit,
|
||||
rootDirectory: globals.fs.currentDirectory,
|
||||
);
|
||||
webDevFS.requireJS.createSync(recursive: true);
|
||||
webDevFS.stackTraceMapper.createSync(recursive: true);
|
||||
|
@ -981,6 +984,7 @@ void main() {
|
|||
nullSafetyMode: NullSafetyMode.sound,
|
||||
ddcModuleSystem: usesDdcModuleSystem,
|
||||
webRenderer: WebRendererMode.canvaskit,
|
||||
rootDirectory: globals.fs.currentDirectory,
|
||||
);
|
||||
webDevFS.requireJS.createSync(recursive: true);
|
||||
webDevFS.stackTraceMapper.createSync(recursive: true);
|
||||
|
@ -1028,6 +1032,7 @@ void main() {
|
|||
nullSafetyMode: NullSafetyMode.sound,
|
||||
ddcModuleSystem: usesDdcModuleSystem,
|
||||
webRenderer: WebRendererMode.canvaskit,
|
||||
rootDirectory: globals.fs.currentDirectory,
|
||||
);
|
||||
webDevFS.requireJS.createSync(recursive: true);
|
||||
webDevFS.stackTraceMapper.createSync(recursive: true);
|
||||
|
@ -1076,6 +1081,7 @@ void main() {
|
|||
nullSafetyMode: NullSafetyMode.sound,
|
||||
ddcModuleSystem: usesDdcModuleSystem,
|
||||
webRenderer: WebRendererMode.auto,
|
||||
rootDirectory: globals.fs.currentDirectory,
|
||||
);
|
||||
webDevFS.requireJS.createSync(recursive: true);
|
||||
webDevFS.stackTraceMapper.createSync(recursive: true);
|
||||
|
@ -1125,6 +1131,7 @@ void main() {
|
|||
nullSafetyMode: NullSafetyMode.unsound,
|
||||
ddcModuleSystem: usesDdcModuleSystem,
|
||||
webRenderer: WebRendererMode.canvaskit,
|
||||
rootDirectory: globals.fs.currentDirectory,
|
||||
);
|
||||
webDevFS.requireJS.createSync(recursive: true);
|
||||
webDevFS.stackTraceMapper.createSync(recursive: true);
|
||||
|
@ -1278,6 +1285,7 @@ void main() {
|
|||
nullSafetyMode: NullSafetyMode.unsound,
|
||||
ddcModuleSystem: usesDdcModuleSystem,
|
||||
webRenderer: WebRendererMode.canvaskit,
|
||||
rootDirectory: globals.fs.currentDirectory,
|
||||
);
|
||||
webDevFS.requireJS.createSync(recursive: true);
|
||||
webDevFS.stackTraceMapper.createSync(recursive: true);
|
||||
|
|
Loading…
Reference in a new issue