Remove Source.behavior, fix bug in depfile invalidation (#43945)

* remove Source.behavior, fix bug in depfile invalidation

* more cleanup of assets

* Add skip

* address comments

* Update build_system.dart
This commit is contained in:
Jonah Williams 2019-11-04 16:37:43 -08:00 committed by GitHub
parent b815f76293
commit 0f6c093d68
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 121 additions and 320 deletions

View file

@ -526,17 +526,20 @@ class _BuildInstance {
await node.target.build(environment);
printTrace('${node.target.name}: Complete');
// If we were missing the depfile, resolve files after executing the
node.inputs
..clear()
..addAll(node.target.resolveInputs(environment).sources);
node.outputs
..clear()
..addAll(node.target.resolveOutputs(environment).sources);
// If we were missing the depfile, resolve input files after executing the
// target so that all file hashes are up to date on the next run.
if (node.missingDepfile) {
node.inputs.clear();
node.outputs.clear();
node.inputs.addAll(node.target.resolveInputs(environment).sources);
node.outputs.addAll(node.target.resolveOutputs(environment).sources);
await fileCache.hashFiles(node.inputs);
}
// Update hashes for output files.
// Always update hashes for output files.
await fileCache.hashFiles(node.outputs);
node.target._writeStamp(node.inputs, node.outputs, environment);
updateGraph();

View file

@ -56,9 +56,13 @@ class Depfile {
/// Given an [depfile] File, write the depfile contents.
///
/// If either [inputs] or [outputs] is empty, does not write to the file.
/// If either [inputs] or [outputs] is empty, ensures the file does not
/// exist.
void writeToFile(File depfile) {
if (inputs.isEmpty || outputs.isEmpty) {
if (depfile.existsSync()) {
depfile.deleteSync();
}
return;
}
final StringBuffer buffer = StringBuffer();

View file

@ -163,15 +163,6 @@ class SourceVisitor implements ResolvedFiles {
}
}
/// Visit a [Source] which contains a [SourceBehavior].
void visitBehavior(SourceBehavior sourceBehavior) {
if (inputs) {
sources.addAll(sourceBehavior.inputs(environment));
} else {
sources.addAll(sourceBehavior.outputs(environment));
}
}
/// Visit a [Source] which is defined by an [Artifact] from the flutter cache.
///
/// If the [Artifact] points to a directory then all child files are included.
@ -194,10 +185,6 @@ abstract class Source {
/// This source is a file-uri which contains some references to magic
/// environment variables.
const factory Source.pattern(String pattern, { bool optional }) = _PatternSource;
/// This source is produced by the [SourceBehavior] class.
const factory Source.behavior(SourceBehavior behavior) = _SourceBehavior;
/// The source is provided by an [Artifact].
///
/// If [artifact] points to a directory then all child files are included.
@ -222,34 +209,10 @@ abstract class Source {
/// evaluated before the build.
///
/// For example, [Source.pattern] and [Source.version] are not implicit
/// provided they do not use any wildcards. [Source.behavior] and
/// [Source.function] are always implicit.
/// provided they do not use any wildcards.
bool get implicit;
}
/// An interface for describing input and output copies together.
abstract class SourceBehavior {
const SourceBehavior();
/// The inputs for a particular target.
List<File> inputs(Environment environment);
/// The outputs for a particular target.
List<File> outputs(Environment environment);
}
class _SourceBehavior implements Source {
const _SourceBehavior(this.value);
final SourceBehavior value;
@override
void accept(SourceVisitor visitor) => visitor.visitBehavior(value);
@override
bool get implicit => true;
}
class _PatternSource implements Source {
const _PatternSource(this.value, { this.optional = false });

View file

@ -10,81 +10,45 @@ import '../../devfs.dart';
import '../../plugins.dart';
import '../../project.dart';
import '../build_system.dart';
import '../depfile.dart';
/// The copying logic for flutter assets.
class AssetBehavior extends SourceBehavior {
const AssetBehavior();
@override
List<File> inputs(Environment environment) {
final AssetBundle assetBundle = AssetBundleFactory.instance.createBundle();
assetBundle.build(
manifestPath: environment.projectDir.childFile('pubspec.yaml').path,
packagesPath: environment.projectDir.childFile('.packages').path,
);
// Filter the file type to remove the files that are generated by this
// command as inputs.
final List<File> results = <File>[];
final Iterable<DevFSFileContent> files = assetBundle.entries.values.whereType<DevFSFileContent>();
for (DevFSFileContent devFsContent in files) {
results.add(fs.file(devFsContent.file.path));
}
return results;
}
@override
List<File> outputs(Environment environment) {
final AssetBundle assetBundle = AssetBundleFactory.instance.createBundle();
assetBundle.build(
manifestPath: environment.projectDir.childFile('pubspec.yaml').path,
packagesPath: environment.projectDir.childFile('.packages').path,
);
final List<File> results = <File>[];
for (String key in assetBundle.entries.keys) {
final File file = fs.file(fs.path.join(environment.buildDir.path, 'flutter_assets', key));
results.add(file);
}
return results;
}
}
/// A specific asset behavior for building bundles.
class AssetOutputBehavior extends SourceBehavior {
const AssetOutputBehavior([this._pathSuffix = '']);
final String _pathSuffix;
@override
List<File> inputs(Environment environment) {
final AssetBundle assetBundle = AssetBundleFactory.instance.createBundle();
assetBundle.build(
manifestPath: environment.projectDir.childFile('pubspec.yaml').path,
packagesPath: environment.projectDir.childFile('.packages').path,
);
// Filter the file type to remove the files that are generated by this
// command as inputs.
final List<File> results = <File>[];
final Iterable<DevFSFileContent> files = assetBundle.entries.values.whereType<DevFSFileContent>();
for (DevFSFileContent devFsContent in files) {
results.add(fs.file(fs.path.join(_pathSuffix, devFsContent.file.path)));
}
return results;
}
@override
List<File> outputs(Environment environment) {
final AssetBundle assetBundle = AssetBundleFactory.instance.createBundle();
assetBundle.build(
manifestPath: environment.projectDir.childFile('pubspec.yaml').path,
packagesPath: environment.projectDir.childFile('.packages').path,
);
final List<File> results = <File>[];
for (String key in assetBundle.entries.keys) {
final File file = fs.file(fs.path.join(environment.outputDir.path, _pathSuffix, key));
results.add(file);
}
return results;
}
/// A helper function to copy an asset bundle into an [environment]'s output
/// directory.
///
/// Returns a [Depfile] containing all assets used in the build.
Future<Depfile> copyAssets(Environment environment, Directory outputDirectory) async {
final File pubspecFile = environment.projectDir.childFile('pubspec.yaml');
final AssetBundle assetBundle = AssetBundleFactory.instance.createBundle();
await assetBundle.build(
manifestPath: pubspecFile.path,
packagesPath: environment.projectDir.childFile('.packages').path,
);
final Pool pool = Pool(kMaxOpenFiles);
final List<File> inputs = <File>[
// An asset manifest with no assets would have zero inputs if not
// for this pubspec file.
pubspecFile,
];
final List<File> outputs = <File>[];
await Future.wait<void>(
assetBundle.entries.entries.map<Future<void>>((MapEntry<String, DevFSContent> entry) async {
final PoolResource resource = await pool.request();
try {
final File file = fs.file(fs.path.join(outputDirectory.path, entry.key));
outputs.add(file);
file.parent.createSync(recursive: true);
final DevFSContent content = entry.value;
if (content is DevFSFileContent && content.file is File) {
inputs.add(fs.file(content.file.path));
await (content.file as File).copy(file.path);
} else {
await file.writeAsBytes(await entry.value.contentsAsBytes());
}
} finally {
resource.release();
}
}));
return Depfile(inputs, outputs);
}
/// Copy the assets defined in the flutter manifest into a build directory.
@ -100,16 +64,12 @@ class CopyAssets extends Target {
@override
List<Source> get inputs => const <Source>[
Source.pattern('{FLUTTER_ROOT}/packages/flutter_tools/lib/src/build_system/targets/assets.dart'),
Source.pattern('{PROJECT_DIR}/pubspec.yaml'),
Source.behavior(AssetBehavior()),
Source.depfile('flutter_assets.d'),
];
@override
List<Source> get outputs => const <Source>[
Source.pattern('{BUILD_DIR}/flutter_assets/AssetManifest.json'),
Source.pattern('{BUILD_DIR}/flutter_assets/FontManifest.json'),
Source.pattern('{BUILD_DIR}/flutter_assets/LICENSE'),
Source.behavior(AssetBehavior()), // <- everything in this subdirectory.
Source.depfile('flutter_assets.d'),
];
@override
@ -117,28 +77,9 @@ class CopyAssets extends Target {
final Directory output = environment
.buildDir
.childDirectory('flutter_assets');
if (output.existsSync()) {
output.deleteSync(recursive: true);
}
output.createSync(recursive: true);
final AssetBundle assetBundle = AssetBundleFactory.instance.createBundle();
await assetBundle.build(
manifestPath: environment.projectDir.childFile('pubspec.yaml').path,
packagesPath: environment.projectDir.childFile('.packages').path,
);
// Limit number of open files to avoid running out of file descriptors.
final Pool pool = Pool(kMaxOpenFiles);
await Future.wait<void>(
assetBundle.entries.entries.map<Future<void>>((MapEntry<String, DevFSContent> entry) async {
final PoolResource resource = await pool.request();
try {
final File file = fs.file(fs.path.join(output.path, entry.key));
file.parent.createSync(recursive: true);
await file.writeAsBytes(await entry.value.contentsAsBytes());
} finally {
resource.release();
}
}));
final Depfile depfile = await copyAssets(environment, output);
depfile.writeToFile(environment.buildDir.childFile('flutter_assets.d'));
}
}

View file

@ -2,18 +2,15 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:pool/pool.dart';
import '../../artifacts.dart';
import '../../asset.dart';
import '../../base/build.dart';
import '../../base/file_system.dart';
import '../../build_info.dart';
import '../../compile.dart';
import '../../devfs.dart';
import '../../globals.dart';
import '../../project.dart';
import '../build_system.dart';
import '../depfile.dart';
import '../exceptions.dart';
import 'assets.dart';
@ -53,7 +50,7 @@ class CopyFlutterBundle extends Target {
Source.artifact(Artifact.vmSnapshotData, mode: BuildMode.debug),
Source.artifact(Artifact.isolateSnapshotData, mode: BuildMode.debug),
Source.pattern('{BUILD_DIR}/app.dill'),
Source.behavior(AssetOutputBehavior()),
Source.depfile('flutter_assets.d'),
];
@override
@ -61,10 +58,7 @@ class CopyFlutterBundle extends Target {
Source.pattern('{OUTPUT_DIR}/vm_snapshot_data'),
Source.pattern('{OUTPUT_DIR}/isolate_snapshot_data'),
Source.pattern('{OUTPUT_DIR}/kernel_blob.bin'),
Source.pattern('{OUTPUT_DIR}/AssetManifest.json'),
Source.pattern('{OUTPUT_DIR}/FontManifest.json'),
Source.pattern('{OUTPUT_DIR}/LICENSE'),
Source.behavior(AssetOutputBehavior()),
Source.depfile('flutter_assets.d'),
];
@override
@ -73,12 +67,6 @@ class CopyFlutterBundle extends Target {
throw MissingDefineException(kBuildMode, 'copy_flutter_bundle');
}
final BuildMode buildMode = getBuildModeForName(environment.defines[kBuildMode]);
// We're not smart enough to only remove assets that are removed. If
// anything changes blow away the whole directory.
if (environment.outputDir.existsSync()) {
environment.outputDir.deleteSync(recursive: true);
}
environment.outputDir.createSync(recursive: true);
// Only copy the prebuilt runtimes and kernel blob in debug mode.
@ -92,10 +80,8 @@ class CopyFlutterBundle extends Target {
fs.file(isolateSnapshotData)
.copySync(environment.outputDir.childFile('isolate_snapshot_data').path);
}
final AssetBundle assetBundle = AssetBundleFactory.instance.createBundle();
await assetBundle.build();
await copyAssets(assetBundle, environment);
final Depfile assetDepfile = await copyAssets(environment, environment.outputDir);
assetDepfile.writeToFile(environment.buildDir.childFile('flutter_assets.d'));
}
@override
@ -104,28 +90,6 @@ class CopyFlutterBundle extends Target {
];
}
/// A helper function to copy an [assetBundle] into an [environment]'s output directory,
/// plus an optional [pathSuffix]
Future<void> copyAssets(AssetBundle assetBundle, Environment environment, [String pathSuffix = '']) async {
final Pool pool = Pool(kMaxOpenFiles);
await Future.wait<void>(
assetBundle.entries.entries.map<Future<void>>((MapEntry<String, DevFSContent> entry) async {
final PoolResource resource = await pool.request();
try {
final File file = fs.file(fs.path.join(environment.outputDir.path, pathSuffix, entry.key));
file.parent.createSync(recursive: true);
final DevFSContent content = entry.value;
if (content is DevFSFileContent && content.file is File) {
await (content.file as File).copy(file.path);
} else {
await file.writeAsBytes(await entry.value.contentsAsBytes());
}
} finally {
resource.release();
}
}));
}
/// Copies the prebuilt flutter bundle for release mode.
class ReleaseCopyFlutterBundle extends CopyFlutterBundle {
const ReleaseCopyFlutterBundle();
@ -135,15 +99,12 @@ class ReleaseCopyFlutterBundle extends CopyFlutterBundle {
@override
List<Source> get inputs => const <Source>[
Source.behavior(AssetOutputBehavior()),
Source.depfile('flutter_assets.d'),
];
@override
List<Source> get outputs => const <Source>[
Source.pattern('{OUTPUT_DIR}/AssetManifest.json'),
Source.pattern('{OUTPUT_DIR}/FontManifest.json'),
Source.pattern('{OUTPUT_DIR}/LICENSE'),
Source.behavior(AssetOutputBehavior()),
Source.depfile('flutter_assets.d'),
];
@override

View file

@ -2,13 +2,9 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:pool/pool.dart';
import '../../artifacts.dart';
import '../../asset.dart';
import '../../base/file_system.dart';
import '../../build_info.dart';
import '../../devfs.dart';
import '../../globals.dart';
import '../build_system.dart';
import '../depfile.dart';
@ -120,16 +116,14 @@ class DebugBundleLinuxAssets extends Target {
List<Source> get inputs => const <Source>[
Source.pattern('{BUILD_DIR}/app.dill'),
Source.pattern('{FLUTTER_ROOT}/packages/flutter_tools/lib/src/build_system/targets/linux.dart'),
Source.behavior(AssetOutputBehavior('flutter_assets')),
Source.depfile('flutter_assets.d'),
Source.pattern('{PROJECT_DIR}/pubspec.yaml'),
];
@override
List<Source> get outputs => const <Source>[
Source.behavior(AssetOutputBehavior('flutter_assets')),
Source.depfile('flutter_assets.d'),
Source.pattern('{OUTPUT_DIR}/flutter_assets/kernel_blob.bin'),
Source.pattern('{OUTPUT_DIR}/flutter_assets/AssetManifest.json'),
Source.pattern('{OUTPUT_DIR}/flutter_assets/FontManifest.json'),
Source.pattern('{OUTPUT_DIR}/flutter_assets/LICENSE'),
];
@override
@ -149,25 +143,7 @@ class DebugBundleLinuxAssets extends Target {
environment.buildDir.childFile('app.dill')
.copySync(outputDirectory.childFile('kernel_blob.bin').path);
}
final AssetBundle assetBundle = AssetBundleFactory.instance.createBundle();
await assetBundle.build();
final Pool pool = Pool(kMaxOpenFiles);
await Future.wait<void>(
assetBundle.entries.entries.map<Future<void>>((MapEntry<String, DevFSContent> entry) async {
final PoolResource resource = await pool.request();
try {
final File file = fs.file(fs.path.join(outputDirectory.path, entry.key));
file.parent.createSync(recursive: true);
final DevFSContent content = entry.value;
if (content is DevFSFileContent && content.file is File) {
await (content.file as File).copy(file.path);
} else {
await file.writeAsBytes(await entry.value.contentsAsBytes());
}
} finally {
resource.release();
}
}));
final Depfile depfile = await copyAssets(environment, outputDirectory);
depfile.writeToFile(environment.buildDir.childFile('flutter_assets.d'));
}
}

View file

@ -2,65 +2,23 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:pool/pool.dart';
import '../../artifacts.dart';
import '../../asset.dart';
import '../../base/build.dart';
import '../../base/file_system.dart';
import '../../base/io.dart';
import '../../base/process.dart';
import '../../base/process_manager.dart';
import '../../build_info.dart';
import '../../devfs.dart';
import '../../globals.dart';
import '../../macos/xcode.dart';
import '../build_system.dart';
import '../depfile.dart';
import '../exceptions.dart';
import 'assets.dart';
import 'dart.dart';
const String _kOutputPrefix = '{OUTPUT_DIR}/FlutterMacOS.framework';
/// The copying logic for flutter assets in macOS.
// TODO(jonahwilliams): remove once build planning lands.
class MacOSAssetBehavior extends SourceBehavior {
const MacOSAssetBehavior();
@override
List<File> inputs(Environment environment) {
final AssetBundle assetBundle = AssetBundleFactory.instance.createBundle();
assetBundle.build(
manifestPath: environment.projectDir.childFile('pubspec.yaml').path,
packagesPath: environment.projectDir.childFile('.packages').path,
);
// Filter the file type to remove the files that are generated by this
// command as inputs.
final List<File> results = <File>[];
final Iterable<DevFSFileContent> files = assetBundle.entries.values.whereType<DevFSFileContent>();
for (DevFSFileContent devFsContent in files) {
results.add(fs.file(devFsContent.file.path));
}
return results;
}
@override
List<File> outputs(Environment environment) {
final AssetBundle assetBundle = AssetBundleFactory.instance.createBundle();
assetBundle.build(
manifestPath: environment.projectDir.childFile('pubspec.yaml').path,
packagesPath: environment.projectDir.childFile('.packages').path,
);
final String prefix = fs.path.join(environment.outputDir.path,
'App.framework', 'Versions', 'A', 'Resources', 'flutter_assets');
final List<File> results = <File>[];
for (String key in assetBundle.entries.keys) {
final File file = fs.file(fs.path.join(prefix, key));
results.add(file);
}
return results;
}
}
/// Copy the macOS framework to the correct copy dir by invoking 'cp -R'.
///
/// This class is abstract to share logic between the three concrete
@ -286,19 +244,15 @@ abstract class MacOSBundleFlutterAssets extends Target {
@override
List<Source> get inputs => const <Source>[
Source.pattern('{PROJECT_DIR}/pubspec.yaml'),
Source.pattern('{BUILD_DIR}/App.framework/App'),
Source.behavior(MacOSAssetBehavior()),
Source.depfile('flutter_assets.d'),
];
@override
List<Source> get outputs => const <Source>[
Source.behavior(MacOSAssetBehavior()),
Source.pattern('{OUTPUT_DIR}/App.framework/Versions/A/App'),
Source.pattern('{OUTPUT_DIR}/App.framework/Versions/A/Resources/Info.plist'),
Source.pattern('{OUTPUT_DIR}/App.framework/Versions/A/Resources/flutter_assets/AssetManifest.json'),
Source.pattern('{OUTPUT_DIR}/App.framework/Versions/A/Resources/flutter_assets/FontManifest.json'),
Source.pattern('{OUTPUT_DIR}/App.framework/Versions/A/Resources/flutter_assets/LICENSE'),
Source.depfile('flutter_assets.d'),
];
@override
@ -326,31 +280,9 @@ abstract class MacOSBundleFlutterAssets extends Target {
.childDirectory('Resources')
.childDirectory('flutter_assets');
assetDirectory.createSync(recursive: true);
final AssetBundle assetBundle = AssetBundleFactory.instance.createBundle();
final int result = await assetBundle.build(
manifestPath: environment.projectDir.childFile('pubspec.yaml').path,
packagesPath: environment.projectDir.childFile('.packages').path,
);
if (result != 0) {
throw Exception('Failed to create asset bundle: $result');
}
// Limit number of open files to avoid running out of file descriptors.
try {
final Pool pool = Pool(kMaxOpenFiles);
await Future.wait<void>(
assetBundle.entries.entries.map<Future<void>>((MapEntry<String, DevFSContent> entry) async {
final PoolResource resource = await pool.request();
try {
final File file = fs.file(fs.path.join(assetDirectory.path, entry.key));
file.parent.createSync(recursive: true);
await file.writeAsBytes(await entry.value.contentsAsBytes());
} finally {
resource.release();
}
}));
} catch (err, st) {
throw Exception('Failed to copy assets: $st');
}
final Depfile depfile = await copyAssets(environment, assetDirectory);
depfile.writeToFile(environment.buildDir.childFile('flutter_assets.d'));
// Copy Info.plist template.
assetDirectory.parent.childFile('Info.plist')
..createSync()

View file

@ -3,7 +3,6 @@
// found in the LICENSE file.
import '../../artifacts.dart';
import '../../asset.dart';
import '../../base/file_system.dart';
import '../../base/io.dart';
import '../../base/process_manager.dart';
@ -183,18 +182,16 @@ class WebReleaseBundle extends Target {
@override
List<Source> get inputs => const <Source>[
Source.pattern('{BUILD_DIR}/main.dart.js'),
Source.behavior(AssetOutputBehavior('assets')),
Source.pattern('{PROJECT_DIR}/pubspec.yaml'),
Source.pattern('{PROJECT_DIR}/web/index.html'),
Source.depfile('flutter_assets.d'),
];
@override
List<Source> get outputs => const <Source>[
Source.pattern('{OUTPUT_DIR}/main.dart.js'),
Source.pattern('{OUTPUT_DIR}/assets/AssetManifest.json'),
Source.pattern('{OUTPUT_DIR}/assets/FontManifest.json'),
Source.pattern('{OUTPUT_DIR}/assets/LICENSE'),
Source.pattern('{OUTPUT_DIR}/index.html'),
Source.behavior(AssetOutputBehavior('assets'))
Source.depfile('flutter_assets.d'),
];
@override
@ -207,12 +204,13 @@ class WebReleaseBundle extends Target {
environment.outputDir.childFile(fs.path.basename(outputFile.path)).path
);
}
final Directory outputDirectory = environment.outputDir.childDirectory('assets');
outputDirectory.createSync(recursive: true);
environment.projectDir
.childDirectory('web')
.childFile('index.html')
.copySync(fs.path.join(environment.outputDir.path, 'index.html'));
final AssetBundle assetBundle = AssetBundleFactory.instance.createBundle();
await assetBundle.build();
await copyAssets(assetBundle, environment, 'assets');
final Depfile depfile = await copyAssets(environment, environment.outputDir.childDirectory('assets'));
depfile.writeToFile(environment.buildDir.childFile('flutter_assets.d'));
}
}

View file

@ -339,6 +339,41 @@ void main() {
expect(environmentA.buildDir.path, isNot(environmentB.buildDir.path));
}));
test('A target with depfile dependencies can delete stale outputs on the first run', () => testbed.run(() async {
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');
fs.file('a.txt').writeAsStringSync('a');
fs.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');
fs.file('a.txt').writeAsStringSync('a');
}
called += 1;
})
..inputs = const <Source>[Source.depfile('example.d')]
..outputs = const <Source>[Source.depfile('example.d')];
fs.file('b.txt').writeAsStringSync('b');
await buildSystem.build(target, environment);
expect(fs.file('a.txt').existsSync(), true);
expect(fs.file('c.txt').existsSync(), true);
expect(called, 1);
// rewrite an input to force a rerun, espect that the old c.txt is deleted.
fs.file('b.txt').writeAsStringSync('ba');
await buildSystem.build(target, environment);
expect(fs.file('a.txt').existsSync(), true);
expect(fs.file('c.txt').existsSync(), false);
expect(called, 2);
}));
}
class MockPlatform extends Mock implements Platform {}

