// Copyright 2014 Google Inc. 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.objc; import static com.google.devtools.build.lib.rules.objc.ObjcProvider.ASSET_CATALOG; import static com.google.devtools.build.lib.rules.objc.ObjcProvider.BREAKPAD_FILE; import static com.google.devtools.build.lib.rules.objc.ObjcProvider.BUNDLE_FILE; import static com.google.devtools.build.lib.rules.objc.ObjcProvider.BUNDLE_IMPORT_DIR; import static com.google.devtools.build.lib.rules.objc.ObjcProvider.CC_LIBRARY; import static com.google.devtools.build.lib.rules.objc.ObjcProvider.DEFINE; import static com.google.devtools.build.lib.rules.objc.ObjcProvider.FLAG; import static com.google.devtools.build.lib.rules.objc.ObjcProvider.FORCE_LOAD_FOR_XCODEGEN; import static com.google.devtools.build.lib.rules.objc.ObjcProvider.FORCE_LOAD_LIBRARY; import static com.google.devtools.build.lib.rules.objc.ObjcProvider.FRAMEWORK_DIR; import static com.google.devtools.build.lib.rules.objc.ObjcProvider.FRAMEWORK_FILE; import static com.google.devtools.build.lib.rules.objc.ObjcProvider.Flag.USES_CPP; import static com.google.devtools.build.lib.rules.objc.ObjcProvider.Flag.USES_SWIFT; import static com.google.devtools.build.lib.rules.objc.ObjcProvider.GCNO; import static com.google.devtools.build.lib.rules.objc.ObjcProvider.GENERAL_RESOURCE_DIR; import static com.google.devtools.build.lib.rules.objc.ObjcProvider.GENERAL_RESOURCE_FILE; import static com.google.devtools.build.lib.rules.objc.ObjcProvider.HEADER; import static com.google.devtools.build.lib.rules.objc.ObjcProvider.IMPORTED_LIBRARY; import static com.google.devtools.build.lib.rules.objc.ObjcProvider.INCLUDE; import static com.google.devtools.build.lib.rules.objc.ObjcProvider.INSTRUMENTED_SOURCE; import static com.google.devtools.build.lib.rules.objc.ObjcProvider.LIBRARY; import static com.google.devtools.build.lib.rules.objc.ObjcProvider.LINKED_BINARY; import static com.google.devtools.build.lib.rules.objc.ObjcProvider.SDK_DYLIB; import static com.google.devtools.build.lib.rules.objc.ObjcProvider.SDK_FRAMEWORK; import static com.google.devtools.build.lib.rules.objc.ObjcProvider.SOURCE; import static com.google.devtools.build.lib.rules.objc.ObjcProvider.STORYBOARD; import static com.google.devtools.build.lib.rules.objc.ObjcProvider.STRINGS; import static com.google.devtools.build.lib.rules.objc.ObjcProvider.WEAK_SDK_FRAMEWORK; import static com.google.devtools.build.lib.rules.objc.ObjcProvider.XCASSETS_DIR; import static com.google.devtools.build.lib.rules.objc.ObjcProvider.XCDATAMODEL; import static com.google.devtools.build.lib.rules.objc.ObjcProvider.XIB; import static com.google.devtools.build.lib.vfs.PathFragment.TO_PATH_FRAGMENT; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Optional; import com.google.common.base.Preconditions; import com.google.common.base.Predicates; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.google.devtools.build.lib.actions.Artifact; import com.google.devtools.build.lib.analysis.RuleConfiguredTarget.Mode; import com.google.devtools.build.lib.analysis.RuleContext; import com.google.devtools.build.lib.analysis.config.BuildConfiguration; import com.google.devtools.build.lib.packages.Type; import com.google.devtools.build.lib.rules.cpp.CcCommon; import com.google.devtools.build.lib.rules.cpp.CcLinkParamsProvider; import com.google.devtools.build.lib.rules.cpp.CppCompilationContext; import com.google.devtools.build.lib.util.FileType; import com.google.devtools.build.lib.util.RegexFilter; import com.google.devtools.build.lib.vfs.PathFragment; import java.util.HashSet; import java.util.List; import java.util.Set; /** * Contains information common to multiple objc_* rules, and provides a unified API for extracting * and accessing it. */ // TODO(bazel-team): Decompose and subsume area-specific logic and data into the various *Support // classes. Make sure to distinguish rule output (providers, runfiles, ...) from intermediate, // rule-internal information. Any provider created by a rule should not be read, only published. public final class ObjcCommon { /** * Provides a way to access attributes that are common to all compilation rules. */ // TODO(bazel-team): Delete and move into support-specific attributes classes once ObjcCommon is // gone. static final class CompilationAttributes { private final RuleContext ruleContext; private final ObjcSdkFrameworks.Attributes sdkFrameworkAttributes; CompilationAttributes(RuleContext ruleContext) { this.ruleContext = Preconditions.checkNotNull(ruleContext); this.sdkFrameworkAttributes = new ObjcSdkFrameworks.Attributes(ruleContext); } ImmutableList hdrs() { return ImmutableList.copyOf(CcCommon.getHeaders(ruleContext)); } Iterable includes() { return Iterables.transform( ruleContext.attributes().get("includes", Type.STRING_LIST), PathFragment.TO_PATH_FRAGMENT); } Iterable sdkIncludes() { return Iterables.transform( ruleContext.attributes().get("sdk_includes", Type.STRING_LIST), PathFragment.TO_PATH_FRAGMENT); } /** * Returns the value of the sdk_frameworks attribute plus frameworks that are included * automatically. */ ImmutableSet sdkFrameworks() { return sdkFrameworkAttributes.sdkFrameworks(); } /** * Returns the value of the weak_sdk_frameworks attribute. */ ImmutableSet weakSdkFrameworks() { return sdkFrameworkAttributes.weakSdkFrameworks(); } /** * Returns the value of the sdk_dylibs attribute. */ ImmutableSet sdkDylibs() { return sdkFrameworkAttributes.sdkDylibs(); } /** * Returns the exec paths of all header search paths that should be added to this target and * dependers on this target, obtained from the {@code includes} attribute. */ ImmutableList headerSearchPaths() { ImmutableList.Builder paths = new ImmutableList.Builder<>(); PathFragment packageFragment = ruleContext.getLabel().getPackageIdentifier().getPathFragment(); List rootFragments = ImmutableList.of( packageFragment, ruleContext.getConfiguration().getGenfilesFragment().getRelative(packageFragment)); Iterable relativeIncludes = Iterables.filter(includes(), Predicates.not(PathFragment.IS_ABSOLUTE)); for (PathFragment include : relativeIncludes) { for (PathFragment rootFragment : rootFragments) { paths.add(rootFragment.getRelative(include).normalize()); } } return paths.build(); } /** * Returns any values specified in this rule's {@code copts} attribute or an empty list if the * attribute does not exist or no values are specified. */ public Iterable copts() { if (!ruleContext.attributes().has("copts", Type.STRING_LIST)) { return ImmutableList.of(); } return ruleContext.getTokenizedStringListAttr("copts"); } /** * Returns any {@code copts} defined on an {@code objc_options} rule that is a dependency of * this rule. */ public Iterable optionsCopts() { if (!ruleContext.attributes().has("options", Type.LABEL)) { return ImmutableList.of(); } OptionsProvider optionsProvider = ruleContext.getPrerequisite("options", Mode.TARGET, OptionsProvider.class); if (optionsProvider == null) { return ImmutableList.of(); } return optionsProvider.getCopts(); } } /** * Provides a way to access attributes that are common to all resources rules. */ // TODO(bazel-team): Delete and move into support-specific attributes classes once ObjcCommon is // gone. static final class ResourceAttributes { private final RuleContext ruleContext; ResourceAttributes(RuleContext ruleContext) { this.ruleContext = ruleContext; } ImmutableList strings() { return ruleContext.getPrerequisiteArtifacts("strings", Mode.TARGET).list(); } ImmutableList xibs() { return ruleContext.getPrerequisiteArtifacts("xibs", Mode.TARGET) .errorsForNonMatching(ObjcRuleClasses.XIB_TYPE) .list(); } ImmutableList storyboards() { return ruleContext.getPrerequisiteArtifacts("storyboards", Mode.TARGET).list(); } ImmutableList resources() { return ruleContext.getPrerequisiteArtifacts("resources", Mode.TARGET).list(); } ImmutableList structuredResources() { return ruleContext.getPrerequisiteArtifacts("structured_resources", Mode.TARGET).list(); } ImmutableList datamodels() { return ruleContext.getPrerequisiteArtifacts("datamodels", Mode.TARGET).list(); } ImmutableList assetCatalogs() { return ruleContext.getPrerequisiteArtifacts("asset_catalogs", Mode.TARGET).list(); } } static class Builder { private RuleContext context; private Optional compilationAttributes = Optional.absent(); private Optional resourceAttributes = Optional.absent(); private Iterable extraSdkFrameworks = ImmutableList.of(); private Iterable extraWeakSdkFrameworks = ImmutableList.of(); private Iterable extraSdkDylibs = ImmutableList.of(); private Iterable frameworkImports = ImmutableList.of(); private Optional compilationArtifacts = Optional.absent(); private Iterable depObjcProviders = ImmutableList.of(); private Iterable directDepObjcProviders = ImmutableList.of(); private Iterable defines = ImmutableList.of(); private Iterable userHeaderSearchPaths = ImmutableList.of(); private Iterable headers = ImmutableList.of(); private IntermediateArtifacts intermediateArtifacts; private boolean alwayslink; private Iterable extraImportLibraries = ImmutableList.of(); private Optional linkedBinary = Optional.absent(); private Optional breakpadFile = Optional.absent(); private Iterable depCcHeaderProviders = ImmutableList.of(); private Iterable depCcLinkProviders = ImmutableList.of(); Builder(RuleContext context) { this.context = Preconditions.checkNotNull(context); } public Builder setCompilationAttributes(CompilationAttributes baseCompilationAttributes) { Preconditions.checkState(!this.compilationAttributes.isPresent(), "compilationAttributes is already set to: %s", this.compilationAttributes); this.compilationAttributes = Optional.of(baseCompilationAttributes); return this; } public Builder setResourceAttributes(ResourceAttributes baseResourceAttributes) { Preconditions.checkState(!this.resourceAttributes.isPresent(), "resourceAttributes is already set to: %s", this.resourceAttributes); this.resourceAttributes = Optional.of(baseResourceAttributes); return this; } Builder addExtraSdkFrameworks(Iterable extraSdkFrameworks) { this.extraSdkFrameworks = Iterables.concat(this.extraSdkFrameworks, extraSdkFrameworks); return this; } Builder addExtraWeakSdkFrameworks(Iterable extraWeakSdkFrameworks) { this.extraWeakSdkFrameworks = Iterables.concat(this.extraWeakSdkFrameworks, extraWeakSdkFrameworks); return this; } Builder addExtraSdkDylibs(Iterable extraSdkDylibs) { this.extraSdkDylibs = Iterables.concat(this.extraSdkDylibs, extraSdkDylibs); return this; } Builder addFrameworkImports(Iterable frameworkImports) { this.frameworkImports = Iterables.concat(this.frameworkImports, frameworkImports); return this; } Builder setCompilationArtifacts(CompilationArtifacts compilationArtifacts) { Preconditions.checkState(!this.compilationArtifacts.isPresent(), "compilationArtifacts is already set to: %s", this.compilationArtifacts); this.compilationArtifacts = Optional.of(compilationArtifacts); return this; } /** * Add providers which will be exposed both to the declaring rule and to any dependers on the * declaring rule. */ Builder addDepObjcProviders(Iterable depObjcProviders) { this.depObjcProviders = Iterables.concat(this.depObjcProviders, depObjcProviders); return this; } /** * Add providers which will only be used by the declaring rule, and won't be propagated to any * dependers on the declaring rule. */ Builder addNonPropagatedDepObjcProviders(Iterable directDepObjcProviders) { this.directDepObjcProviders = Iterables.concat( this.directDepObjcProviders, directDepObjcProviders); return this; } public Builder addUserHeaderSearchPaths(Iterable userHeaderSearchPaths) { this.userHeaderSearchPaths = Iterables.concat(this.userHeaderSearchPaths, userHeaderSearchPaths); return this; } public Builder addDefines(Iterable defines) { this.defines = Iterables.concat(this.defines, defines); return this; } public Builder addHeaders(Iterable headers) { this.headers = Iterables.concat(this.headers, headers); return this; } Builder setIntermediateArtifacts(IntermediateArtifacts intermediateArtifacts) { this.intermediateArtifacts = intermediateArtifacts; return this; } Builder setAlwayslink(boolean alwayslink) { this.alwayslink = alwayslink; return this; } /** * Adds additional static libraries to be linked into the final ObjC application bundle. */ Builder addExtraImportLibraries(Iterable extraImportLibraries) { this.extraImportLibraries = Iterables.concat(this.extraImportLibraries, extraImportLibraries); return this; } /** * Sets a linked binary generated by this rule to be propagated to dependers. */ Builder setLinkedBinary(Artifact linkedBinary) { this.linkedBinary = Optional.of(linkedBinary); return this; } /** * Sets a breakpad file (used by the breakpad crash reporting system) generated by this rule to * be propagated to dependers. */ Builder setBreakpadFile(Artifact breakpadFile) { this.breakpadFile = Optional.of(breakpadFile); return this; } /** * Sets information from {@code cc_library} dependencies to be used during compilation. */ public Builder addDepCcHeaderProviders(Iterable depCcHeaderProviders) { this.depCcHeaderProviders = Iterables.concat(this.depCcHeaderProviders, depCcHeaderProviders); return this; } /** * Sets information from {@code cc_library} dependencies to be used during linking. */ public Builder addDepCcLinkProviders(Iterable depCcLinkProviders) { this.depCcLinkProviders = Iterables.concat(this.depCcLinkProviders, depCcLinkProviders); return this; } ObjcCommon build() { Iterable bundleImports = BundleableFile.bundleImportsFromRule(context); ObjcProvider.Builder objcProvider = new ObjcProvider.Builder() .addAll(IMPORTED_LIBRARY, extraImportLibraries) .addAll(BUNDLE_FILE, bundleImports) .addAll(BUNDLE_IMPORT_DIR, uniqueContainers(BundleableFile.toArtifacts(bundleImports), BUNDLE_CONTAINER_TYPE)) .addAll(SDK_FRAMEWORK, extraSdkFrameworks) .addAll(WEAK_SDK_FRAMEWORK, extraWeakSdkFrameworks) .addAll(SDK_DYLIB, extraSdkDylibs) .addAll(FRAMEWORK_FILE, frameworkImports) .addAll(FRAMEWORK_DIR, uniqueContainers(frameworkImports, FRAMEWORK_CONTAINER_TYPE)) .addAll(INCLUDE, userHeaderSearchPaths) .addAll(DEFINE, defines) .addAll(HEADER, headers) .addTransitiveAndPropagate(depObjcProviders) .addTransitiveWithoutPropagating(directDepObjcProviders); for (CppCompilationContext headerProvider : depCcHeaderProviders) { // TODO(bazel-team): Also account for custom include settings to go into header search paths objcProvider.addTransitiveAndPropagate(HEADER, headerProvider.getDeclaredIncludeSrcs()); } for (CcLinkParamsProvider linkProvider : depCcLinkProviders) { objcProvider.addTransitiveAndPropagate( CC_LIBRARY, linkProvider.getCcLinkParams(true, false).getLibraries()); } if (compilationAttributes.isPresent()) { CompilationAttributes attributes = compilationAttributes.get(); ObjcConfiguration objcConfiguration = ObjcRuleClasses.objcConfiguration(context); Iterable sdkIncludes = Iterables.transform( Interspersing.prependEach( IosSdkCommands.sdkDir(objcConfiguration) + "/usr/include/", PathFragment.safePathStrings(attributes.sdkIncludes())), TO_PATH_FRAGMENT); objcProvider .addAll(HEADER, attributes.hdrs()) .addAll(INCLUDE, attributes.headerSearchPaths()) .addAll(INCLUDE, sdkIncludes) .addAll(SDK_FRAMEWORK, attributes.sdkFrameworks()) .addAll(WEAK_SDK_FRAMEWORK, attributes.weakSdkFrameworks()) .addAll(SDK_DYLIB, attributes.sdkDylibs()); } if (resourceAttributes.isPresent()) { ResourceAttributes attributes = resourceAttributes.get(); objcProvider .addAll(GENERAL_RESOURCE_FILE, attributes.storyboards()) .addAll(GENERAL_RESOURCE_FILE, attributes.resources()) .addAll(GENERAL_RESOURCE_FILE, attributes.strings()) .addAll(GENERAL_RESOURCE_FILE, attributes.xibs()) .addAll( GENERAL_RESOURCE_DIR, xcodeStructuredResourceDirs(attributes.structuredResources())) .addAll(BUNDLE_FILE, BundleableFile.flattenedRawResourceFiles(attributes.resources())) .addAll( BUNDLE_FILE, BundleableFile.structuredRawResourceFiles(attributes.structuredResources())) .addAll( XCASSETS_DIR, uniqueContainers(attributes.assetCatalogs(), ASSET_CATALOG_CONTAINER_TYPE)) .addAll(ASSET_CATALOG, attributes.assetCatalogs()) .addAll(XCDATAMODEL, attributes.datamodels()) .addAll(XIB, attributes.xibs()) .addAll(STRINGS, attributes.strings()) .addAll(STORYBOARD, attributes.storyboards()); } for (CompilationArtifacts artifacts : compilationArtifacts.asSet()) { Iterable allSources = Iterables.concat(artifacts.getSrcs(), artifacts.getNonArcSrcs()); objcProvider.addAll(LIBRARY, artifacts.getArchive().asSet()); objcProvider.addAll(SOURCE, allSources); BuildConfiguration configuration = context.getConfiguration(); RegexFilter filter = configuration.getInstrumentationFilter(); if (configuration.isCodeCoverageEnabled() && filter.isIncluded(context.getLabel().toString())) { for (Artifact source : allSources) { objcProvider.add(INSTRUMENTED_SOURCE, source); objcProvider.add(GCNO, intermediateArtifacts.gcnoFile(source)); } } boolean usesCpp = false; boolean usesSwift = false; for (Artifact sourceFile : Iterables.concat(artifacts.getSrcs(), artifacts.getNonArcSrcs())) { usesCpp = usesCpp || ObjcRuleClasses.CPP_SOURCES.matches(sourceFile.getExecPath()); usesSwift = usesSwift || ObjcRuleClasses.SWIFT_SOURCES.matches(sourceFile.getExecPath()); } if (usesCpp) { objcProvider.add(FLAG, USES_CPP); } if (usesSwift) { objcProvider.add(FLAG, USES_SWIFT); } } if (alwayslink) { for (CompilationArtifacts artifacts : compilationArtifacts.asSet()) { for (Artifact archive : artifacts.getArchive().asSet()) { objcProvider.add(FORCE_LOAD_LIBRARY, archive); objcProvider.add(FORCE_LOAD_FOR_XCODEGEN, String.format( "$(BUILT_PRODUCTS_DIR)/lib%s.a", XcodeProvider.xcodeTargetName(context.getLabel()))); } } for (Artifact archive : extraImportLibraries) { objcProvider.add(FORCE_LOAD_LIBRARY, archive); objcProvider.add(FORCE_LOAD_FOR_XCODEGEN, "$(WORKSPACE_ROOT)/" + archive.getExecPath().getSafePathString()); } } objcProvider.addAll(LINKED_BINARY, linkedBinary.asSet()) .addAll(BREAKPAD_FILE, breakpadFile.asSet()); return new ObjcCommon(objcProvider.build(), compilationArtifacts); } } static final FileType BUNDLE_CONTAINER_TYPE = FileType.of(".bundle"); static final FileType ASSET_CATALOG_CONTAINER_TYPE = FileType.of(".xcassets"); static final FileType FRAMEWORK_CONTAINER_TYPE = FileType.of(".framework"); private final ObjcProvider objcProvider; private final Optional compilationArtifacts; private ObjcCommon( ObjcProvider objcProvider, Optional compilationArtifacts) { this.objcProvider = Preconditions.checkNotNull(objcProvider); this.compilationArtifacts = Preconditions.checkNotNull(compilationArtifacts); } public ObjcProvider getObjcProvider() { return objcProvider; } public Optional getCompilationArtifacts() { return compilationArtifacts; } /** * Returns an {@link Optional} containing the compiled {@code .a} file, or * {@link Optional#absent()} if this object contains no {@link CompilationArtifacts} or the * compilation information has no sources. */ public Optional getCompiledArchive() { for (CompilationArtifacts justCompilationArtifacts : compilationArtifacts.asSet()) { return justCompilationArtifacts.getArchive(); } return Optional.absent(); } /** * Reports any known errors to the {@link RuleContext}. This should be called exactly once for * a target. */ public void reportErrors() { // TODO(bazel-team): Report errors for rules that are not actually useful (i.e. objc_library // without sources or resources, empty objc_bundles) } static ImmutableList userHeaderSearchPaths(BuildConfiguration configuration) { return ImmutableList.of( new PathFragment("."), configuration.getGenfilesFragment()); } /** * Returns the first directory in the sequence of parents of the exec path of the given artifact * that matches {@code type}. For instance, if {@code type} is FileType.of(".foo") and the exec * path of {@code artifact} is {@code a/b/c/bar.foo/d/e}, then the return value is * {@code a/b/c/bar.foo}. */ static Optional nearestContainerMatching(FileType type, Artifact artifact) { PathFragment container = artifact.getExecPath(); do { if (type.matches(container)) { return Optional.of(container); } container = container.getParentDirectory(); } while (container != null); return Optional.absent(); } /** * Similar to {@link #nearestContainerMatching(FileType, Artifact)}, but tries matching several * file types in {@code types}, and returns a path for the first match in the sequence. */ static Optional nearestContainerMatching( Iterable types, Artifact artifact) { for (FileType type : types) { for (PathFragment container : nearestContainerMatching(type, artifact).asSet()) { return Optional.of(container); } } return Optional.absent(); } /** * Returns all directories matching {@code containerType} that contain the items in * {@code artifacts}. This function ignores artifacts that are not in any directory matching * {@code containerType}. */ static Iterable uniqueContainers( Iterable artifacts, FileType containerType) { ImmutableSet.Builder containers = new ImmutableSet.Builder<>(); for (Artifact artifact : artifacts) { containers.addAll(ObjcCommon.nearestContainerMatching(containerType, artifact).asSet()); } return containers.build(); } /** * Returns the Xcode structured resource directory paths. * *

