| // 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:collection/collection.dart' show IterableExtension; |
| 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/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_system/build_system.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/cache.dart'; |
| import 'package:flutter_tools/src/convert.dart'; |
| import 'package:flutter_tools/src/devfs.dart'; |
| import 'package:flutter_tools/src/globals.dart' as globals; |
| |
| import '../../../src/common.dart'; |
| import '../../../src/context.dart'; |
| import '../../../src/fake_process_manager.dart'; |
| |
| void main() { |
| late Environment environment; |
| late FileSystem fileSystem; |
| late BufferLogger logger; |
| |
| setUp(() { |
| fileSystem = MemoryFileSystem.test(); |
| environment = Environment.test( |
| fileSystem.currentDirectory, |
| processManager: FakeProcessManager.any(), |
| artifacts: Artifacts.test(), |
| fileSystem: fileSystem, |
| logger: BufferLogger.test(), |
| platform: FakePlatform(), |
| defines: <String, String>{ |
| kBuildMode: BuildMode.debug.cliName, |
| }, |
| ); |
| fileSystem.file(environment.buildDir.childFile('app.dill')).createSync(recursive: true); |
| fileSystem.file('packages/flutter_tools/lib/src/build_system/targets/assets.dart') |
| .createSync(recursive: true); |
| fileSystem.file('assets/foo/bar.png') |
| .createSync(recursive: true); |
| fileSystem.file('assets/wildcard/#bar.png') |
| .createSync(recursive: true); |
| fileSystem.file('.packages') |
| .createSync(); |
| fileSystem.file('pubspec.yaml') |
| ..createSync() |
| ..writeAsStringSync(''' |
| name: example |
| |
| flutter: |
| assets: |
| - assets/foo/bar.png |
| - assets/wildcard/ |
| '''); |
| logger = BufferLogger.test(); |
| }); |
| |
| testUsingContext('includes LICENSE file inputs in dependencies', () async { |
| fileSystem.file('.packages') |
| .writeAsStringSync('foo:file:///bar/lib'); |
| fileSystem.file('bar/LICENSE') |
| ..createSync(recursive: true) |
| ..writeAsStringSync('THIS IS A LICENSE'); |
| |
| await const CopyAssets().build(environment); |
| |
| final File depfile = environment.buildDir.childFile('flutter_assets.d'); |
| |
| expect(depfile, exists); |
| |
| final Depfile dependencies = environment.depFileService.parse(depfile); |
| |
| expect( |
| dependencies.inputs.firstWhereOrNull((File file) => file.path == '/bar/LICENSE'), |
| isNotNull, |
| ); |
| }, overrides: <Type, Generator>{ |
| FileSystem: () => fileSystem, |
| ProcessManager: () => FakeProcessManager.any(), |
| }); |
| |
| testUsingContext('Copies files to correct asset directory', () async { |
| await const CopyAssets().build(environment); |
| |
| expect(fileSystem.file('${environment.buildDir.path}/flutter_assets/AssetManifest.json'), exists); |
| expect(fileSystem.file('${environment.buildDir.path}/flutter_assets/FontManifest.json'), exists); |
| expect(fileSystem.file('${environment.buildDir.path}/flutter_assets/NOTICES.Z'), exists); |
| // See https://github.com/flutter/flutter/issues/35293 |
| expect(fileSystem.file('${environment.buildDir.path}/flutter_assets/assets/foo/bar.png'), exists); |
| // See https://github.com/flutter/flutter/issues/46163 |
| expect(fileSystem.file('${environment.buildDir.path}/flutter_assets/assets/wildcard/%23bar.png'), exists); |
| }, overrides: <Type, Generator>{ |
| FileSystem: () => fileSystem, |
| ProcessManager: () => FakeProcessManager.any(), |
| }); |
| |
| group("Only copies assets with a flavor if the assets' flavor matches the flavor in the environment", () { |
| testUsingContext('When the environment does not have a flavor defined', () async { |
| fileSystem.file('pubspec.yaml') |
| ..createSync() |
| ..writeAsStringSync(''' |
| name: example |
| flutter: |
| assets: |
| - assets/common/ |
| - path: assets/vanilla/ |
| flavors: |
| - vanilla |
| - path: assets/strawberry/ |
| flavors: |
| - strawberry |
| '''); |
| |
| fileSystem.file('assets/common/image.png').createSync(recursive: true); |
| fileSystem.file('assets/vanilla/ice-cream.png').createSync(recursive: true); |
| fileSystem.file('assets/strawberry/ice-cream.png').createSync(recursive: true); |
| |
| await const CopyAssets().build(environment); |
| |
| expect(fileSystem.file('${environment.buildDir.path}/flutter_assets/assets/common/image.png'), exists); |
| expect(fileSystem.file('${environment.buildDir.path}/flutter_assets/assets/vanilla/ice-cream.png'), isNot(exists)); |
| expect(fileSystem.file('${environment.buildDir.path}/flutter_assets/assets/strawberry/ice-cream.png'), isNot(exists)); |
| }, overrides: <Type, Generator>{ |
| FileSystem: () => fileSystem, |
| ProcessManager: () => FakeProcessManager.any(), |
| }); |
| |
| testUsingContext('When the environment has a flavor defined', () async { |
| environment.defines[kFlavor] = 'strawberry'; |
| fileSystem.file('pubspec.yaml') |
| ..createSync() |
| ..writeAsStringSync(''' |
| name: example |
| flutter: |
| assets: |
| - assets/common/ |
| - path: assets/vanilla/ |
| flavors: |
| - vanilla |
| - path: assets/strawberry/ |
| flavors: |
| - strawberry |
| '''); |
| |
| fileSystem.file('assets/common/image.png').createSync(recursive: true); |
| fileSystem.file('assets/vanilla/ice-cream.png').createSync(recursive: true); |
| fileSystem.file('assets/strawberry/ice-cream.png').createSync(recursive: true); |
| |
| await const CopyAssets().build(environment); |
| |
| expect(fileSystem.file('${environment.buildDir.path}/flutter_assets/assets/common/image.png'), exists); |
| expect(fileSystem.file('${environment.buildDir.path}/flutter_assets/assets/vanilla/ice-cream.png'), isNot(exists)); |
| expect(fileSystem.file('${environment.buildDir.path}/flutter_assets/assets/strawberry/ice-cream.png'), exists); |
| }, overrides: <Type, Generator>{ |
| FileSystem: () => fileSystem, |
| ProcessManager: () => FakeProcessManager.any(), |
| }); |
| }); |
| |
| 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>{ |
| 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 |
| 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>{ |
| 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_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 { |
| fileSystem.file('pubspec.yaml') |
| ..createSync() |
| ..writeAsStringSync(''' |
| name: example |
| |
| flutter: |
| assets: |
| - assets/foo/bar2.png |
| |
| '''); |
| |
| expect(() async => const CopyAssets().build(environment), throwsException); |
| }, overrides: <Type, Generator>{ |
| FileSystem: () => fileSystem, |
| ProcessManager: () => FakeProcessManager.any(), |
| }); |
| |
| testWithoutContext('processSkSLBundle returns null if there is no path ' |
| 'to the bundle', () { |
| |
| expect(processSkSLBundle( |
| null, |
| targetPlatform: TargetPlatform.android, |
| fileSystem: MemoryFileSystem.test(), |
| logger: logger, |
| ), isNull); |
| }); |
| |
| testWithoutContext('processSkSLBundle throws exception if bundle file is ' |
| 'missing', () { |
| |
| expect(() => processSkSLBundle( |
| 'does_not_exist.sksl', |
| targetPlatform: TargetPlatform.android, |
| fileSystem: MemoryFileSystem.test(), |
| logger: logger, |
| ), throwsException); |
| }); |
| |
| testWithoutContext('processSkSLBundle throws exception if the bundle is not ' |
| 'valid JSON', () { |
| |
| final FileSystem fileSystem = MemoryFileSystem.test(); |
| final BufferLogger logger = BufferLogger.test(); |
| fileSystem.file('bundle.sksl').writeAsStringSync('{'); |
| |
| expect(() => processSkSLBundle( |
| 'bundle.sksl', |
| targetPlatform: TargetPlatform.android, |
| fileSystem: fileSystem, |
| logger: logger, |
| ), throwsException); |
| expect(logger.errorText, contains('was not a JSON object')); |
| }); |
| |
| testWithoutContext('processSkSLBundle throws exception if the bundle is not ' |
| 'a JSON object', () { |
| |
| final FileSystem fileSystem = MemoryFileSystem.test(); |
| final BufferLogger logger = BufferLogger.test(); |
| fileSystem.file('bundle.sksl').writeAsStringSync('[]'); |
| |
| expect(() => processSkSLBundle( |
| 'bundle.sksl', |
| targetPlatform: TargetPlatform.android, |
| fileSystem: fileSystem, |
| logger: logger, |
| ), throwsException); |
| expect(logger.errorText, contains('was not a JSON object')); |
| }); |
| |
| testWithoutContext('processSkSLBundle throws an exception if the engine ' |
| 'revision is different', () { |
| |
| final FileSystem fileSystem = MemoryFileSystem.test(); |
| final BufferLogger logger = BufferLogger.test(); |
| fileSystem.file('bundle.sksl').writeAsStringSync(json.encode( |
| <String, String>{ |
| 'engineRevision': '1', |
| }, |
| )); |
| |
| expect(() => processSkSLBundle( |
| 'bundle.sksl', |
| targetPlatform: TargetPlatform.android, |
| fileSystem: fileSystem, |
| logger: logger, |
| engineVersion: '2', |
| ), throwsException); |
| expect(logger.errorText, contains('Expected Flutter 1, but found 2')); |
| }); |
| |
| testWithoutContext('processSkSLBundle warns if the bundle target platform is ' |
| 'different from the current target', () async { |
| |
| final FileSystem fileSystem = MemoryFileSystem.test(); |
| final BufferLogger logger = BufferLogger.test(); |
| fileSystem.file('bundle.sksl').writeAsStringSync(json.encode( |
| <String, Object>{ |
| 'engineRevision': '2', |
| 'platform': 'fuchsia-arm64', |
| 'data': <String, Object>{}, |
| } |
| )); |
| |
| final DevFSContent content = processSkSLBundle( |
| 'bundle.sksl', |
| targetPlatform: TargetPlatform.android, |
| fileSystem: fileSystem, |
| logger: logger, |
| engineVersion: '2', |
| )!; |
| |
| expect(await content.contentsAsBytes(), utf8.encode('{"data":{}}')); |
| expect(logger.errorText, contains('This may lead to less efficient shader caching')); |
| }); |
| |
| testWithoutContext('processSkSLBundle does not warn and produces bundle', () async { |
| |
| final FileSystem fileSystem = MemoryFileSystem.test(); |
| final BufferLogger logger = BufferLogger.test(); |
| fileSystem.file('bundle.sksl').writeAsStringSync(json.encode( |
| <String, Object>{ |
| 'engineRevision': '2', |
| 'platform': 'android', |
| 'data': <String, Object>{}, |
| }, |
| )); |
| |
| final DevFSContent content = processSkSLBundle( |
| 'bundle.sksl', |
| targetPlatform: TargetPlatform.android, |
| fileSystem: fileSystem, |
| logger: logger, |
| engineVersion: '2', |
| )!; |
| |
| expect(await content.contentsAsBytes(), utf8.encode('{"data":{}}')); |
| expect(logger.errorText, isEmpty); |
| }); |
| } |