* Capture reexported libraries, allowable clients, rpaths, shared cache eligibility. * Add support for select Xarch options. * Add diagnostics related to capturing these options. * Add support for verifying these attributes against what is encoded in the dylib.
812 lines
29 KiB
C++
812 lines
29 KiB
C++
//===-- Options.cpp -------------------------------------------------------===//
|
|
//
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "Options.h"
|
|
#include "clang/Basic/DiagnosticIDs.h"
|
|
#include "clang/Driver/Driver.h"
|
|
#include "clang/InstallAPI/FileList.h"
|
|
#include "clang/InstallAPI/HeaderFile.h"
|
|
#include "clang/InstallAPI/InstallAPIDiagnostic.h"
|
|
#include "llvm/BinaryFormat/Magic.h"
|
|
#include "llvm/Support/Program.h"
|
|
#include "llvm/TargetParser/Host.h"
|
|
#include "llvm/TextAPI/DylibReader.h"
|
|
#include "llvm/TextAPI/TextAPIError.h"
|
|
#include "llvm/TextAPI/TextAPIReader.h"
|
|
#include "llvm/TextAPI/TextAPIWriter.h"
|
|
|
|
using namespace llvm;
|
|
using namespace llvm::opt;
|
|
using namespace llvm::MachO;
|
|
|
|
namespace drv = clang::driver::options;
|
|
|
|
namespace clang {
|
|
namespace installapi {
|
|
|
|
/// Create prefix string literals used in InstallAPIOpts.td.
|
|
#define PREFIX(NAME, VALUE) \
|
|
static constexpr llvm::StringLiteral NAME##_init[] = VALUE; \
|
|
static constexpr llvm::ArrayRef<llvm::StringLiteral> NAME( \
|
|
NAME##_init, std::size(NAME##_init) - 1);
|
|
#include "InstallAPIOpts.inc"
|
|
#undef PREFIX
|
|
|
|
static constexpr const llvm::StringLiteral PrefixTable_init[] =
|
|
#define PREFIX_UNION(VALUES) VALUES
|
|
#include "InstallAPIOpts.inc"
|
|
#undef PREFIX_UNION
|
|
;
|
|
static constexpr const ArrayRef<StringLiteral>
|
|
PrefixTable(PrefixTable_init, std::size(PrefixTable_init) - 1);
|
|
|
|
/// Create table mapping all options defined in InstallAPIOpts.td.
|
|
static constexpr OptTable::Info InfoTable[] = {
|
|
#define OPTION(PREFIX, NAME, ID, KIND, GROUP, ALIAS, ALIASARGS, FLAGS, \
|
|
VISIBILITY, PARAM, HELPTEXT, HELPTEXTSFORVARIANTS, METAVAR, \
|
|
VALUES) \
|
|
{PREFIX, \
|
|
NAME, \
|
|
HELPTEXT, \
|
|
HELPTEXTSFORVARIANTS, \
|
|
METAVAR, \
|
|
OPT_##ID, \
|
|
Option::KIND##Class, \
|
|
PARAM, \
|
|
FLAGS, \
|
|
VISIBILITY, \
|
|
OPT_##GROUP, \
|
|
OPT_##ALIAS, \
|
|
ALIASARGS, \
|
|
VALUES},
|
|
#include "InstallAPIOpts.inc"
|
|
#undef OPTION
|
|
};
|
|
|
|
namespace {
|
|
|
|
/// \brief Create OptTable class for parsing actual command line arguments.
|
|
class DriverOptTable : public opt::PrecomputedOptTable {
|
|
public:
|
|
DriverOptTable() : PrecomputedOptTable(InfoTable, PrefixTable) {}
|
|
};
|
|
|
|
} // end anonymous namespace.
|
|
|
|
static llvm::opt::OptTable *createDriverOptTable() {
|
|
return new DriverOptTable();
|
|
}
|
|
|
|
bool Options::processDriverOptions(InputArgList &Args) {
|
|
// Handle inputs.
|
|
llvm::append_range(DriverOpts.FileLists,
|
|
Args.getAllArgValues(drv::OPT_INPUT));
|
|
|
|
// Handle output.
|
|
SmallString<PATH_MAX> OutputPath;
|
|
if (auto *Arg = Args.getLastArg(drv::OPT_o)) {
|
|
OutputPath = Arg->getValue();
|
|
if (OutputPath != "-")
|
|
FM->makeAbsolutePath(OutputPath);
|
|
DriverOpts.OutputPath = std::string(OutputPath);
|
|
}
|
|
if (DriverOpts.OutputPath.empty()) {
|
|
Diags->Report(diag::err_no_output_file);
|
|
return false;
|
|
}
|
|
|
|
// Do basic error checking first for mixing -target and -arch options.
|
|
auto *ArgArch = Args.getLastArgNoClaim(drv::OPT_arch);
|
|
auto *ArgTarget = Args.getLastArgNoClaim(drv::OPT_target);
|
|
auto *ArgTargetVariant =
|
|
Args.getLastArgNoClaim(drv::OPT_darwin_target_variant_triple);
|
|
if (ArgArch && (ArgTarget || ArgTargetVariant)) {
|
|
Diags->Report(clang::diag::err_drv_argument_not_allowed_with)
|
|
<< ArgArch->getAsString(Args)
|
|
<< (ArgTarget ? ArgTarget : ArgTargetVariant)->getAsString(Args);
|
|
return false;
|
|
}
|
|
|
|
auto *ArgMinTargetOS = Args.getLastArgNoClaim(drv::OPT_mtargetos_EQ);
|
|
if ((ArgTarget || ArgTargetVariant) && ArgMinTargetOS) {
|
|
Diags->Report(clang::diag::err_drv_cannot_mix_options)
|
|
<< ArgTarget->getAsString(Args) << ArgMinTargetOS->getAsString(Args);
|
|
return false;
|
|
}
|
|
|
|
// Capture target triples first.
|
|
if (ArgTarget) {
|
|
for (const Arg *A : Args.filtered(drv::OPT_target)) {
|
|
A->claim();
|
|
llvm::Triple TargetTriple(A->getValue());
|
|
Target TAPITarget = Target(TargetTriple);
|
|
if ((TAPITarget.Arch == AK_unknown) ||
|
|
(TAPITarget.Platform == PLATFORM_UNKNOWN)) {
|
|
Diags->Report(clang::diag::err_drv_unsupported_opt_for_target)
|
|
<< "installapi" << TargetTriple.str();
|
|
return false;
|
|
}
|
|
DriverOpts.Targets[TAPITarget] = TargetTriple;
|
|
}
|
|
}
|
|
|
|
DriverOpts.Verbose = Args.hasArgNoClaim(drv::OPT_v);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool Options::processInstallAPIXOptions(InputArgList &Args) {
|
|
for (arg_iterator It = Args.begin(), End = Args.end(); It != End; ++It) {
|
|
if ((*It)->getOption().matches(OPT_Xarch__)) {
|
|
if (!processXarchOption(Args, It))
|
|
return false;
|
|
}
|
|
}
|
|
// TODO: Add support for the all of the X* options installapi supports.
|
|
|
|
return true;
|
|
}
|
|
|
|
bool Options::processXarchOption(InputArgList &Args, arg_iterator Curr) {
|
|
Arg *CurrArg = *Curr;
|
|
Architecture Arch = getArchitectureFromName(CurrArg->getValue(0));
|
|
if (Arch == AK_unknown) {
|
|
Diags->Report(diag::err_drv_invalid_arch_name)
|
|
<< CurrArg->getAsString(Args);
|
|
return false;
|
|
}
|
|
|
|
auto NextIt = std::next(Curr);
|
|
if (NextIt == Args.end()) {
|
|
Diags->Report(diag::err_drv_missing_argument)
|
|
<< CurrArg->getAsString(Args) << 1;
|
|
return false;
|
|
}
|
|
|
|
// InstallAPI has a limited understanding of supported Xarch options.
|
|
// Currently this is restricted to linker inputs.
|
|
const Arg *NextArg = *NextIt;
|
|
switch (NextArg->getOption().getID()) {
|
|
case OPT_allowable_client:
|
|
case OPT_reexport_l:
|
|
case OPT_reexport_framework:
|
|
case OPT_reexport_library:
|
|
case OPT_rpath:
|
|
break;
|
|
default:
|
|
Diags->Report(diag::err_drv_invalid_argument_to_option)
|
|
<< NextArg->getAsString(Args) << CurrArg->getAsString(Args);
|
|
return false;
|
|
}
|
|
|
|
ArgToArchMap[NextArg] = Arch;
|
|
CurrArg->claim();
|
|
|
|
return true;
|
|
}
|
|
|
|
bool Options::processLinkerOptions(InputArgList &Args) {
|
|
// Handle required arguments.
|
|
if (const Arg *A = Args.getLastArg(drv::OPT_install__name))
|
|
LinkerOpts.InstallName = A->getValue();
|
|
if (LinkerOpts.InstallName.empty()) {
|
|
Diags->Report(diag::err_no_install_name);
|
|
return false;
|
|
}
|
|
|
|
// Defaulted or optional arguments.
|
|
if (auto *Arg = Args.getLastArg(drv::OPT_current__version))
|
|
LinkerOpts.CurrentVersion.parse64(Arg->getValue());
|
|
|
|
if (auto *Arg = Args.getLastArg(drv::OPT_compatibility__version))
|
|
LinkerOpts.CompatVersion.parse64(Arg->getValue());
|
|
|
|
if (auto *Arg = Args.getLastArg(drv::OPT_compatibility__version))
|
|
LinkerOpts.CompatVersion.parse64(Arg->getValue());
|
|
|
|
if (auto *Arg = Args.getLastArg(drv::OPT_umbrella))
|
|
LinkerOpts.ParentUmbrella = Arg->getValue();
|
|
|
|
LinkerOpts.IsDylib = Args.hasArg(drv::OPT_dynamiclib);
|
|
|
|
LinkerOpts.AppExtensionSafe = Args.hasFlag(
|
|
drv::OPT_fapplication_extension, drv::OPT_fno_application_extension,
|
|
/*Default=*/LinkerOpts.AppExtensionSafe);
|
|
|
|
if (::getenv("LD_NO_ENCRYPT") != nullptr)
|
|
LinkerOpts.AppExtensionSafe = true;
|
|
|
|
if (::getenv("LD_APPLICATION_EXTENSION_SAFE") != nullptr)
|
|
LinkerOpts.AppExtensionSafe = true;
|
|
|
|
// Capture library paths.
|
|
PathSeq LibraryPaths;
|
|
for (const Arg *A : Args.filtered(drv::OPT_L)) {
|
|
LibraryPaths.emplace_back(A->getValue());
|
|
A->claim();
|
|
}
|
|
|
|
if (!LibraryPaths.empty())
|
|
LinkerOpts.LibPaths = std::move(LibraryPaths);
|
|
|
|
return true;
|
|
}
|
|
|
|
// NOTE: Do not claim any arguments, as they will be passed along for CC1
|
|
// invocations.
|
|
bool Options::processFrontendOptions(InputArgList &Args) {
|
|
// Capture language mode.
|
|
if (auto *A = Args.getLastArgNoClaim(drv::OPT_x)) {
|
|
FEOpts.LangMode = llvm::StringSwitch<clang::Language>(A->getValue())
|
|
.Case("c", clang::Language::C)
|
|
.Case("c++", clang::Language::CXX)
|
|
.Case("objective-c", clang::Language::ObjC)
|
|
.Case("objective-c++", clang::Language::ObjCXX)
|
|
.Default(clang::Language::Unknown);
|
|
|
|
if (FEOpts.LangMode == clang::Language::Unknown) {
|
|
Diags->Report(clang::diag::err_drv_invalid_value)
|
|
<< A->getAsString(Args) << A->getValue();
|
|
return false;
|
|
}
|
|
}
|
|
for (auto *A : Args.filtered(drv::OPT_ObjC, drv::OPT_ObjCXX)) {
|
|
if (A->getOption().matches(drv::OPT_ObjC))
|
|
FEOpts.LangMode = clang::Language::ObjC;
|
|
else
|
|
FEOpts.LangMode = clang::Language::ObjCXX;
|
|
}
|
|
|
|
// Capture Sysroot.
|
|
if (const Arg *A = Args.getLastArgNoClaim(drv::OPT_isysroot)) {
|
|
SmallString<PATH_MAX> Path(A->getValue());
|
|
FM->makeAbsolutePath(Path);
|
|
if (!FM->getOptionalDirectoryRef(Path)) {
|
|
Diags->Report(diag::err_missing_sysroot) << Path;
|
|
return false;
|
|
}
|
|
FEOpts.ISysroot = std::string(Path);
|
|
} else if (FEOpts.ISysroot.empty()) {
|
|
// Mirror CLANG and obtain the isysroot from the SDKROOT environment
|
|
// variable, if it wasn't defined by the command line.
|
|
if (auto *Env = ::getenv("SDKROOT")) {
|
|
if (StringRef(Env) != "/" && llvm::sys::path::is_absolute(Env) &&
|
|
FM->getOptionalFileRef(Env))
|
|
FEOpts.ISysroot = Env;
|
|
}
|
|
}
|
|
|
|
// Capture system frameworks.
|
|
// TODO: Support passing framework paths per platform.
|
|
for (const Arg *A : Args.filtered(drv::OPT_iframework))
|
|
FEOpts.SystemFwkPaths.emplace_back(A->getValue());
|
|
|
|
// Capture framework paths.
|
|
PathSeq FrameworkPaths;
|
|
for (const Arg *A : Args.filtered(drv::OPT_F))
|
|
FrameworkPaths.emplace_back(A->getValue());
|
|
|
|
if (!FrameworkPaths.empty())
|
|
FEOpts.FwkPaths = std::move(FrameworkPaths);
|
|
|
|
// Add default framework/library paths.
|
|
PathSeq DefaultLibraryPaths = {"/usr/lib", "/usr/local/lib"};
|
|
PathSeq DefaultFrameworkPaths = {"/Library/Frameworks",
|
|
"/System/Library/Frameworks"};
|
|
|
|
for (const StringRef LibPath : DefaultLibraryPaths) {
|
|
SmallString<PATH_MAX> Path(FEOpts.ISysroot);
|
|
sys::path::append(Path, LibPath);
|
|
LinkerOpts.LibPaths.emplace_back(Path.str());
|
|
}
|
|
for (const StringRef FwkPath : DefaultFrameworkPaths) {
|
|
SmallString<PATH_MAX> Path(FEOpts.ISysroot);
|
|
sys::path::append(Path, FwkPath);
|
|
FEOpts.SystemFwkPaths.emplace_back(Path.str());
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool Options::addFilePaths(InputArgList &Args, PathSeq &Headers,
|
|
OptSpecifier ID) {
|
|
for (const StringRef Path : Args.getAllArgValues(ID)) {
|
|
if ((bool)FM->getDirectory(Path, /*CacheFailure=*/false)) {
|
|
auto InputHeadersOrErr = enumerateFiles(*FM, Path);
|
|
if (!InputHeadersOrErr) {
|
|
Diags->Report(diag::err_cannot_open_file)
|
|
<< Path << toString(InputHeadersOrErr.takeError());
|
|
return false;
|
|
}
|
|
// Sort headers to ensure deterministic behavior.
|
|
sort(*InputHeadersOrErr);
|
|
for (std::string &H : *InputHeadersOrErr)
|
|
Headers.emplace_back(std::move(H));
|
|
} else
|
|
Headers.emplace_back(Path);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
std::vector<const char *>
|
|
Options::processAndFilterOutInstallAPIOptions(ArrayRef<const char *> Args) {
|
|
std::unique_ptr<llvm::opt::OptTable> Table;
|
|
Table.reset(createDriverOptTable());
|
|
|
|
unsigned MissingArgIndex, MissingArgCount;
|
|
auto ParsedArgs = Table->ParseArgs(Args.slice(1), MissingArgIndex,
|
|
MissingArgCount, Visibility());
|
|
|
|
// Capture InstallAPI only driver options.
|
|
if (!processInstallAPIXOptions(ParsedArgs))
|
|
return {};
|
|
|
|
DriverOpts.Demangle = ParsedArgs.hasArg(OPT_demangle);
|
|
|
|
if (auto *A = ParsedArgs.getLastArg(OPT_filetype)) {
|
|
DriverOpts.OutFT = TextAPIWriter::parseFileType(A->getValue());
|
|
if (DriverOpts.OutFT == FileType::Invalid) {
|
|
Diags->Report(clang::diag::err_drv_invalid_value)
|
|
<< A->getAsString(ParsedArgs) << A->getValue();
|
|
return {};
|
|
}
|
|
}
|
|
|
|
if (const Arg *A = ParsedArgs.getLastArg(OPT_verify_mode_EQ)) {
|
|
DriverOpts.VerifyMode =
|
|
StringSwitch<VerificationMode>(A->getValue())
|
|
.Case("ErrorsOnly", VerificationMode::ErrorsOnly)
|
|
.Case("ErrorsAndWarnings", VerificationMode::ErrorsAndWarnings)
|
|
.Case("Pedantic", VerificationMode::Pedantic)
|
|
.Default(VerificationMode::Invalid);
|
|
|
|
if (DriverOpts.VerifyMode == VerificationMode::Invalid) {
|
|
Diags->Report(clang::diag::err_drv_invalid_value)
|
|
<< A->getAsString(ParsedArgs) << A->getValue();
|
|
return {};
|
|
}
|
|
}
|
|
|
|
if (const Arg *A = ParsedArgs.getLastArg(OPT_verify_against))
|
|
DriverOpts.DylibToVerify = A->getValue();
|
|
|
|
if (const Arg *A = ParsedArgs.getLastArg(OPT_dsym))
|
|
DriverOpts.DSYMPath = A->getValue();
|
|
|
|
DriverOpts.TraceLibraryLocation = ParsedArgs.hasArg(OPT_t);
|
|
|
|
// Linker options not handled by clang driver.
|
|
LinkerOpts.OSLibNotForSharedCache =
|
|
ParsedArgs.hasArg(OPT_not_for_dyld_shared_cache);
|
|
|
|
for (const Arg *A : ParsedArgs.filtered(OPT_allowable_client)) {
|
|
LinkerOpts.AllowableClients[A->getValue()] =
|
|
ArgToArchMap.count(A) ? ArgToArchMap[A] : ArchitectureSet();
|
|
A->claim();
|
|
}
|
|
|
|
for (const Arg *A : ParsedArgs.filtered(OPT_reexport_l)) {
|
|
LinkerOpts.ReexportedLibraries[A->getValue()] =
|
|
ArgToArchMap.count(A) ? ArgToArchMap[A] : ArchitectureSet();
|
|
A->claim();
|
|
}
|
|
|
|
for (const Arg *A : ParsedArgs.filtered(OPT_reexport_library)) {
|
|
LinkerOpts.ReexportedLibraryPaths[A->getValue()] =
|
|
ArgToArchMap.count(A) ? ArgToArchMap[A] : ArchitectureSet();
|
|
A->claim();
|
|
}
|
|
|
|
for (const Arg *A : ParsedArgs.filtered(OPT_reexport_framework)) {
|
|
LinkerOpts.ReexportedFrameworks[A->getValue()] =
|
|
ArgToArchMap.count(A) ? ArgToArchMap[A] : ArchitectureSet();
|
|
A->claim();
|
|
}
|
|
|
|
for (const Arg *A : ParsedArgs.filtered(OPT_rpath)) {
|
|
LinkerOpts.RPaths[A->getValue()] =
|
|
ArgToArchMap.count(A) ? ArgToArchMap[A] : ArchitectureSet();
|
|
A->claim();
|
|
}
|
|
|
|
// Handle exclude & extra header directories or files.
|
|
auto handleAdditionalInputArgs = [&](PathSeq &Headers,
|
|
clang::installapi::ID OptID) {
|
|
if (ParsedArgs.hasArgNoClaim(OptID))
|
|
Headers.clear();
|
|
return addFilePaths(ParsedArgs, Headers, OptID);
|
|
};
|
|
|
|
if (!handleAdditionalInputArgs(DriverOpts.ExtraPublicHeaders,
|
|
OPT_extra_public_header))
|
|
return {};
|
|
|
|
if (!handleAdditionalInputArgs(DriverOpts.ExtraPrivateHeaders,
|
|
OPT_extra_private_header))
|
|
return {};
|
|
if (!handleAdditionalInputArgs(DriverOpts.ExtraProjectHeaders,
|
|
OPT_extra_project_header))
|
|
return {};
|
|
|
|
if (!handleAdditionalInputArgs(DriverOpts.ExcludePublicHeaders,
|
|
OPT_exclude_public_header))
|
|
return {};
|
|
if (!handleAdditionalInputArgs(DriverOpts.ExcludePrivateHeaders,
|
|
OPT_exclude_private_header))
|
|
return {};
|
|
if (!handleAdditionalInputArgs(DriverOpts.ExcludeProjectHeaders,
|
|
OPT_exclude_project_header))
|
|
return {};
|
|
|
|
// Handle umbrella headers.
|
|
if (const Arg *A = ParsedArgs.getLastArg(OPT_public_umbrella_header))
|
|
DriverOpts.PublicUmbrellaHeader = A->getValue();
|
|
|
|
if (const Arg *A = ParsedArgs.getLastArg(OPT_private_umbrella_header))
|
|
DriverOpts.PrivateUmbrellaHeader = A->getValue();
|
|
|
|
if (const Arg *A = ParsedArgs.getLastArg(OPT_project_umbrella_header))
|
|
DriverOpts.ProjectUmbrellaHeader = A->getValue();
|
|
|
|
/// Any unclaimed arguments should be forwarded to the clang driver.
|
|
std::vector<const char *> ClangDriverArgs(ParsedArgs.size());
|
|
for (const Arg *A : ParsedArgs) {
|
|
if (A->isClaimed())
|
|
continue;
|
|
llvm::copy(A->getValues(), std::back_inserter(ClangDriverArgs));
|
|
}
|
|
return ClangDriverArgs;
|
|
}
|
|
|
|
Options::Options(DiagnosticsEngine &Diag, FileManager *FM,
|
|
ArrayRef<const char *> Args, const StringRef ProgName)
|
|
: Diags(&Diag), FM(FM) {
|
|
|
|
// First process InstallAPI specific options.
|
|
auto DriverArgs = processAndFilterOutInstallAPIOptions(Args);
|
|
if (Diags->hasErrorOccurred())
|
|
return;
|
|
|
|
// Set up driver to parse remaining input arguments.
|
|
clang::driver::Driver Driver(ProgName, llvm::sys::getDefaultTargetTriple(),
|
|
*Diags, "clang installapi tool");
|
|
auto TargetAndMode =
|
|
clang::driver::ToolChain::getTargetAndModeFromProgramName(ProgName);
|
|
Driver.setTargetAndMode(TargetAndMode);
|
|
bool HasError = false;
|
|
llvm::opt::InputArgList ArgList =
|
|
Driver.ParseArgStrings(DriverArgs, /*UseDriverMode=*/true, HasError);
|
|
if (HasError)
|
|
return;
|
|
Driver.setCheckInputsExist(false);
|
|
|
|
if (!processDriverOptions(ArgList))
|
|
return;
|
|
|
|
if (!processLinkerOptions(ArgList))
|
|
return;
|
|
|
|
if (!processFrontendOptions(ArgList))
|
|
return;
|
|
|
|
// After all InstallAPI necessary arguments have been collected. Go back and
|
|
// assign values that were unknown before the clang driver opt table was used.
|
|
ArchitectureSet AllArchs;
|
|
llvm::for_each(DriverOpts.Targets,
|
|
[&AllArchs](const auto &T) { AllArchs.set(T.first.Arch); });
|
|
auto assignDefaultLibAttrs = [&AllArchs](LibAttrs &Attrs) {
|
|
for (StringMapEntry<ArchitectureSet> &Entry : Attrs)
|
|
if (Entry.getValue().empty())
|
|
Entry.setValue(AllArchs);
|
|
};
|
|
assignDefaultLibAttrs(LinkerOpts.AllowableClients);
|
|
assignDefaultLibAttrs(LinkerOpts.ReexportedFrameworks);
|
|
assignDefaultLibAttrs(LinkerOpts.ReexportedLibraries);
|
|
assignDefaultLibAttrs(LinkerOpts.ReexportedLibraryPaths);
|
|
assignDefaultLibAttrs(LinkerOpts.RPaths);
|
|
|
|
/// Force cc1 options that should always be on.
|
|
FrontendArgs = {"-fsyntax-only", "-Wprivate-extern"};
|
|
|
|
/// Any unclaimed arguments should be handled by invoking the clang frontend.
|
|
for (const Arg *A : ArgList) {
|
|
if (A->isClaimed())
|
|
continue;
|
|
FrontendArgs.emplace_back(A->getSpelling());
|
|
llvm::copy(A->getValues(), std::back_inserter(FrontendArgs));
|
|
}
|
|
}
|
|
|
|
static const Regex Rule("(.+)/(.+)\\.framework/");
|
|
static StringRef getFrameworkNameFromInstallName(StringRef InstallName) {
|
|
SmallVector<StringRef, 3> Match;
|
|
Rule.match(InstallName, &Match);
|
|
if (Match.empty())
|
|
return "";
|
|
return Match.back();
|
|
}
|
|
|
|
static Expected<std::unique_ptr<InterfaceFile>>
|
|
getInterfaceFile(const StringRef Filename) {
|
|
ErrorOr<std::unique_ptr<MemoryBuffer>> BufferOrErr =
|
|
MemoryBuffer::getFile(Filename);
|
|
if (auto Err = BufferOrErr.getError())
|
|
return errorCodeToError(std::move(Err));
|
|
|
|
auto Buffer = std::move(*BufferOrErr);
|
|
std::unique_ptr<InterfaceFile> IF;
|
|
switch (identify_magic(Buffer->getBuffer())) {
|
|
case file_magic::macho_dynamically_linked_shared_lib:
|
|
LLVM_FALLTHROUGH;
|
|
case file_magic::macho_dynamically_linked_shared_lib_stub:
|
|
LLVM_FALLTHROUGH;
|
|
case file_magic::macho_universal_binary:
|
|
return DylibReader::get(Buffer->getMemBufferRef());
|
|
break;
|
|
case file_magic::tapi_file:
|
|
return TextAPIReader::get(Buffer->getMemBufferRef());
|
|
default:
|
|
return make_error<TextAPIError>(TextAPIErrorCode::InvalidInputFormat,
|
|
"unsupported library file format");
|
|
}
|
|
llvm_unreachable("unexpected failure in getInterface");
|
|
}
|
|
|
|
std::pair<LibAttrs, ReexportedInterfaces> Options::getReexportedLibraries() {
|
|
LibAttrs Reexports;
|
|
ReexportedInterfaces ReexportIFs;
|
|
auto AccumulateReexports = [&](StringRef Path, const ArchitectureSet &Archs) {
|
|
auto ReexportIFOrErr = getInterfaceFile(Path);
|
|
if (!ReexportIFOrErr)
|
|
return false;
|
|
std::unique_ptr<InterfaceFile> Reexport = std::move(*ReexportIFOrErr);
|
|
StringRef InstallName = Reexport->getInstallName();
|
|
assert(!InstallName.empty() && "Parse error for install name");
|
|
Reexports.insert({InstallName, Archs});
|
|
ReexportIFs.emplace_back(std::move(*Reexport));
|
|
return true;
|
|
};
|
|
|
|
// Populate search paths by looking at user paths before system ones.
|
|
PathSeq FwkSearchPaths(FEOpts.FwkPaths.begin(), FEOpts.FwkPaths.end());
|
|
// FIXME: System framework paths need to reset if installapi is invoked with
|
|
// different platforms.
|
|
FwkSearchPaths.insert(FwkSearchPaths.end(), FEOpts.SystemFwkPaths.begin(),
|
|
FEOpts.SystemFwkPaths.end());
|
|
|
|
for (const StringMapEntry<ArchitectureSet> &Lib :
|
|
LinkerOpts.ReexportedLibraries) {
|
|
std::string Name = "lib" + Lib.getKey().str() + ".dylib";
|
|
std::string Path = findLibrary(Name, *FM, {}, LinkerOpts.LibPaths, {});
|
|
if (Path.empty()) {
|
|
Diags->Report(diag::err_cannot_find_reexport) << true << Lib.getKey();
|
|
return {};
|
|
}
|
|
if (DriverOpts.TraceLibraryLocation)
|
|
errs() << Path << "\n";
|
|
|
|
AccumulateReexports(Path, Lib.getValue());
|
|
}
|
|
|
|
for (const StringMapEntry<ArchitectureSet> &Lib :
|
|
LinkerOpts.ReexportedLibraryPaths)
|
|
AccumulateReexports(Lib.getKey(), Lib.getValue());
|
|
|
|
for (const StringMapEntry<ArchitectureSet> &Lib :
|
|
LinkerOpts.ReexportedFrameworks) {
|
|
std::string Name = (Lib.getKey() + ".framework/" + Lib.getKey()).str();
|
|
std::string Path = findLibrary(Name, *FM, FwkSearchPaths, {}, {});
|
|
if (Path.empty()) {
|
|
Diags->Report(diag::err_cannot_find_reexport) << false << Lib.getKey();
|
|
return {};
|
|
}
|
|
if (DriverOpts.TraceLibraryLocation)
|
|
errs() << Path << "\n";
|
|
|
|
AccumulateReexports(Path, Lib.getValue());
|
|
}
|
|
|
|
return {std::move(Reexports), std::move(ReexportIFs)};
|
|
}
|
|
|
|
InstallAPIContext Options::createContext() {
|
|
InstallAPIContext Ctx;
|
|
Ctx.FM = FM;
|
|
Ctx.Diags = Diags;
|
|
|
|
// InstallAPI requires two level namespacing.
|
|
Ctx.BA.TwoLevelNamespace = true;
|
|
|
|
Ctx.BA.InstallName = LinkerOpts.InstallName;
|
|
Ctx.BA.CurrentVersion = LinkerOpts.CurrentVersion;
|
|
Ctx.BA.CompatVersion = LinkerOpts.CompatVersion;
|
|
Ctx.BA.AppExtensionSafe = LinkerOpts.AppExtensionSafe;
|
|
Ctx.BA.ParentUmbrella = LinkerOpts.ParentUmbrella;
|
|
Ctx.BA.OSLibNotForSharedCache = LinkerOpts.OSLibNotForSharedCache;
|
|
Ctx.FT = DriverOpts.OutFT;
|
|
Ctx.OutputLoc = DriverOpts.OutputPath;
|
|
Ctx.LangMode = FEOpts.LangMode;
|
|
|
|
auto [Reexports, ReexportedIFs] = getReexportedLibraries();
|
|
if (Diags->hasErrorOccurred())
|
|
return Ctx;
|
|
Ctx.Reexports = Reexports;
|
|
|
|
// Attempt to find umbrella headers by capturing framework name.
|
|
StringRef FrameworkName;
|
|
if (!LinkerOpts.IsDylib)
|
|
FrameworkName = getFrameworkNameFromInstallName(LinkerOpts.InstallName);
|
|
|
|
// Process inputs.
|
|
for (const std::string &ListPath : DriverOpts.FileLists) {
|
|
auto Buffer = FM->getBufferForFile(ListPath);
|
|
if (auto Err = Buffer.getError()) {
|
|
Diags->Report(diag::err_cannot_open_file) << ListPath << Err.message();
|
|
return Ctx;
|
|
}
|
|
if (auto Err = FileListReader::loadHeaders(std::move(Buffer.get()),
|
|
Ctx.InputHeaders)) {
|
|
Diags->Report(diag::err_cannot_open_file) << ListPath << std::move(Err);
|
|
return Ctx;
|
|
}
|
|
}
|
|
// After initial input has been processed, add any extra headers.
|
|
auto HandleExtraHeaders = [&](PathSeq &Headers, HeaderType Type) -> bool {
|
|
assert(Type != HeaderType::Unknown && "Missing header type.");
|
|
for (const StringRef Path : Headers) {
|
|
if (!FM->getOptionalFileRef(Path)) {
|
|
Diags->Report(diag::err_no_such_header_file) << Path << (unsigned)Type;
|
|
return false;
|
|
}
|
|
SmallString<PATH_MAX> FullPath(Path);
|
|
FM->makeAbsolutePath(FullPath);
|
|
|
|
auto IncludeName = createIncludeHeaderName(FullPath);
|
|
Ctx.InputHeaders.emplace_back(
|
|
FullPath, Type, IncludeName.has_value() ? *IncludeName : "");
|
|
Ctx.InputHeaders.back().setExtra();
|
|
}
|
|
return true;
|
|
};
|
|
|
|
if (!HandleExtraHeaders(DriverOpts.ExtraPublicHeaders, HeaderType::Public) ||
|
|
!HandleExtraHeaders(DriverOpts.ExtraPrivateHeaders,
|
|
HeaderType::Private) ||
|
|
!HandleExtraHeaders(DriverOpts.ExtraProjectHeaders, HeaderType::Project))
|
|
return Ctx;
|
|
|
|
// After all headers have been added, consider excluded headers.
|
|
std::vector<std::unique_ptr<HeaderGlob>> ExcludedHeaderGlobs;
|
|
std::set<FileEntryRef> ExcludedHeaderFiles;
|
|
auto ParseGlobs = [&](const PathSeq &Paths, HeaderType Type) {
|
|
assert(Type != HeaderType::Unknown && "Missing header type.");
|
|
for (const StringRef Path : Paths) {
|
|
auto Glob = HeaderGlob::create(Path, Type);
|
|
if (Glob)
|
|
ExcludedHeaderGlobs.emplace_back(std::move(Glob.get()));
|
|
else {
|
|
consumeError(Glob.takeError());
|
|
if (auto File = FM->getFileRef(Path))
|
|
ExcludedHeaderFiles.emplace(*File);
|
|
else {
|
|
Diags->Report(diag::err_no_such_header_file)
|
|
<< Path << (unsigned)Type;
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
};
|
|
|
|
if (!ParseGlobs(DriverOpts.ExcludePublicHeaders, HeaderType::Public) ||
|
|
!ParseGlobs(DriverOpts.ExcludePrivateHeaders, HeaderType::Private) ||
|
|
!ParseGlobs(DriverOpts.ExcludeProjectHeaders, HeaderType::Project))
|
|
return Ctx;
|
|
|
|
for (HeaderFile &Header : Ctx.InputHeaders) {
|
|
for (auto &Glob : ExcludedHeaderGlobs)
|
|
if (Glob->match(Header))
|
|
Header.setExcluded();
|
|
}
|
|
if (!ExcludedHeaderFiles.empty()) {
|
|
for (HeaderFile &Header : Ctx.InputHeaders) {
|
|
auto FileRef = FM->getFileRef(Header.getPath());
|
|
if (!FileRef)
|
|
continue;
|
|
if (ExcludedHeaderFiles.count(*FileRef))
|
|
Header.setExcluded();
|
|
}
|
|
}
|
|
// Report if glob was ignored.
|
|
for (const auto &Glob : ExcludedHeaderGlobs)
|
|
if (!Glob->didMatch())
|
|
Diags->Report(diag::warn_glob_did_not_match) << Glob->str();
|
|
|
|
// Mark any explicit or inferred umbrella headers. If one exists, move
|
|
// that to the beginning of the input headers.
|
|
auto MarkandMoveUmbrellaInHeaders = [&](llvm::Regex &Regex,
|
|
HeaderType Type) -> bool {
|
|
auto It = find_if(Ctx.InputHeaders, [&Regex, Type](const HeaderFile &H) {
|
|
return (H.getType() == Type) && Regex.match(H.getPath());
|
|
});
|
|
|
|
if (It == Ctx.InputHeaders.end())
|
|
return false;
|
|
It->setUmbrellaHeader();
|
|
|
|
// Because there can be an umbrella header per header type,
|
|
// find the first non umbrella header to swap position with.
|
|
auto BeginPos = find_if(Ctx.InputHeaders, [](const HeaderFile &H) {
|
|
return !H.isUmbrellaHeader();
|
|
});
|
|
if (BeginPos != Ctx.InputHeaders.end() && BeginPos < It)
|
|
std::swap(*BeginPos, *It);
|
|
return true;
|
|
};
|
|
|
|
auto FindUmbrellaHeader = [&](StringRef HeaderPath, HeaderType Type) -> bool {
|
|
assert(Type != HeaderType::Unknown && "Missing header type.");
|
|
if (!HeaderPath.empty()) {
|
|
auto EscapedString = Regex::escape(HeaderPath);
|
|
Regex UmbrellaRegex(EscapedString);
|
|
if (!MarkandMoveUmbrellaInHeaders(UmbrellaRegex, Type)) {
|
|
Diags->Report(diag::err_no_such_umbrella_header_file)
|
|
<< HeaderPath << (unsigned)Type;
|
|
return false;
|
|
}
|
|
} else if (!FrameworkName.empty() && (Type != HeaderType::Project)) {
|
|
auto UmbrellaName = "/" + Regex::escape(FrameworkName);
|
|
if (Type == HeaderType::Public)
|
|
UmbrellaName += "\\.h";
|
|
else
|
|
UmbrellaName += "[_]?Private\\.h";
|
|
Regex UmbrellaRegex(UmbrellaName);
|
|
MarkandMoveUmbrellaInHeaders(UmbrellaRegex, Type);
|
|
}
|
|
return true;
|
|
};
|
|
if (!FindUmbrellaHeader(DriverOpts.PublicUmbrellaHeader,
|
|
HeaderType::Public) ||
|
|
!FindUmbrellaHeader(DriverOpts.PrivateUmbrellaHeader,
|
|
HeaderType::Private) ||
|
|
!FindUmbrellaHeader(DriverOpts.ProjectUmbrellaHeader,
|
|
HeaderType::Project))
|
|
return Ctx;
|
|
|
|
// Parse binary dylib and initialize verifier.
|
|
if (DriverOpts.DylibToVerify.empty()) {
|
|
Ctx.Verifier = std::make_unique<DylibVerifier>();
|
|
return Ctx;
|
|
}
|
|
|
|
auto Buffer = FM->getBufferForFile(DriverOpts.DylibToVerify);
|
|
if (auto Err = Buffer.getError()) {
|
|
Diags->Report(diag::err_cannot_open_file)
|
|
<< DriverOpts.DylibToVerify << Err.message();
|
|
return Ctx;
|
|
}
|
|
|
|
DylibReader::ParseOption PO;
|
|
PO.Undefineds = false;
|
|
Expected<Records> Slices =
|
|
DylibReader::readFile((*Buffer)->getMemBufferRef(), PO);
|
|
if (auto Err = Slices.takeError()) {
|
|
Diags->Report(diag::err_cannot_open_file)
|
|
<< DriverOpts.DylibToVerify << std::move(Err);
|
|
return Ctx;
|
|
}
|
|
|
|
Ctx.Verifier = std::make_unique<DylibVerifier>(
|
|
std::move(*Slices), std::move(ReexportedIFs), Diags,
|
|
DriverOpts.VerifyMode, DriverOpts.Demangle, DriverOpts.DSYMPath);
|
|
return Ctx;
|
|
}
|
|
|
|
} // namespace installapi
|
|
} // namespace clang
|