// 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.ArtifactRoot; 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.syntax.Type; 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 getCheckDepsProtoSources( RuleContext ruleContext, ImmutableList protoSources) { if (protoSources.isEmpty()) { /* a proxy/alias library, return the sources of the direct deps */ NestedSetBuilder 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. * *

Each import is a Artifact/Label pair. */ public static NestedSet collectTransitiveImports(RuleContext ruleContext, ImmutableList protoSources) { NestedSetBuilder 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 collectDependenciesDescriptorSets(RuleContext ruleContext) { NestedSetBuilder result = NestedSetBuilder.stableOrder(); for (ProtoSourcesProvider provider : ruleContext.getPrerequisites("deps", Mode.TARGET, ProtoSourcesProvider.class)) { result.addTransitive(provider.transitiveDescriptorSets()); } return result.build(); } /** * Returns all proto source roots in this lib ({@code currentProtoSourceRoot}) and in its * transitive dependencies. * *

Assumes {@code currentProtoSourceRoot} is the same as the package name. */ public static NestedSet collectTransitiveProtoPathFlags( RuleContext ruleContext, String currentProtoSourceRoot) { NestedSetBuilder protoPath = NestedSetBuilder.stableOrder(); // first add the protoSourceRoot of the current target, if any if (currentProtoSourceRoot != null && !currentProtoSourceRoot.isEmpty()) { protoPath.add(currentProtoSourceRoot); } for (ProtoSourcesProvider provider : ruleContext.getPrerequisites("deps", Mode.TARGET, ProtoSourcesProvider.class)) { protoPath.addTransitive(provider.getTransitiveProtoPathFlags()); } return protoPath.build(); } /** * Returns the {@code proto_source_root} of the current library or null if none is specified. * *

Build will fail if the {@code proto_source_root} of the current library is different than * the package name. */ @Nullable public static String getProtoSourceRoot(RuleContext ruleContext) { String protoSourceRoot = ruleContext.attributes().get("proto_source_root", Type.STRING); if (protoSourceRoot != null && !protoSourceRoot.isEmpty()) { checkProtoSourceRootIsTheSameAsPackage(protoSourceRoot, ruleContext); } return protoSourceRoot; } /** * Returns a set of the {@code proto_source_root} collected from the current library and the * specified attribute. * *

Assumes {@code currentProtoSourceRoot} is the same as the package name. */ private static NestedSet getProtoSourceRootsOfAttribute( RuleContext ruleContext, String currentProtoSourceRoot, String attributeName) { NestedSetBuilder protoSourceRoots = NestedSetBuilder.stableOrder(); if (currentProtoSourceRoot != null && !currentProtoSourceRoot.isEmpty()) { protoSourceRoots.add(currentProtoSourceRoot); } for (ProtoSourcesProvider provider : ruleContext.getPrerequisites(attributeName, Mode.TARGET, ProtoSourcesProvider.class)) { String protoSourceRoot = provider.getProtoSourceRoot(); if (protoSourceRoot != null && !protoSourceRoot.isEmpty()) { protoSourceRoots.add(provider.getProtoSourceRoot()); } } return protoSourceRoots.build(); } /** * Returns a set of the {@code proto_source_root} collected from the current library and the * direct dependencies. * *

Assumes {@code currentProtoSourceRoot} is the same as the package name. */ public static NestedSet getProtoSourceRootsOfDirectDependencies( RuleContext ruleContext, String currentProtoSourceRoot) { return getProtoSourceRootsOfAttribute(ruleContext, currentProtoSourceRoot, "deps"); } /** * Returns a set of the {@code proto_source_root} collected from the current library and the * exported dependencies. * *

Assumes {@code currentProtoSourceRoot} is the same as the package name. */ public static NestedSet getProtoSourceRootsOfExportedDependencies( RuleContext ruleContext, String currentProtoSourceRoot) { return getProtoSourceRootsOfAttribute(ruleContext, currentProtoSourceRoot, "exports"); } private static void checkProtoSourceRootIsTheSameAsPackage( String protoSourceRoot, RuleContext ruleContext) { if (!ruleContext.getLabel().getPackageName().equals(protoSourceRoot)) { ruleContext.attributeError( "proto_source_root", "proto_source_root must be the same as the package name (" + ruleContext.getLabel().getPackageName() + ")." + " not '" + protoSourceRoot + "'." ); } } /** * 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 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 getGeneratedOutputs(RuleContext ruleContext, ImmutableList protoSources, String extension, boolean pythonNames) { ImmutableList.Builder outputsBuilder = new ImmutableList.Builder<>(); ArtifactRoot 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 getGeneratedOutputs(RuleContext ruleContext, ImmutableList 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 computeProtosInDirectDeps(RuleContext ruleContext) { NestedSetBuilder result = NestedSetBuilder.stableOrder(); ImmutableList 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(); } /** * Returns the .proto files that are the direct srcs of the exported dependencies of this rule. */ @Nullable public static NestedSet computeProtosInExportedDeps(RuleContext ruleContext) { NestedSetBuilder result = NestedSetBuilder.stableOrder(); for (ProtoSupportDataProvider provider : ruleContext.getPrerequisites("exports", TARGET, ProtoSupportDataProvider.class)) { result.addTransitive(provider.getSupportData().getProtosInDirectDeps()); } return result.build(); } /** * Decides whether this proto_library should check for strict proto deps. * *

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); } }