blob: 73a040240fd4f5b313593d8c15c2efc01c3b6661 [file] [log] [blame]
Godofredo Contreras567f56b2023-03-03 17:26:48 +00001# Copyright 2015 The Chromium Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5from contextlib import contextmanager
6
7from recipe_engine import recipe_api
8
9
10class GomaApi(recipe_api.RecipeApi):
Godofredo Contreras693a1882023-04-28 18:08:10 +000011 """GomaApi contains helper functions for using goma."""
Godofredo Contreras567f56b2023-03-03 17:26:48 +000012
Godofredo Contreras693a1882023-04-28 18:08:10 +000013 def __init__(self, props, *args, **kwargs):
14 super().__init__(*args, **kwargs)
Godofredo Contreras567f56b2023-03-03 17:26:48 +000015
Godofredo Contreras693a1882023-04-28 18:08:10 +000016 self._enable_arbitrary_toolchains = props.enable_arbitrary_toolchains
17 self._goma_dir = props.goma_dir
18 self._jobs = props.jobs
19 self._server = (
20 props.server or "rbe-prod1.endpoints.fuchsia-infra-goma-prod.cloud.goog"
21 )
22 self._goma_started = False
23 self._goma_log_dir = None
Godofredo Contreras567f56b2023-03-03 17:26:48 +000024
Godofredo Contreras693a1882023-04-28 18:08:10 +000025 @contextmanager
26 def __call__(self):
27 """Make context wrapping goma start/stop."""
28 # Some environment needs to be set for both compiler_proxy and gomacc.
29 # Push those variables used by both into context so the build can use
30 # them.
31 with self.m.context(env={
32 # Allow user to override from the command line.
33 "GOMA_TMP_DIR": self.m.context.env.get(
34 "GOMA_TMP_DIR", self.m.path["cleanup"].join("goma")),
35 "GOMA_USE_LOCAL": False,
36 }):
37 with self.m.step.nest("setup goma"):
38 self._start()
39 try:
40 yield
41 finally:
42 if not self.m.runtime.in_global_shutdown:
43 with self.m.step.nest("teardown goma"):
44 self._stop()
Godofredo Contreras567f56b2023-03-03 17:26:48 +000045
Godofredo Contreras693a1882023-04-28 18:08:10 +000046 @property
47 def jobs(self):
48 """Returns number of jobs for parallel build using Goma."""
49 if self._jobs:
50 return self._jobs
51 # Based on measurements, anything beyond 10*cpu_count won't improve
52 # build speed. For safety, set an upper limit of 1000.
53 return min(10 * self.m.platform.cpu_count, 1000)
Godofredo Contreras567f56b2023-03-03 17:26:48 +000054
Godofredo Contreras693a1882023-04-28 18:08:10 +000055 @property
56 def goma_dir(self):
57 if not self._goma_dir:
58 self._ensure()
59 return self._goma_dir
Godofredo Contreras567f56b2023-03-03 17:26:48 +000060
Godofredo Contreras693a1882023-04-28 18:08:10 +000061 @property
62 def _stats_path(self):
63 return self.m.path.join(self.goma_dir, "goma_stats.json")
Godofredo Contreras3686b632023-04-26 19:46:07 +000064
Godofredo Contreras693a1882023-04-28 18:08:10 +000065 def initialize(self):
66 self._goma_log_dir = self.m.path["cleanup"]
67 if self.m.platform.is_win:
68 self._enable_arbitrary_toolchains = True
Godofredo Contreras567f56b2023-03-03 17:26:48 +000069
Godofredo Contreras693a1882023-04-28 18:08:10 +000070 def set_path(self, path):
71 self._goma_dir = path
Godofredo Contreras567f56b2023-03-03 17:26:48 +000072
Godofredo Contreras693a1882023-04-28 18:08:10 +000073 def _ensure(self):
74 if self._goma_dir:
75 return
Godofredo Contreras567f56b2023-03-03 17:26:48 +000076
Godofredo Contreras693a1882023-04-28 18:08:10 +000077 with self.m.step.nest("ensure goma"), self.m.context(infra_steps=True):
78 self._goma_dir = self.m.path["cache"].join("goma", "client")
79 if self.m.platform.is_mac:
80 # On mac always use x64 package.
81 # TODO(godofredoc): Remove this workaround and unfork once fuchsia has an arm package.
82 package_path = "fuchsia/third_party/goma/client/mac-amd64"
83 else:
84 package_path = "fuchsia/third_party/goma/client/${platform}"
Godofredo Contreras73d61102023-04-26 16:07:00 +000085
Godofredo Contreras693a1882023-04-28 18:08:10 +000086 self.m.cipd.ensure(
87 self._goma_dir,
88 self.m.cipd.EnsureFile().add_package(package_path, "integration"),
89 )
Godofredo Contreras567f56b2023-03-03 17:26:48 +000090
Godofredo Contreras693a1882023-04-28 18:08:10 +000091 def _goma_ctl(self, step_name, args, **kwargs):
92 """Run a goma_ctl.py subcommand."""
93 env = {
94 "GLOG_log_dir":
95 self._goma_log_dir,
96 "GOMA_CACHE_DIR":
97 self.m.path["cache"].join("goma"),
98 "GOMA_DEPS_CACHE_FILE":
99 "goma_deps_cache",
100 "GOMA_LOCAL_OUTPUT_CACHE_DIR":
101 self.m.path["cache"].join("goma", "localoutputcache"),
102 "GOMA_STORE_LOCAL_RUN_OUTPUT":
103 True,
104 "GOMA_SERVER_HOST":
105 self._server,
106 "GOMA_DUMP_STATS_FILE":
Godofredo Contreras567f56b2023-03-03 17:26:48 +0000107 self._stats_path,
Godofredo Contreras693a1882023-04-28 18:08:10 +0000108 # The next power of 2 larger than the currently known largest
109 # output (153565624) from the core.x64 profile build.
110 "GOMA_MAX_SUM_OUTPUT_SIZE_IN_MB":
111 256,
112 }
113 if self._enable_arbitrary_toolchains:
114 env["GOMA_ARBITRARY_TOOLCHAIN_SUPPORT"] = True
115
116 with self.m.context(env=env, infra_steps=True):
117 return self.m.python3(
118 step_name,
119 [self.m.path.join(self.goma_dir, "goma_ctl.py")] + list(args),
120 **kwargs,
121 )
122
123 def _run_jsonstatus(self):
124 step = self._goma_ctl(
125 "goma jsonstatus",
126 ["jsonstatus", self.m.json.output(add_json_log=True)],
127 step_test_data=lambda: self.m.json.test_api.output({"foo": "bar"}),
128 )
129 if step.json.output is None:
130 step.presentation.status = self.m.step.WARNING
131
132 def _upload_goma_stats(self):
133 stats = self.m.file.read_json(
134 "read goma_stats.json",
135 self._stats_path,
136 test_data={},
137 include_log=False,
138 )
139 if not (self.m.buildbucket.builder_name and self.m.buildbucket.build.id):
140 # Skip the upload if it does not have build input information.
141 return
142 stats["build_info"] = {
143 "build_id": self.m.buildbucket.build.id,
144 "builder": self.m.buildbucket.builder_name,
145 "time_stamp": str(self.m.time.utcnow()),
146 "time_stamp_int": self.m.time.ms_since_epoch(),
147 }
148 self.m.step.active_result.presentation.logs["json.output"
149 ] = self.m.json.dumps(
150 stats, indent=4
151 ).splitlines()
152
153 self.m.bqupload.insert(
154 step_name="upload goma stats to bigquery",
155 project="fuchsia-infra",
156 dataset="artifacts",
157 table="builds_beta_goma",
158 rows=[stats],
159 ok_ret="all",
160 )
161
162 def _start(self):
163 """Start goma compiler proxy."""
164 assert not self._goma_started
165
166 self._ensure()
167
168 try:
169 self._goma_ctl("start goma", ["restart"])
170 self._goma_started = True
171 except self.m.step.StepFailure: # pragma: no cover
Rob Mohraf8e6362023-10-27 21:17:17 +0000172 deferred = []
173 deferred.append(self.m.defer(self._run_jsonstatus))
174 deferred.append(self.m.defer(self._goma_ctl, "stop goma (start failure)", ["stop"]))
175 self.m.defer.collect(deferred)
Godofredo Contreras693a1882023-04-28 18:08:10 +0000176 raise
177
178 def _stop(self):
179 """Stop goma compiler proxy."""
180 assert self._goma_started
181
Rob Mohraf8e6362023-10-27 21:17:17 +0000182 deferred = []
183 deferred.append(self.m.defer(self._run_jsonstatus))
184 deferred.append(self.m.defer(self._goma_ctl, "goma stats", ["stat"]))
185 deferred.append(self.m.defer(self._goma_ctl, "stop goma", ["stop"]))
186 self.m.defer.collect(deferred)
Godofredo Contreras693a1882023-04-28 18:08:10 +0000187
188 self._goma_started = False
189
190 compiler_proxy_warning_log_path = self._goma_log_dir.join(
191 "compiler_proxy.WARNING"
192 )
193 # Not all builds use goma, so it might not exist.
194 self.m.path.mock_add_paths(compiler_proxy_warning_log_path)
195 if self.m.path.exists(compiler_proxy_warning_log_path):
196 try:
197 self.m.file.read_text(
198 "read goma_client warning log",
199 compiler_proxy_warning_log_path,
200 test_data="test log",
Godofredo Contreras567f56b2023-03-03 17:26:48 +0000201 )
Godofredo Contreras693a1882023-04-28 18:08:10 +0000202 except self.m.step.StepFailure: # pragma: no cover
203 # Ignore. Not a big deal.
204 pass
Godofredo Contreras567f56b2023-03-03 17:26:48 +0000205
Godofredo Contreras693a1882023-04-28 18:08:10 +0000206 self._upload_goma_stats()