Skip to content

Commit 7a09195

Browse files
committed
Merge pull request #440 from dart-lang/transformer3
Simple transformer to run ddc in pub build/serve
2 parents 98b21b0 + f2a2625 commit 7a09195

21 files changed

+588
-17
lines changed

pkg/dev_compiler/lib/src/codegen/js_codegen.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3589,7 +3589,8 @@ class JSGenerator extends CodeGenerator {
35893589
? Uri.parse('http://${flags.host}:${flags.port}/')
35903590
: null;
35913591
return writeJsLibrary(module, out, compiler.inputBaseDir, serverUri,
3592-
emitSourceMaps: options.emitSourceMaps);
3592+
emitSourceMaps: options.emitSourceMaps,
3593+
fileSystem: compiler.fileSystem);
35933594
}
35943595
}
35953596

pkg/dev_compiler/lib/src/codegen/js_printer.dart

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,15 @@ import 'package:source_maps/source_maps.dart' show SourceMapSpan;
1212
import 'package:source_span/source_span.dart' show SourceLocation;
1313

1414
import '../js/js_ast.dart' as JS;
15-
import '../utils.dart' show computeHash, locationForOffset;
15+
import '../utils.dart' show FileSystem, computeHash, locationForOffset;
1616

1717
import 'js_names.dart' show TemporaryNamer;
1818

1919
String writeJsLibrary(
2020
JS.Program jsTree, String outputPath, String inputDir, Uri serverUri,
21-
{bool emitSourceMaps: false}) {
21+
{bool emitSourceMaps: false, FileSystem fileSystem}) {
2222
var outFilename = path.basename(outputPath);
2323
var outDir = path.dirname(outputPath);
24-
new Directory(outDir).createSync(recursive: true);
2524

2625
JS.JavaScriptPrintingContext context;
2726
if (emitSourceMaps) {
@@ -54,11 +53,11 @@ String writeJsLibrary(
5453
// "names": ["state","print"]
5554
sourceMapText =
5655
sourceMapText.replaceAll('\n ', '').replaceAll('\n ]', ']');
57-
new File('$outputPath.map').writeAsStringSync('$sourceMapText\n');
56+
fileSystem.writeAsStringSync('$outputPath.map', '$sourceMapText\n');
5857
} else {
5958
text = (context as JS.SimpleJavaScriptPrintingContext).getText();
6059
}
61-
new File(outputPath).writeAsStringSync(text);
60+
fileSystem.writeAsStringSync(outputPath, text);
6261
if (jsTree.scriptTag != null) {
6362
// Mark executable.
6463
// TODO(jmesserly): should only do this if the input file was executable?

pkg/dev_compiler/lib/src/compiler.dart

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ import 'info.dart'
3232
import 'options.dart';
3333
import 'report.dart';
3434
import 'report/html_reporter.dart';
35-
import 'utils.dart' show isStrongModeError;
35+
import 'utils.dart' show FileSystem, isStrongModeError;
3636

3737
/// Sets up the type checker logger to print a span that highlights error
3838
/// messages.
@@ -100,12 +100,13 @@ class BatchCompiler extends AbstractCompiler {
100100
final _pendingLibraries = <LibraryUnit>[];
101101

102102
BatchCompiler(AnalysisContext context, CompilerOptions options,
103-
{AnalysisErrorListener reporter})
103+
{AnalysisErrorListener reporter,
104+
FileSystem fileSystem: const FileSystem()})
104105
: super(
105106
context,
106107
options,
107-
new ErrorCollector(
108-
reporter ?? AnalysisErrorListener.NULL_LISTENER)) {
108+
new ErrorCollector(reporter ?? AnalysisErrorListener.NULL_LISTENER),
109+
fileSystem) {
109110
_inputBaseDir = options.inputBaseDir;
110111
if (outputDir != null) {
111112
_jsGen = new JSGenerator(this);
@@ -264,8 +265,7 @@ class BatchCompiler extends AbstractCompiler {
264265
output.lastModifiedSync() == input.lastModifiedSync()) {
265266
continue;
266267
}
267-
new Directory(path.dirname(output.path)).createSync(recursive: true);
268-
input.copySync(output.path);
268+
fileSystem.copySync(input.path, output.path);
269269
}
270270
}
271271

@@ -304,10 +304,8 @@ class BatchCompiler extends AbstractCompiler {
304304
}
305305
}
306306

307-
var outputFile = getOutputPath(source.uri);
308-
new File(outputFile)
309-
..createSync(recursive: true)
310-
..writeAsStringSync(document.outerHtml + '\n');
307+
fileSystem.writeAsStringSync(
308+
getOutputPath(source.uri), document.outerHtml + '\n');
311309
}
312310

313311
html.DocumentFragment _linkLibraries(
@@ -357,8 +355,10 @@ abstract class AbstractCompiler {
357355
final CompilerOptions options;
358356
final AnalysisContext context;
359357
final AnalysisErrorListener reporter;
358+
final FileSystem fileSystem;
360359

361-
AbstractCompiler(this.context, this.options, [AnalysisErrorListener listener])
360+
AbstractCompiler(this.context, this.options,
361+
[AnalysisErrorListener listener, this.fileSystem = const FileSystem()])
362362
: reporter = listener ?? AnalysisErrorListener.NULL_LISTENER;
363363

364364
String get outputDir => options.codegenOptions.outputDir;
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'package:analyzer/src/generated/engine.dart';
6+
import 'package:analyzer/src/generated/source.dart' show Source, UriKind;
7+
import 'package:barback/barback.dart' show Asset, TimestampedData;
8+
import 'package:path/path.dart' as path;
9+
10+
class AssetSource implements Source {
11+
final Uri uri;
12+
final Asset asset;
13+
final String contentString;
14+
AssetSource(this.uri, this.asset, this.contentString);
15+
16+
@override toString() => 'AssetSource($uri, ${asset.id})';
17+
18+
@override
19+
TimestampedData<String> get contents =>
20+
new TimestampedData(modificationStamp, contentString);
21+
22+
@override
23+
String get encoding => null;
24+
25+
@override
26+
bool exists() => true;
27+
28+
@override
29+
String get fullName => uri.toString();
30+
31+
@override
32+
bool get isInSystemLibrary => uriKind == UriKind.DART_URI;
33+
34+
@override
35+
int get modificationStamp => 0;
36+
37+
@override
38+
Uri resolveRelativeUri(Uri relativeUri) {
39+
var resolvedPath = path.join(path.dirname(uri.path), relativeUri.path);
40+
return new Uri(scheme: uri.scheme, path: resolvedPath);
41+
}
42+
43+
@override
44+
String get shortName => uri.toString();
45+
46+
@override
47+
Source get source => this;
48+
49+
@override
50+
UriKind get uriKind {
51+
switch (uri.scheme) {
52+
case 'package':
53+
return UriKind.PACKAGE_URI;
54+
55+
case 'dart':
56+
return UriKind.DART_URI;
57+
58+
case 'file':
59+
return UriKind.FILE_URI;
60+
61+
default:
62+
throw new StateError(uri.toString());
63+
}
64+
}
65+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
library dev_compiler.src.transformer.asset_universe;
6+
7+
import 'dart:async';
8+
9+
import 'package:analyzer/analyzer.dart' show UriBasedDirective, parseDirectives;
10+
import 'package:barback/barback.dart' show Asset, AssetId;
11+
12+
import 'asset_source.dart';
13+
import 'uri_resolver.dart' show assetIdToUri, resolveAssetId;
14+
15+
/// Set of assets sources available for analysis / compilation.
16+
class AssetUniverse {
17+
final _assetCache = <AssetId, AssetSource>{};
18+
19+
Iterable<AssetId> get assetIds => _assetCache.keys;
20+
21+
AssetSource getAssetSource(AssetId id) {
22+
var source = _assetCache[id];
23+
if (source == null) {
24+
throw new ArgumentError(id.toString());
25+
}
26+
return source;
27+
}
28+
29+
/// Recursively loads the asset with [id] and all its transitive dependencies.
30+
Future scanSources(AssetId id, Future<Asset> getInput(AssetId id)) async {
31+
if (_assetCache.containsKey(id)) return;
32+
33+
var asset = await getInput(id);
34+
var contents = await asset.readAsString();
35+
_assetCache[id] =
36+
new AssetSource(Uri.parse(assetIdToUri(id)), asset, contents);
37+
38+
var deps = _getDependentAssetIds(id, contents);
39+
await Future.wait(deps.map((depId) => scanSources(depId, getInput)));
40+
}
41+
42+
Iterable<AssetId> _getDependentAssetIds(AssetId id, String contents) sync* {
43+
var directives = parseDirectives(contents, suppressErrors: true).directives;
44+
for (var directive in directives) {
45+
if (directive is UriBasedDirective) {
46+
var uri = directive.uri.stringValue;
47+
var assetId = resolveAssetId(Uri.parse(uri), fromAssetId: id);
48+
if (assetId != null) yield assetId;
49+
}
50+
}
51+
}
52+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
library dev_compiler.src.transformer.error_listener;
6+
7+
import 'package:barback/barback.dart' show TransformLogger;
8+
import 'package:analyzer/analyzer.dart'
9+
show AnalysisError, ErrorSeverity, AnalysisErrorListener;
10+
11+
class TransformAnalysisErrorListener extends AnalysisErrorListener {
12+
TransformLogger _logger;
13+
TransformAnalysisErrorListener(this._logger);
14+
15+
@override
16+
void onError(AnalysisError error) {
17+
// TODO(ochafik): Proper location / span.
18+
switch (error.errorCode.errorSeverity) {
19+
case ErrorSeverity.ERROR:
20+
_logger.error(error.message);
21+
break;
22+
case ErrorSeverity.WARNING:
23+
_logger.warning(error.message);
24+
break;
25+
default:
26+
_logger.info(error.message);
27+
break;
28+
}
29+
}
30+
}
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
library dev_compiler.src.transformer.transformer;
6+
7+
import 'dart:async';
8+
import 'dart:io';
9+
10+
import 'package:barback/barback.dart';
11+
import 'package:analyzer/src/generated/engine.dart'
12+
show AnalysisEngine, AnalysisOptionsImpl;
13+
import 'package:path/path.dart' as path;
14+
15+
import 'asset_universe.dart';
16+
import 'error_listener.dart';
17+
import 'uri_resolver.dart' show assetIdToUri, createSourceFactory;
18+
import '../compiler.dart';
19+
import '../options.dart';
20+
import '../utils.dart';
21+
import 'package:analyzer/src/generated/engine.dart';
22+
23+
const String _fakeRuntimeDir = "<runtime>";
24+
25+
/// Disclaimer: this transformer is experimental and not optimized. It may run
26+
/// out of memory for large applications: please use DDC's command-line runner
27+
/// instead whenever possible.
28+
class DdcTransformer extends AggregateTransformer {
29+
final List<String> _ddcArgs;
30+
31+
DdcTransformer.asPlugin(BarbackSettings settings)
32+
: _ddcArgs = settings.configuration['args'] ?? [];
33+
34+
@override
35+
apply(AggregateTransform transform) async {
36+
var inputs = await transform.primaryInputs.toList();
37+
38+
// The analyzer's source factory mechanism is synchronous, so we can't
39+
// have it wait upon transform.getInput. Instead, we build the whole
40+
// universe (scanning transitive dependencies and reading their sources),
41+
// so as to get a non-async source getter.
42+
// Note: This means we use a lot of memory: one way to fix it would be to
43+
// propagate asynchonous calls throughout the analyzer.
44+
var universe = new AssetUniverse();
45+
await Future.wait(
46+
inputs.map((a) => universe.scanSources(a.id, transform.getInput)));
47+
48+
// TODO(ochafik): invesigate the us of createAnalysisContextWithSources
49+
// instead.
50+
var context = AnalysisEngine.instance.createAnalysisContext();
51+
context.analysisOptions = _makeAnalysisOptions();
52+
context.sourceFactory = createSourceFactory(universe.getAssetSource);
53+
54+
// Virtual file system that writes into the transformer's outputs.
55+
var fileSystem = new _TransformerFileSystem(
56+
transform.logger, transform.package, transform.addOutput,
57+
// Seed the runtime files into our file system:
58+
inputs: await _readRuntimeFiles(transform.getInput));
59+
60+
var compiler = new BatchCompiler(
61+
context,
62+
// Note that the output directory needs not exist, and the runtime
63+
// directory is a special value that corresponds to the seeding of
64+
// runtimeFiles above.
65+
parseOptions([]
66+
..addAll(_ddcArgs)
67+
..addAll([
68+
'-o',
69+
fileSystem.outputDir.path,
70+
'--runtime-dir',
71+
_fakeRuntimeDir
72+
])),
73+
reporter: new TransformAnalysisErrorListener(transform.logger),
74+
fileSystem: fileSystem);
75+
76+
for (var asset in inputs) {
77+
compiler.compileFromUriString(assetIdToUri(asset.id));
78+
}
79+
}
80+
81+
// TODO(ochafik): Provide more control over these options.
82+
AnalysisOptions _makeAnalysisOptions() => new AnalysisOptionsImpl()
83+
..cacheSize = 256 // # of sources to cache ASTs for.
84+
..preserveComments = true
85+
..analyzeFunctionBodies = true
86+
..strongMode = true;
87+
88+
/// Read the runtime files from the transformer (they're available as
89+
/// resources of package:dev_compiler),
90+
Future<Map<String, String>> _readRuntimeFiles(
91+
Future<Asset> getInput(AssetId id)) async {
92+
var runtimeFiles = <String, String>{};
93+
for (var file in defaultRuntimeFiles) {
94+
var asset =
95+
await getInput(new AssetId('dev_compiler', 'lib/runtime/$file'));
96+
runtimeFiles[path.join(_fakeRuntimeDir, file)] =
97+
await asset.readAsString();
98+
}
99+
return runtimeFiles;
100+
}
101+
102+
/// We just transform all .dart and .html files in one go.
103+
@override
104+
classifyPrimary(AssetId id) =>
105+
id.extension == '.dart' || id.extension == '.html' ? '<dart>' : null;
106+
}
107+
108+
/// Type of [Transform.addOutput] and [AggregateTransform.addOutput].
109+
typedef void AssetOutputAdder(Asset asset);
110+
111+
/// Virtual file system that outputs files into a transformer.
112+
class _TransformerFileSystem implements FileSystem {
113+
final String _package;
114+
final Directory outputDir = Directory.current;
115+
final String outputPrefix;
116+
final AssetOutputAdder _addOutput;
117+
final Map<String, String> inputs;
118+
final TransformLogger _logger;
119+
_TransformerFileSystem(this._logger, this._package, this._addOutput,
120+
{this.inputs, this.outputPrefix: 'web/'});
121+
122+
@override
123+
void writeAsStringSync(String file, String contents) {
124+
var id = new AssetId(
125+
_package, outputPrefix + path.relative(file, from: outputDir.path));
126+
_logger.fine('Adding output $id');
127+
_addOutput(new Asset.fromString(id, contents));
128+
}
129+
130+
@override
131+
void copySync(String src, String dest) {
132+
writeAsStringSync(dest, inputs[src] ?? new File(src).readAsStringSync());
133+
}
134+
}

0 commit comments

Comments
 (0)