com.google.devtools.build.lib.rules.android.NativeLibs.java Source code

Java tutorial

Introduction

Here is the source code for com.google.devtools.build.lib.rules.android.NativeLibs.java

Source

// 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.android;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Multimap;
import com.google.devtools.build.lib.actions.Artifact;
import com.google.devtools.build.lib.analysis.RuleContext;
import com.google.devtools.build.lib.analysis.SourceManifestAction;
import com.google.devtools.build.lib.analysis.SourceManifestAction.ManifestType;
import com.google.devtools.build.lib.analysis.TransitiveInfoCollection;
import com.google.devtools.build.lib.analysis.actions.FileWriteAction;
import com.google.devtools.build.lib.analysis.actions.SymlinkTreeAction;
import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
import com.google.devtools.build.lib.collect.nestedset.NestedSet;
import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
import com.google.devtools.build.lib.rules.cpp.CcLinkParams;
import com.google.devtools.build.lib.rules.cpp.CcToolchainProvider;
import com.google.devtools.build.lib.rules.cpp.CppFileTypes;
import com.google.devtools.build.lib.rules.cpp.LinkerInput;
import com.google.devtools.build.lib.rules.nativedeps.NativeDepsHelper;
import com.google.devtools.build.lib.vfs.PathFragment;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import javax.annotation.Nullable;

/** Represents the collection of native libraries (.so) to be installed in the APK. */
@Immutable
public final class NativeLibs {
    public static final NativeLibs EMPTY = new NativeLibs(ImmutableMap.<String, Iterable<Artifact>>of(), null);

    public static NativeLibs fromLinkedNativeDeps(RuleContext ruleContext, String nativeDepsFileName,
            Multimap<String, TransitiveInfoCollection> depsByArchitecture,
            Map<String, CcToolchainProvider> toolchainMap, Map<String, BuildConfiguration> configurationMap)
            throws InterruptedException {
        Map<String, Iterable<Artifact>> result = new LinkedHashMap<>();
        String nativeDepsLibraryBasename = null;
        for (Map.Entry<String, Collection<TransitiveInfoCollection>> entry : depsByArchitecture.asMap()
                .entrySet()) {
            CcLinkParams linkParams = AndroidCommon
                    .getCcLinkParamsStore(entry.getValue(),
                            ImmutableList.of("-Wl,-soname=lib" + ruleContext.getLabel().getName()))
                    .get(/* linkingStatically */ true, /* linkShared */ true);

            Artifact nativeDepsLibrary = NativeDepsHelper.linkAndroidNativeDepsIfPresent(ruleContext, linkParams,
                    configurationMap.get(entry.getKey()), toolchainMap.get(entry.getKey()));

            ImmutableList.Builder<Artifact> librariesBuilder = ImmutableList.builder();
            if (nativeDepsLibrary != null) {
                librariesBuilder.add(nativeDepsLibrary);
                nativeDepsLibraryBasename = nativeDepsLibrary.getExecPath().getBaseName();
            }
            librariesBuilder
                    .addAll(filterUniqueSharedLibraries(ruleContext, nativeDepsLibrary, linkParams.getLibraries()));
            ImmutableList<Artifact> libraries = librariesBuilder.build();

            if (!libraries.isEmpty()) {
                result.put(entry.getKey(), libraries);
            }
        }
        if (result.isEmpty()) {
            return NativeLibs.EMPTY;
        } else if (nativeDepsLibraryBasename == null) {
            return new NativeLibs(ImmutableMap.copyOf(result), null);
        } else {
            // The native deps name file must be the only file in its directory because ApkBuilder does
            // not have an option to add a particular file to the .apk, only one to add every file in a
            // particular directory.
            Artifact nativeDepsName = ruleContext.getUniqueDirectoryArtifact("nativedeps_filename",
                    nativeDepsFileName, ruleContext.getBinOrGenfilesDirectory());
            ruleContext.registerAction(
                    FileWriteAction.create(ruleContext, nativeDepsName, nativeDepsLibraryBasename, false));

            return new NativeLibs(ImmutableMap.copyOf(result), nativeDepsName);
        }
    }

    // Map from architecture (CPU folder to place the library in) to libraries for that CPU
    private final ImmutableMap<String, Iterable<Artifact>> nativeLibs;
    @Nullable
    private final Artifact nativeLibsName;

