| // Copyright 2015 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. |
| |
| package com.google.devtools.build.lib.rules.proto; |
| |
| import static com.google.devtools.build.lib.analysis.configuredtargets.RuleConfiguredTarget.Mode.TARGET; |
| import static com.google.devtools.build.lib.collect.nestedset.Order.STABLE_ORDER; |
| |
| import com.google.common.annotations.VisibleForTesting; |
| import com.google.common.collect.ImmutableList; |
| import com.google.devtools.build.lib.actions.Artifact; |
| import com.google.devtools.build.lib.actions.Root; |
| import com.google.devtools.build.lib.analysis.RuleContext; |
| import com.google.devtools.build.lib.analysis.Runfiles; |
| import com.google.devtools.build.lib.analysis.TransitiveInfoCollection; |
| import com.google.devtools.build.lib.analysis.config.BuildConfiguration; |
| import com.google.devtools.build.lib.analysis.configuredtargets.RuleConfiguredTarget.Mode; |
| import com.google.devtools.build.lib.cmdline.Label; |
| import com.google.devtools.build.lib.collect.nestedset.NestedSet; |
| import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder; |
| import com.google.devtools.build.lib.packages.BuildType; |
| import com.google.devtools.build.lib.vfs.FileSystemUtils; |
| import com.google.devtools.build.lib.vfs.PathFragment; |
| import javax.annotation.Nullable; |
| |
| /** |
| * Utility functions for proto_library and proto aspect implementations. |
| */ |
| public class ProtoCommon { |
| private ProtoCommon() { |
| throw new UnsupportedOperationException(); |
| } |
| |
| /** |
| * Gets the direct sources of a proto library. If protoSources is not empty, the value is just |
| * protoSources. Otherwise, it's the combined sources of all direct dependencies of the given |
| * RuleContext. |
| * |
| * @param ruleContext the proto library rule context. |
| * @param protoSources the direct proto sources. |
| * @return the direct sources of a proto library. |
| */ |
| public static NestedSet<Artifact> getCheckDepsProtoSources( |
| RuleContext ruleContext, ImmutableList<Artifact> protoSources) { |
| |
| if (protoSources.isEmpty()) { |
| /* a proxy/alias library, return the sources of the direct deps */ |
| NestedSetBuilder<Artifact> builder = NestedSetBuilder.stableOrder(); |
| for (TransitiveInfoCollection provider : ruleContext.getPrerequisites("deps", Mode.TARGET)) { |
| ProtoSourcesProvider sources = provider.getProvider(ProtoSourcesProvider.class); |
| if (sources != null) { |
| builder.addTransitive(sources.getCheckDepsProtoSources()); |
| } |
| } |
| return builder.build(); |
| } else { |
| return NestedSetBuilder.wrap(STABLE_ORDER, protoSources); |
| } |
| } |
| |
| /** |
| * Collects all .proto files in this lib and its transitive dependencies. |
| * |
| * <p>Each import is a Artifact/Label pair. |
| */ |
| public static NestedSet<Artifact> collectTransitiveImports(RuleContext ruleContext, |
| ImmutableList<Artifact> protoSources) { |
| NestedSetBuilder<Artifact> importsBuilder = NestedSetBuilder.naiveLinkOrder(); |
| |
| importsBuilder.addAll(protoSources); |
| |
| for (ProtoSourcesProvider dep : ruleContext.getPrerequisites( |
| "deps", Mode.TARGET, ProtoSourcesProvider.class)) { |
| importsBuilder.addTransitive(dep.getTransitiveImports()); |
| } |
| |
| return importsBuilder.build(); |
| } |
| |
| public static NestedSet<Artifact> collectDependenciesDescriptorSets(RuleContext ruleContext) { |
| NestedSetBuilder<Artifact> result = NestedSetBuilder.stableOrder(); |
| |
| for (ProtoSourcesProvider provider : |
| ruleContext.getPrerequisites("deps", Mode.TARGET, ProtoSourcesProvider.class)) { |
| result.addTransitive(provider.transitiveDescriptorSets()); |
| } |
| return result.build(); |
| } |
| |
| /** |
| * Check that .proto files in sources are from the same package. This is done to avoid clashes |
| * with the generated sources. |
| */ |
| public static void checkSourceFilesAreInSamePackage(RuleContext ruleContext) { |
| // TODO(bazel-team): this does not work with filegroups that contain files |
| // that are not in the package |
| for (Label source : ruleContext.attributes().get("srcs", BuildType.LABEL_LIST)) { |
| if (!isConfiguredTargetInSamePackage(ruleContext, source)) { |
| ruleContext.attributeError( |
| "srcs", |
| "Proto source with label '" + source + "' must be in same package as consuming rule."); |
| } |
| } |
| } |
| |
| private static boolean isConfiguredTargetInSamePackage(RuleContext ruleContext, Label source) { |
| return ruleContext.getLabel().getPackageIdentifier().equals(source.getPackageIdentifier()); |
| } |
| |
| public static Runfiles.Builder createDataRunfilesProvider( |
| final NestedSet<Artifact> transitiveProtoSources, RuleContext ruleContext) { |
| // We assume that the proto sources will not have conflicting artifacts |
| // with the same root relative path |
| return new Runfiles.Builder( |
| ruleContext.getWorkspaceName(), ruleContext.getConfiguration().legacyExternalRunfiles()) |
| .addTransitiveArtifactsWrappedInStableOrder(transitiveProtoSources); |
| } |
| |
| // ================================================================= |
| // Protocol compiler invocation stuff. |
| |
| /** |
| * Each language-specific initialization method will call this to construct |
| * Artifacts representing its protocol compiler outputs. |
| * |
| * @param extension Remove ".proto" and replace it with this to produce |
| * the output file name, e.g. ".pb.cc". |
| * @param pythonNames If true, replace hyphens in the file name |
| * with underscores, as required for Python modules. |
| */ |
| public static ImmutableList<Artifact> getGeneratedOutputs(RuleContext ruleContext, |
| ImmutableList<Artifact> protoSources, String extension, boolean pythonNames) { |
| ImmutableList.Builder<Artifact> outputsBuilder = new ImmutableList.Builder<>(); |
| Root genfiles = ruleContext.getConfiguration().getGenfilesDirectory( |
| ruleContext.getRule().getRepository()); |
| for (Artifact src : protoSources) { |
| PathFragment srcPath = src.getRootRelativePath(); |
| if (pythonNames) { |
| srcPath = srcPath.replaceName(srcPath.getBaseName().replace('-', '_')); |
| } |
| // Note that two proto_library rules can have the same source file, so this is actually a |
| // shared action. NB: This can probably result in action conflicts if the proto_library rules |
| // are not the same. |
| outputsBuilder.add( |
| ruleContext.getShareableArtifact(FileSystemUtils.replaceExtension(srcPath, extension), |
| genfiles)); |
| } |
| return outputsBuilder.build(); |
| } |
| |
| /** |
| * Each language-specific initialization method will call this to construct |
| * Artifacts representing its protocol compiler outputs. |
| * |
| * @param extension Remove ".proto" and replace it with this to produce |
| * the output file name, e.g. ".pb.cc". |
| */ |
| public static ImmutableList<Artifact> getGeneratedOutputs(RuleContext ruleContext, |
| ImmutableList<Artifact> protoSources, String extension) { |
| return getGeneratedOutputs(ruleContext, protoSources, extension, false); |
| } |
| |
| /** |
| * Returns the .proto files that are the direct srcs of the direct-dependencies of this rule. If |
| * the current rule is an alias proto_library (=no srcs), we use the direct srcs of the |
| * direct-dependencies of our direct-dependencies. |
| */ |
| @Nullable |
| public static NestedSet<Artifact> computeProtosInDirectDeps(RuleContext ruleContext) { |
| NestedSetBuilder<Artifact> result = NestedSetBuilder.stableOrder(); |
| ImmutableList<Artifact> srcs = ruleContext.getPrerequisiteArtifacts("srcs", TARGET).list(); |
| if (srcs.isEmpty()) { |
| for (ProtoSupportDataProvider provider : |
| ruleContext.getPrerequisites("deps", TARGET, ProtoSupportDataProvider.class)) { |
| result.addTransitive(provider.getSupportData().getProtosInDirectDeps()); |
| } |
| } else { |
| for (ProtoSourcesProvider provider : |
| ruleContext.getPrerequisites("deps", TARGET, ProtoSourcesProvider.class)) { |
| result.addTransitive(provider.getCheckDepsProtoSources()); |
| } |
| result.addAll(srcs); |
| } |
| return result.build(); |
| } |
| |
| /** |
| * Decides whether this proto_library should check for strict proto deps. |
| * |
| * <p>Takes into account command-line flags, package-level attributes and rule attributes. |
| */ |
| @VisibleForTesting |
| public static boolean areDepsStrict(RuleContext ruleContext) { |
| BuildConfiguration.StrictDepsMode flagValue = |
| ruleContext.getFragment(ProtoConfiguration.class).strictProtoDeps(); |
| if (flagValue == BuildConfiguration.StrictDepsMode.OFF) { |
| return false; |
| } |
| if (flagValue == BuildConfiguration.StrictDepsMode.ERROR |
| || flagValue == BuildConfiguration.StrictDepsMode.WARN) { |
| return true; |
| } |
| return (flagValue == BuildConfiguration.StrictDepsMode.STRICT); |
| } |
| } |