For a checked-in source artifact "//a/b/res/sub_dir/d" included by objc rule "//a/b:c", * "a/b/res" will be returned. For a generated source artifact "res/sub_dir/d" owned by genrule * "//a/b:c", "bazel-out/.../genfiles/a/b/res" will be returned. * *

When XCode sees a included resource directory of "a/b/res", the entire directory structure * up to "res" will be copied into the app bundle. */ private static Iterable xcodeStructuredResourceDirs(Iterable artifacts) { ImmutableSet.Builder containers = new ImmutableSet.Builder<>(); for (Artifact artifact : artifacts) { PathFragment ownerRuleDirectory = artifact.getArtifactOwner().getLabel().getPackageFragment(); String containerName = artifact.getRootRelativePath().relativeTo(ownerRuleDirectory).getSegment(0); PathFragment rootExecPath = artifact.getRoot().getExecPath(); containers.add(rootExecPath.getRelative(ownerRuleDirectory.getRelative(containerName))); } return containers.build(); } /** * Similar to {@link #nearestContainerMatching(FileType, Artifact)}, but returns the container * closest to the root that matches the given type. */ static Optional farthestContainerMatching(FileType type, Artifact artifact) { PathFragment container = artifact.getExecPath(); Optional lastMatch = Optional.absent(); do { if (type.matches(container)) { lastMatch = Optional.of(container); } container = container.getParentDirectory(); } while (container != null); return lastMatch; } static Iterable notInContainerErrors( Iterable artifacts, FileType containerType) { return notInContainerErrors(artifacts, ImmutableList.of(containerType)); } @VisibleForTesting static final String NOT_IN_CONTAINER_ERROR_FORMAT = "File '%s' is not in a directory of one of these type(s): %s"; static Iterable notInContainerErrors( Iterable artifacts, Iterable containerTypes) { Set errors = new HashSet<>(); for (Artifact artifact : artifacts) { boolean inContainer = nearestContainerMatching(containerTypes, artifact).isPresent(); if (!inContainer) { errors.add(String.format(NOT_IN_CONTAINER_ERROR_FORMAT, artifact.getExecPath(), Iterables.toString(containerTypes))); } } return errors; } }