| // 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: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/utils.dart'; |
| import 'package:flutter_tools/src/build_system/build_system.dart'; |
| import 'package:flutter_tools/src/build_system/exceptions.dart'; |
| import 'package:flutter_tools/src/convert.dart'; |
| |
| import '../../src/common.dart'; |
| import '../../src/context.dart'; |
| |
| void main() { |
| FileSystem fileSystem; |
| Environment environment; |
| Target fooTarget; |
| Target barTarget; |
| Target fizzTarget; |
| Target sharedTarget; |
| int fooInvocations; |
| int barInvocations; |
| int shared; |
| |
| setUp(() { |
| fileSystem = MemoryFileSystem.test(); |
| fooInvocations = 0; |
| barInvocations = 0; |
| shared = 0; |
| |
| /// Create various test targets. |
| fooTarget = TestTarget((Environment environment) async { |
| environment |
| .buildDir |
| .childFile('out') |
| ..createSync(recursive: true) |
| ..writeAsStringSync('hey'); |
| fooInvocations++; |
| }) |
| ..name = 'foo' |
| ..inputs = const <Source>[ |
| Source.pattern('{PROJECT_DIR}/foo.dart'), |
| ] |
| ..outputs = const <Source>[ |
| Source.pattern('{BUILD_DIR}/out'), |
| ] |
| ..dependencies = <Target>[]; |
| barTarget = TestTarget((Environment environment) async { |
| environment.buildDir |
| .childFile('bar') |
| ..createSync(recursive: true) |
| ..writeAsStringSync('there'); |
| barInvocations++; |
| }) |
| ..name = 'bar' |
| ..inputs = const <Source>[ |
| Source.pattern('{BUILD_DIR}/out'), |
| ] |
| ..outputs = const <Source>[ |
| Source.pattern('{BUILD_DIR}/bar'), |
| ] |
| ..dependencies = <Target>[]; |
| fizzTarget = TestTarget((Environment environment) async { |
| throw Exception('something bad happens'); |
| }) |
| ..name = 'fizz' |
| ..inputs = const <Source>[ |
| Source.pattern('{BUILD_DIR}/out'), |
| ] |
| ..outputs = const <Source>[ |
| Source.pattern('{BUILD_DIR}/fizz'), |
| ] |
| ..dependencies = <Target>[fooTarget]; |
| sharedTarget = TestTarget((Environment environment) async { |
| shared += 1; |
| }) |
| ..name = 'shared' |
| ..inputs = const <Source>[ |
| Source.pattern('{PROJECT_DIR}/foo.dart'), |
| ]; |
| final Artifacts artifacts = Artifacts.test(); |
| environment = Environment.test( |
| fileSystem.currentDirectory, |
| artifacts: artifacts, |
| processManager: FakeProcessManager.any(), |
| fileSystem: fileSystem, |
| logger: BufferLogger.test(), |
| ); |
| fileSystem.file('foo.dart') |
| ..createSync(recursive: true) |
| ..writeAsStringSync(''); |
| fileSystem.file('pubspec.yaml').createSync(); |
| }); |
| |
| testWithoutContext('Does not throw exception if asked to build with missing inputs', () async { |
| final BuildSystem buildSystem = setUpBuildSystem(fileSystem); |
| |
| // Delete required input file. |
| fileSystem.file('foo.dart').deleteSync(); |
| final BuildResult buildResult = await buildSystem.build(fooTarget, environment); |
| |
| expect(buildResult.hasException, false); |
| }); |
| |
| testWithoutContext('Does not throw exception if it does not produce a specified output', () async { |
| final BuildSystem buildSystem = setUpBuildSystem(fileSystem); |
| |
| // This target is document as producing foo.dart but does not actually |
| // output this value. |
| final Target badTarget = TestTarget((Environment environment) async {}) |
| ..inputs = const <Source>[ |
| Source.pattern('{PROJECT_DIR}/foo.dart'), |
| ] |
| ..outputs = const <Source>[ |
| Source.pattern('{BUILD_DIR}/out'), |
| ]; |
| final BuildResult result = await buildSystem.build(badTarget, environment); |
| |
| expect(result.hasException, false); |
| }); |
| |
| testWithoutContext('Saves a stamp file with inputs and outputs', () async { |
| final BuildSystem buildSystem = setUpBuildSystem(fileSystem); |
| await buildSystem.build(fooTarget, environment); |
| final File stampFile = fileSystem.file( |
| '${environment.buildDir.path}/foo.stamp'); |
| |
| expect(stampFile, exists); |
| |
| final Map<String, dynamic> stampContents = castStringKeyedMap( |
| json.decode(stampFile.readAsStringSync())); |
| |
| expect(stampContents, containsPair('inputs', <Object>['/foo.dart'])); |
| }); |
| |
| testWithoutContext('Creates a BuildResult with inputs and outputs', () async { |
| final BuildSystem buildSystem = setUpBuildSystem(fileSystem); |
| final BuildResult result = await buildSystem.build(fooTarget, environment); |
| |
| expect(result.inputFiles.single.path, '/foo.dart'); |
| expect(result.outputFiles.single.path, '${environment.buildDir.path}/out'); |
| }); |
| |
| testWithoutContext('Does not re-invoke build if stamp is valid', () async { |
| final BuildSystem buildSystem = setUpBuildSystem(fileSystem); |
| |
| await buildSystem.build(fooTarget, environment); |
| await buildSystem.build(fooTarget, environment); |
| |
| expect(fooInvocations, 1); |
| }); |
| |
| testWithoutContext('Re-invoke build if input is modified', () async { |
| final BuildSystem buildSystem = setUpBuildSystem(fileSystem); |
| await buildSystem.build(fooTarget, environment); |
| |
| fileSystem.file('foo.dart').writeAsStringSync('new contents'); |
| |
| await buildSystem.build(fooTarget, environment); |
| |
| expect(fooInvocations, 2); |
| }); |
| |
| testWithoutContext('does not re-invoke build if input timestamp changes', () async { |
| final BuildSystem buildSystem = setUpBuildSystem(fileSystem); |
| await buildSystem.build(fooTarget, environment); |
| |
| // The file was previously empty so this does not modify it. |
| fileSystem.file('foo.dart').writeAsStringSync(''); |
| await buildSystem.build(fooTarget, environment); |
| |
| expect(fooInvocations, 1); |
| }); |
| |
| testWithoutContext('does not re-invoke build if output timestamp changes', () async { |
| final BuildSystem buildSystem = setUpBuildSystem(fileSystem); |
| await buildSystem.build(fooTarget, environment); |
| |
| // This is the same content that the output file previously |
| // contained. |
| environment.buildDir.childFile('out').writeAsStringSync('hey'); |
| await buildSystem.build(fooTarget, environment); |
| |
| expect(fooInvocations, 1); |
| }); |
| |
| |
| testWithoutContext('Re-invoke build if output is modified', () async { |
| final BuildSystem buildSystem = setUpBuildSystem(fileSystem); |
| await buildSystem.build(fooTarget, environment); |
| |
| environment.buildDir.childFile('out').writeAsStringSync('Something different'); |
| |
| await buildSystem.build(fooTarget, environment); |
| |
| expect(fooInvocations, 2); |
| }); |
| |
| testWithoutContext('Runs dependencies of targets', () async { |
| final BuildSystem buildSystem = setUpBuildSystem(fileSystem); |
| barTarget.dependencies.add(fooTarget); |
| |
| await buildSystem.build(barTarget, environment); |
| |
| expect(fileSystem.file('${environment.buildDir.path}/bar'), exists); |
| expect(fooInvocations, 1); |
| expect(barInvocations, 1); |
| }); |
| |
| testWithoutContext('Only invokes shared dependencies once', () async { |
| final BuildSystem buildSystem = setUpBuildSystem(fileSystem); |
| fooTarget.dependencies.add(sharedTarget); |
| barTarget.dependencies.add(sharedTarget); |
| barTarget.dependencies.add(fooTarget); |
| |
| await buildSystem.build(barTarget, environment); |
| |
| expect(shared, 1); |
| }); |
| |
| testWithoutContext('Automatically cleans old outputs when build graph changes', () async { |
| final BuildSystem buildSystem = setUpBuildSystem(fileSystem); |
| final TestTarget testTarget = TestTarget((Environment envionment) async { |
| environment.buildDir.childFile('foo.out').createSync(); |
| }) |
| ..inputs = const <Source>[Source.pattern('{PROJECT_DIR}/foo.dart')] |
| ..outputs = const <Source>[Source.pattern('{BUILD_DIR}/foo.out')]; |
| fileSystem.file('foo.dart').createSync(); |
| |
| await buildSystem.build(testTarget, environment); |
| |
| expect(environment.buildDir.childFile('foo.out'), exists); |
| |
| final TestTarget testTarget2 = TestTarget((Environment envionment) async { |
| environment.buildDir.childFile('bar.out').createSync(); |
| }) |
| ..inputs = const <Source>[Source.pattern('{PROJECT_DIR}/foo.dart')] |
| ..outputs = const <Source>[Source.pattern('{BUILD_DIR}/bar.out')]; |
| |
| await buildSystem.build(testTarget2, environment); |
| |
| expect(environment.buildDir.childFile('bar.out'), exists); |
| expect(environment.buildDir.childFile('foo.out'), isNot(exists)); |
| }); |
| |
| testWithoutContext('Does not crash when filesytem and cache are out of sync', () async { |
| final BuildSystem buildSystem = setUpBuildSystem(fileSystem); |
| final TestTarget testWithoutContextTarget = TestTarget((Environment environment) async { |
| environment.buildDir.childFile('foo.out').createSync(); |
| }) |
| ..inputs = const <Source>[Source.pattern('{PROJECT_DIR}/foo.dart')] |
| ..outputs = const <Source>[Source.pattern('{BUILD_DIR}/foo.out')]; |
| fileSystem.file('foo.dart').createSync(); |
| |
| await buildSystem.build(testWithoutContextTarget, environment); |
| |
| expect(environment.buildDir.childFile('foo.out'), exists); |
| environment.buildDir.childFile('foo.out').deleteSync(); |
| |
| final TestTarget testWithoutContextTarget2 = TestTarget((Environment environment) async { |
| environment.buildDir.childFile('bar.out').createSync(); |
| }) |
| ..inputs = const <Source>[Source.pattern('{PROJECT_DIR}/foo.dart')] |
| ..outputs = const <Source>[Source.pattern('{BUILD_DIR}/bar.out')]; |
| |
| await buildSystem.build(testWithoutContextTarget2, environment); |
| |
| expect(environment.buildDir.childFile('bar.out'), exists); |
| expect(environment.buildDir.childFile('foo.out'), isNot(exists)); |
| }); |
| |
| testWithoutContext('Reruns build if stamp is corrupted', () async { |
| final BuildSystem buildSystem = setUpBuildSystem(fileSystem); |
| final TestTarget testWithoutContextTarget = TestTarget((Environment envionment) async { |
| environment.buildDir.childFile('foo.out').createSync(); |
| }) |
| ..inputs = const <Source>[Source.pattern('{PROJECT_DIR}/foo.dart')] |
| ..outputs = const <Source>[Source.pattern('{BUILD_DIR}/foo.out')]; |
| fileSystem.file('foo.dart').createSync(); |
| await buildSystem.build(testWithoutContextTarget, environment); |
| |
| // invalid JSON |
| environment.buildDir.childFile('testWithoutContext.stamp').writeAsStringSync('{X'); |
| await buildSystem.build(testWithoutContextTarget, environment); |
| |
| // empty file |
| environment.buildDir.childFile('testWithoutContext.stamp').writeAsStringSync(''); |
| await buildSystem.build(testWithoutContextTarget, environment); |
| |
| // invalid format |
| environment.buildDir.childFile('testWithoutContext.stamp').writeAsStringSync('{"inputs": 2, "outputs": 3}'); |
| await buildSystem.build(testWithoutContextTarget, environment); |
| }); |
| |
| |
| testWithoutContext('handles a throwing build action without crashing', () async { |
| final BuildSystem buildSystem = setUpBuildSystem(fileSystem); |
| final BuildResult result = await buildSystem.build(fizzTarget, environment); |
| |
| expect(result.hasException, true); |
| }); |
| |
| testWithoutContext('Can describe itself with JSON output', () { |
| environment.buildDir.createSync(recursive: true); |
| |
| expect(fooTarget.toJson(environment), <String, dynamic>{ |
| 'inputs': <Object>[ |
| '/foo.dart', |
| ], |
| 'outputs': <Object>[ |
| fileSystem.path.join(environment.buildDir.path, 'out'), |
| ], |
| 'dependencies': <Object>[], |
| 'name': 'foo', |
| 'stamp': fileSystem.path.join(environment.buildDir.path, 'foo.stamp'), |
| }); |
| }); |
| |
| testWithoutContext('Can find dependency cycles', () { |
| final Target barTarget = TestTarget()..name = 'bar'; |
| final Target fooTarget = TestTarget()..name = 'foo'; |
| barTarget.dependencies.add(fooTarget); |
| fooTarget.dependencies.add(barTarget); |
| |
| expect(() => checkCycles(barTarget), throwsA(isA<CycleException>())); |
| }); |
| |
| testWithoutContext('Target with depfile dependency will not run twice without invalidation', () async { |
| final BuildSystem buildSystem = setUpBuildSystem(fileSystem); |
| int called = 0; |
| final TestTarget target = TestTarget((Environment environment) async { |
| environment.buildDir |
| .childFile('example.d') |
| .writeAsStringSync('a.txt: b.txt'); |
| fileSystem.file('a.txt').writeAsStringSync('a'); |
| called += 1; |
| }) |
| ..depfiles = <String>['example.d']; |
| fileSystem.file('b.txt').writeAsStringSync('b'); |
| |
| await buildSystem.build(target, environment); |
| |
| expect(fileSystem.file('a.txt'), exists); |
| expect(called, 1); |
| |
| // Second build is up to date due to depfile parse. |
| await buildSystem.build(target, environment); |
| |
| expect(called, 1); |
| }); |
| |
| testWithoutContext('Target with depfile dependency will not run twice without ' |
| 'invalidation in incremental builds', () async { |
| final BuildSystem buildSystem = setUpBuildSystem(fileSystem); |
| int called = 0; |
| final TestTarget target = TestTarget((Environment environment) async { |
| environment.buildDir |
| .childFile('example.d') |
| .writeAsStringSync('a.txt: b.txt'); |
| fileSystem.file('a.txt').writeAsStringSync('a'); |
| called += 1; |
| }) |
| ..depfiles = <String>['example.d']; |
| fileSystem.file('b.txt').writeAsStringSync('b'); |
| |
| final BuildResult result = await buildSystem |
| .buildIncremental(target, environment, null); |
| |
| expect(fileSystem.file('a.txt'), exists); |
| expect(called, 1); |
| |
| // Second build is up to date due to depfile parse. |
| await buildSystem.buildIncremental(target, environment, result); |
| |
| expect(called, 1); |
| }); |
| |
| testWithoutContext('output directory is an input to the build', () async { |
| final Environment environmentA = Environment.test( |
| fileSystem.currentDirectory, |
| outputDir: fileSystem.directory('a'), |
| artifacts: Artifacts.test(), |
| processManager: FakeProcessManager.any(), |
| fileSystem: fileSystem, |
| logger: BufferLogger.test(), |
| ); |
| final Environment environmentB = Environment.test( |
| fileSystem.currentDirectory, |
| outputDir: fileSystem.directory('b'), |
| artifacts: Artifacts.test(), |
| processManager: FakeProcessManager.any(), |
| fileSystem: fileSystem, |
| logger: BufferLogger.test(), |
| ); |
| |
| expect(environmentA.buildDir.path, isNot(environmentB.buildDir.path)); |
| }); |
| |
| testWithoutContext('Additional inputs do not change the build configuration', () async { |
| final Environment environmentA = Environment.test( |
| fileSystem.currentDirectory, |
| artifacts: Artifacts.test(), |
| processManager: FakeProcessManager.any(), |
| fileSystem: fileSystem, |
| logger: BufferLogger.test(), |
| inputs: <String, String>{ |
| 'C': 'D', |
| } |
| ); |
| final Environment environmentB = Environment.test( |
| fileSystem.currentDirectory, |
| artifacts: Artifacts.test(), |
| processManager: FakeProcessManager.any(), |
| fileSystem: fileSystem, |
| logger: BufferLogger.test(), |
| inputs: <String, String>{ |
| 'A': 'B', |
| } |
| ); |
| |
| expect(environmentA.buildDir.path, equals(environmentB.buildDir.path)); |
| }); |
| |
| testWithoutContext('A target with depfile dependencies can delete stale outputs on the first run', () async { |
| final BuildSystem buildSystem = setUpBuildSystem(fileSystem); |
| int called = 0; |
| final TestTarget target = TestTarget((Environment environment) async { |
| if (called == 0) { |
| environment.buildDir.childFile('example.d') |
| .writeAsStringSync('a.txt c.txt: b.txt'); |
| fileSystem.file('a.txt').writeAsStringSync('a'); |
| fileSystem.file('c.txt').writeAsStringSync('a'); |
| } else { |
| // On second run, we no longer claim c.txt as an output. |
| environment.buildDir.childFile('example.d') |
| .writeAsStringSync('a.txt: b.txt'); |
| fileSystem.file('a.txt').writeAsStringSync('a'); |
| } |
| called += 1; |
| }) |
| ..depfiles = const <String>['example.d']; |
| fileSystem.file('b.txt').writeAsStringSync('b'); |
| |
| await buildSystem.build(target, environment); |
| |
| expect(fileSystem.file('a.txt'), exists); |
| expect(fileSystem.file('c.txt'), exists); |
| expect(called, 1); |
| |
| // rewrite an input to force a rerun, expect that the old c.txt is deleted. |
| fileSystem.file('b.txt').writeAsStringSync('ba'); |
| await buildSystem.build(target, environment); |
| |
| expect(fileSystem.file('a.txt'), exists); |
| expect(fileSystem.file('c.txt'), isNot(exists)); |
| expect(called, 2); |
| }); |
| |
| testWithoutContext('trackSharedBuildDirectory handles a missing .last_build_id', () { |
| FlutterBuildSystem( |
| fileSystem: fileSystem, |
| logger: BufferLogger.test(), |
| platform: FakePlatform(), |
| ).trackSharedBuildDirectory(environment, fileSystem, <String, File>{}); |
| |
| expect(environment.outputDir.childFile('.last_build_id'), exists); |
| expect(environment.outputDir.childFile('.last_build_id').readAsStringSync(), |
| '6666cd76f96956469e7be39d750cc7d9'); |
| }); |
| |
| testWithoutContext('trackSharedBuildDirectory handles a missing output dir', () { |
| final Environment environment = Environment.test( |
| fileSystem.currentDirectory, |
| outputDir: fileSystem.directory('a/b/c/d'), |
| artifacts: Artifacts.test(), |
| processManager: FakeProcessManager.any(), |
| fileSystem: fileSystem, |
| logger: BufferLogger.test(), |
| ); |
| FlutterBuildSystem( |
| fileSystem: fileSystem, |
| logger: BufferLogger.test(), |
| platform: FakePlatform(), |
| ).trackSharedBuildDirectory(environment, fileSystem, <String, File>{}); |
| |
| expect(environment.outputDir.childFile('.last_build_id'), exists); |
| expect(environment.outputDir.childFile('.last_build_id').readAsStringSync(), |
| '5954e2278dd01e1c4e747578776eeb94'); |
| }); |
| |
| testWithoutContext('trackSharedBuildDirectory does not modify .last_build_id when config is identical', () { |
| environment.outputDir.childFile('.last_build_id') |
| ..writeAsStringSync('6666cd76f96956469e7be39d750cc7d9') |
| ..setLastModifiedSync(DateTime(1991, 8, 23)); |
| FlutterBuildSystem( |
| fileSystem: fileSystem, |
| logger: BufferLogger.test(), |
| platform: FakePlatform(), |
| ).trackSharedBuildDirectory(environment, fileSystem, <String, File>{}); |
| |
| expect(environment.outputDir.childFile('.last_build_id').lastModifiedSync(), |
| DateTime(1991, 8, 23)); |
| }); |
| |
| testWithoutContext('trackSharedBuildDirectory does not delete files when outputs.json is missing', () { |
| environment.outputDir |
| .childFile('.last_build_id') |
| .writeAsStringSync('foo'); |
| environment.buildDir.parent |
| .childDirectory('foo') |
| .createSync(recursive: true); |
| environment.outputDir |
| .childFile('stale') |
| .createSync(); |
| FlutterBuildSystem( |
| fileSystem: fileSystem, |
| logger: BufferLogger.test(), |
| platform: FakePlatform(), |
| ).trackSharedBuildDirectory(environment, fileSystem, <String, File>{}); |
| |
| expect(environment.outputDir.childFile('.last_build_id').readAsStringSync(), |
| '6666cd76f96956469e7be39d750cc7d9'); |
| expect(environment.outputDir.childFile('stale'), exists); |
| }); |
| |
| testWithoutContext('trackSharedBuildDirectory deletes files in outputs.json but not in current outputs', () { |
| environment.outputDir |
| .childFile('.last_build_id') |
| .writeAsStringSync('foo'); |
| final Directory otherBuildDir = environment.buildDir.parent |
| .childDirectory('foo') |
| ..createSync(recursive: true); |
| final File staleFile = environment.outputDir |
| .childFile('stale') |
| ..createSync(); |
| otherBuildDir.childFile('outputs.json') |
| .writeAsStringSync(json.encode(<String>[staleFile.absolute.path])); |
| FlutterBuildSystem( |
| fileSystem: fileSystem, |
| logger: BufferLogger.test(), |
| platform: FakePlatform(), |
| ).trackSharedBuildDirectory(environment, fileSystem, <String, File>{}); |
| |
| expect(environment.outputDir.childFile('.last_build_id').readAsStringSync(), |
| '6666cd76f96956469e7be39d750cc7d9'); |
| expect(environment.outputDir.childFile('stale'), isNot(exists)); |
| }); |
| |
| testWithoutContext('multiple builds to the same output directory do no leave stale artifacts', () async { |
| final BuildSystem buildSystem = setUpBuildSystem(fileSystem); |
| final Environment testEnvironmentDebug = Environment.test( |
| fileSystem.currentDirectory, |
| outputDir: fileSystem.directory('output'), |
| defines: <String, String>{ |
| 'config': 'debug', |
| }, |
| artifacts: Artifacts.test(), |
| processManager: FakeProcessManager.any(), |
| logger: BufferLogger.test(), |
| fileSystem: fileSystem, |
| ); |
| final Environment testEnvironmentProfle = Environment.test( |
| fileSystem.currentDirectory, |
| outputDir: fileSystem.directory('output'), |
| defines: <String, String>{ |
| 'config': 'profile', |
| }, |
| artifacts: Artifacts.test(), |
| processManager: FakeProcessManager.any(), |
| logger: BufferLogger.test(), |
| fileSystem: fileSystem, |
| ); |
| |
| final TestTarget debugTarget = TestTarget((Environment environment) async { |
| environment.outputDir.childFile('debug').createSync(); |
| })..outputs = const <Source>[Source.pattern('{OUTPUT_DIR}/debug')]; |
| final TestTarget releaseTarget = TestTarget((Environment environment) async { |
| environment.outputDir.childFile('release').createSync(); |
| })..outputs = const <Source>[Source.pattern('{OUTPUT_DIR}/release')]; |
| |
| await buildSystem.build(debugTarget, testEnvironmentDebug); |
| |
| // Verify debug output was created |
| expect(fileSystem.file('output/debug'), exists); |
| |
| await buildSystem.build(releaseTarget, testEnvironmentProfle); |
| |
| // Last build config is updated properly |
| expect(testEnvironmentProfle.outputDir.childFile('.last_build_id'), exists); |
| expect(testEnvironmentProfle.outputDir.childFile('.last_build_id').readAsStringSync(), |
| 'c20b3747fb2aa148cc4fd39bfbbd894f'); |
| |
| // Verify debug output removeds |
| expect(fileSystem.file('output/debug'), isNot(exists)); |
| expect(fileSystem.file('output/release'), exists); |
| }); |
| |
| testWithoutContext('A target using canSkip can create a conditional output', () async { |
| final BuildSystem buildSystem = setUpBuildSystem(fileSystem); |
| final File bar = environment.buildDir.childFile('bar'); |
| final File foo = environment.buildDir.childFile('foo'); |
| |
| // The target will write a file `foo`, but only if `bar` already exists. |
| final TestTarget target = TestTarget( |
| (Environment environment) async { |
| foo.writeAsStringSync(bar.readAsStringSync()); |
| environment.buildDir |
| .childFile('example.d') |
| .writeAsStringSync('${foo.path}: ${bar.path}'); |
| }, |
| (Environment environment) { |
| return !environment.buildDir.childFile('bar').existsSync(); |
| } |
| ) |
| ..depfiles = const <String>['example.d']; |
| |
| // bar does not exist, there should be no inputs/outputs. |
| final BuildResult firstResult = await buildSystem.build(target, environment); |
| |
| expect(foo, isNot(exists)); |
| expect(firstResult.inputFiles, isEmpty); |
| expect(firstResult.outputFiles, isEmpty); |
| |
| // bar is created, the target should be able to run. |
| bar.writeAsStringSync('content-1'); |
| final BuildResult secondResult = await buildSystem.build(target, environment); |
| |
| expect(foo, exists); |
| expect(secondResult.inputFiles.map((File file) => file.path), <String>[bar.path]); |
| expect(secondResult.outputFiles.map((File file) => file.path), <String>[foo.path]); |
| |
| // bar is destroyed, foo is also deleted. |
| bar.deleteSync(); |
| final BuildResult thirdResult = await buildSystem.build(target, environment); |
| |
| expect(foo, isNot(exists)); |
| expect(thirdResult.inputFiles, isEmpty); |
| expect(thirdResult.outputFiles, isEmpty); |
| }); |
| |
| } |
| |
| BuildSystem setUpBuildSystem(FileSystem fileSystem) { |
| return FlutterBuildSystem( |
| fileSystem: fileSystem, |
| logger: BufferLogger.test(), |
| platform: FakePlatform(operatingSystem: 'linux'), |
| ); |
| } |
| |
| class TestTarget extends Target { |
| TestTarget([this._build, this._canSkip]); |
| |
| final Future<void> Function(Environment environment) _build; |
| |
| final bool Function(Environment environment) _canSkip; |
| |
| @override |
| bool canSkip(Environment environment) { |
| if (_canSkip != null) { |
| return _canSkip(environment); |
| } |
| return super.canSkip(environment); |
| } |
| |
| @override |
| Future<void> build(Environment environment) => _build(environment); |
| |
| @override |
| List<Target> dependencies = <Target>[]; |
| |
| @override |
| List<Source> inputs = <Source>[]; |
| |
| @override |
| List<String> depfiles = <String>[]; |
| |
| @override |
| String name = 'test'; |
| |
| @override |
| List<Source> outputs = <Source>[]; |
| } |