diff --git a/packages/flutter_tools/lib/src/build_system/targets/assets.dart b/packages/flutter_tools/lib/src/build_system/targets/assets.dart index 4ffc08b939..efd8b3339f 100644 --- a/packages/flutter_tools/lib/src/build_system/targets/assets.dart +++ b/packages/flutter_tools/lib/src/build_system/targets/assets.dart @@ -154,7 +154,8 @@ Future copyAssets( ); doCopy = false; if (failure != null) { - throwToolExit(failure.message); + throwToolExit('User-defined transformation of asset "${entry.key}" failed.\n' + '${failure.message}'); } } case AssetKind.font: diff --git a/packages/flutter_tools/lib/src/build_system/tools/asset_transformer.dart b/packages/flutter_tools/lib/src/build_system/tools/asset_transformer.dart index be56ea84ff..d8953707ff 100644 --- a/packages/flutter_tools/lib/src/build_system/tools/asset_transformer.dart +++ b/packages/flutter_tools/lib/src/build_system/tools/asset_transformer.dart @@ -55,16 +55,21 @@ final class AssetTransformer { required Logger logger, }) async { - String getTempFilePath(int transformStep) { + final Directory tempDirectory = _fileSystem.systemTempDirectory.createTempSync(); + + int transformStep = 0; + File nextTempFile() { final String basename = _fileSystem.path.basename(asset.path); final String ext = _fileSystem.path.extension(asset.path); - return '$basename-transformOutput$transformStep$ext'; + + final File result = tempDirectory.childFile('$basename-transformOutput$transformStep$ext'); + transformStep++; + return result; } - File tempInputFile = _fileSystem.systemTempDirectory.childFile(getTempFilePath(0)); + File tempInputFile = nextTempFile(); await asset.copy(tempInputFile.path); - File tempOutputFile = _fileSystem.systemTempDirectory.childFile(getTempFilePath(1)); - ErrorHandlingFileSystem.deleteIfExists(tempOutputFile); + File tempOutputFile = nextTempFile(); final Stopwatch stopwatch = Stopwatch()..start(); try { @@ -78,10 +83,7 @@ final class AssetTransformer { ); if (transformerFailure != null) { - return AssetTransformationFailure( - 'User-defined transformation of asset "${asset.path}" failed.\n' - '${transformerFailure.message}', - ); + return AssetTransformationFailure(transformerFailure.message); } ErrorHandlingFileSystem.deleteIfExists(tempInputFile); @@ -90,15 +92,13 @@ final class AssetTransformer { await tempOutputFile.copy(outputPath); } else { tempInputFile = tempOutputFile; - tempOutputFile = _fileSystem.systemTempDirectory.childFile(getTempFilePath(i+2)); - ErrorHandlingFileSystem.deleteIfExists(tempOutputFile); + tempOutputFile = nextTempFile(); } } logger.printTrace("Finished transforming asset at path '${asset.path}' (${stopwatch.elapsedMilliseconds}ms)"); } finally { - ErrorHandlingFileSystem.deleteIfExists(tempInputFile); - ErrorHandlingFileSystem.deleteIfExists(tempOutputFile); + ErrorHandlingFileSystem.deleteIfExists(tempDirectory, recursive: true); } return null; @@ -124,6 +124,11 @@ final class AssetTransformer { ...transformerArguments, ]; + // Delete the output file if it already exists for whatever reason. + // With this, we can check for the existence of the file after transformation + // to make sure the transformer produced an output file. + ErrorHandlingFileSystem.deleteIfExists(output); + logger.printTrace("Transforming asset using command '${command.join(' ')}'"); final ProcessResult result = await _processManager.run( command, diff --git a/packages/flutter_tools/lib/src/bundle_builder.dart b/packages/flutter_tools/lib/src/bundle_builder.dart index 302d023ef5..9e6b0605c7 100644 --- a/packages/flutter_tools/lib/src/bundle_builder.dart +++ b/packages/flutter_tools/lib/src/bundle_builder.dart @@ -213,7 +213,8 @@ Future writeBundle( ); doCopy = false; if (failure != null) { - throwToolExit(failure.message); + throwToolExit('User-defined transformation of asset "${entry.key}" failed.\n' + '${failure.message}'); } case AssetKind.font: break; diff --git a/packages/flutter_tools/test/general.shard/build_system/targets/asset_transformer_test.dart b/packages/flutter_tools/test/general.shard/build_system/targets/asset_transformer_test.dart index dc84653cf9..c8549e186d 100644 --- a/packages/flutter_tools/test/general.shard/build_system/targets/asset_transformer_test.dart +++ b/packages/flutter_tools/test/general.shard/build_system/targets/asset_transformer_test.dart @@ -31,8 +31,8 @@ void main() { artifacts.getArtifactPath(Artifact.engineDartBinary), 'run', 'my_copy_transformer', - '--input=/.tmp_rand0/asset.txt-transformOutput0.txt', - '--output=/.tmp_rand0/asset.txt-transformOutput1.txt', + '--input=/.tmp_rand0/rand0/asset.txt-transformOutput0.txt', + '--output=/.tmp_rand0/rand0/asset.txt-transformOutput1.txt', '-f', '--my_option', 'my_option_value', @@ -95,8 +95,8 @@ void main() { dartBinaryPath, 'run', 'my_copy_transformer', - '--input=/.tmp_rand0/asset.txt-transformOutput0.txt', - '--output=/.tmp_rand0/asset.txt-transformOutput1.txt', + '--input=/.tmp_rand0/rand0/asset.txt-transformOutput0.txt', + '--output=/.tmp_rand0/rand0/asset.txt-transformOutput1.txt', ], onRun: (List args) { final ArgResults parsedArgs = (ArgParser() @@ -136,10 +136,9 @@ void main() { 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 +Full command: $dartBinaryPath run my_copy_transformer --input=/.tmp_rand0/rand0/asset.txt-transformOutput0.txt --output=/.tmp_rand0/rand0/asset.txt-transformOutput1.txt stdout: Beginning transformation stderr: @@ -162,8 +161,8 @@ Something went wrong'''); dartBinaryPath, 'run', 'my_transformer', - '--input=/.tmp_rand0/asset.txt-transformOutput0.txt', - '--output=/.tmp_rand0/asset.txt-transformOutput1.txt', + '--input=/.tmp_rand0/rand0/asset.txt-transformOutput0.txt', + '--output=/.tmp_rand0/rand0/asset.txt-transformOutput1.txt', ], onRun: (_) { // Do nothing. @@ -196,11 +195,10 @@ Something went wrong'''); 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 +Input file provided to transformer: "/.tmp_rand0/rand0/asset.txt-transformOutput0.txt" +Expected output file at: "/.tmp_rand0/rand0/asset.txt-transformOutput1.txt" +Full command: $dartBinaryPath run my_transformer --input=/.tmp_rand0/rand0/asset.txt-transformOutput0.txt --output=/.tmp_rand0/rand0/asset.txt-transformOutput1.txt stdout: stderr: @@ -225,8 +223,8 @@ Transformation failed, but I forgot to exit with a non-zero code.''' dartBinaryPath, 'run', 'my_lowercase_transformer', - '--input=/.tmp_rand0/asset.txt-transformOutput0.txt', - '--output=/.tmp_rand0/asset.txt-transformOutput1.txt', + '--input=/.tmp_rand0/rand0/asset.txt-transformOutput0.txt', + '--output=/.tmp_rand0/rand0/asset.txt-transformOutput1.txt', ], onRun: (List args) { final ArgResults parsedArgs = (ArgParser() @@ -245,8 +243,8 @@ Transformation failed, but I forgot to exit with a non-zero code.''' dartBinaryPath, 'run', 'my_distance_from_ascii_a_transformer', - '--input=/.tmp_rand0/asset.txt-transformOutput1.txt', - '--output=/.tmp_rand0/asset.txt-transformOutput2.txt', + '--input=/.tmp_rand0/rand0/asset.txt-transformOutput1.txt', + '--output=/.tmp_rand0/rand0/asset.txt-transformOutput2.txt', ], onRun: (List args) { final ArgResults parsedArgs = (ArgParser() @@ -314,8 +312,8 @@ Transformation failed, but I forgot to exit with a non-zero code.''' dartBinaryPath, 'run', 'my_lowercase_transformer', - '--input=/.tmp_rand0/asset.txt-transformOutput0.txt', - '--output=/.tmp_rand0/asset.txt-transformOutput1.txt', + '--input=/.tmp_rand0/rand0/asset.txt-transformOutput0.txt', + '--output=/.tmp_rand0/rand0/asset.txt-transformOutput1.txt', ], onRun: (List args) { final ArgResults parsedArgs = (ArgParser() @@ -334,8 +332,8 @@ Transformation failed, but I forgot to exit with a non-zero code.''' dartBinaryPath, 'run', 'my_distance_from_ascii_a_transformer', - '--input=/.tmp_rand0/asset.txt-transformOutput1.txt', - '--output=/.tmp_rand0/asset.txt-transformOutput2.txt', + '--input=/.tmp_rand0/rand0/asset.txt-transformOutput1.txt', + '--output=/.tmp_rand0/rand0/asset.txt-transformOutput2.txt', ], onRun: (List args) { // Do nothing. @@ -374,11 +372,10 @@ Transformation failed, but I forgot to exit with a non-zero code.''' 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 +Input file provided to transformer: "/.tmp_rand0/rand0/asset.txt-transformOutput1.txt" +Expected output file at: "/.tmp_rand0/rand0/asset.txt-transformOutput2.txt" +Full command: Artifact.engineDartBinary run my_distance_from_ascii_a_transformer --input=/.tmp_rand0/rand0/asset.txt-transformOutput1.txt --output=/.tmp_rand0/rand0/asset.txt-transformOutput2.txt stdout: stderr: diff --git a/packages/flutter_tools/test/general.shard/build_system/targets/assets_test.dart b/packages/flutter_tools/test/general.shard/build_system/targets/assets_test.dart index 6d33eeec29..86e55bf52c 100644 --- a/packages/flutter_tools/test/general.shard/build_system/targets/assets_test.dart +++ b/packages/flutter_tools/test/general.shard/build_system/targets/assets_test.dart @@ -252,6 +252,7 @@ flutter: ), }); + testUsingContext('exits tool if an asset transformation fails', () async { Cache.flutterRoot = Cache.defaultFlutterRoot( platform: globals.platform, @@ -289,7 +290,7 @@ flutter: await expectToolExitLater( const CopyAssets().build(environment), - startsWith('User-defined transformation of asset "/input.txt" failed.\n'), + startsWith('User-defined transformation of asset "input.txt" failed.\n'), ); expect(globals.processManager, hasNoRemainingExpectations); }, overrides: { @@ -316,6 +317,90 @@ flutter: ), }); + testUsingContext('asset transformation, per each asset, uses unique paths for temporary files', () async { + final List inputFilePaths = []; + final List outputFilePaths = []; + + final FakeCommand transformerCommand = FakeCommand( + command: [ + Artifacts.test().getArtifactPath(Artifact.engineDartBinary), + 'run', + 'my_capitalizer_transformer', + RegExp('--input=.*'), + RegExp('--output=.*'), + ], + onRun: (List args) { + final ArgResults parsedArgs = (ArgParser() + ..addOption('input') + ..addOption('output')) + .parse(args); + + final String input = parsedArgs['input'] as String; + final String output = parsedArgs['output'] as String; + + inputFilePaths.add(input); + outputFilePaths.add(output); + + fileSystem.file(output) + ..createSync() + ..writeAsStringSync('foo'); + }, + ); + + Cache.flutterRoot = Cache.defaultFlutterRoot( + platform: globals.platform, + fileSystem: fileSystem, + userMessages: UserMessages(), + ); + + final Environment environment = Environment.test( + fileSystem.currentDirectory, + processManager: FakeProcessManager.list( + [ + transformerCommand, + transformerCommand, + ], + ), + artifacts: Artifacts.test(), + fileSystem: fileSystem, + logger: logger, + platform: globals.platform, + defines: { + kBuildMode: BuildMode.debug.cliName, + }, + ); + + await fileSystem.file('.packages').create(); + + fileSystem.file('pubspec.yaml') + ..createSync() + ..writeAsStringSync(''' + name: example + flutter: + assets: + - path: input.txt + transformers: + - package: my_capitalizer_transformer + '''); + + fileSystem.file('input.txt') + ..createSync(recursive: true) + ..writeAsStringSync('abc'); + + fileSystem.directory('2x').childFile('input.txt') + ..createSync(recursive: true) + ..writeAsStringSync('def'); + + await const CopyAssets().build(environment); + + expect(inputFilePaths.toSet(), hasLength(inputFilePaths.length)); + expect(outputFilePaths.toSet(), hasLength(outputFilePaths.length)); + }, overrides: { + Logger: () => logger, + FileSystem: () => fileSystem, + Platform: () => FakePlatform(), + ProcessManager: () => FakeProcessManager.empty(), + }); testUsingContext('Throws exception if pubspec contains missing files', () async { fileSystem.file('pubspec.yaml') diff --git a/packages/flutter_tools/test/general.shard/bundle_builder_test.dart b/packages/flutter_tools/test/general.shard/bundle_builder_test.dart index 53dac1a0b1..281e50e0f3 100644 --- a/packages/flutter_tools/test/general.shard/bundle_builder_test.dart +++ b/packages/flutter_tools/test/general.shard/bundle_builder_test.dart @@ -71,8 +71,8 @@ void main() { artifacts.getArtifactPath(Artifact.engineDartBinary), 'run', 'increment', - '--input=/.tmp_rand0/my-asset.txt-transformOutput0.txt', - '--output=/.tmp_rand0/my-asset.txt-transformOutput1.txt' + '--input=/.tmp_rand0/rand0/my-asset.txt-transformOutput0.txt', + '--output=/.tmp_rand0/rand0/my-asset.txt-transformOutput1.txt' ], onRun: (List command) { final ArgResults argParseResults = (ArgParser() diff --git a/packages/flutter_tools/test/general.shard/devfs_test.dart b/packages/flutter_tools/test/general.shard/devfs_test.dart index 98b9078fac..ddff7c6ec9 100644 --- a/packages/flutter_tools/test/general.shard/devfs_test.dart +++ b/packages/flutter_tools/test/general.shard/devfs_test.dart @@ -733,8 +733,8 @@ void main() { artifacts.getArtifactPath(Artifact.engineDartBinary), 'run', 'increment', - '--input=/.tmp_rand0/retransformerInput-asset.txt-transformOutput0.txt', - '--output=/.tmp_rand0/retransformerInput-asset.txt-transformOutput1.txt', + '--input=/.tmp_rand0/rand0/retransformerInput-asset.txt-transformOutput0.txt', + '--output=/.tmp_rand0/rand0/retransformerInput-asset.txt-transformOutput1.txt', ], onRun: (List command) { final ArgResults argParseResults = (ArgParser() @@ -831,8 +831,8 @@ void main() { artifacts.getArtifactPath(Artifact.engineDartBinary), 'run', 'increment', - '--input=/.tmp_rand0/retransformerInput-asset.txt-transformOutput0.txt', - '--output=/.tmp_rand0/retransformerInput-asset.txt-transformOutput1.txt', + '--input=/.tmp_rand0/rand0/retransformerInput-asset.txt-transformOutput0.txt', + '--output=/.tmp_rand0/rand0/retransformerInput-asset.txt-transformOutput1.txt', ], exitCode: 1, ), @@ -895,10 +895,9 @@ void main() { 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' + 'Full command: Artifact.engineDartBinary run increment --input=/.tmp_rand0/rand0/retransformerInput-asset.txt-transformOutput0.txt --output=/.tmp_rand0/rand0/retransformerInput-asset.txt-transformOutput1.txt\n' 'stdout:\n' '\n' 'stderr:\n'