/*
* Copyright 2015-present Facebook, Inc.
*
* 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.facebook.buck.android.relinker;
import com.facebook.buck.android.NdkCxxPlatforms;
import com.facebook.buck.cxx.CxxBuckConfig;
import com.facebook.buck.cxx.CxxLink;
import com.facebook.buck.cxx.Linker;
import com.facebook.buck.cxx.LinkerMapMode;
import com.facebook.buck.io.MorePaths;
import com.facebook.buck.model.BuildTargets;
import com.facebook.buck.model.InternalFlavor;
import com.facebook.buck.rules.AbstractBuildRuleWithResolver;
import com.facebook.buck.rules.AddToRuleKey;
import com.facebook.buck.rules.BuildContext;
import com.facebook.buck.rules.BuildRuleParams;
import com.facebook.buck.rules.BuildableContext;
import com.facebook.buck.rules.ExplicitBuildTargetSourcePath;
import com.facebook.buck.rules.OverrideScheduleRule;
import com.facebook.buck.rules.RuleScheduleInfo;
import com.facebook.buck.rules.SourcePath;
import com.facebook.buck.rules.SourcePathResolver;
import com.facebook.buck.rules.SourcePathRuleFinder;
import com.facebook.buck.rules.Tool;
import com.facebook.buck.rules.args.Arg;
import com.facebook.buck.rules.args.StringArg;
import com.facebook.buck.step.AbstractExecutionStep;
import com.facebook.buck.step.ExecutionContext;
import com.facebook.buck.step.Step;
import com.facebook.buck.step.StepExecutionResult;
import com.facebook.buck.step.fs.MakeCleanDirectoryStep;
import com.facebook.buck.util.ProcessExecutor;
import com.google.common.base.Charsets;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.Set;
import javax.annotation.Nullable;
class RelinkerRule extends AbstractBuildRuleWithResolver implements OverrideScheduleRule {
@AddToRuleKey private final ImmutableSortedSet<SourcePath> symbolsNeededPaths;
@AddToRuleKey private final NdkCxxPlatforms.TargetCpuType cpuType;
@AddToRuleKey private final SourcePath baseLibSourcePath;
@AddToRuleKey private final Tool objdump;
@AddToRuleKey private final ImmutableList<Arg> linkerArgs;
@AddToRuleKey @Nullable private final Linker linker;
private final BuildRuleParams buildRuleParams;
private final CxxBuckConfig cxxBuckConfig;
private final SourcePathResolver pathResolver;
public RelinkerRule(
BuildRuleParams buildRuleParams,
SourcePathResolver resolver,
SourcePathRuleFinder ruleFinder,
ImmutableSortedSet<SourcePath> symbolsNeededPaths,
NdkCxxPlatforms.TargetCpuType cpuType,
Tool objdump,
CxxBuckConfig cxxBuckConfig,
SourcePath baseLibSourcePath,
@Nullable Linker linker,
ImmutableList<Arg> linkerArgs) {
super(withDepsFromArgs(buildRuleParams, ruleFinder, linkerArgs), resolver);
this.pathResolver = resolver;
this.cpuType = cpuType;
this.objdump = objdump;
this.cxxBuckConfig = cxxBuckConfig;
this.linkerArgs = linkerArgs;
this.buildRuleParams = buildRuleParams;
this.symbolsNeededPaths = symbolsNeededPaths;
this.baseLibSourcePath = baseLibSourcePath;
this.linker = linker;
}
private static BuildRuleParams withDepsFromArgs(
BuildRuleParams params, SourcePathRuleFinder ruleFinder, ImmutableList<Arg> args) {
return params.copyAppendingExtraDeps(
Iterables.concat(Iterables.transform(args, arg -> arg.getDeps(ruleFinder))));
}
private static String getVersionScript(Set<String> needed, Set<String> provided) {
Set<String> keep =
new ImmutableSet.Builder<String>()
.addAll(Sets.intersection(needed, provided))
.addAll(
Sets.filter(
provided,
s -> {
if (s.contains("JNI_OnLoad")) {
return true;
}
if (s.contains("Java_")) {
return true;
}
return false;
}))
.build();
String res = "{\n";
if (!keep.isEmpty()) {
res += "global:\n";
}
for (String s : keep) {
res += " " + s + ";\n";
}
res += "local: *;\n};\n";
return res;
}
public SourcePath getLibFileSourcePath() {
return new ExplicitBuildTargetSourcePath(buildRuleParams.getBuildTarget(), getLibFilePath());
}
public SourcePath getSymbolsNeededPath() {
return new ExplicitBuildTargetSourcePath(
buildRuleParams.getBuildTarget(), getSymbolsNeededOutPath());
}
@Override
public ImmutableList<Step> getBuildSteps(
BuildContext context, final BuildableContext buildableContext) {
final ImmutableList.Builder<Step> relinkerSteps = ImmutableList.builder();
if (linker != null) {
ImmutableList<Arg> args =
ImmutableList.<Arg>builder()
.addAll(linkerArgs)
.add(StringArg.of("-Wl,--version-script=" + getRelativeVersionFilePath().toString()))
.build();
relinkerSteps.addAll(
new CxxLink(
buildRuleParams
.withAppendedFlavor(InternalFlavor.of("cxx-link"))
.withoutFlavor(LinkerMapMode.NO_LINKER_MAP.getFlavor()),
linker,
getLibFilePath(),
args,
cxxBuckConfig.getLinkScheduleInfo(),
cxxBuckConfig.shouldCacheLinks(),
/* thinLto */ false)
.getBuildSteps(context, buildableContext));
buildableContext.recordArtifact(getRelativeVersionFilePath());
}
buildableContext.recordArtifact(getSymbolsNeededOutPath());
return new ImmutableList.Builder<Step>()
.addAll(MakeCleanDirectoryStep.of(getProjectFilesystem(), getScratchDirPath()))
.add(
new AbstractExecutionStep("xdso-dce relinker") {
@Override
public StepExecutionResult execute(ExecutionContext context)
throws IOException, InterruptedException {
ImmutableSet<String> symbolsNeeded = readSymbolsNeeded();
if (linker == null) {
getProjectFilesystem().copyFile(getBaseLibPath(), getLibFilePath());
buildableContext.recordArtifact(getLibFilePath());
} else {
writeVersionScript(context.getProcessExecutor(), symbolsNeeded);
for (Step s : relinkerSteps.build()) {
StepExecutionResult executionResult = s.execute(context);
if (!executionResult.isSuccess()) {
return StepExecutionResult.ERROR;
}
}
}
writeSymbols(
getSymbolsNeededOutPath(),
Sets.union(
symbolsNeeded,
getSymbols(context.getProcessExecutor(), getLibFilePath()).undefined));
return StepExecutionResult.SUCCESS;
}
})
.build();
}
@Override
public SourcePath getSourcePathToOutput() {
return new ExplicitBuildTargetSourcePath(getBuildTarget(), getLibFilePath());
}
@Override
public RuleScheduleInfo getRuleScheduleInfo() {
return cxxBuckConfig.getLinkScheduleInfo().orElse(RuleScheduleInfo.DEFAULT);
}
private Path getScratchPath() {
// ld doesn't seem to like commas in the version script path so we construct one without commas.
Path path = BuildTargets.getScratchPath(getProjectFilesystem(), getBuildTarget(), "%s");
String dirname = path.getFileName().toString().replace(',', '.');
return path.getParent().resolve(dirname);
}
private Path getBaseLibPath() {
return pathResolver.getAbsolutePath(baseLibSourcePath);
}
private Path getScratchDirPath() {
return getScratchPath().resolve(cpuType.toString());
}
private Path getScratchFilePath(String suffix) {
return getScratchDirPath()
.resolve(MorePaths.getNameWithoutExtension(getBaseLibPath()) + suffix);
}
private Path getLibFilePath() {
return getScratchDirPath().resolve(getBaseLibPath().getFileName());
}
private Symbols getSymbols(ProcessExecutor executor, Path path)
throws IOException, InterruptedException {
return Symbols.getSymbols(executor, objdump, pathResolver, absolutify(path));
}
private Path getRelativeVersionFilePath() {
return getScratchFilePath("__version.exp");
}
private void writeVersionScript(ProcessExecutor executor, ImmutableSet<String> symbolsNeeded)
throws IOException, InterruptedException {
Symbols sym = getSymbols(executor, getBaseLibPath());
Set<String> defined = Sets.difference(sym.all, sym.undefined);
String versionScript = getVersionScript(symbolsNeeded, defined);
Files.write(
absolutify(getRelativeVersionFilePath()),
versionScript.getBytes(Charsets.UTF_8),
StandardOpenOption.CREATE);
}
private Path absolutify(Path p) {
return getProjectFilesystem().resolve(p);
}
private Path getSymbolsNeededOutPath() {
return getScratchFilePath(".symbols");
}
private void writeSymbols(Path dest, Set<String> symbols) throws IOException {
Files.write(
absolutify(dest),
ImmutableSortedSet.copyOf(symbols),
Charsets.UTF_8,
StandardOpenOption.CREATE);
}
private ImmutableSet<String> readSymbolsNeeded() throws IOException {
ImmutableSet.Builder<String> symbolsNeeded = ImmutableSet.builder();
for (SourcePath source : symbolsNeededPaths) {
symbolsNeeded.addAll(
Files.readAllLines(pathResolver.getAbsolutePath(source), Charsets.UTF_8));
}
return symbolsNeeded.build();
}
}