Migration: add `assemble_resources.dart` script.

This script does the same job as `generate_resources.dart`, but it
doesn't search the filesystem for the input files to use, nor invoke
the compiler to create the `migration.js` file.  Instead, it simply
accepts a list of input files on the command line, and outputs their
contents in an appropriate form to be stored in `resources.g.dart`.

This script will allow us to integrate more cleanly with the internal
build system.

Change-Id: I4b5445d1f5fd7eccbb5fb36f6ee4fa97d2cea87e
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/254421
Reviewed-by: Samuel Rawlins <srawlins@google.com>
Commit-Queue: Paul Berry <paulberry@google.com>
diff --git a/pkg/nnbd_migration/tool/codegen/assemble_resources.dart b/pkg/nnbd_migration/tool/codegen/assemble_resources.dart
new file mode 100644
index 0000000..27fa5dc
--- /dev/null
+++ b/pkg/nnbd_migration/tool/codegen/assemble_resources.dart
@@ -0,0 +1,34 @@
+// Copyright (c) 2022, 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.
+
+// This script provides an alternative way of creating the
+// lib/src/front_end/resources/resources.g.dart file, based on a set of provided
+// files that has already been suitably compiled.
+
+import 'dart:io';
+
+import 'package:args/args.dart';
+
+import 'generate_resources.dart';
+
+main(List<String> args) {
+  var argParser = ArgParser()
+    ..addOption('output', abbr: 'o', help: 'Output to FILE', valueHelp: 'FILE')
+    ..addFlag('help', abbr: 'h', negatable: false, help: 'Show help');
+  var parsedArgs = argParser.parse(args);
+  if (parsedArgs['help'] as bool) {
+    print('Usage: dart assemble_resources.dart INPUT_FILES');
+    print('');
+    print(argParser.usage);
+    exit(1);
+  }
+  var content =
+      generateResourceFile([for (var arg in parsedArgs.rest) File(arg)]);
+  var output = parsedArgs['output'] as String?;
+  if (output == null) {
+    stdout.write(content);
+  } else {
+    File(output).writeAsStringSync(content);
+  }
+}
diff --git a/pkg/nnbd_migration/tool/codegen/generate_resources.dart b/pkg/nnbd_migration/tool/codegen/generate_resources.dart
index 83cfd8c..2ba2ae73 100644
--- a/pkg/nnbd_migration/tool/codegen/generate_resources.dart
+++ b/pkg/nnbd_migration/tool/codegen/generate_resources.dart
@@ -110,11 +110,12 @@
 }
 
 void createResourcesGDart() {
-  var content =
-      generateResourceFile(sortDir(resourceDir.listSync()).where((entity) {
-    var name = path.basename(entity.path);
-    return entity is File && resourceTypes.contains(path.extension(name));
-  }).cast<File>());
+  var content = generateResourceFile(
+      sortDir(resourceDir.listSync()).where((entity) {
+        var name = path.basename(entity.path);
+        return entity is File && resourceTypes.contains(path.extension(name));
+      }).cast<File>(),
+      sourcesMd5: _computeSourcesMd5());
 
   // write the content
   resourcesFile.writeAsStringSync(content);
@@ -149,7 +150,7 @@
   exit(1);
 }
 
-String generateResourceFile(Iterable<File> resources) {
+String generateResourceFile(Iterable<File> resources, {String? sourcesMd5}) {
   var filePath = path.relative(Platform.script.toFilePath());
   var buf = StringBuffer('''
 // Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
@@ -196,17 +197,10 @@
 
     buf.writeln();
     buf.writeln('String? _$name;');
-    if (name == path.basename(javascriptOutput.path).replaceAll('.', '_')) {
+    if (sourcesMd5 != null &&
+        name == path.basename(javascriptOutput.path).replaceAll('.', '_')) {
       // Write out the crc for the dart code.
-      var sourceCode = StringBuffer();
-      // collect the dart source code
-      for (var entity in sortDir(dartSources.parent.listSync())) {
-        if (entity.path.endsWith('.dart')) {
-          sourceCode.write((entity as File).readAsStringSync());
-        }
-      }
-      buf.writeln(
-          "// migration_dart md5 is '${md5String(sourceCode.toString())}'");
+      buf.writeln("// migration_dart md5 is '$sourcesMd5'");
     } else {
       // highlight_css md5 is 'fb012626bafd286510d32da815dae448'
       buf.writeln("// $name md5 is '${md5String(source)}'");
@@ -272,13 +266,7 @@
   }
 
   // verify the compiled dart code
-  var sourceCode = StringBuffer();
-  for (var entity in sortDir(dartSources.parent.listSync())) {
-    if (entity.path.endsWith('.dart')) {
-      sourceCode.write((entity as File).readAsStringSync());
-    }
-  }
-  var hash = md5String(sourceCode.toString());
+  String hash = _computeSourcesMd5();
   if (hash != resourceHashes['migration_dart']) {
     failVerification('Compiled javascript not up to date in resources.g.dart');
   }
@@ -286,4 +274,16 @@
   print('Generated resources up to date.');
 }
 
+String _computeSourcesMd5() {
+  var sourceCode = StringBuffer();
+  // collect the dart source code
+  for (var entity in sortDir(dartSources.parent.listSync())) {
+    if (entity.path.endsWith('.dart')) {
+      sourceCode.write((entity as File).readAsStringSync());
+    }
+  }
+  var sourcesMd5 = md5String(sourceCode.toString());
+  return sourcesMd5;
+}
+
 typedef VerificationFunction = void Function(String);