| # Copyright 2022 The Chromium Authors |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| """Library for defining polymorphic builders.""" |
| |
| load("@stdlib//internal/graph.star", "graph") |
| load("@stdlib//internal/luci/common.star", "builder_ref", "keys") |
| load("./builders.star", "builder", "defaults") |
| load("./nodes.star", "nodes") |
| load("//project.star", "settings") |
| |
| _LAUNCHER = nodes.create_bucket_scoped_node_type("polymorphic-launcher") |
| _RUNNER = nodes.create_link_node_type("polymorphic-runner", _LAUNCHER, nodes.BUILDER) |
| _TARGET_BUILDER = nodes.create_link_node_type("polymorphic-target", _LAUNCHER, nodes.BUILDER) |
| _TARGET_TESTER = nodes.create_link_node_type("polymorphic-target-tester", nodes.BUILDER, nodes.BUILDER) |
| |
| def _builder_ref_to_builder_id(ref): |
| bucket, builder = ref.split("/", 1) |
| return dict( |
| project = settings.project, |
| bucket = bucket, |
| builder = builder, |
| ) |
| |
| def _target_builder(*, builder, dimensions = None, testers = None): |
| """Details for a target builder for a polymorphic launcher. |
| |
| Args: |
| builder: (str) The bucket-qualified reference to the builder that |
| performs the polymoprhic runs. |
| dimensions: (dimensions.dimensions) Additional dimensions to set for the |
| target builder. Any dimensions specified here will override |
| dimensions on the runner builder. An empty dimension value will |
| remove the dimension when the runner builder is triggered for the |
| target builder. |
| testers: (list[str]) An optional list of testers to restrict the |
| operation to. If not specified, then the operation will include all |
| testers that are triggered by the target builder. |
| """ |
| if dimensions: |
| dimensions = dimensions.resolve(*builder.split("/")) |
| return struct( |
| builder = builder, |
| dimensions = dimensions, |
| testers = testers, |
| ) |
| |
| def _launcher( |
| *, |
| name, |
| runner, |
| target_builders, |
| **kwargs): |
| """Define a polymorphic launcher builder. |
| |
| The executable will default to the `chromium_polymorphic/launcher` recipe |
| and the properties will be updated to set the `runner_builder` and |
| `target_builder` properties as required by the recipe. |
| |
| Args: |
| name: (str) The name of the builder. |
| runner: (str) Bucket-qualified reference to the builder that performs |
| the polymorphic runs. |
| target_builders: (list[str|target_builder]) The target builders that the |
| runner builder should be triggered for. Can either be an object |
| returned by polymorphic.target_builder or a string with the |
| bucket-qualified reference to the target builder. It should be noted |
| that an empty list has different behavior from the default: none of |
| the triggered testers will be included in the operation. |
| **kwargs: Additional keyword arguments to be passed onto |
| `builders.builder`. |
| |
| Returns: |
| The lucicfg keyset for the builder |
| """ |
| if not target_builders: |
| fail("target_builders must not be empty") |
| target_builders = [_target_builder(builder = t) if type(t) == type("") else t for t in target_builders] |
| bucket = defaults.get_value_from_kwargs("bucket", kwargs) |
| |
| launcher_key = _LAUNCHER.add(bucket, name, props = dict( |
| runner = runner, |
| target_builders = target_builders, |
| )) |
| graph.add_edge(keys.project(), launcher_key) |
| |
| # Create links to the runner and target builders. We don't actually do |
| # anything with the links, but lucicfg will check that the nodes that are |
| # linked to were actually added (i.e. that the referenced builders actually |
| # exist). |
| _RUNNER.link(launcher_key, runner) |
| for t in target_builders: |
| _TARGET_BUILDER.link(launcher_key, t.builder) |
| if t.testers != None: |
| for tester in t.testers: |
| _TARGET_TESTER.link(launcher_key, tester) |
| |
| kwargs.setdefault("executable", "recipe:chromium_polymorphic/launcher") |
| kwargs.setdefault("resultdb_enable", False) |
| |
| return builder( |
| name = name, |
| **kwargs |
| ) |
| |
| polymorphic = struct( |
| launcher = _launcher, |
| target_builder = _target_builder, |
| ) |
| |
| def _get_tester_group_and_name(context_node, builder_proto_by_key, tester_ref): |
| builder_ref_node = graph.node(keys.builder_ref(tester_ref)) |
| builder_node = builder_ref.follow(builder_ref_node, context_node) |
| builder_proto = builder_proto_by_key[builder_node.key] |
| builder_group = json.decode(builder_proto.properties)["builder_group"] |
| return { |
| "group": builder_group, |
| "builder": builder_node.key.id, |
| } |
| |
| def _target_builder_prop(context_node, builder_proto_by_key, target_builder): |
| p = {"builder_id": _builder_ref_to_builder_id(target_builder.builder)} |
| if target_builder.dimensions: |
| p["dimensions"] = target_builder.dimensions |
| if target_builder.testers != None: |
| testers = [] |
| p["tester_filter"] = {"testers": testers} |
| for t in target_builder.testers: |
| testers.append(_get_tester_group_and_name(context_node, builder_proto_by_key, t)) |
| return p |
| |
| def _generate_launcher_properties(ctx): |
| cfg = None |
| for f in ctx.output: |
| if f.startswith("luci/cr-buildbucket"): |
| cfg = ctx.output[f] |
| break |
| if cfg == None: |
| fail("There is no buildbucket configuration file to update properties") |
| |
| builder_proto_by_key = {} |
| for bucket in cfg.buckets: |
| if not proto.has(bucket, "swarming"): |
| continue |
| bucket_name = bucket.name |
| for builder in bucket.swarming.builders: |
| builder_name = builder.name |
| builder_proto_by_key[keys.builder(bucket_name, builder_name)] = builder |
| |
| for bucket in cfg.buckets: |
| if not proto.has(bucket, "swarming"): |
| continue |
| bucket_name = bucket.name |
| for builder in bucket.swarming.builders: |
| builder_name = builder.name |
| node = _LAUNCHER.get(bucket_name, builder_name) |
| if not node: |
| continue |
| |
| properties = json.decode(builder.properties) |
| |
| properties.update({ |
| "runner_builder": _builder_ref_to_builder_id(node.props.runner), |
| "target_builders": [_target_builder_prop(node, builder_proto_by_key, t) for t in node.props.target_builders], |
| }) |
| |
| builder.properties = json.encode(properties) |
| |
| lucicfg.generator(_generate_launcher_properties) |