diff options
Diffstat (limited to 'rules/processing_pipeline.bzl')
-rw-r--r-- | rules/processing_pipeline.bzl | 161 |
1 files changed, 161 insertions, 0 deletions
diff --git a/rules/processing_pipeline.bzl b/rules/processing_pipeline.bzl new file mode 100644 index 0000000..6ba2ae7 --- /dev/null +++ b/rules/processing_pipeline.bzl @@ -0,0 +1,161 @@ +# Copyright 2020 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Common implementation for processing pipelines.""" + +PROVIDERS = "providers" +VALIDATION_OUTPUTS = "validation_outputs" + +# TODO(djwhang): When a provider type can be retrieved from a Starlark provider +# ProviderInfo is necessary. Once this is possible, processor methods can have a +# uniform method signature foo(ctx, target_ctx) where we can pull the provider +# off the target_ctx using the provider type. +# +# Yes, this effectively leads to producing a build rule like system within a +# build rule, rather than resorting to rule based composition. +ProviderInfo = provider( + "Stores metadata about the actual Starlark provider returned.", + fields = dict( + name = "The type of the provider", + value = "The actual provider", + runfiles = "Runfiles to pass to the DefaultInfo provider", + ), +) + +_ProcessingPipelineInfo = provider( + "Stores functions that forms a rule's implementation.", + fields = dict( + processors = "Ordered dictionary of processing functions.", + finalize = "Function to form the final providers to propagate.", + ), +) + +def _make_processing_pipeline(processors = dict(), finalize = None): + """Creates the combined processing pipeline. + + Args: + processors: Ordered dictionary of processing functions. + finalize: Function to form the final providers to propagate. + + Returns: + A _ProcessingPipelineInfo provider. + """ + return _ProcessingPipelineInfo( + processors = processors, + finalize = finalize, + ) + +def _run(ctx, java_package, processing_pipeline): + """Runs the processing pipeline and populates the target context. + + Args: + ctx: The context. + java_package: The java package resolved from the target's path + or the custom_package attr. + processing_pipeline: The _ProcessingPipelineInfo provider for this target. + + Returns: + The output of the _ProcessingPipelineInfo.finalize function. + """ + target_ctx = dict( + java_package = java_package, + providers = [], + validation_outputs = [], + runfiles = ctx.runfiles(), + ) + + for execute in processing_pipeline.processors.values(): + info = execute(ctx, **target_ctx) + if info: + if info.name in target_ctx: + fail("%s provider already registered in target context" % info.name) + target_ctx[info.name] = info.value + target_ctx[PROVIDERS].extend(getattr(info.value, PROVIDERS, [])) + target_ctx[VALIDATION_OUTPUTS].extend(getattr(info.value, VALIDATION_OUTPUTS, [])) + if hasattr(info, "runfiles") and info.runfiles: + target_ctx["runfiles"] = target_ctx["runfiles"].merge(info.runfiles) + + return processing_pipeline.finalize(ctx, **target_ctx) + +def _prepend(processors, **new_processors): + """Prepends processors in a given processing pipeline. + + Args: + processors: The dictionary representing the processing pipeline. + **new_processors: The processors to add where the key represents the + name of the processor and value is the function pointer to the new + processor. + + Returns: + A dictionary which represents the new processing pipeline. + """ + updated_processors = dict() + for name, processor in new_processors.items(): + updated_processors[name] = processor + + for key in processors.keys(): + updated_processors[key] = processors[key] + + return updated_processors + +def _append(processors, **new_processors): + """Appends processors in a given processing pipeline. + + Args: + processors: The dictionary representing the processing pipeline. + **new_processors: The processors to append where the key represents the + name of the processor and value is the function pointer to the new + processor. + + Returns: + A dictionary which represents the new processing pipeline. + """ + updated_processors = dict(processors) + for name, processor in new_processors.items(): + updated_processors[name] = processor + + return updated_processors + +def _replace(processors, **new_processors): + """Replace processors in a given processing pipeline. + + Args: + processors: The dictionary representing the processing pipeline. + **new_processors: The processors to override where the key represents the + name of the processor and value is the function pointer to the new + processor. + + Returns: + A dictionary which represents the new processing pipeline. + """ + updated_processors = dict(processors) + for name, processor in new_processors.items(): + if name not in processors: + fail("Error, %s not found, unable to override." % name) + + # NOTE: Overwriting an existing value does not break iteration order. + # However, if a new processor is being added that needs to be injected + # between other processors, the processing pipeline dictionary will need + # to be recreated. + updated_processors[name] = processor + + return updated_processors + +processing_pipeline = struct( + make_processing_pipeline = _make_processing_pipeline, + run = _run, + prepend = _prepend, + append = _append, + replace = _replace, +) |