Unverified Commit 72343ee0 authored by Jonah Williams's avatar Jonah Williams Committed by GitHub

[flutter_tools] refactor build_system and targets to be context-free (#53268)

parent 4605b51a
......@@ -95,7 +95,11 @@ class AotBuilder {
kExtraFrontEndOptions: buildInfo.extraFrontEndOptions.join(','),
if (platform == TargetPlatform.ios)
kIosArchs: iosBuildArchs.map(getNameForDarwinArch).join(' ')
}
},
artifacts: globals.artifacts,
fileSystem: globals.fs,
logger: globals.logger,
processManager: globals.processManager,
);
final BuildResult result = await globals.buildSystem.build(target, environment);
status?.stop();
......
......@@ -382,7 +382,7 @@ class BufferLogger extends Logger {
_timeoutConfiguration = timeoutConfiguration,
_stopwatchFactory = stopwatchFactory;
@visibleForTesting
/// Create a [BufferLogger] with test preferences.
BufferLogger.test({
Terminal terminal,
OutputPreferences outputPreferences,
......
......@@ -8,13 +8,16 @@ import 'package:async/async.dart';
import 'package:convert/convert.dart';
import 'package:crypto/crypto.dart';
import 'package:meta/meta.dart';
import 'package:platform/platform.dart';
import 'package:pool/pool.dart';
import 'package:process/process.dart';
import '../artifacts.dart';
import '../base/file_system.dart';
import '../base/logger.dart';
import '../base/utils.dart';
import '../cache.dart';
import '../convert.dart';
import '../globals.dart' as globals;
import 'exceptions.dart';
import 'file_hash_store.dart';
import 'source.dart';
......@@ -286,6 +289,10 @@ class Environment {
@required Directory outputDir,
@required Directory cacheDir,
@required Directory flutterRootDir,
@required FileSystem fileSystem,
@required Logger logger,
@required Artifacts artifacts,
@required ProcessManager processManager,
Directory buildDir,
Map<String, String> defines = const <String, String>{},
}) {
......@@ -314,6 +321,10 @@ class Environment {
cacheDir: cacheDir,
defines: defines,
flutterRootDir: flutterRootDir,
fileSystem: fileSystem,
logger: logger,
artifacts: artifacts,
processManager: processManager,
);
}
......@@ -327,6 +338,10 @@ class Environment {
Directory flutterRootDir,
Directory buildDir,
Map<String, String> defines = const <String, String>{},
@required FileSystem fileSystem,
@required Logger logger,
@required Artifacts artifacts,
@required ProcessManager processManager,
}) {
return Environment(
projectDir: projectDir ?? testDirectory,
......@@ -335,6 +350,10 @@ class Environment {
flutterRootDir: flutterRootDir ?? testDirectory,
buildDir: buildDir,
defines: defines,
fileSystem: fileSystem,
logger: logger,
artifacts: artifacts,
processManager: processManager,
);
}
......@@ -346,6 +365,10 @@ class Environment {
@required this.cacheDir,
@required this.defines,
@required this.flutterRootDir,
@required this.processManager,
@required this.logger,
@required this.fileSystem,
@required this.artifacts,
});
/// The [Source] value which is substituted with the path to [projectDir].
......@@ -399,6 +422,14 @@ class Environment {
/// The root build directory shared by all builds.
final Directory rootBuildDir;
final ProcessManager processManager;
final Logger logger;
final Artifacts artifacts;
final FileSystem fileSystem;
}
/// The result information from the build system.
......@@ -422,7 +453,17 @@ class BuildResult {
/// The build system is responsible for invoking and ordering [Target]s.
class BuildSystem {
const BuildSystem();
const BuildSystem({
@required FileSystem fileSystem,
@required Platform platform,
@required Logger logger,
}) : _fileSystem = fileSystem,
_platform = platform,
_logger = logger;
final FileSystem _fileSystem;
final Platform _platform;
final Logger _logger;
/// Build `target` and all of its dependencies.
Future<BuildResult> build(
......@@ -436,15 +477,22 @@ class BuildSystem {
// Load file hash store from previous builds.
final FileHashStore fileCache = FileHashStore(
environment: environment,
fileSystem: globals.fs,
logger: globals.logger,
fileSystem: _fileSystem,
logger: _logger,
)..initialize();
// Perform sanity checks on build.
checkCycles(target);
final Node node = target._toNode(environment);
final _BuildInstance buildInstance = _BuildInstance(environment, fileCache, buildSystemConfig);
final _BuildInstance buildInstance = _BuildInstance(
environment: environment,
fileCache: fileCache,
buildSystemConfig: buildSystemConfig,
logger: _logger,
fileSystem: _fileSystem,
platform: _platform,
);
bool passed = true;
try {
passed = await buildInstance.invokeTarget(node);
......@@ -486,9 +534,18 @@ class BuildSystem {
/// An active instance of a build.
class _BuildInstance {
_BuildInstance(this.environment, this.fileCache, this.buildSystemConfig)
: resourcePool = Pool(buildSystemConfig.resourcePoolSize ?? globals.platform?.numberOfProcessors ?? 1);
_BuildInstance({
this.environment,
this.fileCache,
this.buildSystemConfig,
this.logger,
this.fileSystem,
Platform platform,
})
: resourcePool = Pool(buildSystemConfig.resourcePoolSize ?? platform?.numberOfProcessors ?? 1);
final Logger logger;
final FileSystem fileSystem;
final BuildSystemConfig buildSystemConfig;
final Pool resourcePool;
final Map<String, AsyncMemoizer<bool>> pending = <String, AsyncMemoizer<bool>>{};
......@@ -545,17 +602,17 @@ class _BuildInstance {
// If we're missing a depfile, wait until after evaluating the target to
// compute changes.
final bool canSkip = !node.missingDepfile &&
await node.computeChanges(environment, fileCache);
await node.computeChanges(environment, fileCache, fileSystem, logger);
if (canSkip) {
skipped = true;
globals.printTrace('Skipping target: ${node.target.name}');
logger.printTrace('Skipping target: ${node.target.name}');
updateGraph();
return passed;
}
globals.printTrace('${node.target.name}: Starting due to ${node.invalidatedReasons}');
logger.printTrace('${node.target.name}: Starting due to ${node.invalidatedReasons}');
await node.target.build(environment);
globals.printTrace('${node.target.name}: Complete');
logger.printTrace('${node.target.name}: Complete');
node.inputs
..clear()
......@@ -581,7 +638,7 @@ class _BuildInstance {
if (outputFiles.containsKey(previousOutput)) {
continue;
}
final File previousFile = globals.fs.file(previousOutput);
final File previousFile = fileSystem.file(previousOutput);
if (previousFile.existsSync()) {
previousFile.deleteSync();
}
......@@ -771,6 +828,8 @@ class Node {
Future<bool> computeChanges(
Environment environment,
FileHashStore fileHashStore,
FileSystem fileSystem,
Logger logger,
) async {
final Set<String> currentOutputPaths = <String>{
for (final File file in outputs) file.path,
......@@ -808,7 +867,7 @@ class Node {
// if this isn't a current output file there is no reason to compute the hash.
continue;
}
final File file = globals.fs.file(previousOutput);
final File file = fileSystem.file(previousOutput);
if (!file.existsSync()) {
invalidatedReasons.add(InvalidatedReason.outputMissing);
_dirty = true;
......@@ -833,7 +892,7 @@ class Node {
if (missingInputs.isNotEmpty) {
_dirty = true;
final String missingMessage = missingInputs.map((File file) => file.path).join(', ');
globals.printTrace('invalidated build due to missing files: $missingMessage');
logger.printTrace('invalidated build due to missing files: $missingMessage');
invalidatedReasons.add(InvalidatedReason.inputMissing);
}
......
......@@ -5,7 +5,6 @@
import '../artifacts.dart';
import '../base/file_system.dart';
import '../build_info.dart';
import '../globals.dart' as globals;
import 'build_system.dart';
import 'exceptions.dart';
......@@ -24,7 +23,7 @@ abstract class ResolvedFiles {
/// Collects sources for a [Target] into a single list of [FileSystemEntities].
class SourceVisitor implements ResolvedFiles {
/// Create a new [SourceVisitor] from an [Environment].
SourceVisitor(this.environment, [this.inputs = true]);
SourceVisitor(this.environment, [ this.inputs = true ]);
/// The current environment.
final Environment environment;
......@@ -56,7 +55,7 @@ class SourceVisitor implements ResolvedFiles {
final String contents = depfile.readAsStringSync();
final List<String> colonSeparated = contents.split(': ');
if (colonSeparated.length != 2) {
globals.printError('Invalid depfile: ${depfile.path}');
environment.logger.printError('Invalid depfile: ${depfile.path}');
return;
}
if (inputs) {
......@@ -78,7 +77,7 @@ class SourceVisitor implements ResolvedFiles {
.map<String>((String path) => path.replaceAllMapped(_escapeExpr, (Match match) => match.group(1)).trim())
.where((String path) => path.isNotEmpty)
.toSet()
.map((String path) => globals.fs.file(path));
.map(environment.fileSystem.file);
}
/// Visit a [Source] which contains a file URL.
......@@ -101,35 +100,36 @@ class SourceVisitor implements ResolvedFiles {
switch (rawParts.first) {
case Environment.kProjectDirectory:
segments.addAll(
globals.fs.path.split(environment.projectDir.resolveSymbolicLinksSync()));
environment.fileSystem.path.split(environment.projectDir.resolveSymbolicLinksSync()));
break;
case Environment.kBuildDirectory:
segments.addAll(globals.fs.path.split(
segments.addAll(environment.fileSystem.path.split(
environment.buildDir.resolveSymbolicLinksSync()));
break;
case Environment.kCacheDirectory:
segments.addAll(
globals.fs.path.split(environment.cacheDir.resolveSymbolicLinksSync()));
environment.fileSystem.path.split(environment.cacheDir.resolveSymbolicLinksSync()));
break;
case Environment.kFlutterRootDirectory:
// flutter root will not contain a symbolic link.
segments.addAll(
globals.fs.path.split(environment.flutterRootDir.absolute.path));
environment.fileSystem.path.split(environment.flutterRootDir.absolute.path));
break;
case Environment.kOutputDirectory:
segments.addAll(
globals.fs.path.split(environment.outputDir.resolveSymbolicLinksSync()));
environment.fileSystem.path.split(environment.outputDir.resolveSymbolicLinksSync()));
break;
default:
throw InvalidPatternException(pattern);
}
rawParts.skip(1).forEach(segments.add);
final String filePath = globals.fs.path.joinAll(segments);
final String filePath = environment.fileSystem.path.joinAll(segments);
if (!hasWildcard) {
if (optional && !globals.fs.isFileSync(filePath)) {
if (optional && !environment.fileSystem.isFileSync(filePath)) {
return;
}
sources.add(globals.fs.file(globals.fs.path.normalize(filePath)));
sources.add(environment.fileSystem.file(
environment.fileSystem.path.normalize(filePath)));
return;
}
// Perform a simple match by splitting the wildcard containing file one
......@@ -143,21 +143,21 @@ class SourceVisitor implements ResolvedFiles {
if (wildcardSegments.length > 2) {
throw InvalidPatternException(pattern);
}
if (!globals.fs.directory(filePath).existsSync()) {
if (!environment.fileSystem.directory(filePath).existsSync()) {
throw Exception('$filePath does not exist!');
}
for (final FileSystemEntity entity in globals.fs.directory(filePath).listSync()) {
final String filename = globals.fs.path.basename(entity.path);
for (final FileSystemEntity entity in environment.fileSystem.directory(filePath).listSync()) {
final String filename = environment.fileSystem.path.basename(entity.path);
if (wildcardSegments.isEmpty) {
sources.add(globals.fs.file(entity.absolute));
sources.add(environment.fileSystem.file(entity.absolute));
} else if (wildcardSegments.length == 1) {
if (filename.startsWith(wildcardSegments[0]) ||
filename.endsWith(wildcardSegments[0])) {
sources.add(globals.fs.file(entity.absolute));
sources.add(environment.fileSystem.file(entity.absolute));
}
} else if (filename.startsWith(wildcardSegments[0])) {
if (filename.substring(wildcardSegments[0].length).endsWith(wildcardSegments[1])) {
sources.add(globals.fs.file(entity.absolute));
sources.add(environment.fileSystem.file(entity.absolute));
}
}
}
......@@ -167,15 +167,16 @@ class SourceVisitor implements ResolvedFiles {
///
/// If the [Artifact] points to a directory then all child files are included.
void visitArtifact(Artifact artifact, TargetPlatform platform, BuildMode mode) {
final String path = globals.artifacts.getArtifactPath(artifact, platform: platform, mode: mode);
if (globals.fs.isDirectorySync(path)) {
final String path = environment.artifacts
.getArtifactPath(artifact, platform: platform, mode: mode);
if (environment.fileSystem.isDirectorySync(path)) {
sources.addAll(<File>[
for (FileSystemEntity entity in globals.fs.directory(path).listSync(recursive: true))
for (FileSystemEntity entity in environment.fileSystem.directory(path).listSync(recursive: true))
if (entity is File)
entity,
]);
} else {
sources.add(globals.fs.file(path));
sources.add(environment.fileSystem.file(path));
}
}
}
......
......@@ -5,7 +5,6 @@
import '../../artifacts.dart';
import '../../base/file_system.dart';
import '../../build_info.dart';
import '../../globals.dart' as globals;
import '../build_system.dart';
/// Copies the Windows desktop embedding files to the copy directory.
......@@ -40,21 +39,24 @@ class UnpackWindows extends Target {
@override
Future<void> build(Environment environment) async {
// This path needs to match the prefix in the rule below.
final String basePath = globals.artifacts.getArtifactPath(Artifact.windowsDesktopPath);
for (final File input in globals.fs.directory(basePath)
final String basePath = environment.artifacts
.getArtifactPath(Artifact.windowsDesktopPath);
for (final File input in environment.fileSystem.directory(basePath)
.listSync(recursive: true)
.whereType<File>()) {
final String outputPath = globals.fs.path.join(
final String outputPath = environment.fileSystem.path.join(
environment.projectDir.path,
'windows',
'flutter',
globals.fs.path.relative(input.path, from: basePath),
environment.fileSystem.path
.relative(input.path, from: basePath),
);
final File destinationFile = globals.fs.file(outputPath);
final File destinationFile = environment.fileSystem.file(outputPath);
if (!destinationFile.parent.existsSync()) {
destinationFile.parent.createSync(recursive: true);
}
globals.fs.file(input).copySync(destinationFile.path);
environment.fileSystem
.file(input).copySync(destinationFile.path);
}
}
}
......@@ -132,6 +132,10 @@ Future<void> buildWithAssemble({
if (dartDefines != null && dartDefines.isNotEmpty)
kDartDefines: jsonEncode(dartDefines),
},
artifacts: globals.artifacts,
fileSystem: globals.fs,
logger: globals.logger,
processManager: globals.processManager,
);
final Target target = buildMode == BuildMode.debug
? const CopyFlutterBundle()
......
......@@ -151,6 +151,10 @@ class AssembleCommand extends FlutterCommand {
defines: _parseDefines(stringsArg('define')),
cacheDir: globals.cache.getRoot(),
flutterRootDir: globals.fs.directory(Cache.flutterRoot),
artifacts: globals.artifacts,
fileSystem: globals.fs,
logger: globals.logger,
processManager: globals.processManager,
);
return result;
}
......
......@@ -89,7 +89,11 @@ Future<T> runInContext<T>(
platform: globals.platform,
),
AssetBundleFactory: () => AssetBundleFactory.defaultInstance,
BuildSystem: () => const BuildSystem(),
BuildSystem: () => BuildSystem(
fileSystem: globals.fs,
logger: globals.logger,
platform: globals.platform,
),
Cache: () => Cache(
fileSystem: globals.fs,
logger: globals.logger,
......
......@@ -53,6 +53,10 @@ Future<void> buildWeb(
kCspMode: csp.toString(),
kIconTreeShakerFlag: buildInfo.treeShakeIcons.toString(),
},
artifacts: globals.artifacts,
fileSystem: globals.fs,
logger: globals.logger,
processManager: globals.processManager,
));
if (!result.success) {
for (final ExceptionMeasurement measurement in result.exceptions.values) {
......
......@@ -2,28 +2,23 @@
// 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/logger.dart';
import 'package:mockito/mockito.dart';
import 'package:platform/platform.dart';
import 'package:flutter_tools/src/base/file_system.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/cache.dart';
import 'package:flutter_tools/src/convert.dart';
import 'package:flutter_tools/src/globals.dart' as globals;
import 'package:mockito/mockito.dart';
import '../../src/common.dart';
import '../../src/context.dart';
import '../../src/testbed.dart';
void main() {
setUpAll(() {
Cache.disableLocking();
});
const BuildSystem buildSystem = BuildSystem();
Testbed testbed;
MockPlatform mockPlatform;
FileSystem fileSystem;
Environment environment;
Target fooTarget;
Target barTarget;
......@@ -34,14 +29,12 @@ void main() {
int shared;
setUp(() {
fileSystem = MemoryFileSystem.test();
fooInvocations = 0;
barInvocations = 0;
shared = 0;
mockPlatform = MockPlatform();
// Keep file paths the same.
when(mockPlatform.isWindows).thenReturn(false);
/// Create various testing targets.
/// Create various test targets.
fooTarget = TestTarget((Environment environment) async {
environment
.buildDir
......@@ -91,31 +84,34 @@ void main() {
..inputs = const <Source>[
Source.pattern('{PROJECT_DIR}/foo.dart'),
];
testbed = Testbed(
setup: () {
environment = Environment.test(
globals.fs.currentDirectory,
fileSystem.currentDirectory,
artifacts: MockArtifacts(),
processManager: FakeProcessManager.any(),
fileSystem: fileSystem,
logger: BufferLogger.test(),
);
globals.fs.file('foo.dart')
fileSystem.file('foo.dart')
..createSync(recursive: true)
..writeAsStringSync('');
globals.fs.file('pubspec.yaml').createSync();
},
overrides: <Type, Generator>{
Platform: () => mockPlatform,
},
);
fileSystem.file('pubspec.yaml').createSync();
});
test('Does not throw exception if asked to build with missing inputs', () => testbed.run(() async {
testWithoutContext('Does not throw exception if asked to build with missing inputs', () async {
final BuildSystem buildSystem = setUpBuildSystem(fileSystem);
// Delete required input file.
globals.fs.file('foo.dart').deleteSync();
fileSystem.file('foo.dart').deleteSync();
final BuildResult buildResult = await buildSystem.build(fooTarget, environment);
expect(buildResult.hasException, false);
}));
});
test('Does not throw exception if it does not produce a specified output', () => testbed.run(() async {
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'),
......@@ -126,81 +122,98 @@ void main() {
final BuildResult result = await buildSystem.build(badTarget, environment);
expect(result.hasException, false);
}));
});
test('Saves a stamp file with inputs and outputs', () => testbed.run(() async {
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 File stampFile = globals.fs.file(globals.fs.path.join(environment.buildDir.path, 'foo.stamp'));
expect(stampFile.existsSync(), true);
final Map<String, dynamic> stampContents = castStringKeyedMap(
json.decode(stampFile.readAsStringSync()));
final Map<String, dynamic> stampContents = castStringKeyedMap(json.decode(stampFile.readAsStringSync()));
expect(stampContents['inputs'], <Object>['/foo.dart']);
}));
expect(stampContents, containsPair('inputs', <Object>['/foo.dart']));
});
test('Creates a BuildResult with inputs and outputs', () => testbed.run(() async {
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, globals.fs.path.absolute('foo.dart'));
expect(result.outputFiles.single.path,
globals.fs.path.absolute(globals.fs.path.join(environment.buildDir.path, 'out')));
}));
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);
test('Does not re-invoke build if stamp is valid', () => testbed.run(() async {
await buildSystem.build(fooTarget, environment);
await buildSystem.build(fooTarget, environment);
expect(fooInvocations, 1);
}));
});
test('Re-invoke build if input is modified', () => testbed.run(() async {
testWithoutContext('Re-invoke build if input is modified', () async {
final BuildSystem buildSystem = setUpBuildSystem(fileSystem);
await buildSystem.build(fooTarget, environment);
globals.fs.file('foo.dart').writeAsStringSync('new contents');
fileSystem.file('foo.dart').writeAsStringSync('new contents');
await buildSystem.build(fooTarget, environment);
expect(fooInvocations, 2);
}));
});
test('does not re-invoke build if input timestamp changes', () => testbed.run(() async {
testWithoutContext('does not re-invoke build if input timestamp changes', () async {
final BuildSystem buildSystem = setUpBuildSystem(fileSystem);
await buildSystem.build(fooTarget, environment);
globals.fs.file('foo.dart').writeAsStringSync('');
// The file was previously empty so this does not modify it.
fileSystem.file('foo.dart').writeAsStringSync('');
await buildSystem.build(fooTarget, environment);
expect(fooInvocations, 1);
}));
});
test('does not re-invoke build if output timestamp changes', () => testbed.run(() async {
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);
}));
});
test('Re-invoke build if output is modified', () => testbed.run(() async {
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);
}));
});
test('Runs dependencies of targets', () => testbed.run(() async {
testWithoutContext('Runs dependencies of targets', () async {
final BuildSystem buildSystem = setUpBuildSystem(fileSystem);
barTarget.dependencies.add(fooTarget);
await buildSystem.build(barTarget, environment);
expect(globals.fs.file(globals.fs.path.join(environment.buildDir.path, 'bar')).existsSync(), true);
expect(fileSystem.file('${environment.buildDir.path}/bar'), exists);
expect(fooInvocations, 1);
expect(barInvocations, 1);
}));
});
test('Only invokes shared dependencies once', () => testbed.run(() async {
testWithoutContext('Only invokes shared dependencies once', () async {
final BuildSystem buildSystem = setUpBuildSystem(fileSystem);
fooTarget.dependencies.add(sharedTarget);
barTarget.dependencies.add(sharedTarget);
barTarget.dependencies.add(fooTarget);
......@@ -208,19 +221,20 @@ void main() {
await buildSystem.build(barTarget, environment);
expect(shared, 1);
}));
});
test('Automatically cleans old outputs when dag changes', () => testbed.run(() async {
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')];
globals.fs.file('foo.dart').createSync();
fileSystem.file('foo.dart').createSync();
await buildSystem.build(testTarget, environment);
expect(environment.buildDir.childFile('foo.out').existsSync(), true);
expect(environment.buildDir.childFile('foo.out'), exists);
final TestTarget testTarget2 = TestTarget((Environment envionment) async {
environment.buildDir.childFile('bar.out').createSync();
......@@ -230,80 +244,84 @@ void main() {
await buildSystem.build(testTarget2, environment);
expect(environment.buildDir.childFile('bar.out').existsSync(), true);
expect(environment.buildDir.childFile('foo.out').existsSync(), false);
}));
expect(environment.buildDir.childFile('bar.out'), exists);
expect(environment.buildDir.childFile('foo.out'), isNot(exists));
});
test('Does not crash when filesytem and cache are out of sync', () => testbed.run(() async {
final TestTarget testTarget = TestTarget((Environment environment) async {
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')];
globals.fs.file('foo.dart').createSync();
fileSystem.file('foo.dart').createSync();
await buildSystem.build(testTarget, environment);
await buildSystem.build(testWithoutContextTarget, environment);
expect(environment.buildDir.childFile('foo.out').existsSync(), true);
expect(environment.buildDir.childFile('foo.out'), exists);
environment.buildDir.childFile('foo.out').deleteSync();
final TestTarget testTarget2 = TestTarget((Environment environment) async {
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(testTarget2, environment);
await buildSystem.build(testWithoutContextTarget2, environment);
expect(environment.buildDir.childFile('bar.out').existsSync(), true);
expect(environment.buildDir.childFile('foo.out').existsSync(), false);
}));
expect(environment.buildDir.childFile('bar.out'), exists);
expect(environment.buildDir.childFile('foo.out'), isNot(exists));
});
test('reruns build if stamp is corrupted', () => testbed.run(() async {
final TestTarget testTarget = TestTarget((Environment envionment) async {
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')];
globals.fs.file('foo.dart').createSync();
await buildSystem.build(testTarget, environment);
fileSystem.file('foo.dart').createSync();
await buildSystem.build(testWithoutContextTarget, environment);
// invalid JSON
environment.buildDir.childFile('test.stamp').writeAsStringSync('{X');
await buildSystem.build(testTarget, environment);
environment.buildDir.childFile('testWithoutContext.stamp').writeAsStringSync('{X');
await buildSystem.build(testWithoutContextTarget, environment);
// empty file
environment.buildDir.childFile('test.stamp').writeAsStringSync('');
await buildSystem.build(testTarget, environment);
environment.buildDir.childFile('testWithoutContext.stamp').writeAsStringSync('');
await buildSystem.build(testWithoutContextTarget, environment);
// invalid format
environment.buildDir.childFile('test.stamp').writeAsStringSync('{"inputs": 2, "outputs": 3}');
await buildSystem.build(testTarget, environment);
}));
environment.buildDir.childFile('testWithoutContext.stamp').writeAsStringSync('{"inputs": 2, "outputs": 3}');
await buildSystem.build(testWithoutContextTarget, environment);
});
test('handles a throwing build action', () => testbed.run(() async {
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);
}));
});
test('Can describe itself with JSON output', () => testbed.run(() {
testWithoutContext('Can describe itself with JSON output', () {
environment.buildDir.createSync(recursive: true);
expect(fooTarget.toJson(environment), <String, dynamic>{
'inputs': <Object>[
'/foo.dart',
],
'outputs': <Object>[
globals.fs.path.join(environment.buildDir.path, 'out'),
fileSystem.path.join(environment.buildDir.path, 'out'),
],
'dependencies': <Object>[],
'name': 'foo',
'stamp': globals.fs.path.join(environment.buildDir.path, 'foo.stamp'),
'stamp': fileSystem.path.join(environment.buildDir.path, 'foo.stamp'),
});
});
}));
test('Can find dependency cycles', () {
testWithoutContext('Can find dependency cycles', () {
final Target barTarget = TestTarget()..name = 'bar';
final Target fooTarget = TestTarget()..name = 'foo';
barTarget.dependencies.add(fooTarget);
......@@ -312,73 +330,94 @@ void main() {
expect(() => checkCycles(barTarget), throwsA(isA<CycleException>()));
});
test('Target with depfile dependency will not run twice without invalidation', () => testbed.run(() async {
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')
environment.buildDir
.childFile('example.d')
.writeAsStringSync('a.txt: b.txt');
globals.fs.file('a.txt').writeAsStringSync('a');
fileSystem.file('a.txt').writeAsStringSync('a');
called += 1;
})
..depfiles = <String>['example.d'];
globals.fs.file('b.txt').writeAsStringSync('b');
fileSystem.file('b.txt').writeAsStringSync('b');
await buildSystem.build(target, environment);
expect(globals.fs.file('a.txt').existsSync(), true);
expect(fileSystem.file('a.txt'), exists);
expect(called, 1);
// Second build is up to date due to depfil parse.
// Second build is up to date due to depfile parse.
await buildSystem.build(target, environment);
expect(called, 1);
}));
});
test('output directory is an input to the build', () => testbed.run(() async {
final Environment environmentA = Environment.test(globals.fs.currentDirectory, outputDir: globals.fs.directory('a'));
final Environment environmentB = Environment.test(globals.fs.currentDirectory, outputDir: globals.fs.directory('b'));
testWithoutContext('output directory is an input to the build', () async {
final Environment environmentA = Environment.test(
fileSystem.currentDirectory,
outputDir: fileSystem.directory('a'),
artifacts: MockArtifacts(),
processManager: FakeProcessManager.any(),
fileSystem: fileSystem,
logger: BufferLogger.test(),
);
final Environment environmentB = Environment.test(
fileSystem.currentDirectory,
outputDir: fileSystem.directory('b'),
artifacts: MockArtifacts(),
processManager: FakeProcessManager.any(),
fileSystem: fileSystem,
logger: BufferLogger.test(),
);
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 {
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');
globals.fs.file('a.txt').writeAsStringSync('a');
globals.fs.file('c.txt').writeAsStringSync('a');
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');
globals.fs.file('a.txt').writeAsStringSync('a');
fileSystem.file('a.txt').writeAsStringSync('a');
}
called += 1;
})
..depfiles = const <String>['example.d'];
globals.fs.file('b.txt').writeAsStringSync('b');
fileSystem.file('b.txt').writeAsStringSync('b');
await buildSystem.build(target, environment);
expect(globals.fs.file('a.txt').existsSync(), true);
expect(globals.fs.file('c.txt').existsSync(), true);
expect(fileSystem.file('a.txt'), exists);
expect(fileSystem.file('c.txt'), exists);
expect(called, 1);
// rewrite an input to force a rerun, espect that the old c.txt is deleted.
globals.fs.file('b.txt').writeAsStringSync('ba');
// 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(globals.fs.file('a.txt').existsSync(), true);
expect(globals.fs.file('c.txt').existsSync(), false);
expect(fileSystem.file('a.txt'), exists);
expect(fileSystem.file('c.txt'), isNot(exists));
expect(called, 2);
}));
});
}
class MockPlatform extends Mock implements Platform {}
// Work-around for silly lint check.
T nonconst<T>(T input) => input;
BuildSystem setUpBuildSystem(FileSystem fileSystem) {
return BuildSystem(
fileSystem: fileSystem,
logger: BufferLogger.test(),
platform: FakePlatform(operatingSystem: 'linux'),
);
}
class TestTarget extends Target {
TestTarget([this._build]);
......@@ -403,3 +442,5 @@ class TestTarget extends Target {
@override
List<Source> outputs = <Source>[];
}
class MockArtifacts extends Mock implements Artifacts {}
......@@ -5,6 +5,7 @@
import 'dart:typed_data';
import 'package:file/memory.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/terminal.dart';
......@@ -14,6 +15,7 @@ import 'package:mockito/mockito.dart';
import 'package:platform/platform.dart';
import '../../src/common.dart';
import '../../src/fake_process_manager.dart';
void main() {
Environment environment;
......@@ -29,6 +31,10 @@ void main() {
fileSystem.directory('build').createSync();
environment = Environment.test(
fileSystem.currentDirectory,
artifacts: MockArtifacts(),
processManager: FakeProcessManager.any(),
logger: logger,
fileSystem: fileSystem,
);
environment.buildDir.createSync(recursive: true);
});
......@@ -168,4 +174,6 @@ class FakeForwardingFileSystem extends ForwardingFileSystem {
@override
File file(dynamic path) => files[path] ?? super.file(path);
}
class MockFile extends Mock implements File {}
class MockArtifacts extends Mock implements Artifacts {}
......@@ -14,6 +14,7 @@ import 'package:mockito/mockito.dart';
import 'package:platform/platform.dart';
import '../../src/common.dart';
import '../../src/context.dart';
import '../../src/testbed.dart';
void main() {
......@@ -32,6 +33,10 @@ void main() {
environment = Environment.test(
globals.fs.currentDirectory,
outputDir: outputs,
artifacts: globals.artifacts, // using real artifacts
processManager: FakeProcessManager.any(),
fileSystem: globals.fs,
logger: globals.logger,
);
visitor = SourceVisitor(environment);
environment.buildDir.createSync(recursive: true);
......@@ -212,3 +217,4 @@ void main() {
}
class MockPlatform extends Mock implements Platform {}
class MockArtifacts extends Mock implements Artifacts {}
......@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter_tools/src/artifacts.dart';
import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/build_info.dart';
import 'package:flutter_tools/src/build_system/build_system.dart';
......@@ -9,6 +10,7 @@ import 'package:flutter_tools/src/build_system/targets/android.dart';
import 'package:flutter_tools/src/build_system/targets/dart.dart';
import 'package:flutter_tools/src/globals.dart' as globals;
import 'package:flutter_tools/src/cache.dart';
import 'package:mockito/mockito.dart';
import 'package:platform/platform.dart';
import '../../../src/common.dart';
......@@ -32,7 +34,11 @@ void main() {
outputDir: globals.fs.directory('out')..createSync(),
defines: <String, String>{
kBuildMode: 'debug',
}
},
processManager: fakeProcessManager,
artifacts: MockArtifacts(),
fileSystem: globals.fs,
logger: globals.logger,
);
environment.buildDir.createSync(recursive: true);
......@@ -45,7 +51,6 @@ void main() {
hostDirectory.childFile('vm_isolate_snapshot.bin').createSync();
hostDirectory.childFile('isolate_snapshot.bin').createSync();
await const DebugAndroidApplication().build(environment);
expect(globals.fs.file(globals.fs.path.join('out', 'flutter_assets', 'isolate_snapshot_data')).existsSync(), true);
......@@ -59,7 +64,11 @@ void main() {
outputDir: globals.fs.directory('out')..createSync(),
defines: <String, String>{
kBuildMode: 'profile',
}
},
artifacts: MockArtifacts(),
processManager: fakeProcessManager,
fileSystem: globals.fs,
logger: globals.logger,
);
environment.buildDir.createSync(recursive: true);
......@@ -78,7 +87,11 @@ void main() {
outputDir: globals.fs.directory('out')..createSync(),
defines: <String, String>{
kBuildMode: 'release',
}
},
artifacts: MockArtifacts(),
processManager: fakeProcessManager,
fileSystem: globals.fs,
logger: globals.logger,
);
environment.buildDir.createSync(recursive: true);
......@@ -92,15 +105,19 @@ void main() {
});
testbed.test('AndroidAot can build provided target platform', () async {
fakeProcessManager = FakeProcessManager.list(<FakeCommand>[]);
final Environment environment = Environment.test(
globals.fs.currentDirectory,
outputDir: globals.fs.directory('out')..createSync(),
defines: <String, String>{
kBuildMode: 'release',
}
},
artifacts: MockArtifacts(),
processManager: FakeProcessManager.list(<FakeCommand>[]),
fileSystem: globals.fs,
logger: globals.logger,
);
fakeProcessManager = FakeProcessManager.list(<FakeCommand>[
FakeCommand(command: <String>[
fakeProcessManager.addCommand(FakeCommand(command: <String>[
globals.fs.path.absolute(globals.fs.path.join('android-arm64-release', 'linux-x64', 'gen_snapshot')),
'--deterministic',
'--snapshot_kind=app-aot-elf',
......@@ -111,18 +128,21 @@ void main() {
environment.buildDir.childFile('app.dill').path,
],
)
]);
);
environment.buildDir.createSync(recursive: true);
environment.buildDir.childFile('app.dill').createSync();
environment.projectDir.childFile('.packages').writeAsStringSync('\n');
const AndroidAot androidAot = AndroidAot(TargetPlatform.android_arm64, BuildMode.release);
await androidAot.build(environment);
expect(fakeProcessManager.hasRemainingExpectations, false);
}, overrides: <Type, Generator>{
ProcessManager: () => fakeProcessManager,
});
testbed.test('kExtraGenSnapshotOptions passes values to gen_snapshot', () async {
fakeProcessManager = FakeProcessManager.list(<FakeCommand>[]);
final Environment environment = Environment.test(
globals.fs.currentDirectory,
outputDir: globals.fs.directory('out')..createSync(),
......@@ -130,9 +150,13 @@ void main() {
kBuildMode: 'release',
kExtraGenSnapshotOptions: 'foo,bar,baz=2',
kTargetPlatform: 'android-arm',
}
},
processManager: fakeProcessManager,
artifacts: MockArtifacts(),
fileSystem: globals.fs,
logger: globals.logger,
);
fakeProcessManager = FakeProcessManager.list(<FakeCommand>[
fakeProcessManager.addCommand(
FakeCommand(command: <String>[
globals.fs.path.absolute(globals.fs.path.join('android-arm64-release', 'linux-x64', 'gen_snapshot')),
'--deterministic',
......@@ -145,8 +169,7 @@ void main() {
'--no-causal-async-stacks',
'--lazy-async-stacks',
environment.buildDir.childFile('app.dill').path
])
]);
]));
environment.buildDir.createSync(recursive: true);
environment.buildDir.childFile('app.dill').createSync();
environment.projectDir.childFile('.packages').writeAsStringSync('\n');
......@@ -163,7 +186,11 @@ void main() {
outputDir: globals.fs.directory('out')..createSync(),
defines: <String, String>{
kBuildMode: 'release',
}
},
processManager: fakeProcessManager,
artifacts: MockArtifacts(),
fileSystem: globals.fs,
logger: globals.logger,
);
environment.buildDir.createSync(recursive: true);
const AndroidAot androidAot = AndroidAot(TargetPlatform.android_arm64, BuildMode.release);
......@@ -181,3 +208,5 @@ void main() {
.childFile('app.so').existsSync(), true);
});
}
class MockArtifacts extends Mock implements Artifacts {}
\ No newline at end of file
......@@ -4,10 +4,13 @@
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/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:mockito/mockito.dart';
import 'package:platform/platform.dart';
import '../../../src/common.dart';
......@@ -23,6 +26,10 @@ void main() {
fileSystem = MemoryFileSystem.test();
environment = Environment.test(
fileSystem.currentDirectory,
processManager: FakeProcessManager.any(),
artifacts: MockArtifacts(),
fileSystem: fileSystem,
logger: BufferLogger.test(),
);
fileSystem.file(environment.buildDir.childFile('app.dill')).createSync(recursive: true);
fileSystem.file('packages/flutter_tools/lib/src/build_system/targets/assets.dart')
......@@ -91,3 +98,5 @@ flutter:
Platform: () => platform,
});
}
class MockArtifacts extends Mock implements Artifacts {}
......@@ -43,6 +43,10 @@ void main() {
kBuildMode: getNameForBuildMode(BuildMode.profile),
kTargetPlatform: getNameForTargetPlatform(TargetPlatform.android_arm),
},
artifacts: artifacts,
processManager: processManager,
fileSystem: globals.fs,
logger: globals.logger,
);
androidEnvironment.buildDir.createSync(recursive: true);
iosEnvironment = Environment.test(
......@@ -51,6 +55,10 @@ void main() {
kBuildMode: getNameForBuildMode(BuildMode.profile),
kTargetPlatform: getNameForTargetPlatform(TargetPlatform.ios),
},
artifacts: artifacts,
processManager: processManager,
fileSystem: globals.fs,
logger: globals.logger,
);
iosEnvironment.buildDir.createSync(recursive: true);
artifacts = CachedArtifacts(
......@@ -261,6 +269,10 @@ void main() {
kBuildMode: getNameForBuildMode(BuildMode.debug),
kTargetPlatform: getNameForTargetPlatform(TargetPlatform.android_arm),
},
processManager: processManager,
artifacts: artifacts,
fileSystem: globals.fs,
logger: globals.logger,
);
final String build = testEnvironment.buildDir.path;
processManager = FakeProcessManager.list(<FakeCommand>[
......
......@@ -113,6 +113,10 @@ void main() {
return Environment.test(
fs.directory('/icon_test')..createSync(recursive: true),
defines: defines,
artifacts: mockArtifacts,
processManager: FakeProcessManager.any(),
fileSystem: fs,
logger: BufferLogger.test(),
);
}
......
......@@ -39,9 +39,16 @@ void main() {
setUp(() {
testbed = Testbed(setup: () {
environment = Environment.test(globals.fs.currentDirectory, defines: <String, String>{
environment = Environment.test(
globals.fs.currentDirectory,
defines: <String, String>{
kTargetPlatform: 'ios',
});
},
processManager: processManager,
artifacts: MockArtifacts(),
logger: globals.logger,
fileSystem: globals.fs,
);
});
});
......
......@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter_tools/src/artifacts.dart';
import 'package:platform/platform.dart';
import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/build_system/build_system.dart';
......@@ -12,11 +13,12 @@ import 'package:flutter_tools/src/globals.dart' as globals;
import 'package:mockito/mockito.dart';
import '../../../src/common.dart';
import '../../../src/context.dart';
import '../../../src/testbed.dart';
void main() {
Testbed testbed;
const BuildSystem buildSystem = BuildSystem();
BuildSystem buildSystem;
Environment environment;
MockPlatform mockPlatform;
......@@ -33,11 +35,20 @@ void main() {
when(mockPlatform.environment).thenReturn(Map<String, String>.unmodifiable(<String, String>{}));
testbed = Testbed(setup: () {
Cache.flutterRoot = '';
buildSystem = BuildSystem(
logger: globals.logger,
platform: globals.platform,
fileSystem: globals.fs,
);
environment = Environment.test(
globals.fs.currentDirectory,
defines: <String, String>{
kBuildMode: 'debug',
}
},
artifacts: MockArtifacts(),
processManager: FakeProcessManager.any(),
fileSystem: globals.fs,
logger: globals.logger,
);
globals.fs.file('bin/cache/artifacts/engine/linux-x64/unrelated-stuff').createSync(recursive: true);
globals.fs.file('bin/cache/artifacts/engine/linux-x64/libflutter_linux_glfw.so').createSync(recursive: true);
......@@ -105,3 +116,4 @@ void main() {
}
class MockPlatform extends Mock implements Platform {}
class MockArtifacts extends Mock implements Artifacts {}
......@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter_tools/src/artifacts.dart';
import 'package:flutter_tools/src/base/build.dart';
import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/io.dart';
......@@ -16,6 +17,7 @@ import 'package:process/process.dart';
import 'package:platform/platform.dart';
import '../../../src/common.dart';
import '../../../src/fake_process_manager.dart';
import '../../../src/testbed.dart';
const String _kInputPrefix = 'bin/cache/artifacts/engine/darwin-x64/FlutterMacOS.framework';
......@@ -71,6 +73,10 @@ void main() {
kBuildMode: 'debug',
kTargetPlatform: 'darwin-x64',
},
artifacts: MockArtifacts(),
processManager: FakeProcessManager.any(),
logger: globals.logger,
fileSystem: globals.fs,
);
}, overrides: <Type, Generator>{
ProcessManager: () => MockProcessManager(),
......@@ -195,6 +201,7 @@ class MockPlatform extends Mock implements Platform {}
class MockProcessManager extends Mock implements ProcessManager {}
class MockGenSnapshot extends Mock implements GenSnapshot {}
class MockXcode extends Mock implements Xcode {}
class MockArtifacts extends Mock implements Artifacts {}
class FakeProcessResult implements ProcessResult {
@override
int exitCode;
......
......@@ -3,6 +3,7 @@
// found in the LICENSE file.
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/build_system/build_system.dart';
......@@ -55,7 +56,11 @@ void main() {
outputDir: globals.fs.currentDirectory.childDirectory('bar'),
defines: <String, String>{
kTargetFile: globals.fs.path.join('foo', 'lib', 'main.dart'),
}
},
artifacts: MockArtifacts(),
processManager: FakeProcessManager.any(),
logger: globals.logger,
fileSystem: globals.fs,
);
depfileService = DepfileService(
fileSystem: globals.fs,
......@@ -460,3 +465,4 @@ void main() {
}
class MockProcessManager extends Mock implements ProcessManager {}
class MockArtifacts extends Mock implements Artifacts {}
......@@ -3,102 +3,77 @@
// 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/logger.dart';
import 'package:mockito/mockito.dart';
import 'package:platform/platform.dart';
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/windows.dart';
import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/globals.dart' as globals;
import 'package:mockito/mockito.dart';
import 'package:platform/platform.dart';
import '../../../src/common.dart';
import '../../../src/fake_process_manager.dart';
import '../../../src/testbed.dart';
final Platform kWindowsPlatform = FakePlatform(
operatingSystem: 'windows',
environment: <String, String>{},
);
const List<String> kRequiredFiles = <String>[
r'C:\bin\cache\artifacts\engine\windows-x64\flutter_export.h',
r'C:\bin\cache\artifacts\engine\windows-x64\flutter_messenger.h',
r'C:\bin\cache\artifacts\engine\windows-x64\flutter_windows.dll',
r'C:\bin\cache\artifacts\engine\windows-x64\flutter_windows.dll.exp',
r'C:\bin\cache\artifacts\engine\windows-x64\flutter_windows.dll.lib',
r'C:\bin\cache\artifacts\engine\windows-x64\flutter_windows.dll.pdb',
r'C:\bin\cache\artifacts\engine\windows-x64\lutter_export.h',
r'C:\bin\cache\artifacts\engine\windows-x64\flutter_messenger.h',
r'C:\bin\cache\artifacts\engine\windows-x64\flutter_plugin_registrar.h',
r'C:\bin\cache\artifacts\engine\windows-x64\flutter_windows.h',
r'C:\bin\cache\artifacts\engine\windows-x64\icudtl.dat',
r'C:\bin\cache\artifacts\engine\windows-x64\cpp_client_wrapper\foo',
r'C:\packages\flutter_tools\lib\src\build_system\targets\windows.dart',
];
void main() {
Testbed testbed;
const BuildSystem buildSystem = BuildSystem();
Environment environment;
Platform platform;
setUpAll(() {
Cache.disableLocking();
Cache.flutterRoot = '';
});
FileSystem fileSystem;
setUp(() {
platform = MockPlatform();
when(platform.isWindows).thenReturn(true);
when(platform.isMacOS).thenReturn(false);
when(platform.isLinux).thenReturn(false);
when(platform.pathSeparator).thenReturn(r'\');
testbed = Testbed(setup: () {
final MockArtifacts artifacts = MockArtifacts();
when(artifacts.getArtifactPath(Artifact.windowsDesktopPath))
.thenReturn(r'C:\bin\cache\artifacts\engine\windows-x64\');
fileSystem = MemoryFileSystem.test(style: FileSystemStyle.windows);
environment = Environment.test(
globals.fs.currentDirectory,
fileSystem.currentDirectory,
artifacts: artifacts,
processManager: FakeProcessManager.any(),
fileSystem: fileSystem,
logger: BufferLogger.test(),
);
globals.fs.file(r'C:\bin\cache\artifacts\engine\windows-x64\flutter_export.h').createSync(recursive: true);
globals.fs.file(r'C:\bin\cache\artifacts\engine\windows-x64\flutter_messenger.h').createSync();
globals.fs.file(r'C:\bin\cache\artifacts\engine\windows-x64\flutter_windows.dll').createSync();
globals.fs.file(r'C:\bin\cache\artifacts\engine\windows-x64\flutter_windows.dll.exp').createSync();
globals.fs.file(r'C:\bin\cache\artifacts\engine\windows-x64\flutter_windows.dll.lib').createSync();
globals.fs.file(r'C:\bin\cache\artifacts\engine\windows-x64\flutter_windows.dll.pdb').createSync();
globals.fs.file(r'C:\bin\cache\artifacts\engine\windows-x64\lutter_export.h').createSync();
globals.fs.file(r'C:\bin\cache\artifacts\engine\windows-x64\flutter_messenger.h').createSync();
globals.fs.file(r'C:\bin\cache\artifacts\engine\windows-x64\flutter_plugin_registrar.h').createSync();
globals.fs.file(r'C:\bin\cache\artifacts\engine\windows-x64\flutter_windows.h').createSync();
globals.fs.file(r'C:\bin\cache\artifacts\engine\windows-x64\icudtl.dat').createSync();
globals.fs.file(r'C:\bin\cache\artifacts\engine\windows-x64\cpp_client_wrapper\foo').createSync(recursive: true);
globals.fs.file(r'C:\packages\flutter_tools\lib\src\build_system\targets\windows.dart').createSync(recursive: true);
globals.fs.directory('windows').createSync();
}, overrides: <Type, Generator>{
FileSystem: () => MemoryFileSystem(style: FileSystemStyle.windows),
ProcessManager: () => FakeProcessManager.any(),
Platform: () => platform,
});
for (final String path in kRequiredFiles) {
fileSystem.file(path).createSync(recursive: true);
}
fileSystem.directory('windows').createSync();
});
test('Copies files to correct cache directory', () => testbed.run(() async {
await buildSystem.build(const UnpackWindows(), environment);
expect(globals.fs.file(r'C:\windows\flutter\flutter_export.h').existsSync(), true);
expect(globals.fs.file(r'C:\windows\flutter\flutter_messenger.h').existsSync(), true);
expect(globals.fs.file(r'C:\windows\flutter\flutter_windows.dll').existsSync(), true);
expect(globals.fs.file(r'C:\windows\flutter\flutter_windows.dll.exp').existsSync(), true);
expect(globals.fs.file(r'C:\windows\flutter\flutter_windows.dll.lib').existsSync(), true);
expect(globals.fs.file(r'C:\windows\flutter\flutter_windows.dll.pdb').existsSync(), true);
expect(globals.fs.file(r'C:\windows\flutter\flutter_export.h').existsSync(), true);
expect(globals.fs.file(r'C:\windows\flutter\flutter_messenger.h').existsSync(), true);
expect(globals.fs.file(r'C:\windows\flutter\flutter_plugin_registrar.h').existsSync(), true);
expect(globals.fs.file(r'C:\windows\flutter\flutter_windows.h').existsSync(), true);
expect(globals.fs.file(r'C:\windows\flutter\icudtl.dat').existsSync(), true);
expect(globals.fs.file(r'C:\windows\flutter\cpp_client_wrapper\foo').existsSync(), true);
}));
test('Does not re-copy files unecessarily', () => testbed.run(() async {
await buildSystem.build(const UnpackWindows(), environment);
// Set a date in the far distant past to deal with the limited resolution
// of the windows filesystem.
final DateTime theDistantPast = DateTime(1991, 8, 23);
globals.fs.file(r'C:\windows\flutter\flutter_export.h').setLastModifiedSync(theDistantPast);
await buildSystem.build(const UnpackWindows(), environment);
testWithoutContext('UnpackWindows copies files to the correct cache directory', () async {
await const UnpackWindows().build(environment);
expect(globals.fs.file(r'C:\windows\flutter\flutter_export.h').statSync().modified, equals(theDistantPast));
}));
test('Detects changes in input cache files', () => testbed.run(() async {
await buildSystem.build(const UnpackWindows(), environment);
// Set a date in the far distant past to deal with the limited resolution
// of the windows filesystem.
final DateTime theDistantPast = DateTime(1991, 8, 23);
globals.fs.file(r'C:\windows\flutter\flutter_export.h').setLastModifiedSync(theDistantPast);
final DateTime modified = globals.fs.file(r'C:\windows\flutter\flutter_export.h').statSync().modified;
globals.fs.file(r'C:\bin\cache\artifacts\engine\windows-x64\flutter_export.h').writeAsStringSync('asd'); // modify cache.
await buildSystem.build(const UnpackWindows(), environment);
expect(globals.fs.file(r'C:\windows\flutter\flutter_export.h').statSync().modified, isNot(modified));
}));
expect(fileSystem.file(r'C:\windows\flutter\flutter_export.h'), exists);
expect(fileSystem.file(r'C:\windows\flutter\flutter_messenger.h'), exists);
expect(fileSystem.file(r'C:\windows\flutter\flutter_windows.dll'), exists);
expect(fileSystem.file(r'C:\windows\flutter\flutter_windows.dll.exp'), exists);
expect(fileSystem.file(r'C:\windows\flutter\flutter_windows.dll.lib'), exists);
expect(fileSystem.file(r'C:\windows\flutter\flutter_windows.dll.pdb'), exists);
expect(fileSystem.file(r'C:\windows\flutter\flutter_export.h'), exists);
expect(fileSystem.file(r'C:\windows\flutter\flutter_messenger.h'), exists);
expect(fileSystem.file(r'C:\windows\flutter\flutter_plugin_registrar.h'), exists);
expect(fileSystem.file(r'C:\windows\flutter\flutter_windows.h'), exists);
expect(fileSystem.file(r'C:\windows\flutter\icudtl.dat'), exists);
expect(fileSystem.file(r'C:\windows\flutter\cpp_client_wrapper\foo'), exists);
});
}
class MockPlatform extends Mock implements Platform {}
class MockArtifacts extends Mock implements Artifacts {}
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