Skip to content

track build outputs in a file for subsequent builds #36

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Feb 8, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ build/
.pub/
pubspec.lock

# Files generated by build runs
.build

# Generated by tool/create_all_test.dart
tool/test_all.dart

Expand Down
6 changes: 6 additions & 0 deletions lib/src/asset/cache.dart
Original file line number Diff line number Diff line change
Expand Up @@ -97,4 +97,10 @@ class CachedAssetWriter extends AssetWriter {
_cache.put(asset);
return _writer.writeAsString(asset, encoding: encoding);
}

@override
Future delete(AssetId id) {
_cache.remove(id);
return _writer.delete(id);
}
}
16 changes: 12 additions & 4 deletions lib/src/asset/file_based.dart
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,8 @@ class FileBasedAssetReader implements AssetReader {
var packageNode = packageGraph[inputSet.package];
var packagePath = packageNode.location.toFilePath();
for (var glob in inputSet.globs) {
var fileStream = glob
.list(followLinks: false, root: packagePath)
.where((e) =>
e is File && !ignoredDirs.contains(path.split(e.path)[1]));
var fileStream = glob.list(followLinks: false, root: packagePath).where(
(e) => e is File && !ignoredDirs.contains(path.split(e.path)[1]));
await for (var entity in fileStream) {
var id = _fileToAssetId(entity, packageNode);
if (!seenAssets.add(id)) continue;
Expand Down Expand Up @@ -98,6 +96,16 @@ class FileBasedAssetWriter implements AssetWriter {
await file.create(recursive: true);
await file.writeAsString(asset.stringContents, encoding: encoding);
}

@override
delete(AssetId id) async {
assert(id.package == packageGraph.root.name);

var file = _fileFor(id, packageGraph);
if (await file.exists()) {
await file.delete();
}
}
}

/// Returns a [File] for [id] given [packageGraph].
Expand Down
3 changes: 3 additions & 0 deletions lib/src/asset/writer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ import 'dart:async';
import 'dart:convert';

import 'asset.dart';
import 'id.dart';

abstract class AssetWriter {
Future writeAsString(Asset asset, {Encoding encoding: UTF8});

Future delete(AssetId id);
}
38 changes: 33 additions & 5 deletions lib/src/generate/build.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'dart:async';
import 'dart:convert';

import 'package:logging/logging.dart';
import 'package:path/path.dart' as path;
Expand Down Expand Up @@ -49,17 +50,43 @@ Future<BuildResult> build(List<List<Phase>> phaseGroups,
new CachedAssetReader(cache, new FileBasedAssetReader(packageGraph));
writer ??=
new CachedAssetWriter(cache, new FileBasedAssetWriter(packageGraph));
var result = runZoned(() {
return _runPhases(phaseGroups);
}, onError: (e, s) {
return new BuildResult(BuildStatus.Failure, BuildType.Full, [],
exception: e, stackTrace: s);

/// Asset containing previous build outputs.
var buildOuputsId =
new AssetId(packageGraph.root.name, '.build/build_outputs.json');

/// Run the build in a zone.
var result = await runZoned(() async {
try {
/// Read in previous build_outputs file, and delete them all.
if (await _reader.hasInput(buildOuputsId)) {
var previousOutputs =
JSON.decode(await _reader.readAsString(buildOuputsId));
await writer.delete(buildOuputsId);
await Future.wait(previousOutputs.map((output) {
return writer.delete(new AssetId.deserialize(output));
}));
}

/// Run a fresh build.
return _runPhases(phaseGroups);
} catch(e, s) {
return new BuildResult(BuildStatus.Failure, BuildType.Full, [],
exception: e, stackTrace: s);
}
}, zoneValues: {
_assetReaderKey: reader,
_assetWriterKey: writer,
_packageGraphKey: packageGraph,
});

await logListener.cancel();

// Write out the new build_outputs file.
var buildOutputsAsset = new Asset(buildOuputsId,
JSON.encode(result.outputs.map((output) => output.id.serialize()).toList()));
await writer.writeAsString(buildOutputsAsset);

return result;
}

Expand Down Expand Up @@ -91,6 +118,7 @@ Future<BuildResult> _runPhases(List<List<Phase>> phaseGroups) async {
}
}
}

/// Once the group is done, add all outputs so they can be used in the next
/// phase.
for (var output in groupOutputs) {
Expand Down
5 changes: 5 additions & 0 deletions lib/src/transformer/transformer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ class _TransformAssetReader implements AssetReader {
transform.readInputAsString(toBarbackAssetId(id), encoding: encoding);

@override

/// No way to implement this, but luckily its not necessary.
Stream<build.AssetId> listAssetIds(_) => throw new UnimplementedError();
}
Expand All @@ -114,6 +115,10 @@ class _TransformAssetWriter implements AssetWriter {
transform.addOutput(toBarbackAsset(asset));
return new Future.value(null);
}

@override
Future delete(build.AssetId id) =>
throw new UnsupportedError('_TransformAssetWriter can\'t delete files.');
}

/// All the expected outputs for [id] given [builders].
Expand Down
7 changes: 7 additions & 0 deletions test/asset/cache_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -170,5 +170,12 @@ main() {
await writer.writeAsString(a);
expect(() => writer.writeAsString(a), throwsArgumentError);
});

test('delete deletes from cache and writer', () async {
await writer.writeAsString(a);
await writer.delete(a.id);
expect(cache.get(a.id), isNull);
expect(childWriterAssets[a.id], isNull);
});
});
}
22 changes: 17 additions & 5 deletions test/asset/file_based_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -69,15 +69,15 @@ main() {

test('can list files based on simple InputSets', () async {
var inputSets = [
new InputSet('basic_pkg'),
new InputSet('a'),
new InputSet('basic_pkg', filePatterns: ['{lib,web}/**']),
new InputSet('a', filePatterns: ['lib/**']),
];
expect(await reader.listAssetIds(inputSets).toList(), unorderedEquals([
makeAssetId('basic_pkg|lib/hello.txt'),
makeAssetId('basic_pkg|web/hello.txt'),
makeAssetId('a|lib/a.txt'),
]));
}, skip: 'Fails for multiple reasons.');
});