    @VisibleForTesting
    NativeLibs(ImmutableMap<String, Iterable<Artifact>> nativeLibs, @Nullable Artifact nativeLibsName) {
        this.nativeLibs = nativeLibs;
        this.nativeLibsName = nativeLibsName;
    }

    /**
     * Returns a map from the name of the architecture (CPU folder to place the library in) to the
     * set of libraries for that architecture.
     */
    public Map<String, Iterable<Artifact>> getMap() {
        return nativeLibs;
    }

    public ImmutableSet<Artifact> getAllNativeLibs() {
        ImmutableSet.Builder<Artifact> result = ImmutableSet.builder();

        for (Iterable<Artifact> libs : nativeLibs.values()) {
            result.addAll(libs);
        }

        return result.build();
    }

    public Artifact createApkBuilderSymlinks(RuleContext ruleContext) {
        Map<PathFragment, Artifact> symlinks = new LinkedHashMap<>();
        for (Map.Entry<String, Iterable<Artifact>> entry : nativeLibs.entrySet()) {
            String arch = entry.getKey();
            for (Artifact lib : entry.getValue()) {
                symlinks.put(new PathFragment(arch + "/" + lib.getExecPath().getBaseName()), lib);
            }
        }

        if (symlinks.isEmpty()) {
            return null;
        }

        Artifact inputManifest = AndroidBinary.getDxArtifact(ruleContext, "native_symlinks.manifest");
        ruleContext.registerAction(new SourceManifestAction.Builder(ruleContext.getWorkspaceName(),
                ManifestType.SOURCE_SYMLINKS, ruleContext.getActionOwner(), inputManifest,
                ruleContext.getConfiguration().legacyExternalRunfiles()).addRootSymlinks(symlinks).build());
        Artifact outputManifest = AndroidBinary.getDxArtifact(ruleContext, "native_symlinks/MANIFEST");
        Artifact nativeLibsMiddleman = ruleContext.getAnalysisEnvironment().getMiddlemanFactory()
                .createRunfilesMiddleman(ruleContext.getActionOwner(), null, symlinks.values(),
                        ruleContext.getConfiguration().getMiddlemanDirectory(ruleContext.getRule().getRepository()),
                        "android_native_libs");

        ruleContext.registerAction(
                new SymlinkTreeAction(ruleContext.getActionOwner(), inputManifest, nativeLibsMiddleman,
                        outputManifest, false, ruleContext.getConfiguration().getLocalShellEnvironment(),
                        ruleContext.getConfiguration().runfilesEnabled()));
        return outputManifest;
    }

    /**
     * Returns the artifact containing the names of the native libraries or null if it does not exist.
     *
     * <p>This artifact will be put in the root directory of the .apk and can be used to load the
     * libraries programmatically without knowing their names.
     */
    @Nullable
    public Artifact getName() {
        return nativeLibsName;
    }

    private static Iterable<Artifact> filterUniqueSharedLibraries(RuleContext ruleContext, Artifact linkedLibrary,
            NestedSet<? extends LinkerInput> libraries) {
        Map<String, Artifact> basenames = new HashMap<>();
        Set<Artifact> artifacts = new HashSet<>();
        if (linkedLibrary != null) {
            basenames.put(linkedLibrary.getExecPath().getBaseName(), linkedLibrary);
        }
        for (LinkerInput linkerInput : libraries) {
            String name = linkerInput.getArtifact().getFilename();
            if (!(CppFileTypes.SHARED_LIBRARY.matches(name)
                    || CppFileTypes.VERSIONED_SHARED_LIBRARY.matches(name))) {
                // This is not a shared library and will not be loaded by Android, so skip it.
                continue;
            }
            Artifact artifact = linkerInput.getOriginalLibraryArtifact();
            if (!artifacts.add(artifact)) {
                // We have already reached this library, e.g., through a different solib symlink.
                continue;
            }
            String basename = artifact.getExecPath().getBaseName();
            Artifact oldArtifact = basenames.put(basename, artifact);
            if (oldArtifact != null) {
                // There may be name collisions in the libraries which were provided, so
                // check for this at this step.
                ruleContext.ruleError("Each library in the transitive closure must have a unique basename to avoid "
                        + "name collisions when packaged into an apk, but two libraries have the basename '"
                        + basename + "': " + artifact.prettyPrint() + " and " + oldArtifact.prettyPrint()
                        + ((oldArtifact == linkedLibrary) ? " (the library compiled for this target)" : ""));
            }
        }
        return artifacts;
    }
}