| # 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, | |
| ) |