[build] Allow builders to opt-out of goma using recipe module properties

This allows builders to be opt-ed out of goma by specifying the
`$dart/build` message with a JSON value of `{ "disable_goma": true }`.

Change-Id: Ia8a47b9d91e56cef51553b0b592efa2955a2a9ec
Reviewed-on: https://dart-review.googlesource.com/c/recipes/+/173961
Reviewed-by: Karl Klose <karlklose@google.com>
Commit-Queue: Alexander Thomas <athom@google.com>
diff --git a/README.recipes.md b/README.recipes.md
index 6a7fa89..c4ed263 100644
--- a/README.recipes.md
+++ b/README.recipes.md
@@ -44,17 +44,17 @@
 &mdash; **def [schedule](/recipe_modules/bisect_build/api.py#29)(self, repo_url, reason, is_experimental=False):**
 ### *recipe_modules* / [build](/recipe_modules/build)
 
-[DEPS](/recipe_modules/build/__init__.py#5): [build/goma][build/recipe_modules/goma], [depot\_tools/depot\_tools][depot_tools/recipe_modules/depot_tools], [recipe\_engine/context][recipe_engine/recipe_modules/context], [recipe\_engine/path][recipe_engine/recipe_modules/path], [recipe\_engine/python][recipe_engine/recipe_modules/python], [recipe\_engine/step][recipe_engine/recipe_modules/step]
+[DEPS](/recipe_modules/build/__init__.py#5): [build/goma][build/recipe_modules/goma], [depot\_tools/depot\_tools][depot_tools/recipe_modules/depot_tools], [recipe\_engine/context][recipe_engine/recipe_modules/context], [recipe\_engine/path][recipe_engine/recipe_modules/path], [recipe\_engine/properties][recipe_engine/recipe_modules/properties], [recipe\_engine/python][recipe_engine/recipe_modules/python], [recipe\_engine/step][recipe_engine/recipe_modules/step]
 
-#### **class [BuildApi](/recipe_modules/build/api.py#8)([RecipeApi][recipe_engine/wkt/RecipeApi]):**
+#### **class [BuildApi](/recipe_modules/build/api.py#7)([RecipeApi][recipe_engine/wkt/RecipeApi]):**
 
 Recipe module for building the Dart SDK with and without goma.
 
-&mdash; **def [build](/recipe_modules/build/api.py#15)(self, name='build dart', args=None):**
+&mdash; **def [build](/recipe_modules/build/api.py#16)(self, name='build dart', args=None):**
 
 Builds dart using the specified args
 
-&mdash; **def [gn](/recipe_modules/build/api.py#23)(self, name='gn', args=None):**
+&mdash; **def [gn](/recipe_modules/build/api.py#29)(self, name='gn', args=None):**
 
 Runs gn.py using the specified args
 ### *recipe_modules* / [dart](/recipe_modules/dart)
@@ -124,14 +124,14 @@
 &mdash; **def [RunSteps](/recipe_modules/bisect_build/tests/tests.py#39)(api, current_failure, is_experimental):**
 ### *recipes* / [build:tests/test\_build](/recipe_modules/build/tests/test_build.py)
 
-[DEPS](/recipe_modules/build/tests/test_build.py#3): [build](#recipe_modules-build), [recipe\_engine/step][recipe_engine/recipe_modules/step]
+[DEPS](/recipe_modules/build/tests/test_build.py#5): [build](#recipe_modules-build), [recipe\_engine/properties][recipe_engine/recipe_modules/properties], [recipe\_engine/step][recipe_engine/recipe_modules/step]
 
-&mdash; **def [RunSteps](/recipe_modules/build/tests/test_build.py#9)(api):**
+&mdash; **def [RunSteps](/recipe_modules/build/tests/test_build.py#12)(api):**
 ### *recipes* / [build:tests/test\_gn](/recipe_modules/build/tests/test_gn.py)
 
-[DEPS](/recipe_modules/build/tests/test_gn.py#3): [build](#recipe_modules-build), [recipe\_engine/step][recipe_engine/recipe_modules/step]
+[DEPS](/recipe_modules/build/tests/test_gn.py#5): [build](#recipe_modules-build), [recipe\_engine/properties][recipe_engine/recipe_modules/properties], [recipe\_engine/step][recipe_engine/recipe_modules/step]
 
-&mdash; **def [RunSteps](/recipe_modules/build/tests/test_gn.py#9)(api):**
+&mdash; **def [RunSteps](/recipe_modules/build/tests/test_gn.py#12)(api):**
 ### *recipes* / [cron/cron](/recipes/cron/cron.py)
 
 [DEPS](/recipes/cron/cron.py#21): [depot\_tools/gitiles][depot_tools/recipe_modules/gitiles], [recipe\_engine/buildbucket][recipe_engine/recipe_modules/buildbucket], [recipe\_engine/properties][recipe_engine/recipe_modules/properties]
diff --git a/recipe_modules/build/__init__.py b/recipe_modules/build/__init__.py
index b528446..82ca905 100644
--- a/recipe_modules/build/__init__.py
+++ b/recipe_modules/build/__init__.py
@@ -7,6 +7,11 @@
     'depot_tools/depot_tools',
     'recipe_engine/context',
     'recipe_engine/path',
+    'recipe_engine/properties',
     'recipe_engine/python',
     'recipe_engine/step',
 ]
+
+from PB.recipe_modules.dart.build.build import Build
+
+PROPERTIES = Build
diff --git a/recipe_modules/build/api.py b/recipe_modules/build/api.py
index b3b000a..b1a2a85 100644
--- a/recipe_modules/build/api.py
+++ b/recipe_modules/build/api.py
@@ -4,13 +4,14 @@
 
 from recipe_engine import recipe_api
 
-
 class BuildApi(recipe_api.RecipeApi):
   """Recipe module for building the Dart SDK with and without goma."""
 
-  def __init__(self, *args, **kwargs):
+  def __init__(self, properties, *args, **kwargs):
     super(BuildApi, self).__init__(*args, **kwargs)
-    self._goma_ensured = False
+    self._goma = not properties.disable_goma
+    # No need to download goma if it's disabled.
+    self._goma_ensured = properties.disable_goma
 
   def build(self, name='build dart', args=None):
     """Builds dart using the specified args"""
@@ -18,12 +19,19 @@
       args = []
     with self.m.depot_tools.on_path(), self.m.context(
         cwd=self.m.path['checkout']):
-      self._build_with_goma(name, args)
+      if self._goma:
+        self._build_with_goma(name, args)
+      else:
+        if '--no-goma' not in args:
+          args = args + ['--no-goma']
+        self._run_build_py(name, args)
 
   def gn(self, name='gn', args=None):
     """Runs gn.py using the specified args"""
     self._ensure_goma()
-    with self.m.context(env={'GOMA_DIR': self.m.goma.goma_dir}):
+    with self.m.context(env=self._build_env):
+      if not self._goma and '--no-goma' not in args:
+        args = args + ['--no-goma']
       self.m.python(
           name, self.m.path['checkout'].join('tools', 'gn.py'), args=args)
 
@@ -38,7 +46,7 @@
   def _build_with_goma(self, name, args):
     self._ensure_goma()
     build_exit_status = None
-    with self.m.context(env={'GOMA_DIR': self.m.goma.goma_dir}):
+    with self.m.context(env=self._build_env):
       try:
         self.m.goma.start()
         args = args + ['--no-start-goma', '-j200']
@@ -53,3 +61,7 @@
     if not self._goma_ensured:
       self.m.goma.ensure_goma()
       self._goma_ensured = True
+
+  @property
+  def _build_env(self):
+    return {'GOMA_DIR': self.m.goma.goma_dir} if self._goma else {}
diff --git a/recipe_modules/build/build.proto b/recipe_modules/build/build.proto
new file mode 100644
index 0000000..b991c88
--- /dev/null
+++ b/recipe_modules/build/build.proto
@@ -0,0 +1,11 @@
+// Copyright (c) 2020, the Dart project authors. All rights reserved. Use of
+// this source code is governed by a BSD-style license that can be found in the
+// LICENSE file.
+syntax = "proto3";
+
+package recipe_modules.dart.build;
+
+message Build {
+  // Disables goma if set to true, defaults to false.
+  bool disable_goma = 1;
+}
diff --git a/recipe_modules/build/tests/test_build.expected/no-goma.json b/recipe_modules/build/tests/test_build.expected/no-goma.json
new file mode 100644
index 0000000..a6ca7ed
--- /dev/null
+++ b/recipe_modules/build/tests/test_build.expected/no-goma.json
@@ -0,0 +1,25 @@
+[
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "None/tools/build.py",
+      "--super-fast",
+      "--no-goma"
+    ],
+    "cwd": "None",
+    "env_suffixes": {
+      "DEPOT_TOOLS_UPDATE": [
+        "0"
+      ],
+      "PATH": [
+        "RECIPE_REPO[depot_tools]"
+      ]
+    },
+    "name": "build it",
+    "timeout": 3000
+  },
+  {
+    "name": "$result"
+  }
+]
\ No newline at end of file
diff --git a/recipe_modules/build/tests/test_build.py b/recipe_modules/build/tests/test_build.py
index 3341692..92b7c2f 100644
--- a/recipe_modules/build/tests/test_build.py
+++ b/recipe_modules/build/tests/test_build.py
@@ -1,7 +1,10 @@
 from recipe_engine import post_process
 
+from PB.recipe_modules.dart.build.build import Build
+
 DEPS = [
     'build',
+    'recipe_engine/properties',
     'recipe_engine/step',
 ]
 
@@ -18,6 +21,18 @@
   )
 
   yield api.test(
+      'no-goma',
+      api.properties(**{'$dart/build':Build(disable_goma=True)}),
+      api.post_process(post_process.StepCommandContains,
+                'build it', [
+                    '--super-fast',
+                    '--no-goma',
+                ]),
+      api.post_process(post_process.DoesNotRunRE, '.*goma.*'),
+      api.post_process(post_process.StatusSuccess),
+  )
+
+  yield api.test(
       'fail',
       api.step_data('build it', retcode=1),
       api.post_process(post_process.StatusFailure),
diff --git a/recipe_modules/build/tests/test_gn.expected/no-goma.json b/recipe_modules/build/tests/test_gn.expected/no-goma.json
new file mode 100644
index 0000000..abb15cb
--- /dev/null
+++ b/recipe_modules/build/tests/test_gn.expected/no-goma.json
@@ -0,0 +1,15 @@
+[
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "None/tools/gn.py",
+      "--super-fast",
+      "--no-goma"
+    ],
+    "name": "gn it"
+  },
+  {
+    "name": "$result"
+  }
+]
\ No newline at end of file
diff --git a/recipe_modules/build/tests/test_gn.py b/recipe_modules/build/tests/test_gn.py
index ed3cbd0..9cfe7fc 100644
--- a/recipe_modules/build/tests/test_gn.py
+++ b/recipe_modules/build/tests/test_gn.py
@@ -1,7 +1,10 @@
 from recipe_engine import post_process
 
+from PB.recipe_modules.dart.build.build import Build
+
 DEPS = [
     'build',
+    'recipe_engine/properties',
     'recipe_engine/step',
 ]
 
@@ -18,6 +21,18 @@
   )
 
   yield api.test(
+      'no-goma',
+      api.properties(**{'$dart/build':Build(disable_goma=True)}),
+      api.post_process(post_process.StepCommandContains,
+                'gn it', [
+                    '--super-fast',
+                    '--no-goma',
+                ]),
+      api.post_process(post_process.DoesNotRunRE, '.*goma.*'),
+      api.post_process(post_process.StatusSuccess),
+  )
+
+  yield api.test(
       'fail',
       api.step_data('gn it', retcode=1),
       api.post_process(post_process.StatusFailure),