View file

@ -41,7 +41,6 @@ void main() {
test('configures implicit vs explict correctly', () => testbed.run(() {
expect(const Source.pattern('{PROJECT_DIR}/foo').implicit, false);
expect(const Source.pattern('{PROJECT_DIR}/*foo').implicit, true);
expect(Source.behavior(TestBehavior()).implicit, true);
}));
test('can substitute {PROJECT_DIR}/foo', () => testbed.run(() {
@ -217,17 +216,4 @@ void main() {
}));
}
class TestBehavior extends SourceBehavior {
@override
List<File> inputs(Environment environment) {
return null;
}
@override
List<File> outputs(Environment environment) {
return null;
}
}
class MockPlatform extends Mock implements Platform {}

View file

@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:io';
import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/build_system/build_system.dart';
import 'package:flutter_tools/src/build_system/targets/assets.dart';
@ -65,7 +67,7 @@ flutter:
// See https://github.com/flutter/flutter/issues/35293
expect(fs.file(fs.path.join(environment.buildDir.path, 'flutter_assets', 'assets/foo/bar.png')).existsSync(), false);
}));
}), skip: Platform.isWindows); // See https://github.com/google/file.dart/issues/131
test('FlutterPlugins updates required files as needed', () => testbed.run(() async {
fs.file('pubspec.yaml')