/* * 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.cxx; import com.facebook.buck.model.BuildTarget; import com.facebook.buck.model.BuildTargets; import com.facebook.buck.rules.AbstractBuildRule; import com.facebook.buck.rules.AddToRuleKey; import com.facebook.buck.rules.BuildContext; import com.facebook.buck.rules.BuildRule; import com.facebook.buck.rules.BuildRuleParams; import com.facebook.buck.rules.BuildRuleResolver; import com.facebook.buck.rules.BuildableContext; import com.facebook.buck.rules.ExplicitBuildTargetSourcePath; import com.facebook.buck.rules.SourcePath; import com.facebook.buck.rules.SourcePathRuleFinder; import com.facebook.buck.rules.Tool; import com.facebook.buck.shell.DefaultShellStep; import com.facebook.buck.shell.ShellStep; import com.facebook.buck.step.ExecutionContext; import com.facebook.buck.step.Step; import com.facebook.buck.step.fs.MkdirStep; import com.facebook.buck.step.fs.WriteFileStep; import com.facebook.buck.util.ProcessExecutor; import com.google.common.base.Charsets; import com.google.common.base.Suppliers; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSortedSet; import com.google.common.io.ByteSource; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.StringReader; import java.nio.file.Path; import java.util.LinkedHashSet; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.StreamSupport; /** * A {@link SymbolNameTool} implementation using a POSIX-compliant `nm` utility * (http://pubs.opengroup.org/onlinepubs/009696699/utilities/nm.html). */ public class PosixNmSymbolNameTool implements SymbolNameTool { private final Tool nm; public PosixNmSymbolNameTool(Tool nm) { this.nm = nm; } @Override public SourcePath createUndefinedSymbolsFile( BuildRuleParams baseParams, BuildRuleResolver ruleResolver, SourcePathRuleFinder ruleFinder, BuildTarget target, Iterable<? extends SourcePath> linkerInputs) { UndefinedSymbolsFile rule = ruleResolver.addToIndex( new UndefinedSymbolsFile( baseParams .withBuildTarget(target) .copyReplacingDeclaredAndExtraDeps( Suppliers.ofInstance( ImmutableSortedSet.<BuildRule>naturalOrder() .addAll(nm.getDeps(ruleFinder)) .addAll(ruleFinder.filterBuildRuleInputs(linkerInputs)) .build()), Suppliers.ofInstance(ImmutableSortedSet.of())), nm, linkerInputs)); return rule.getSourcePathToOutput(); } private static class UndefinedSymbolsFile extends AbstractBuildRule { @AddToRuleKey private final Tool nm; @AddToRuleKey private final Iterable<? extends SourcePath> inputs; public UndefinedSymbolsFile( BuildRuleParams buildRuleParams, Tool nm, Iterable<? extends SourcePath> inputs) { super(buildRuleParams); this.nm = nm; this.inputs = inputs; } private Path getUndefinedSymbolsPath() { return BuildTargets.getGenPath( getProjectFilesystem(), getBuildTarget(), "%s/undefined_symbols.txt"); } @Override public ImmutableList<Step> getBuildSteps( BuildContext context, final BuildableContext buildableContext) { final Path output = getUndefinedSymbolsPath(); // Cache the symbols file. buildableContext.recordArtifact(output); // Run `nm` on the inputs. final ShellStep shellStep = new DefaultShellStep( getProjectFilesystem().getRootPath(), ImmutableList.<String>builder() .addAll(nm.getCommandPrefix(context.getSourcePathResolver())) // Prepend all lines with the name of the input file to which it // corresponds. Added only to make parsing the output a bit easier. .add("-A") // Generate output in a portable output format. .add("-P") // Only list external symbols. .add("-g") // Only list undefined symbols. .add("-u") .addAll( StreamSupport.stream(inputs.spliterator(), false) .map(context.getSourcePathResolver()::getAbsolutePath) .map(Object::toString) .iterator()) .build(), nm.getEnvironment(context.getSourcePathResolver())) { @Override protected void addOptions( ExecutionContext context, ImmutableSet.Builder<ProcessExecutor.Option> options) { options.add(ProcessExecutor.Option.EXPECTING_STD_OUT); } }; // Parse the output from running `nm` and write all symbols to the symbol file. MkdirStep mkdirStep = MkdirStep.of(getProjectFilesystem(), output.getParent()); WriteFileStep writeFileStep = new WriteFileStep( getProjectFilesystem(), new ByteSource() { @Override public InputStream openStream() throws IOException { Set<String> symbols = new LinkedHashSet<>(); Pattern pattern = Pattern.compile("^\\S+: (?<name>\\S+) .*"); try (BufferedReader reader = new BufferedReader(new StringReader(shellStep.getStdout()))) { String line; while ((line = reader.readLine()) != null) { Matcher matcher = pattern.matcher(line); if (matcher.matches()) { symbols.add(matcher.group("name")); } } } StringBuilder builder = new StringBuilder(); for (String symbol : symbols) { builder.append(symbol); builder.append(System.lineSeparator()); } return new ByteArrayInputStream(builder.toString().getBytes(Charsets.UTF_8)); } }, output, /* executable */ true); return ImmutableList.of(shellStep, mkdirStep, writeFileStep); } @Override public SourcePath getSourcePathToOutput() { return new ExplicitBuildTargetSourcePath(getBuildTarget(), getUndefinedSymbolsPath()); } } }