Unverified Commit f53b32eb authored by Jonah Williams's avatar Jonah Williams Committed by GitHub

refactor depfile usage and update linux rule (#42487)

parent 315471bf
...@@ -502,7 +502,7 @@ class _BuildInstance { ...@@ -502,7 +502,7 @@ class _BuildInstance {
outputFiles[output.path] = output; outputFiles[output.path] = output;
} }
for (File input in node.inputs) { for (File input in node.inputs) {
final String resolvedPath = input.resolveSymbolicLinksSync(); final String resolvedPath = input.absolute.path;
if (outputFiles.containsKey(resolvedPath)) { if (outputFiles.containsKey(resolvedPath)) {
continue; continue;
} }
...@@ -704,7 +704,7 @@ class Node { ...@@ -704,7 +704,7 @@ class Node {
/// One or more reasons why a task was invalidated. /// One or more reasons why a task was invalidated.
/// ///
/// May be empty if the task was skipped. /// May be empty if the task was skipped.
final Set<InvalidedReason> invalidatedReasons = <InvalidedReason>{}; final Set<InvalidatedReason> invalidatedReasons = <InvalidatedReason>{};
/// Whether this node needs an action performed. /// Whether this node needs an action performed.
bool get dirty => _dirty; bool get dirty => _dirty;
...@@ -735,7 +735,7 @@ class Node { ...@@ -735,7 +735,7 @@ class Node {
if (fileHashStore.currentHashes.containsKey(absolutePath)) { if (fileHashStore.currentHashes.containsKey(absolutePath)) {
final String currentHash = fileHashStore.currentHashes[absolutePath]; final String currentHash = fileHashStore.currentHashes[absolutePath];
if (currentHash != previousHash) { if (currentHash != previousHash) {
invalidatedReasons.add(InvalidedReason.inputChanged); invalidatedReasons.add(InvalidatedReason.inputChanged);
_dirty = true; _dirty = true;
} }
} else { } else {
...@@ -749,13 +749,13 @@ class Node { ...@@ -749,13 +749,13 @@ class Node {
// output paths changed. // output paths changed.
if (!currentOutputPaths.contains(previousOutput)) { if (!currentOutputPaths.contains(previousOutput)) {
_dirty = true; _dirty = true;
invalidatedReasons.add(InvalidedReason.outputSetChanged); invalidatedReasons.add(InvalidatedReason.outputSetChanged);
// if this isn't a current output file there is no reason to compute the hash. // if this isn't a current output file there is no reason to compute the hash.
continue; continue;
} }
final File file = fs.file(previousOutput); final File file = fs.file(previousOutput);
if (!file.existsSync()) { if (!file.existsSync()) {
invalidatedReasons.add(InvalidedReason.outputMissing); invalidatedReasons.add(InvalidatedReason.outputMissing);
_dirty = true; _dirty = true;
continue; continue;
} }
...@@ -764,7 +764,7 @@ class Node { ...@@ -764,7 +764,7 @@ class Node {
if (fileHashStore.currentHashes.containsKey(absolutePath)) { if (fileHashStore.currentHashes.containsKey(absolutePath)) {
final String currentHash = fileHashStore.currentHashes[absolutePath]; final String currentHash = fileHashStore.currentHashes[absolutePath];
if (currentHash != previousHash) { if (currentHash != previousHash) {
invalidatedReasons.add(InvalidedReason.outputChanged); invalidatedReasons.add(InvalidatedReason.outputChanged);
_dirty = true; _dirty = true;
} }
} else { } else {
...@@ -772,9 +772,14 @@ class Node { ...@@ -772,9 +772,14 @@ class Node {
} }
} }
// If we depend on a file that doesnt exist on disk, kill the build. // If we depend on a file that doesnt exist on disk, mark the build as
// dirty. if the rule is not correctly specified, this will result in it
// always being rerun.
if (missingInputs.isNotEmpty) { if (missingInputs.isNotEmpty) {
throw MissingInputException(missingInputs, target.name); _dirty = true;
final String missingMessage = missingInputs.map((File file) => file.path).join(', ');
printTrace('invalidated build due to missing files: $missingMessage');
invalidatedReasons.add(InvalidatedReason.inputMissing);
} }
// If we have files to hash, compute them asynchronously and then // If we have files to hash, compute them asynchronously and then
...@@ -782,7 +787,7 @@ class Node { ...@@ -782,7 +787,7 @@ class Node {
if (sourcesToHash.isNotEmpty) { if (sourcesToHash.isNotEmpty) {
final List<File> dirty = await fileHashStore.hashFiles(sourcesToHash); final List<File> dirty = await fileHashStore.hashFiles(sourcesToHash);
if (dirty.isNotEmpty) { if (dirty.isNotEmpty) {
invalidatedReasons.add(InvalidedReason.inputChanged); invalidatedReasons.add(InvalidatedReason.inputChanged);
_dirty = true; _dirty = true;
} }
} }
...@@ -790,8 +795,12 @@ class Node { ...@@ -790,8 +795,12 @@ class Node {
} }
} }
/// A description of why a task was rerun. /// A description of why a target was rerun.
enum InvalidedReason { enum InvalidatedReason {
/// An input file that was expected is missing. This can occur when using
/// depfile dependencies, or if a target is incorrectly specified.
inputMissing,
/// An input file has an updated hash. /// An input file has an updated hash.
inputChanged, inputChanged,
......
// Copyright 2019 The Chromium 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 '../base/file_system.dart';
import '../base/platform.dart';
import '../globals.dart';
/// A class for representing depfile formats.
class Depfile {
/// Create a [Depfile] from a list of [input] files and [output] files.
const Depfile(this.inputs, this.outputs);
/// Parse the depfile contents from [file].
///
/// If the syntax is invalid, returns an empty [Depfile].
factory Depfile.parse(File file) {
final String contents = file.readAsStringSync();
final List<String> colonSeparated = contents.split(': ');
if (colonSeparated.length != 2) {
printError('Invalid depfile: ${file.path}');
return const Depfile(<File>[], <File>[]);
}
final List<File> inputs = _processList(colonSeparated[1].trim());
final List<File> outputs = _processList(colonSeparated[0].trim());
return Depfile(inputs, outputs);
}
/// The input files for this depfile.
final List<File> inputs;
/// The output files for this depfile.
final List<File> outputs;
/// Given an [depfile] File, write the depfile contents.
///
/// If either [inputs] or [outputs] is empty, does not write to the file.
void writeToFile(File depfile) {
if (inputs.isEmpty || outputs.isEmpty) {
return;
}
final StringBuffer buffer = StringBuffer();
_writeFilesToBuffer(outputs, buffer);
buffer.write(': ');
_writeFilesToBuffer(inputs, buffer);
depfile.writeAsStringSync(buffer.toString());
}
void _writeFilesToBuffer(List<File> files, StringBuffer buffer) {
for (File outputFile in files) {
if (platform.isWindows) {
// Paths in a depfile have to be escaped on windows.
final String escapedPath = outputFile.path.replaceAll(r'\', r'\\');
buffer.write(' $escapedPath');
} else {
buffer.write(' ${outputFile.path}');
}
}
}
static final RegExp _separatorExpr = RegExp(r'([^\\]) ');
static final RegExp _escapeExpr = RegExp(r'\\(.)');
static List<File> _processList(String rawText) {
return rawText
// Put every file on right-hand side on the separate line
.replaceAllMapped(_separatorExpr, (Match match) => '${match.group(1)}\n')
.split('\n')
// Expand escape sequences, so that '\ ', for example,ß becomes ' '
.map<String>((String path) => path.replaceAllMapped(_escapeExpr, (Match match) => match.group(1)).trim())
.where((String path) => path.isNotEmpty)
// The tool doesn't write duplicates to these lists. This call is an attempt to
// be resillient to the outputs of other tools which write or user edits to depfiles.
.toSet()
.map((String path) => fs.file(path))
.toList();
}
}
...@@ -11,10 +11,22 @@ import '../../build_info.dart'; ...@@ -11,10 +11,22 @@ import '../../build_info.dart';
import '../../devfs.dart'; import '../../devfs.dart';
import '../../globals.dart'; import '../../globals.dart';
import '../build_system.dart'; import '../build_system.dart';
import '../depfile.dart';
import '../exceptions.dart'; import '../exceptions.dart';
import 'assets.dart'; import 'assets.dart';
import 'dart.dart'; import 'dart.dart';
/// The only files/subdirectories we care out.
const List<String> _kLinuxArtifacts = <String>[
'libflutter_linux_glfw.so',
'flutter_export.h',
'flutter_messenger.h',
'flutter_plugin_registrar.h',
'flutter_glfw.h',
'icudtl.dat',
'cpp_client_wrapper_glfw/',
];
/// Copies the Linux desktop embedding files to the copy directory. /// Copies the Linux desktop embedding files to the copy directory.
class UnpackLinuxDebug extends Target { class UnpackLinuxDebug extends Target {
const UnpackLinuxDebug(); const UnpackLinuxDebug();
...@@ -25,17 +37,12 @@ class UnpackLinuxDebug extends Target { ...@@ -25,17 +37,12 @@ class UnpackLinuxDebug extends Target {
@override @override
List<Source> get inputs => const <Source>[ List<Source> get inputs => const <Source>[
Source.pattern('{FLUTTER_ROOT}/packages/flutter_tools/lib/src/build_system/targets/linux.dart'), Source.pattern('{FLUTTER_ROOT}/packages/flutter_tools/lib/src/build_system/targets/linux.dart'),
Source.artifact(Artifact.linuxDesktopPath, mode: BuildMode.debug), Source.depfile('linux_engine_sources.d'),
]; ];
@override @override
List<Source> get outputs => const <Source>[ List<Source> get outputs => const <Source>[
Source.pattern('{PROJECT_DIR}/linux/flutter/ephemeral/libflutter_linux_glfw.so'), Source.depfile('linux_engine_sources.d'),
Source.pattern('{PROJECT_DIR}/linux/flutter/ephemeral/flutter_export.h'),
Source.pattern('{PROJECT_DIR}/linux/flutter/ephemeral/flutter_messenger.h'),
Source.pattern('{PROJECT_DIR}/linux/flutter/ephemeral/flutter_plugin_registrar.h'),
Source.pattern('{PROJECT_DIR}/linux/flutter/ephemeral/flutter_glfw.h'),
Source.pattern('{PROJECT_DIR}/linux/flutter/ephemeral/icudtl.dat'),
]; ];
@override @override
...@@ -44,22 +51,55 @@ class UnpackLinuxDebug extends Target { ...@@ -44,22 +51,55 @@ class UnpackLinuxDebug extends Target {
@override @override
Future<void> build(Environment environment) async { Future<void> build(Environment environment) async {
final String basePath = artifacts.getArtifactPath(Artifact.linuxDesktopPath); final String basePath = artifacts.getArtifactPath(Artifact.linuxDesktopPath);
for (File input in fs.directory(basePath) final List<File> inputs = <File>[];
.listSync(recursive: true) final List<File> outputs = <File>[];
.whereType<File>()) { final String outputPrefix = fs.path.join(
final String outputPath = fs.path.join( environment.projectDir.path,
environment.projectDir.path, 'linux',
'linux', 'flutter',
'flutter', 'ephemeral',
'ephemeral', );
fs.path.relative(input.path, from: basePath), // The native linux artifacts are composed of 6 files and a directory (listed above)
); // which need to be copied to the target directory.
final File destinationFile = fs.file(outputPath); for (String artifact in _kLinuxArtifacts) {
if (!destinationFile.parent.existsSync()) { final String entityPath = fs.path.join(basePath, artifact);
destinationFile.parent.createSync(recursive: true); // If this artifact is a file, just copy the source over.
if (fs.isFileSync(entityPath)) {
final String outputPath = fs.path.join(
outputPrefix,
fs.path.relative(entityPath, from: basePath),
);
final File destinationFile = fs.file(outputPath);
if (!destinationFile.parent.existsSync()) {
destinationFile.parent.createSync(recursive: true);
}
final File inputFile = fs.file(entityPath);
inputFile.copySync(destinationFile.path);
inputs.add(inputFile);
outputs.add(destinationFile);
continue;
}
// If the artifact is the directory cpp_client_wrapper, recursively
// copy every file from it.
for (File input in fs.directory(entityPath)
.listSync(recursive: true)
.whereType<File>()) {
final String outputPath = fs.path.join(
outputPrefix,
fs.path.relative(input.path, from: basePath),
);
final File destinationFile = fs.file(outputPath);
if (!destinationFile.parent.existsSync()) {
destinationFile.parent.createSync(recursive: true);
}
final File inputFile = fs.file(input);
inputFile.copySync(destinationFile.path);
inputs.add(inputFile);
outputs.add(destinationFile);
} }
fs.file(input).copySync(destinationFile.path);
} }
final Depfile depfile = Depfile(inputs, outputs);
depfile.writeToFile(environment.buildDir.childFile('linux_engine_sources.d'));
} }
} }
......
...@@ -7,15 +7,13 @@ import 'dart:async'; ...@@ -7,15 +7,13 @@ import 'dart:async';
import 'package:meta/meta.dart'; import 'package:meta/meta.dart';
import 'package:pool/pool.dart'; import 'package:pool/pool.dart';
import 'artifacts.dart';
import 'asset.dart'; import 'asset.dart';
import 'base/common.dart'; import 'base/common.dart';
import 'base/file_system.dart'; import 'base/file_system.dart';
import 'base/platform.dart';
import 'build_info.dart'; import 'build_info.dart';
import 'build_system/build_system.dart'; import 'build_system/build_system.dart';
import 'build_system/depfile.dart';
import 'build_system/targets/dart.dart'; import 'build_system/targets/dart.dart';
import 'compile.dart';
import 'dart/package_map.dart'; import 'dart/package_map.dart';
import 'devfs.dart'; import 'devfs.dart';
import 'globals.dart'; import 'globals.dart';
...@@ -45,10 +43,6 @@ String getKernelPathForTransformerOptions( ...@@ -45,10 +43,6 @@ String getKernelPathForTransformerOptions(
const String defaultPrivateKeyPath = 'privatekey.der'; const String defaultPrivateKeyPath = 'privatekey.der';
const String _kKernelKey = 'kernel_blob.bin';
const String _kVMSnapshotData = 'vm_snapshot_data';
const String _kIsolateSnapshotData = 'isolate_snapshot_data';
/// Provides a `build` method that builds the bundle. /// Provides a `build` method that builds the bundle.
class BundleBuilder { class BundleBuilder {
/// Builds the bundle for the given target platform. /// Builds the bundle for the given target platform.
...@@ -72,73 +66,29 @@ class BundleBuilder { ...@@ -72,73 +66,29 @@ class BundleBuilder {
List<String> extraGenSnapshotOptions = const <String>[], List<String> extraGenSnapshotOptions = const <String>[],
List<String> fileSystemRoots, List<String> fileSystemRoots,
String fileSystemScheme, String fileSystemScheme,
bool shouldBuildWithAssemble = false,
}) async { }) async {
mainPath ??= defaultMainPath; mainPath ??= defaultMainPath;
depfilePath ??= defaultDepfilePath; depfilePath ??= defaultDepfilePath;
assetDirPath ??= getAssetBuildDirectory(); assetDirPath ??= getAssetBuildDirectory();
packagesPath ??= fs.path.absolute(PackageMap.globalPackagesPath); packagesPath ??= fs.path.absolute(PackageMap.globalPackagesPath);
applicationKernelFilePath ??= getDefaultApplicationKernelPath(trackWidgetCreation: trackWidgetCreation);
final FlutterProject flutterProject = FlutterProject.current(); final FlutterProject flutterProject = FlutterProject.current();
await buildWithAssemble(
if (shouldBuildWithAssemble) { buildMode: buildMode ?? BuildMode.debug,
await buildWithAssemble( targetPlatform: platform,
buildMode: buildMode ?? BuildMode.debug, mainPath: mainPath,
targetPlatform: platform, flutterProject: flutterProject,
mainPath: mainPath, outputDir: assetDirPath,
flutterProject: flutterProject, depfilePath: depfilePath,
outputDir: assetDirPath, precompiled: precompiledSnapshot,
depfilePath: depfilePath,
precompiled: precompiledSnapshot,
);
return;
}
DevFSContent kernelContent;
if (!precompiledSnapshot) {
if ((extraFrontEndOptions != null) && extraFrontEndOptions.isNotEmpty) {
printTrace('Extra front-end options: $extraFrontEndOptions');
}
ensureDirectoryExists(applicationKernelFilePath);
final KernelCompiler kernelCompiler = await kernelCompilerFactory.create(flutterProject);
final CompilerOutput compilerOutput = await kernelCompiler.compile(
sdkRoot: artifacts.getArtifactPath(Artifact.flutterPatchedSdkPath, mode: buildMode),
mainPath: fs.file(mainPath).absolute.path,
outputFilePath: applicationKernelFilePath,
depFilePath: depfilePath,
buildMode: buildMode,
trackWidgetCreation: trackWidgetCreation,
extraFrontEndOptions: extraFrontEndOptions,
fileSystemRoots: fileSystemRoots,
fileSystemScheme: fileSystemScheme,
packagesPath: packagesPath,
);
if (compilerOutput?.outputFilename == null) {
throwToolExit('Compiler failed on $mainPath');
}
kernelContent = DevFSFileContent(fs.file(compilerOutput.outputFilename));
fs.directory(getBuildDirectory()).childFile('frontend_server.d')
.writeAsStringSync('frontend_server.d: ${artifacts.getArtifactPath(Artifact.frontendServerSnapshotForEngineDartSdk)}\n');
}
final AssetBundle assets = await buildAssets(
manifestPath: manifestPath,
assetDirPath: assetDirPath,
packagesPath: packagesPath,
reportLicensedPackages: reportLicensedPackages,
); );
if (assets == null) { // Work around for flutter_tester placing kernel artifacts in odd places.
throwToolExit('Error building assets', exitCode: 1); if (applicationKernelFilePath != null) {
final File outputDill = fs.directory(assetDirPath).childFile('kernel_blob.bin');
if (outputDill.existsSync()) {
outputDill.copySync(applicationKernelFilePath);
}
} }
return;
await assemble(
buildMode: buildMode,
assetBundle: assets,
kernelContent: kernelContent,
privateKeyPath: privateKeyPath,
assetDirPath: assetDirPath,
);
} }
} }
...@@ -178,30 +128,13 @@ Future<void> buildWithAssemble({ ...@@ -178,30 +128,13 @@ Future<void> buildWithAssemble({
} }
throwToolExit('Failed to build bundle.'); throwToolExit('Failed to build bundle.');
} }
if (depfilePath != null) {
// Output depfile format: final Depfile depfile = Depfile(result.inputFiles, result.outputFiles);
final StringBuffer buffer = StringBuffer(); final File outputDepfile = fs.file(depfilePath);
buffer.write('flutter_bundle'); if (!outputDepfile.parent.existsSync()) {
_writeFilesToBuffer(result.outputFiles, buffer); outputDepfile.parent.createSync(recursive: true);
buffer.write(': ');
_writeFilesToBuffer(result.inputFiles, buffer);
final File depfile = fs.file(depfilePath);
if (!depfile.parent.existsSync()) {
depfile.parent.createSync(recursive: true);
}
depfile.writeAsStringSync(buffer.toString());
}
void _writeFilesToBuffer(List<File> files, StringBuffer buffer) {
for (File outputFile in files) {
if (platform.isWindows) {
// Paths in a depfile have to be escaped on windows.
final String escapedPath = outputFile.path.replaceAll(r'\', r'\\');
buffer.write(' $escapedPath');
} else {
buffer.write(' ${outputFile.path}');
} }
depfile.writeToFile(outputDepfile);
} }
} }
...@@ -231,32 +164,6 @@ Future<AssetBundle> buildAssets({ ...@@ -231,32 +164,6 @@ Future<AssetBundle> buildAssets({
return assetBundle; return assetBundle;
} }
Future<void> assemble({
BuildMode buildMode,
AssetBundle assetBundle,
DevFSContent kernelContent,
String privateKeyPath = defaultPrivateKeyPath,
String assetDirPath,
}) async {
assetDirPath ??= getAssetBuildDirectory();
printTrace('Building bundle');
final Map<String, DevFSContent> assetEntries = Map<String, DevFSContent>.from(assetBundle.entries);
if (kernelContent != null) {
final String vmSnapshotData = artifacts.getArtifactPath(Artifact.vmSnapshotData, mode: buildMode);
final String isolateSnapshotData = artifacts.getArtifactPath(Artifact.isolateSnapshotData, mode: buildMode);
assetEntries[_kKernelKey] = kernelContent;
assetEntries[_kVMSnapshotData] = DevFSFileContent(fs.file(vmSnapshotData));
assetEntries[_kIsolateSnapshotData] = DevFSFileContent(fs.file(isolateSnapshotData));
}
printTrace('Writing asset files to $assetDirPath');
ensureDirectoryExists(assetDirPath);
await writeBundle(fs.directory(assetDirPath), assetEntries);
printTrace('Wrote $assetDirPath');
}
Future<void> writeBundle( Future<void> writeBundle(
Directory bundleDir, Directory bundleDir,
Map<String, DevFSContent> assetEntries, Map<String, DevFSContent> assetEntries,
......
...@@ -138,7 +138,6 @@ class BuildBundleCommand extends BuildSubCommand { ...@@ -138,7 +138,6 @@ class BuildBundleCommand extends BuildSubCommand {
extraGenSnapshotOptions: argResults[FlutterOptions.kExtraGenSnapshotOptions], extraGenSnapshotOptions: argResults[FlutterOptions.kExtraGenSnapshotOptions],
fileSystemScheme: argResults['filesystem-scheme'], fileSystemScheme: argResults['filesystem-scheme'],
fileSystemRoots: argResults['filesystem-root'], fileSystemRoots: argResults['filesystem-root'],
shouldBuildWithAssemble: true,
); );
return null; return null;
} }
......
...@@ -145,12 +145,13 @@ class FlutterTesterDevice extends Device { ...@@ -145,12 +145,13 @@ class FlutterTesterDevice extends Device {
trackWidgetCreation: buildInfo.trackWidgetCreation, trackWidgetCreation: buildInfo.trackWidgetCreation,
); );
await BundleBuilder().build( await BundleBuilder().build(
buildMode: buildInfo.mode,
mainPath: mainPath, mainPath: mainPath,
assetDirPath: assetDirPath, assetDirPath: assetDirPath,
applicationKernelFilePath: applicationKernelFilePath, applicationKernelFilePath: applicationKernelFilePath,
precompiledSnapshot: false, precompiledSnapshot: false,
buildMode: buildInfo.mode,
trackWidgetCreation: buildInfo.trackWidgetCreation, trackWidgetCreation: buildInfo.trackWidgetCreation,
platform: getTargetPlatformForName(getNameForHostPlatform(getCurrentHostPlatform())),
); );
command.add('--flutter-assets-dir=$assetDirPath'); command.add('--flutter-assets-dir=$assetDirPath');
......
...@@ -106,13 +106,12 @@ void main() { ...@@ -106,13 +106,12 @@ void main() {
); );
}); });
test('Throws exception if asked to build with missing inputs', () => testbed.run(() async { test('Does not throw exception if asked to build with missing inputs', () => testbed.run(() async {
// Delete required input file. // Delete required input file.
fs.file('foo.dart').deleteSync(); fs.file('foo.dart').deleteSync();
final BuildResult buildResult = await buildSystem.build(fooTarget, environment); final BuildResult buildResult = await buildSystem.build(fooTarget, environment);
expect(buildResult.hasException, true); expect(buildResult.hasException, false);
expect(buildResult.exceptions.values.single.exception, isInstanceOf<MissingInputException>());
})); }));
test('Throws exception if it does not produce a specified output', () => testbed.run(() async { test('Throws exception if it does not produce a specified output', () => testbed.run(() async {
......
// Copyright 2019 The Chromium 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:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/build_system/depfile.dart';
import '../../src/common.dart';
import '../../src/testbed.dart';
void main() {
Testbed testbed;
setUp(() {
testbed = Testbed();
});
test('Can parse depfile from file', () => testbed.run(() {
final File depfileSource = fs.file('example.d')..writeAsStringSync('''
a.txt: b.txt
''');
final Depfile depfile = Depfile.parse(depfileSource);
expect(depfile.inputs.single.path, 'b.txt');
expect(depfile.outputs.single.path, 'a.txt');
}));
test('Can parse depfile with multiple inputs', () => testbed.run(() {
final FileSystem fs = MemoryFileSystem();
final File depfileSource = fs.file('example.d')..writeAsStringSync('''
a.txt: b.txt c.txt d.txt
''');
final Depfile depfile = Depfile.parse(depfileSource);
expect(depfile.inputs.map((File file) => file.path), <String>[
'b.txt',
'c.txt',
'd.txt',
]);
expect(depfile.outputs.single.path, 'a.txt');
}));
test('Can parse depfile with multiple outputs', () => testbed.run(() {
final FileSystem fs = MemoryFileSystem();
final File depfileSource = fs.file('example.d')..writeAsStringSync('''
a.txt c.txt d.txt: b.txt
''');
final Depfile depfile = Depfile.parse(depfileSource);
expect(depfile.inputs.single.path, 'b.txt');
expect(depfile.outputs.map((File file) => file.path), <String>[
'a.txt',
'c.txt',
'd.txt',
]);
}));
test('Can parse depfile with windows file paths', () => testbed.run(() {
final FileSystem fs = MemoryFileSystem();
final File depfileSource = fs.file('example.d')..writeAsStringSync(r'''
C:\\a.txt: C:\\b.txt
''');
final Depfile depfile = Depfile.parse(depfileSource);
expect(depfile.inputs.single.path, r'C:\b.txt');
expect(depfile.outputs.single.path, r'C:\a.txt');
}, overrides: <Type, Generator>{
FileSystem: () => MemoryFileSystem(style: FileSystemStyle.windows),
}));
test('Resillient to weird whitespace', () => testbed.run(() {
final FileSystem fs = MemoryFileSystem();
final File depfileSource = fs.file('example.d')..writeAsStringSync(r'''
a.txt
: b.txt c.txt
''');
final Depfile depfile = Depfile.parse(depfileSource);
expect(depfile.inputs, hasLength(2));
expect(depfile.outputs.single.path, 'a.txt');
}));
test('Resillient to duplicate files', () => testbed.run(() {
final FileSystem fs = MemoryFileSystem();
final File depfileSource = fs.file('example.d')..writeAsStringSync(r'''
a.txt: b.txt b.txt
''');
final Depfile depfile = Depfile.parse(depfileSource);
expect(depfile.inputs.single.path, 'b.txt');
expect(depfile.outputs.single.path, 'a.txt');
}));
test('Resillient to malformed file, missing :', () => testbed.run(() {
final FileSystem fs = MemoryFileSystem();
final File depfileSource = fs.file('example.d')..writeAsStringSync(r'''
a.text b.txt
''');
final Depfile depfile = Depfile.parse(depfileSource);
expect(depfile.inputs, isEmpty);
expect(depfile.outputs, isEmpty);
}));
}
...@@ -39,6 +39,7 @@ void main() { ...@@ -39,6 +39,7 @@ void main() {
kBuildMode: 'debug', kBuildMode: 'debug',
} }
); );
fs.file('bin/cache/artifacts/engine/linux-x64/unrelated-stuff').createSync(recursive: true);
fs.file('bin/cache/artifacts/engine/linux-x64/libflutter_linux_glfw.so').createSync(recursive: true); fs.file('bin/cache/artifacts/engine/linux-x64/libflutter_linux_glfw.so').createSync(recursive: true);
fs.file('bin/cache/artifacts/engine/linux-x64/flutter_export.h').createSync(); fs.file('bin/cache/artifacts/engine/linux-x64/flutter_export.h').createSync();
fs.file('bin/cache/artifacts/engine/linux-x64/flutter_messenger.h').createSync(); fs.file('bin/cache/artifacts/engine/linux-x64/flutter_messenger.h').createSync();
...@@ -53,7 +54,7 @@ void main() { ...@@ -53,7 +54,7 @@ void main() {
}); });
}); });
test('Copies files to correct cache directory', () => testbed.run(() async { test('Copies files to correct cache directory, excluding unrelated code', () => testbed.run(() async {
final BuildResult result = await buildSystem.build(const UnpackLinuxDebug(), environment); final BuildResult result = await buildSystem.build(const UnpackLinuxDebug(), environment);
expect(result.hasException, false); expect(result.hasException, false);
...@@ -64,6 +65,7 @@ void main() { ...@@ -64,6 +65,7 @@ void main() {
expect(fs.file('linux/flutter/ephemeral/flutter_glfw.h').existsSync(), true); expect(fs.file('linux/flutter/ephemeral/flutter_glfw.h').existsSync(), true);
expect(fs.file('linux/flutter/ephemeral/icudtl.dat').existsSync(), true); expect(fs.file('linux/flutter/ephemeral/icudtl.dat').existsSync(), true);
expect(fs.file('linux/flutter/ephemeral/cpp_client_wrapper_glfw/foo').existsSync(), true); expect(fs.file('linux/flutter/ephemeral/cpp_client_wrapper_glfw/foo').existsSync(), true);
expect(fs.file('linux/flutter/ephemeral/unrelated-stuff').existsSync(), false);
})); }));
test('Does not re-copy files unecessarily', () => testbed.run(() async { test('Does not re-copy files unecessarily', () => testbed.run(() async {
......
...@@ -43,7 +43,7 @@ void main() { ...@@ -43,7 +43,7 @@ void main() {
); );
expect(fs.file(fs.path.join('example', 'kernel_blob.bin')).existsSync(), true); expect(fs.file(fs.path.join('example', 'kernel_blob.bin')).existsSync(), true);
expect(fs.file(fs.path.join('example', 'LICENSE')).existsSync(), true); expect(fs.file(fs.path.join('example', 'LICENSE')).existsSync(), true);
expect(fs.file(fs.path.join('example.d')).existsSync(), true); expect(fs.file(fs.path.join('example.d')).existsSync(), false);
})); }));
test('Handles build system failure', () => testbed.run(() { test('Handles build system failure', () => testbed.run(() {
......
...@@ -9,10 +9,8 @@ import 'package:file/memory.dart'; ...@@ -9,10 +9,8 @@ import 'package:file/memory.dart';
import 'package:flutter_tools/src/artifacts.dart'; import 'package:flutter_tools/src/artifacts.dart';
import 'package:flutter_tools/src/base/platform.dart'; import 'package:flutter_tools/src/base/platform.dart';
import 'package:flutter_tools/src/build_info.dart'; import 'package:flutter_tools/src/build_info.dart';
import 'package:flutter_tools/src/cache.dart'; import 'package:flutter_tools/src/build_system/build_system.dart';
import 'package:flutter_tools/src/compile.dart';
import 'package:flutter_tools/src/device.dart'; import 'package:flutter_tools/src/device.dart';
import 'package:flutter_tools/src/project.dart';
import 'package:flutter_tools/src/tester/flutter_tester.dart'; import 'package:flutter_tools/src/tester/flutter_tester.dart';
import 'package:mockito/mockito.dart'; import 'package:mockito/mockito.dart';
import 'package:process/process.dart'; import 'package:process/process.dart';
...@@ -102,24 +100,23 @@ void main() { ...@@ -102,24 +100,23 @@ void main() {
String mainPath; String mainPath;
MockArtifacts mockArtifacts; MockArtifacts mockArtifacts;
MockKernelCompiler mockKernelCompiler;
MockProcessManager mockProcessManager; MockProcessManager mockProcessManager;
MockProcess mockProcess; MockProcess mockProcess;
MockBuildSystem mockBuildSystem;
final Map<Type, Generator> startOverrides = <Type, Generator>{ final Map<Type, Generator> startOverrides = <Type, Generator>{
Platform: () => FakePlatform(operatingSystem: 'linux'), Platform: () => FakePlatform(operatingSystem: 'linux'),
FileSystem: () => fs, FileSystem: () => fs,
ProcessManager: () => mockProcessManager, ProcessManager: () => mockProcessManager,
Cache: () => Cache(rootOverride: fs.directory(flutterRoot)),
KernelCompilerFactory: () => FakeKernelCompilerFactory(mockKernelCompiler),
Artifacts: () => mockArtifacts, Artifacts: () => mockArtifacts,
BuildSystem: () => mockBuildSystem,
}; };
setUp(() { setUp(() {
mockBuildSystem = MockBuildSystem();
flutterRoot = fs.path.join('home', 'me', 'flutter'); flutterRoot = fs.path.join('home', 'me', 'flutter');
flutterTesterPath = fs.path.join(flutterRoot, 'bin', 'cache', flutterTesterPath = fs.path.join(flutterRoot, 'bin', 'cache',
'artifacts', 'engine', 'linux-x64', 'flutter_tester'); 'artifacts', 'engine', 'linux-x64', 'flutter_tester');
final File flutterTesterFile = fs.file(flutterTesterPath); final File flutterTesterFile = fs.file(flutterTesterPath);
flutterTesterFile.parent.createSync(recursive: true); flutterTesterFile.parent.createSync(recursive: true);
flutterTesterFile.writeAsBytesSync(const <int>[]); flutterTesterFile.writeAsBytesSync(const <int>[]);
...@@ -139,24 +136,23 @@ void main() { ...@@ -139,24 +136,23 @@ void main() {
mode: anyNamed('mode') mode: anyNamed('mode')
)).thenReturn(artifactPath); )).thenReturn(artifactPath);
mockKernelCompiler = MockKernelCompiler(); when(mockBuildSystem.build(
any,
any,
)).thenAnswer((_) async {
fs.file('$mainPath.dill').createSync(recursive: true);
return BuildResult(success: true);
});
}); });
testUsingContext('not debug', () async { testUsingContext('not debug', () async {
final LaunchResult result = await device.startApp(null, final LaunchResult result = await device.startApp(null,
mainPath: mainPath, mainPath: mainPath,
debuggingOptions: DebuggingOptions.disabled(const BuildInfo(BuildMode.release, null))); debuggingOptions: DebuggingOptions.disabled(const BuildInfo(BuildMode.release, null)));
expect(result.started, isFalse); expect(result.started, isFalse);
}, overrides: startOverrides); }, overrides: startOverrides);
testUsingContext('no flutter_tester', () async {
fs.file(flutterTesterPath).deleteSync();
expect(() async {
await device.startApp(null,
mainPath: mainPath,
debuggingOptions: DebuggingOptions.disabled(const BuildInfo(BuildMode.debug, null)));
}, throwsToolExit());
}, overrides: startOverrides);
testUsingContext('start', () async { testUsingContext('start', () async {
final Uri observatoryUri = Uri.parse('http://127.0.0.1:6666/'); final Uri observatoryUri = Uri.parse('http://127.0.0.1:6666/');
...@@ -168,22 +164,6 @@ Hello! ...@@ -168,22 +164,6 @@ Hello!
.codeUnits, .codeUnits,
])); ]));
when(mockKernelCompiler.compile(
sdkRoot: anyNamed('sdkRoot'),
mainPath: anyNamed('mainPath'),
outputFilePath: anyNamed('outputFilePath'),
depFilePath: anyNamed('depFilePath'),
buildMode: BuildMode.debug,
trackWidgetCreation: anyNamed('trackWidgetCreation'),
extraFrontEndOptions: anyNamed('extraFrontEndOptions'),
fileSystemRoots: anyNamed('fileSystemRoots'),
fileSystemScheme: anyNamed('fileSystemScheme'),
packagesPath: anyNamed('packagesPath'),
)).thenAnswer((_) async {
fs.file('$mainPath.dill').createSync(recursive: true);
return CompilerOutput('$mainPath.dill', 0, <Uri>[]);
});
final LaunchResult result = await device.startApp(null, final LaunchResult result = await device.startApp(null,
mainPath: mainPath, mainPath: mainPath,
debuggingOptions: DebuggingOptions.enabled(const BuildInfo(BuildMode.debug, null))); debuggingOptions: DebuggingOptions.enabled(const BuildInfo(BuildMode.debug, null)));
...@@ -195,15 +175,5 @@ Hello! ...@@ -195,15 +175,5 @@ Hello!
}); });
} }
class MockBuildSystem extends Mock implements BuildSystem {}
class MockArtifacts extends Mock implements Artifacts {} class MockArtifacts extends Mock implements Artifacts {}
class MockKernelCompiler extends Mock implements KernelCompiler {}
class FakeKernelCompilerFactory implements KernelCompilerFactory {
FakeKernelCompilerFactory(this.kernelCompiler);
final KernelCompiler kernelCompiler;
@override
Future<KernelCompiler> create(FlutterProject flutterProject) async {
return kernelCompiler;
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment