Validate that generated dart code is rebuilt (#1717)

* basically works

* WIP: basic build works

* WIP: back to original

* Fix up grinder and builder so they work well together

* Fix package warnings, move builder

* Restore clearing temp directories

* Make checks actually work by taking into account written-in-place files (even with --output)

* make it work on windows

* make builder work on windows

* Add inline debugging information

* More debugging

* Eliminate remnants of cache creation

* Make logging a little nicer

* Review comments

* clean up path usage for windows

* quiver dep update for .64

* pin everything to .63 until pub fixed

* Bypass conflicting outputs warning

* Remove accidental build output directory
diff --git a/.travis.yml b/.travis.yml
index 794905e..e18a7ad 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,7 +1,7 @@
 language: dart
 sudo: false
 dart:
-  - "dev/raw/latest"
+  - "dev/raw/2.0.0-dev.63.0"
 env:
   - DARTDOC_BOT=main
   # TODO(devoncarew): add angulardart support
diff --git a/appveyor.yml b/appveyor.yml
index d4bd319..07eef01 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -3,7 +3,7 @@
 # BSD-style license that can be found in the LICENSE file.
 
 install:
-  - ps: wget https://storage.googleapis.com/dart-archive/channels/dev/raw/latest/sdk/dartsdk-windows-x64-release.zip -OutFile dart-sdk.zip
+  - ps: wget https://storage.googleapis.com/dart-archive/channels/dev/raw/2.0.0-dev.63.0/sdk/dartsdk-windows-x64-release.zip -OutFile dart-sdk.zip
   - cmd: echo "Unzipping dart-sdk..."
   - cmd: 7z x dart-sdk.zip -o"C:\tools" -y > nul
   - set PATH=%PATH%;C:\tools\dart-sdk\bin
diff --git a/build.yaml b/build.yaml
new file mode 100644
index 0000000..5a8d324
--- /dev/null
+++ b/build.yaml
@@ -0,0 +1,17 @@
+builders:
+  resource_builder:
+    # TODO(jcollins-g): switch to "tool/builder.dart" for next build release
+    import: "../../../tool/builder.dart"
+    builder_factories: ["resourceBuilder"]
+    build_extensions: {'$lib$': ['src/html/resources.g.dart']}
+    build_to: "source"
+    auto_apply: none
+
+targets:
+  builder:
+    sources: ["tool/builder.dart"]
+
+  $default:
+    sources:
+      exclude: ["tool/builder.dart"]
+    builders: {"dartdoc|resource_builder": {enabled: true}}
diff --git a/lib/src/html/resources.g.dart b/lib/src/html/resources.g.dart
index d2dfcc4..2d833be 100644
--- a/lib/src/html/resources.g.dart
+++ b/lib/src/html/resources.g.dart
@@ -1,7 +1,5 @@
 // WARNING: This file is auto-generated. Do not taunt.
 
-library dartdoc.html.resources;
-
 const List<String> resource_names = const [
   'package:dartdoc/resources/URI.js',
   'package:dartdoc/resources/css/bootstrap.css',
diff --git a/pubspec.lock b/pubspec.lock
index 3f4eddd..88e0924 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -29,6 +29,55 @@
       url: "https://pub.dartlang.org"
     source: hosted
     version: "1.0.3"
+  build:
+    dependency: "direct dev"
+    description:
+      name: build
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "0.12.6"
+  build_config:
+    dependency: transitive
+    description:
+      name: build_config
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "0.3.0"
+  build_resolvers:
+    dependency: transitive
+    description:
+      name: build_resolvers
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "0.2.0+2"
+  build_runner:
+    dependency: "direct dev"
+    description:
+      name: build_runner
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "0.8.10"
+  build_runner_core:
+    dependency: transitive
+    description:
+      name: build_runner_core
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "0.1.0"
+  built_collection:
+    dependency: transitive
+    description:
+      name: built_collection
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "3.1.1"
+  built_value:
+    dependency: transitive
+    description:
+      name: built_value
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "5.5.1"
   charcode:
     dependency: transitive
     description:
@@ -43,6 +92,13 @@
       url: "https://pub.dartlang.org"
     source: hosted
     version: "0.1.2+1"
+  code_builder:
+    dependency: transitive
+    description:
+      name: code_builder
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "3.1.0"
   collection:
     dependency: "direct main"
     description:
@@ -71,6 +127,13 @@
       url: "https://pub.dartlang.org"
     source: hosted
     version: "0.14.1"
+  dart_style:
+    dependency: transitive
+    description:
+      name: dart_style
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "1.0.14"
   dhttpd:
     dependency: "direct dev"
     description:
@@ -78,6 +141,13 @@
       url: "https://pub.dartlang.org"
     source: hosted
     version: "2.0.0"
+  fixnum:
+    dependency: transitive
+    description:
+      name: fixnum
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "0.10.7"
   front_end:
     dependency: "direct main"
     description:
@@ -86,12 +156,19 @@
     source: hosted
     version: "0.1.1"
   glob:
-    dependency: transitive
+    dependency: "direct dev"
     description:
       name: glob
       url: "https://pub.dartlang.org"
     source: hosted
     version: "1.1.5"
+  graphs:
+    dependency: transitive
+    description:
+      name: graphs
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "0.1.2"
   grinder:
     dependency: "direct dev"
     description:
@@ -141,6 +218,13 @@
       url: "https://pub.dartlang.org"
     source: hosted
     version: "0.6.1"
+  json_annotation:
+    dependency: transitive
+    description:
+      name: json_annotation
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "0.2.8"
   kernel:
     dependency: transitive
     description:
@@ -246,13 +330,20 @@
       url: "https://pub.dartlang.org"
     source: hosted
     version: "1.3.7"
+  pubspec_parse:
+    dependency: transitive
+    description:
+      name: pubspec_parse
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "0.1.1"
   quiver:
     dependency: "direct main"
     description:
       name: quiver
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "0.27.0"
+    version: "0.29.0+1"
   quiver_hashcode:
     dependency: transitive
     description:
@@ -330,6 +421,13 @@
       url: "https://pub.dartlang.org"
     source: hosted
     version: "1.6.4"
+  stream_transform:
+    dependency: transitive
+    description:
+      name: stream_transform
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "0.0.14"
   string_scanner:
     dependency: transitive
     description:
@@ -394,4 +492,4 @@
     source: hosted
     version: "2.1.13"
 sdks:
-  dart: ">=2.0.0-dev.59.0 <=2.0.0-dev.62.0"
+  dart: ">=2.0.0-dev.59.0 <=2.0.0-dev.63.0"
diff --git a/pubspec.yaml b/pubspec.yaml
index b8f610c..8cf8c18 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -7,7 +7,7 @@
 environment:
   sdk: '>=2.0.0-dev.59.0 <3.0.0'
 dependencies:
-  analyzer: 0.32.1
+  analyzer: ^0.32.1
   args: '>=1.4.1 <2.0.0'
   collection: ^1.2.0
   front_end: ^0.1.1
@@ -21,13 +21,16 @@
   package_config: '>=0.1.5 <2.0.0'
   path: ^1.3.0
   pub_semver: ^1.3.7
-  quiver: ^0.27.0
+  quiver: ^0.29.0
   resource: ^2.1.2
   stack_trace: ^1.4.2
   tuple: ^1.0.1
   yaml: ^2.1.0
 dev_dependencies:
+  build: ^0.12.6
+  build_runner: ^0.8.10
   dhttpd: ^2.0.0
+  glob: ^1.1.5
   grinder: ^0.8.2
   io: ^0.3.0
   http: ^0.11.0
diff --git a/tool/builder.dart b/tool/builder.dart
new file mode 100644
index 0000000..4ad3a21
--- /dev/null
+++ b/tool/builder.dart
@@ -0,0 +1,43 @@
+// Copyright (c) 2018, 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:async';
+
+import 'package:build/build.dart';
+import 'package:glob/glob.dart';
+import 'package:path/path.dart' as pathLib;
+
+String _resourcesFile(Iterable<String> packagePaths) => '''
+// WARNING: This file is auto-generated. Do not taunt.
+
+const List<String> resource_names = const [
+${packagePaths.map((p) => "  '$p'").join(',\n')}
+];
+''';
+
+class ResourceBuilder implements Builder {
+  final BuilderOptions builderOptions;
+  ResourceBuilder(this.builderOptions);
+
+  static final _allResources = new Glob('lib/resources/**');
+  @override
+  Future build(BuildStep buildStep) async {
+    var packagePaths = <String>[];
+    await for (AssetId asset in buildStep.findAssets(_allResources)) {
+      packagePaths.add(asset.uri.toString());
+    }
+    packagePaths.sort();
+    await buildStep.writeAsString(
+        new AssetId(buildStep.inputId.package,
+            pathLib.url.join('lib', 'src', 'html', 'resources.g.dart')),
+        _resourcesFile(packagePaths));
+  }
+
+  @override
+  final Map<String, List<String>> buildExtensions = const {
+    r'$lib$': const ['src/html/resources.g.dart']
+  };
+}
+
+Builder resourceBuilder(BuilderOptions options) => new ResourceBuilder(options);
diff --git a/tool/grind.dart b/tool/grind.dart
index 1a21c23..71603f3 100644
--- a/tool/grind.dart
+++ b/tool/grind.dart
@@ -175,7 +175,7 @@
 }
 
 @Task('analyze, test, and self-test dartdoc')
-@Depends(analyze, test, testDartdoc)
+@Depends(analyze, checkBuild, test, testDartdoc)
 buildbot() => null;
 
 @Task('Generate docs for the Dart SDK')
@@ -651,68 +651,58 @@
   return version;
 }
 
-@Task('Find transformers used by this project')
-findTransformers() async {
-  var dotPackages = new File('.packages');
-  if (!dotPackages.existsSync()) {
-    fail('No .packages file found in ${Directory.current}');
-  }
-
-  var foundAnyTransformers = false;
-
-  dotPackages
-      .readAsLinesSync()
-      .where((line) => !line.startsWith('#'))
-      .map((line) => line.split(':file://'))
-      .forEach((List<String> mapping) {
-    var pubspec = new File(mapping.last.replaceFirst('lib/', 'pubspec.yaml'));
-    if (pubspec.existsSync()) {
-      var yamlDoc = yaml.loadYaml(pubspec.readAsStringSync());
-      if (yamlDoc['transformers'] != null) {
-        log('${mapping.first} has transformers!');
-        foundAnyTransformers = true;
-      }
-    } else {
-      log('No pubspec found for ${mapping.first}, tried ${pubspec}');
-    }
-  });
-
-  if (!foundAnyTransformers) {
-    log('No transformers found');
-  }
+@Task('Rebuild generated files')
+build() async {
+  var launcher = new SubprocessLauncher('build');
+  await launcher.runStreamed(sdkBin('pub'), ['run', 'build_runner', 'build', '--delete-conflicting-outputs']);
 }
 
-@Task('Make sure all the resource files are present')
-indexResources() {
-  var sourcePath = pathLib.join('lib', 'resources');
-  if (!new Directory(sourcePath).existsSync()) {
-    throw new StateError('lib/resources directory not found');
-  }
-  var outDir = new Directory(pathLib.join('lib'));
-  var out =
-      new File(pathLib.join(outDir.path, 'src', 'html', 'resources.g.dart'));
-  out.createSync(recursive: true);
-  var buffer = new StringBuffer()
-    ..write('// WARNING: This file is auto-generated. Do not taunt.\n\n')
-    ..write('library dartdoc.html.resources;\n\n')
-    ..write('const List<String> resource_names = const [\n');
-  var packagePaths = [];
-  for (var fileName in listDir(sourcePath, recursive: true)) {
-    if (!FileSystemEntity.isDirectorySync(fileName)) {
-      var packageified = fileName.replaceFirst('lib/', 'package:dartdoc/');
-      packagePaths.add(packageified);
+/// Paths in this list are relative to lib/.
+final _generated_files_list = <String>['src/html/resources.g.dart']
+    .map((s) => pathLib.joinAll(pathLib.posix.split(s)));
+
+@Task('Verify generated files are up to date')
+checkBuild() async {
+  var originalFileContents = new Map<String, String>();
+  var differentFiles = <String>[];
+  var launcher = new SubprocessLauncher('check-build');
+
+  // Load original file contents into memory before running the builder;
+  // it modifies them in place.
+  for (String relPath in _generated_files_list) {
+    String origPath = pathLib.joinAll(['lib', relPath]);
+    File oldVersion = new File(origPath);
+    if (oldVersion.existsSync()) {
+      originalFileContents[relPath] = oldVersion.readAsStringSync();
     }
   }
-  packagePaths.sort();
-  buffer.write(packagePaths.map((p) => "  '$p'").join(',\n'));
-  buffer.write('\n];\n');
-  out.writeAsString(buffer.toString());
+
+  await launcher.runStreamed(sdkBin('pub'), ['run', 'build_runner', 'build', '--delete-conflicting-outputs']);
+  for (String relPath in _generated_files_list) {
+    File newVersion = new File(pathLib.join('lib', relPath));
+    if (!await newVersion.exists()) {
+      log('${newVersion.path} does not exist\n');
+      differentFiles.add(relPath);
+    } else if (originalFileContents[relPath] !=
+        await newVersion.readAsString()) {
+      log('${newVersion.path} has changed to: \n${newVersion.readAsStringSync()})');
+      differentFiles.add(relPath);
+    }
+  }
+
+  if (differentFiles.isNotEmpty) {
+    fail('The following generated files needed to be rebuilt:\n'
+        '  ${differentFiles.map((f) => pathLib.join('lib', f)).join("\n  ")}\n'
+        'Rebuild them with "grind build" and check the results in.');
+  }
 }
 
 @Task('Publish to pub.dartlang')
-@Depends(checkChangelogHasVersion)
+@Depends(checkChangelogHasVersion, buildbot)
 publish() async {
-  log('run : pub publish');
+  var launcher = new SubprocessLauncher('publish-dryrun');
+  await launcher.runStreamed('pub', ['publish', '-n']);
+  log('\nTo publish, run:\n  pub publish');
 }
 
 @Task('Run all the tests.')