From 61aefab464056dad0afd739da942553ec988f0fc Mon Sep 17 00:00:00 2001
From: "nweiz@google.com" <nweiz@google.com>
Date: Fri, 24 Apr 2015 21:40:49 +0000
Subject: [PATCH] Support defining environment constants for dart2js via the
 command-line.

These constants only work for dart2js because neither Dartium nor the isolate
API support defining them.

BUG= http://dartbug.com/15806
R=rnystrom@google.com

Review URL: https://codereview.chromium.org//1106713003

git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge@45416 260f80e4-7a28-3924-810f-c04153c831b5
---
 lib/src/barback/asset_environment.dart      | 14 ++++-
 lib/src/barback/dart2js_transformer.dart    |  6 +-
 lib/src/command/build.dart                  | 12 +++-
 lib/src/command/serve.dart                  |  9 ++-
 lib/src/utils.dart                          | 10 ++++
 test/dart2js/environment_constant_test.dart | 65 +++++++++++++++++++++
 6 files changed, 109 insertions(+), 7 deletions(-)
 create mode 100644 test/dart2js/environment_constant_test.dart

diff --git a/lib/src/barback/asset_environment.dart b/lib/src/barback/asset_environment.dart
index c07d8600..e3c8d26d 100644
--- a/lib/src/barback/asset_environment.dart
+++ b/lib/src/barback/asset_environment.dart
@@ -60,15 +60,19 @@ class AssetEnvironment {
   /// entrypoints are loaded. Each entrypoint is expected to refer to a Dart
   /// library.
   ///
+  /// If [environmentConstants] is passed, the constants it defines are passed
+  /// on to the built-in dart2js transformer.
+  ///
   /// Returns a [Future] that completes to the environment once the inputs,
   /// transformers, and server are loaded and ready.
   static Future<AssetEnvironment> create(Entrypoint entrypoint,
       BarbackMode mode, {WatcherType watcherType, String hostname, int basePort,
       Iterable<String> packages, Iterable<AssetId> entrypoints,
-      bool useDart2JS: true}) {
+      Map<String, String> environmentConstants, bool useDart2JS: true}) {
     if (watcherType == null) watcherType = WatcherType.NONE;
     if (hostname == null) hostname = "localhost";
     if (basePort == null) basePort = 0;
+    if (environmentConstants == null) environmentConstants = {};
 
     return log.progress("Loading asset environment", () async {
       var graph = await entrypoint.loadPackageGraph();
@@ -77,7 +81,7 @@ class AssetEnvironment {
       barback.log.listen(_log);
 
       var environment = new AssetEnvironment._(graph, barback, mode,
-          watcherType, hostname, basePort);
+          watcherType, hostname, basePort, environmentConstants);
 
       await environment._load(entrypoints: entrypoints, useDart2JS: useDart2JS);
       return environment;
@@ -125,6 +129,9 @@ class AssetEnvironment {
   /// The mode to run the transformers in.
   final BarbackMode mode;
 
+  /// Constants to passed to the built-in dart2js transformer.
+  final Map<String, String> environmentConstants;
+
   /// The [Transformer]s that should be appended by default to the root
   /// package's transformer cascade. Will be empty if there are none.
   final _builtInTransformers = <Transformer>[];
@@ -158,7 +165,8 @@ class AssetEnvironment {
   Set<AssetId> _modifiedSources;
 
   AssetEnvironment._(this.graph, this.barback, this.mode,
-        this._watcherType, this._hostname, this._basePort);
+        this._watcherType, this._hostname, this._basePort,
+        this.environmentConstants);
 
   /// Gets the built-in [Transformer]s that should be added to [package].
   ///
diff --git a/lib/src/barback/dart2js_transformer.dart b/lib/src/barback/dart2js_transformer.dart
index 8c7ff14c..9097e074 100644
--- a/lib/src/barback/dart2js_transformer.dart
+++ b/lib/src/barback/dart2js_transformer.dart
@@ -166,13 +166,15 @@ class Dart2JSTransformer extends Transformer implements LazyTransformer {
 
   /// Parses and returns the "environment" configuration option.
   Map<String, String> get _configEnvironment {
-    if (!_settings.configuration.containsKey('environment')) return null;
+    if (!_settings.configuration.containsKey('environment')) {
+      return _environment.environmentConstants;
+    }
 
     var environment = _settings.configuration['environment'];
     if (environment is Map &&
         environment.keys.every((key) => key is String) &&
         environment.values.every((key) => key is String)) {
-      return environment;
+      return mergeMaps(environment, _environment.environmentConstants);
     }
 
     throw new FormatException('Invalid value for \$dart2js.environment: '
diff --git a/lib/src/command/build.dart b/lib/src/command/build.dart
index 1a88922c..b07cd95d 100644
--- a/lib/src/command/build.dart
+++ b/lib/src/command/build.dart
@@ -35,6 +35,10 @@ class BuildCommand extends BarbackCommand {
   int builtFiles = 0;
 
   BuildCommand() {
+    argParser.addOption("define", abbr: "D",
+        help: "Defines an environment constant for dart2js.",
+        allowMultiple: true, splitCommas: false);
+
     argParser.addOption("format",
         help: "How output should be displayed.",
         allowed: ["text", "json"], defaultsTo: "text");
@@ -50,10 +54,16 @@ class BuildCommand extends BarbackCommand {
     var errorsJson = [];
     var logJson = [];
 
+    var environmentConstants = new Map.fromIterable(argResults["define"],
+        key: (pair) => pair.split("=").first,
+        value: (pair) => pair.split("=").last);
+
     // Since this server will only be hit by the transformer loader and isn't
     // user-facing, just use an IPv4 address to avoid a weird bug on the
     // OS X buildbots.
-    return AssetEnvironment.create(entrypoint, mode, useDart2JS: true)
+    return AssetEnvironment.create(entrypoint, mode,
+            environmentConstants: environmentConstants,
+            useDart2JS: true)
         .then((environment) {
       // Show in-progress errors, but not results. Those get handled
       // implicitly by getAllAssets().
diff --git a/lib/src/command/serve.dart b/lib/src/command/serve.dart
index 32074fc9..c5b47469 100644
--- a/lib/src/command/serve.dart
+++ b/lib/src/command/serve.dart
@@ -59,6 +59,9 @@ class ServeCommand extends BarbackCommand {
   final _completer = new Completer();
 
   ServeCommand() {
+    argParser.addOption("define", abbr: "D",
+        help: "Defines an environment constant for dart2js.",
+        allowMultiple: true, splitCommas: false);
     argParser.addOption('hostname', defaultsTo: 'localhost',
         help: 'The hostname to listen on.');
     argParser.addOption('port', defaultsTo: '8080',
@@ -88,9 +91,13 @@ class ServeCommand extends BarbackCommand {
     var watcherType = argResults['force-poll'] ?
         WatcherType.POLLING : WatcherType.AUTO;
 
+    var environmentConstants = new Map.fromIterable(argResults["define"],
+        key: (pair) => pair.split("=").first,
+        value: (pair) => pair.split("=").last);
+
     var environment = await AssetEnvironment.create(entrypoint, mode,
         watcherType: watcherType, hostname: hostname, basePort: port,
-        useDart2JS: useDart2JS);
+        useDart2JS: useDart2JS, environmentConstants: environmentConstants);
     var directoryLength = sourceDirectories.map((dir) => dir.length)
         .reduce(math.max);
 
diff --git a/lib/src/utils.dart b/lib/src/utils.dart
index 14d81a67..d0eb936f 100644
--- a/lib/src/utils.dart
+++ b/lib/src/utils.dart
@@ -371,6 +371,16 @@ Future<Map> mapFromIterableAsync(Iterable iter, {key(element),
   })).then((_) => map);
 }
 
+/// Returns a new map with all entries in both [map1] and [map2].
+///
+/// If there are overlapping keys, [map2]'s value wins.
+Map mergeMaps(Map map1, Map map2) {
+  var result = {};
+  result.addAll(map1);
+  result.addAll(map2);
+  return result;
+}
+
 /// Returns the transitive closure of [graph].
 ///
 /// This assumes [graph] represents a graph with a vertex for each key and an
diff --git a/test/dart2js/environment_constant_test.dart b/test/dart2js/environment_constant_test.dart
new file mode 100644
index 00000000..dab1260f
--- /dev/null
+++ b/test/dart2js/environment_constant_test.dart
@@ -0,0 +1,65 @@
+// Copyright (c) 2014, 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 'package:scheduled_test/scheduled_test.dart';
+
+import '../descriptor.dart' as d;
+import '../serve/utils.dart';
+import '../test_pub.dart';
+
+main() {
+  initConfig();
+  group("passes environment constants to dart2js", () {
+    setUp(() {
+      // Dart2js can take a long time to compile dart code, so we increase the
+      // timeout to cope with that.
+      currentSchedule.timeout *= 3;
+
+      d.dir(appPath, [
+        d.appPubspec(),
+        d.dir('web', [
+          d.file('file.dart',
+              'void main() => print(const String.fromEnvironment("name"));')
+        ])
+      ]).create();
+    });
+
+    integration('from "pub build"', () {
+      schedulePub(args: ["build", "--define", "name=fblthp"],
+          output: new RegExp(r'Built 1 file to "build".'));
+
+      d.dir(appPath, [
+        d.dir('build', [
+          d.dir('web', [
+            d.matcherFile('file.dart.js', contains('fblthp')),
+          ])
+        ])
+      ]).validate();
+    });
+
+    integration('from "pub serve"', () {
+      pubServe(args: ["--define", "name=fblthp"]);
+      requestShouldSucceed("file.dart.js", contains("fblthp"));
+      endPubServe();
+    });
+
+    integration('which takes precedence over the pubspec', () {
+      d.dir(appPath, [
+        d.pubspec({
+          "name": "myapp",
+          "transformers": [
+            {"\$dart2js": {"environment": {"name": "slartibartfast"}}}
+          ]
+        })
+      ]).create();
+
+      pubServe(args: ["--define", "name=fblthp"]);
+      requestShouldSucceed("file.dart.js", allOf([
+        contains("fblthp"),
+        isNot(contains("slartibartfast"))
+      ]));
+      endPubServe();
+    });
+  });
+}
-- 
GitLab