test('can list files based on InputSets with globs', () async {
var inputSets = [
Expand All @@ -94,14 +94,16 @@ main() {
group('FileBasedAssetWriter', () {
final writer = new FileBasedAssetWriter(packageGraph);

test('can output files in the application package', () async {
test('can output and delete files in the application package', () async {
var asset = makeAsset('basic_pkg|test_file.txt', 'test');
await writer.writeAsString(asset);
var id = asset.id;
var file = new File('test/fixtures/${id.package}/${id.path}');
expect(await file.exists(), isTrue);
expect(await file.readAsString(), 'test');
await file.delete();

await writer.delete(asset.id);
expect(await file.exists(), isFalse);
});

test('can\'t output files in package dependencies', () async {
Expand All @@ -113,5 +115,15 @@ main() {
var asset = makeAsset('foo|bar.txt');
expect(writer.writeAsString(asset), throwsA(invalidOutputException));
});

test('can\'t delete files in package dependencies', () async {
var id = makeAssetId('a|test.txt');
expect(writer.delete(id), throws);
});

test('can\'t delete files in arbitrary dependencies', () async {
var id = makeAssetId('foo|test.txt');
expect(writer.delete(id), throws);
});
});
}
4 changes: 4 additions & 0 deletions test/common/in_memory_writer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,8 @@ class InMemoryAssetWriter implements AssetWriter {
Future writeAsString(Asset asset, {Encoding encoding: UTF8}) async {
assets[asset.id] = asset.stringContents;
}

Future delete(AssetId id) async {
assets.remove(id);
}
}
2 changes: 2 additions & 0 deletions test/common/stub_writer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,6 @@ import 'package:build/build.dart';
class StubAssetWriter implements AssetWriter {
Future writeAsString(Asset asset, {Encoding encoding: UTF8}) =>
new Future.value(null);

Future delete(AssetId id) => new Future.value(null);
}
45 changes: 41 additions & 4 deletions test/generate/build_test.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'dart:convert';

import 'package:test/test.dart';

import 'package:build/build.dart';
Expand Down Expand Up @@ -172,17 +174,52 @@ main() {
'the BuildStep level.');
});

test('tracks previous outputs in a build_outputs.json file', () async {
var phases = [
[
new Phase([new CopyBuilder()], [new InputSet('a')]),
]
];
final writer = new InMemoryAssetWriter();
await testPhases(phases, {'a|web/a.txt': 'a', 'a|lib/b.txt': 'b'},
outputs: {'a|web/a.txt.copy': 'a', 'a|lib/b.txt.copy': 'b'},
writer: writer);

var outputId = makeAssetId('a|.build/build_outputs.json');
expect(writer.assets, contains(outputId));
var outputs = JSON.decode(writer.assets[outputId]);
expect(
outputs,
unorderedEquals([
['a', 'web/a.txt.copy'],
['a', 'lib/b.txt.copy'],
]));
});

test('outputs from previous full builds shouldn\'t be inputs to later ones',
() {},
skip: 'Unimplemented: https://github.com/dart-lang/build/issues/34');
() async {
var phases = [
[
new Phase([new CopyBuilder()], [new InputSet('a')]),
]
];
final writer = new InMemoryAssetWriter();
var inputs = {'a|web/a.txt': 'a', 'a|lib/b.txt': 'b'};
var outputs = {'a|web/a.txt.copy': 'a', 'a|lib/b.txt.copy': 'b'};
// First run, nothing special.
await testPhases(phases, inputs, outputs: outputs, writer: writer);
// Second run, should have no extra outputs.
await testPhases(phases, inputs, outputs: outputs, writer: writer);
});
}

testPhases(List<List<Phase>> phases, Map<String, String> inputs,
{Map<String, String> outputs,
PackageGraph packageGraph,
BuildStatus status: BuildStatus.Success,
exceptionMatcher}) async {
final writer = new InMemoryAssetWriter();
exceptionMatcher,
InMemoryAssetWriter writer}) async {
writer ??= new InMemoryAssetWriter();
final actualAssets = writer.assets;
final reader = new InMemoryAssetReader(actualAssets);
inputs.forEach((serializedId, contents) {
Expand Down