// Copyright 2014 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.objc;
import static com.google.devtools.build.lib.collect.nestedset.Order.LINK_ORDER;
import static com.google.devtools.build.lib.collect.nestedset.Order.STABLE_ORDER;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import com.google.devtools.build.lib.actions.Artifact;
import com.google.devtools.build.lib.analysis.TransitiveInfoProvider;
import com.google.devtools.build.lib.collect.nestedset.NestedSet;
import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
import com.google.devtools.build.lib.collect.nestedset.Order;
import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
import com.google.devtools.build.lib.rules.cpp.CppModuleMap;
import com.google.devtools.build.lib.rules.cpp.LinkerInputs;
import com.google.devtools.build.lib.skylarkinterface.SkylarkCallable;
import com.google.devtools.build.lib.skylarkinterface.SkylarkModule;
import com.google.devtools.build.lib.syntax.ClassObject.SkylarkClassObject;
import com.google.devtools.build.lib.syntax.SkylarkNestedSet;
import com.google.devtools.build.lib.util.Preconditions;
import com.google.devtools.build.lib.vfs.PathFragment;
import com.google.devtools.build.xcode.xcodegen.proto.XcodeGenProtos.TargetControl;
import java.util.HashMap;
import java.util.Map;
/**
* A provider that provides all compiling and linking information in the transitive closure of its
* deps that are needed for building Objective-C rules.
*/
@Immutable
public final class ObjcProvider implements TransitiveInfoProvider {
/**
* The name skylark dependents can use to access a Skylark provider containing information
* from a target's ObjcProvider.
*/
public static final String OBJC_SKYLARK_PROVIDER_NAME = "objc";
/**
* The name skylark dependents can use to export a native objc provider to depending native
* rules.
*
*
This constant must be different from OBJC_SKYLARK_PROVIDER_NAME to prevent skylark rules
* from automatically exporting the ObjcProvider provided to them by dependants. This can
* lead to duplicate symbol linker errors.
*/
public static final String OBJC_SKYLARK_PROVIDER_TO_EXPORT_NAME = "objc_export";
/**
* Represents one of the things this provider can provide transitively. Things are provided as
* {@link NestedSet}s of type E.
*/
@SkylarkModule(name = "Key", doc = "An ObjcProvider key.")
public static class Key {
private final Order order;
private final String skylarkKeyName;
private final Class type;
private Key(Order order, String skylarkKeyName, Class type) {
this.order = Preconditions.checkNotNull(order);
this.skylarkKeyName = skylarkKeyName;
this.type = type;
}
/**
* Returns the name of the collection represented by this key in the Skylark provider.
*/
@SkylarkCallable(name = "name", structField = true)
public String getSkylarkKeyName() {
return skylarkKeyName;
}
/**
* Returns the type of nested set keyed in the ObjcProvider by this key.
*/
public Class getType() {
return type;
}
}
public static final Key LIBRARY = new Key<>(LINK_ORDER, "library", Artifact.class);
public static final Key IMPORTED_LIBRARY =
new Key<>(LINK_ORDER, "imported_library", Artifact.class);
/**
* Single-architecture linked binaries to be combined for the final multi-architecture binary.
*/
public static final Key LINKED_BINARY =
new Key<>(STABLE_ORDER, "linked_binary", Artifact.class);
/**
* Indicates which libraries to load with {@code -force_load}. This is a subset of the union of
* the {@link #LIBRARY} and {@link #IMPORTED_LIBRARY} sets.
*/
public static final Key FORCE_LOAD_LIBRARY =
new Key<>(LINK_ORDER, "force_load_library", Artifact.class);
/**
* Libraries to pass with -force_load flags when setting the linkopts in Xcodegen. This is needed
* in addition to {@link #FORCE_LOAD_LIBRARY} because that one, contains a mixture of import
* archives (which are not built by Xcode) and built-from-source library archives (which are built
* by Xcode). Archives that are built by Xcode are placed directly under
* {@code BUILT_PRODUCTS_DIR} while those not built by Xcode appear somewhere in the Bazel
* workspace under {@code WORKSPACE_ROOT}.
*/
public static final Key FORCE_LOAD_FOR_XCODEGEN =
new Key<>(LINK_ORDER, "force_load_for_xcodegen", String.class);
/**
* Contains all header files. These may be either public or private headers.
*/
public static final Key HEADER = new Key<>(STABLE_ORDER, "header", Artifact.class);
/**
* Contains all source files.
*/
public static final Key SOURCE = new Key<>(STABLE_ORDER, "source", Artifact.class);
/**
* Include search paths specified with {@code -I} on the command line. Also known as header search
* paths (and distinct from user header search paths).
*/
public static final Key INCLUDE =
new Key<>(LINK_ORDER, "include", PathFragment.class);
/**
* Include search paths specified with {@code -isystem} on the command line.
*/
public static final Key INCLUDE_SYSTEM =
new Key<>(LINK_ORDER, "include_system", PathFragment.class);
/**
* Key for values in {@code defines} attributes. These are passed as {@code -D} flags to all
* invocations of the compiler for this target and all depending targets.
*/
public static final Key DEFINE = new Key<>(STABLE_ORDER, "define", String.class);
public static final Key ASSET_CATALOG =
new Key<>(STABLE_ORDER, "asset_catalog", Artifact.class);
/**
* Added to {@link TargetControl#getGeneralResourceFileList()} when running Xcodegen.
*/
public static final Key GENERAL_RESOURCE_FILE =
new Key<>(STABLE_ORDER, "general_resource_file", Artifact.class);
/**
* Resource directories added to {@link TargetControl#getGeneralResourceFileList()} when running
* Xcodegen. When copying files inside resource directories to the app bundle, XCode will preserve
* the directory structures of the copied files.
*/
public static final Key GENERAL_RESOURCE_DIR =
new Key<>(STABLE_ORDER, "general_resource_dir", PathFragment.class);
/**
* Exec paths of {@code .bundle} directories corresponding to imported bundles to link.
* These are passed to Xcodegen.
*/
public static final Key BUNDLE_IMPORT_DIR =
new Key<>(STABLE_ORDER, "bundle_import_dir", PathFragment.class);
/**
* Files that are plopped into the final bundle at some arbitrary bundle path. Note that these are
* not passed to Xcodegen, and these don't include information about where the file originated
* from.
*/
public static final Key BUNDLE_FILE =
new Key<>(STABLE_ORDER, "bundle_file", BundleableFile.class);
public static final Key XCASSETS_DIR =
new Key<>(STABLE_ORDER, "xcassets_dir", PathFragment.class);
public static final Key SDK_DYLIB = new Key<>(STABLE_ORDER, "sdk_dylib", String.class);
public static final Key SDK_FRAMEWORK =
new Key<>(STABLE_ORDER, "sdk_framework", SdkFramework.class);
public static final Key WEAK_SDK_FRAMEWORK =
new Key<>(STABLE_ORDER, "weak_sdk_framework", SdkFramework.class);
public static final Key XCDATAMODEL =
new Key<>(STABLE_ORDER, "xcdatamodel", Artifact.class);
public static final Key FLAG = new Key<>(STABLE_ORDER, "flag", Flag.class);
/**
* Clang module maps, used to enforce proper use of private header files.
*/
public static final Key MODULE_MAP =
new Key<>(STABLE_ORDER, "module_map", Artifact.class);
/**
* Information about this provider's module map, in the form of a {@link CppModuleMap}. This
* is intransitive, and can be used to get just the target's module map to pass to clang or to
* get the module maps for direct but not transitive dependencies. You should only add module maps
* for this key using {@link Builder#addWithoutPropagating}.
*/
public static final Key TOP_LEVEL_MODULE_MAP =
new Key<>(STABLE_ORDER, "top_level_module_map", CppModuleMap.class);
/**
* Merge zips to include in the bundle. The entries of these zip files are included in the final
* bundle with the same path. The entries in the merge zips should not include the bundle root
* path (e.g. {@code Foo.app}).
*/
public static final Key MERGE_ZIP =
new Key<>(STABLE_ORDER, "merge_zip", Artifact.class);
/**
* Merge zips to include in the ipa and outside the bundle root.
*
* e.g. For a bundle Test.ipa, unzipped content will be in:
* Test.ipa/
* Test.ipa/Payload
* Test.ipa/Payload/Test.app
*/
public static final Key ROOT_MERGE_ZIP =
new Key<>(STABLE_ORDER, "root_merge_zip", Artifact.class);
/**
* Exec paths of {@code .framework} directories corresponding to frameworks to link. These cause
* -F arguments (framework search paths) to be added to each compile action, and -framework (link
* framework) arguments to be added to each link action.
*/
public static final Key FRAMEWORK_DIR =
new Key<>(LINK_ORDER, "framework_dir", PathFragment.class);
/**
* Files in {@code .framework} directories that should be included as inputs when compiling and
* linking.
*/
public static final Key FRAMEWORK_FILE =
new Key<>(STABLE_ORDER, "framework_file", Artifact.class);
/**
* Bundles which should be linked in as a nested bundle to the final application.
*/
public static final Key NESTED_BUNDLE =
new Key<>(STABLE_ORDER, "nested_bundle", Bundling.class);
/**
* Artifact containing information on debug symbols.
*/
public static final Key DEBUG_SYMBOLS =
new Key<>(STABLE_ORDER, "debug_symbols", Artifact.class);
/**
* Artifact containing the plist of the debug symbols.
*/
public static final Key DEBUG_SYMBOLS_PLIST =
new Key<>(STABLE_ORDER, "debug_symbols_plist", Artifact.class);
/**
* Generated breakpad file containing debug information used by the breakpad crash reporting
* system.
*/
public static final Key BREAKPAD_FILE =
new Key<>(STABLE_ORDER, "breakpad_file", Artifact.class);
/**
* Single-architecture link map for a binary.
*/
public static final Key LINKMAP_FILE =
new Key<>(STABLE_ORDER, "linkmap_file", Artifact.class);
/**
* Artifacts for storyboard sources.
*/
public static final Key STORYBOARD =
new Key<>(STABLE_ORDER, "storyboard", Artifact.class);
/**
* Artifacts for .xib file sources.
*/
public static final Key XIB = new Key<>(STABLE_ORDER, "xib", Artifact.class);
/**
* Artifacts for strings source files.
*/
public static final Key STRINGS = new Key<>(STABLE_ORDER, "strings", Artifact.class);
/**
* Linking information from cc dependencies.
*/
public static final Key CC_LIBRARY =
new Key<>(LINK_ORDER, "cc_library", LinkerInputs.LibraryToLink.class);
/**
* Linking options from dependencies.
*/
public static final Key LINKOPT = new Key<>(LINK_ORDER, "linkopt", String.class);
/**
* Static libraries that are built from J2ObjC-translated Java code.
*/
public static final Key J2OBJC_LIBRARY =
new Key<>(LINK_ORDER, "j2objc_library", Artifact.class);
/**
* Flags that apply to a transitive build dependency tree. Each item in the enum corresponds to a
* flag. If the item is included in the key {@link #FLAG}, then the flag is considered set.
*/
public enum Flag {
/**
* Indicates that C++ (or Objective-C++) is used in any source file. This affects how the linker
* is invoked.
*/
USES_CPP,
/**
* Indicates that Swift source files are present. This affects bundling, compiling and linking
* actions.
*/
USES_SWIFT,
/**
* Indicates that the resulting bundle will have embedded frameworks. This affects linking step.
*/
USES_FRAMEWORKS,
/**
* Indicates that watch os 1 extension is present in the bundle.
*/
HAS_WATCH1_EXTENSION
}
private final ImmutableMap, NestedSet>> items;
// Items which should be passed to direct dependers, but not transitive dependers.
private final ImmutableMap, NestedSet>> nonPropagatedItems;
private ObjcProvider(
ImmutableMap, NestedSet>> items,
ImmutableMap, NestedSet>> nonPropagatedItems) {
this.items = Preconditions.checkNotNull(items);
this.nonPropagatedItems = Preconditions.checkNotNull(nonPropagatedItems);
}
/**
* All artifacts, bundleable files, etc. of the type specified by {@code key}.
*/
@SuppressWarnings("unchecked")
public NestedSet get(Key key) {
Preconditions.checkNotNull(key);
NestedSetBuilder builder = new NestedSetBuilder<>(key.order);
if (nonPropagatedItems.containsKey(key)) {
builder.addTransitive((NestedSet) nonPropagatedItems.get(key));
}
if (items.containsKey(key)) {
builder.addTransitive((NestedSet) items.get(key));
}
return builder.build();
}
/**
* Indicates whether {@code flag} is set on this provider.
*/
public boolean is(Flag flag) {
return Iterables.contains(get(FLAG), flag);
}
/**
* Indicates whether this provider has any asset catalogs. This is true whenever some target in
* its transitive dependency tree specifies a non-empty {@code asset_catalogs} attribute.
*/
public boolean hasAssetCatalogs() {
return !get(XCASSETS_DIR).isEmpty();
}
/**
* Returns a {@code SkylarkClassObject} containing values from this provider that is suitable
* for a skylark provider.
*/
public SkylarkClassObject toSkylarkProvider() {
ImmutableMap.Builder providerBuilder = ImmutableMap.builder();
for (Key> key : SkylarkKeyStore.KEYS_FOR_SKYLARK) {
providerBuilder.put(key.getSkylarkKeyName(), new SkylarkNestedSet(key.getType(), get(key)));
}
return new SkylarkClassObject(providerBuilder.build(), "No such attribute '%s'");
}
/**
* Returns an {@code ObjcProvider} from a given skylark provider. For each candidate key
* in the ObjcProvider, will check the given skylark provider to see if that key is represented
* in the returned struct.
*/
public static ObjcProvider fromSkylarkProvider(SkylarkClassObject skylarkProvider) {
Builder builder = new Builder();
for (Key> key : SkylarkKeyStore.KEYS_FOR_SKYLARK) {
SkylarkNestedSet skylarkSet =
(SkylarkNestedSet) skylarkProvider.getValue(key.getSkylarkKeyName());
if (skylarkSet != null) {
builder.uncheckedAddAll(key, skylarkSet.getSet(key.getType()), builder.items);
}
}
return builder.build();
}
/**
* A builder for this context with an API that is optimized for collecting information from
* several transitive dependencies.
*/
public static final class Builder {
private final Map, NestedSetBuilder>> items = new HashMap<>();
private final Map, NestedSetBuilder>> nonPropagatedItems = new HashMap<>();
private static void maybeAddEmptyBuilder(Map, NestedSetBuilder>> set, Key> key) {
if (!set.containsKey(key)) {
set.put(key, new NestedSetBuilder<>(key.order));
}
}
@SuppressWarnings({"rawtypes", "unchecked"})
private void uncheckedAddAll(Key key, Iterable toAdd, Map, NestedSetBuilder>> set) {
maybeAddEmptyBuilder(set, key);
set.get(key).addAll(toAdd);
}
@SuppressWarnings({"rawtypes", "unchecked"})
private void uncheckedAddTransitive(Key key, NestedSet toAdd,
Map, NestedSetBuilder>> set) {
maybeAddEmptyBuilder(set, key);
set.get(key).addTransitive(toAdd);
}
/**
* Adds elements in items, and propagate them to any (transitive) dependers on this
* ObjcProvider.
*/
public Builder addTransitiveAndPropagate(Key key, NestedSet items) {
uncheckedAddTransitive(key, items, this.items);
return this;
}
/**
* Add all elements from provider, and propagate them to any (transitive) dependers on this
* ObjcProvider.
*/
public Builder addTransitiveAndPropagate(ObjcProvider provider) {
for (Map.Entry, NestedSet>> typeEntry : provider.items.entrySet()) {
uncheckedAddTransitive(typeEntry.getKey(), typeEntry.getValue(), this.items);
}
return this;
}
/**
* Add all elements from a single key of the given provider, and propagate them to any
* (transitive) dependers on this ObjcProvider.
*/
public Builder addTransitiveAndPropagate(Key key, ObjcProvider provider) {
addTransitiveAndPropagate(key, provider.get(key));
return this;
}
/**
* Add all elements from providers, and propagate them to any (transitive) dependers on this
* ObjcProvider.
*/
public Builder addTransitiveAndPropagate(Iterable providers) {
for (ObjcProvider provider : providers) {
addTransitiveAndPropagate(provider);
}
return this;
}
/**
* Add elements from providers, but don't propagate them to any dependers on this ObjcProvider.
* These elements will be exposed to {@link #get(Key)} calls, but not to any ObjcProviders
* which add this provider to themselves.
*/
public Builder addTransitiveWithoutPropagating(Iterable providers) {
for (ObjcProvider provider : providers) {
for (Map.Entry, NestedSet>> typeEntry : provider.items.entrySet()) {
uncheckedAddTransitive(typeEntry.getKey(), typeEntry.getValue(), this.nonPropagatedItems);
}
}
return this;
}
/**
* Add element, and propagate it to any (transitive) dependers on this ObjcProvider.
*/
public Builder add(Key key, E toAdd) {
uncheckedAddAll(key, ImmutableList.of(toAdd), this.items);
return this;
}
/**
* Add elements in toAdd, and propagate them to any (transitive) dependers on this ObjcProvider.
*/
public Builder addAll(Key key, Iterable extends E> toAdd) {
uncheckedAddAll(key, toAdd, this.items);
return this;
}
public ObjcProvider build() {
ImmutableMap.Builder, NestedSet>> propagated = new ImmutableMap.Builder<>();
for (Map.Entry, NestedSetBuilder>> typeEntry : items.entrySet()) {
propagated.put(typeEntry.getKey(), typeEntry.getValue().build());
}
ImmutableMap.Builder, NestedSet>> nonPropagated = new ImmutableMap.Builder<>();
for (Map.Entry, NestedSetBuilder>> typeEntry : nonPropagatedItems.entrySet()) {
nonPropagated.put(typeEntry.getKey(), typeEntry.getValue().build());
}
return new ObjcProvider(propagated.build(), nonPropagated.build());
}
}
}