| OLD | NEW |
| 1 # Copyright 2013 The LUCI Authors. All rights reserved. | 1 # Copyright 2013 The LUCI Authors. All rights reserved. |
| 2 # Use of this source code is governed under the Apache License, Version 2.0 | 2 # Use of this source code is governed under the Apache License, Version 2.0 |
| 3 # that can be found in the LICENSE file. | 3 # that can be found in the LICENSE file. |
| 4 | 4 |
| 5 import contextlib | 5 import contextlib |
| 6 import copy | 6 import copy |
| 7 import types |
| 7 | 8 |
| 8 from recipe_engine import recipe_api | 9 from recipe_engine import recipe_api |
| 10 from recipe_engine.config_types import Path |
| 11 from recipe_engine.util import Placeholder |
| 9 | 12 |
| 10 | 13 |
| 11 # Inherit from RecipeApiPlain because the only thing which is a step is | 14 # Inherit from RecipeApiPlain because the only thing which is a step is |
| 12 # run_from_dict() | 15 # run_from_dict() |
| 13 class StepApi(recipe_api.RecipeApiPlain): | 16 class StepApi(recipe_api.RecipeApiPlain): |
| 14 | 17 |
| 15 step_client = recipe_api.RequireClient('step') | 18 step_client = recipe_api.RequireClient('step') |
| 16 | 19 |
| 17 def __init__(self, step_properties, **kwargs): | 20 def __init__(self, step_properties, **kwargs): |
| 18 super(StepApi, self).__init__(**kwargs) | 21 super(StepApi, self).__init__(**kwargs) |
| (...skipping 73 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 92 """ | 95 """ |
| 93 step_result = self(name, []) | 96 step_result = self(name, []) |
| 94 with self.m.context(name_prefix=name, increment_nest_level=True): | 97 with self.m.context(name_prefix=name, increment_nest_level=True): |
| 95 yield step_result | 98 yield step_result |
| 96 | 99 |
| 97 @property | 100 @property |
| 98 def defer_results(self): | 101 def defer_results(self): |
| 99 """ See recipe_api.py for docs. """ | 102 """ See recipe_api.py for docs. """ |
| 100 return recipe_api.defer_results | 103 return recipe_api.defer_results |
| 101 | 104 |
| 105 @recipe_api.composite_step |
| 102 def __call__(self, name, cmd, ok_ret=None, infra_step=False, wrapper=(), | 106 def __call__(self, name, cmd, ok_ret=None, infra_step=False, wrapper=(), |
| 103 timeout=None, allow_subannotations=None, | 107 timeout=None, allow_subannotations=None, |
| 104 trigger_specs=None, stdout=None, stderr=None, stdin=None, | 108 trigger_specs=None, stdout=None, stderr=None, stdin=None, |
| 105 step_test_data=None): | 109 step_test_data=None): |
| 106 """Returns a step dictionary which is compatible with annotator.py. | 110 """Returns a step dictionary which is compatible with annotator.py. |
| 107 | 111 |
| 108 Args: | 112 Args: |
| 109 name (string): The name of this step. | 113 name (string): The name of this step. |
| 110 cmd (list of strings): in the style of subprocess.Popen or None to create | 114 cmd (list of strings): in the style of subprocess.Popen or None to create |
| 111 a no-op fake step. | 115 a no-op fake step. |
| (...skipping 18 matching lines...) Expand all Loading... |
| 130 in annotator's stderr. | 134 in annotator's stderr. |
| 131 stdin: Placeholder to read step stdin from. | 135 stdin: Placeholder to read step stdin from. |
| 132 step_test_data (func -> recipe_test_api.StepTestData): A factory which | 136 step_test_data (func -> recipe_test_api.StepTestData): A factory which |
| 133 returns a StepTestData object that will be used as the default test | 137 returns a StepTestData object that will be used as the default test |
| 134 data for this step. The recipe author can override/augment this object | 138 data for this step. The recipe author can override/augment this object |
| 135 in the GenTests function. | 139 in the GenTests function. |
| 136 | 140 |
| 137 Returns: | 141 Returns: |
| 138 Opaque step object produced and understood by recipe engine. | 142 Opaque step object produced and understood by recipe engine. |
| 139 """ | 143 """ |
| 140 kwargs = {} | |
| 141 if allow_subannotations is not None: | |
| 142 kwargs['allow_subannotations'] = allow_subannotations | |
| 143 if trigger_specs: | |
| 144 kwargs['trigger_specs'] = trigger_specs | |
| 145 if stdout: | |
| 146 kwargs['stdout'] = stdout | |
| 147 if stderr: | |
| 148 kwargs['stderr'] = stderr | |
| 149 if stdin: | |
| 150 kwargs['stdin'] = stdin | |
| 151 if step_test_data: | |
| 152 kwargs['step_test_data'] = step_test_data | |
| 153 assert cmd is None or isinstance(cmd, list) | |
| 154 if not ok_ret: | |
| 155 ok_ret = {0} | |
| 156 if ok_ret in ('any', 'all'): | |
| 157 ok_ret = set(range(-256, 256)) | |
| 158 | |
| 159 if cmd is not None: | |
| 160 command = list(wrapper) | |
| 161 command += cmd | |
| 162 kwargs['cmd'] = command | |
| 163 | |
| 164 kwargs['timeout'] = timeout | |
| 165 kwargs['ok_ret'] = ok_ret | |
| 166 | |
| 167 # Calculate our full step name. If a step already has that name, add an | 144 # Calculate our full step name. If a step already has that name, add an |
| 168 # index to the end of it. | 145 # index to the end of it. |
| 169 # | 146 # |
| 170 # Note that another step could exist with that index already added to it | 147 # Note that another step could exist with that index already added to it |
| 171 # by the user. If this happens, we'll continue appending indexes until we | 148 # by the user. If this happens, we'll continue appending indexes until we |
| 172 # have a unique step name. | 149 # have a unique step name. |
| 173 with self.m.context(name_prefix=name): | 150 with self.m.context(name_prefix=name): |
| 174 base_name = self.m.context.name_prefix | 151 base_name = self.m.context.name_prefix |
| 175 name_suffix = '' | 152 name_suffix = '' |
| 176 | 153 |
| 177 while True: | 154 while True: |
| 178 full_name = base_name + name_suffix | 155 full_name = base_name + name_suffix |
| 179 if full_name not in self._seen_steps: | 156 if full_name not in self._seen_steps: |
| 180 break | 157 break |
| 181 | 158 |
| 182 step_count = self._step_names.setdefault(full_name, 1) + 1 | 159 step_count = self._step_names.setdefault(full_name, 1) + 1 |
| 183 self._step_names[full_name] = step_count | 160 self._step_names[full_name] = step_count |
| 184 name_suffix = ' (%d)' % step_count | 161 name_suffix = ' (%d)' % step_count |
| 185 self._seen_steps.add(full_name) | 162 self._seen_steps.add(full_name) |
| 186 | 163 |
| 164 assert isinstance(cmd, (types.NoneType, list)) |
| 165 if cmd is not None: |
| 166 cmd = list(wrapper) + cmd |
| 167 assert all(isinstance(x, (int, long, basestring, Path, Placeholder)) |
| 168 for x in cmd) |
| 169 |
| 187 cwd = self.m.context.cwd | 170 cwd = self.m.context.cwd |
| 188 if cwd is not None and cwd != self.m.path['start_dir']: | 171 if cwd and cwd == self.m.path['start_dir']: |
| 189 kwargs['cwd'] = cwd | 172 cwd = None |
| 190 kwargs['env'] = self.m.context.env | 173 |
| 174 env = self.m.context.env |
| 191 if self._prefix_path: | 175 if self._prefix_path: |
| 192 ps = self.m.path.pathsep | 176 ps = self.m.path.pathsep |
| 193 prefix = ps.join(self._prefix_path) | 177 prefix = ps.join(self._prefix_path) |
| 194 suffix = kwargs['env'].get('PATH', '%(PATH)s') | 178 suffix = env.get('PATH', '%(PATH)s') |
| 195 kwargs['env']['PATH'] = '%s%s%s' % (prefix, ps, suffix) | 179 env['PATH'] = '%s%s%s' % (prefix, ps, suffix) |
| 196 kwargs['infra_step'] = self.m.context.infra_step or bool(infra_step) | |
| 197 kwargs['step_nest_level'] = self.m.context.nest_level | |
| 198 kwargs['name'] = full_name | |
| 199 kwargs['base_name'] = name | |
| 200 | 180 |
| 201 schema = self.make_config() | 181 if ok_ret in ('any', 'all'): |
| 202 schema.set_val(kwargs) | 182 ok_ret = range(-256, 256) |
| 203 return self.run_from_dict(schema.as_jsonish()) | |
| 204 | 183 |
| 205 # TODO(martiniss) delete, and make generator_script use **kwargs on step() | 184 return self.step_client.run_step(self.step_client.StepConfig( |
| 206 @recipe_api.composite_step | 185 name=full_name, |
| 207 def run_from_dict(self, dct): | 186 base_name=full_name or name, |
| 208 return self.step_client.run_step(dct) | 187 cmd=cmd, |
| 188 cwd=cwd, |
| 189 env=env, |
| 190 allow_subannotations=bool(allow_subannotations), |
| 191 trigger_specs=[self._make_trigger_spec(trig) |
| 192 for trig in (trigger_specs or ())], |
| 193 timeout=timeout, |
| 194 infra_step=self.m.context.infra_step or bool(infra_step), |
| 195 stdout=stdout, |
| 196 stderr=stderr, |
| 197 stdin=stdin, |
| 198 ok_ret=ok_ret, |
| 199 step_test_data=step_test_data, |
| 200 nest_level=self.m.context.nest_level, |
| 201 )) |
| 202 |
| 203 def _make_trigger_spec(self, trig): |
| 204 buildbot_changes = trig.get('buildbot_changes') |
| 205 assert isinstance(buildbot_changes, (types.NoneType, list)) |
| 206 |
| 207 critical = trig.get('critical') |
| 208 return self.step_client.TriggerSpec( |
| 209 bucket=trig.get('bucket'), |
| 210 builder_name=trig['builder_name'], |
| 211 properties=trig.get('properties'), |
| 212 buildbot_changes=buildbot_changes, |
| 213 tags=trig.get('tags'), |
| 214 critical=bool(critical) if critical is not None else (True), |
| 215 ) |
| OLD | NEW |