osxcross/wrapper/main.cpp
Thomas Pöchtrager 5afdf2b471 Wrapper changes:
* Support for generating fat object files with gcc and '-foc-use-gcc-libstdc++'
  has been removed.

  This feature was not 100% correctly implemented; using multiple source files
  did not work, i.e.: 'o32-g++ -m32 -m64 a.cpp b.cpp' would have failed;
  And I refuse to implement that, instead I am removing all source file handling
  from the wrapper with this commit for simplicity.

  This feature should be implemented in the gcc driver instead.

  This does NOT affect clang's fat object file support, which is implemented in
  clang's darwin driver.

* '-oc-use-gcc-lib' has been renamed to '-foc-use-gcc-libstdc++'.

* Added support for '-stdc++' and '-gstdc++' compiler "shortcuts"

  o32-clang++ --> uses libstdc++ for <= 10.8 and libc++ for >= 10.9

  o32-clang++-libc++ --> uses the SDK's libc++
  o32-clang++-stdc++ --> uses the SDK's libstdc++
  o32-clang++-gstdc++ --> uses gcc's (build_gcc.sh) libstdc++

* Entirely rewrote the command line parser; the previous one wasn't very
  readable.

* Minor Readme Updates

* Added unit tests

* Removed OSXCROSS_C_STANDARD / OSXCROSS_CXX_STANDARD support

  I am no longer parsing -std=, so this feature has to be dropped.
  Setting the language standard via an env variable isn't a good idea anyway.

* Removed unneeded stuff

Other Changes:

* Version bump to 0.11
2015-08-22 23:15:27 +02:00

557 lines
14 KiB
C++

/***********************************************************************
* OSXCross Compiler Wrapper *
* Copyright (C) 2014, 2015 by Thomas Poechtrager *
* t.poechtrager@gmail.com *
* *
* This program is free software; you can redistribute it and/or *
* modify it under the terms of the GNU General Public License *
* as published by the Free Software Foundation; either version 2 *
* of the License, or (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the Free Software *
* Foundation, Inc., *
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
***********************************************************************/
/*
* Debug messages can be enabled by setting 'OCDEBUG' (ENV) to >= 1.
*/
#include "compat.h"
#include <vector>
#include <string>
#include <sstream>
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <cstdio>
#include <climits>
#include <cassert>
#ifndef _WIN32
#include <unistd.h>
#include <sys/wait.h>
#endif
#include "tools.h"
#include "target.h"
#include "progs.h"
using namespace tools;
using namespace target;
namespace {
int unittest = 0;
void warnExtension(const char *extension) {
static bool noextwarnings = !!getenv("OSXCROSS_NO_EXTENSION_WARNINGS");
if (noextwarnings)
return;
warn << extension << " is an osxcross extension" << warn.endl();
warninfo << "you can silence this warning via "
<< "'OSXCROSS_NO_EXTENSION_WARNINGS=1' (env)" << warninfo.endl();
}
__attribute__((unused))
void warnDeprecated(const char *flag, const char *replacement = nullptr) {
if (replacement)
warn << flag << " is deprecated; "
<< "please use " << replacement << " instead"
<< warn.endl();
else
warn << flag << " is deprecated and will be "
<< "removed soon" << warn.endl();
}
//
// Command Line Options
//
namespace commandopts {
typedef bool (*optFun)(Target &target, const char *opt, const char *val,
char **);
bool versionmin(Target &target, const char *, const char *val, char **) {
target.OSNum = parseOSVersion(val);
if (target.OSNum != val)
warn << "'-mmacosx-version-min=' (" << target.OSNum.Str()
<< " != " << val << ")" << warn.endl();
return true;
}
bool arch(Target &target, const char *opt, const char *val, char **) {
Arch arch;
if (!strcmp(opt, "-arch")) {
arch = parseArch(val);
if (arch == Arch::unknown)
warn << "'-arch': unknown architecture '" << val << "'"
<< warn.endl();
const char *name = getArchName(arch);
if (strcmp(val, name))
warn << "'-arch': '" << val << "' != '" << name << "'" << warn.endl();
} else {
if (!strcmp(opt, "-m16") || !strcmp(opt, "-mx32")) {
err << "'" << opt << "' is not supported" << err.endl();
return false;
} else if (!strcmp(opt, "-m32")) {
arch = Arch::i386;
} else if (!strcmp(opt, "-m64")) {
arch = Arch::x86_64;
} else {
__builtin_unreachable();
}
}
if (target.isClang())
target.addArch(arch);
else
target.arch = arch;
return true;
}
bool stdlib(Target &target, const char *, const char *val, char **) {
if (target.isGCC())
warnExtension("'-stdlib='");
size_t i = 0;
for (auto stdlibname : StdLibNames) {
if (!strcmp(val, stdlibname)) {
target.stdlib = static_cast<StdLib>(i);
break;
}
++i;
}
if (i == (sizeof(StdLibNames) / sizeof(StdLibNames[0]))) {
err << "value of '-stdlib=' must be ";
for (size_t j = 0; j < i; ++j) {
err << "'" << StdLibNames[j] << "'";
if (j == i - 2)
err << " or ";
else if (j < i - 2)
err << ", ";
}
err << err.endl();
return false;
}
return true;
}
bool outputname(Target &target, const char *, const char *val, char **) {
target.outputname = val;
return true;
}
bool usegcclibstdcxx(Target &target, const char *, const char *, char **) {
target.stdlib = StdLib::libstdcxx;
target.usegcclibs = true;
return true;
}
bool runprog(Target &target, const char *, const char *progname, char **cargs) {
auto *prog = program::getprog(progname);
if (!prog)
exit(EXIT_FAILURE);
std::vector<char *> args;
args.push_back(const_cast<char *>(progname));
while (*cargs)
args.push_back(*cargs++);
args.push_back(nullptr);
(*prog)(args.size() - 1, args.data(), target);
}
bool checkincludepath(Target &, const char *opt, const char *path, char **) {
#ifndef __APPLE__
constexpr const char *DangerousIncludePaths[] = { "/usr/include",
"/usr/local/include" };
static bool noinccheck = !!getenv("OSXCROSS_NO_INCLUDE_PATH_WARNINGS");
if (noinccheck)
return true;
#ifndef _WIN32
char buf[PATH_MAX + 1];
const char *rpath = realpath(path, buf);
if (!rpath)
rpath = path;
#else
const char *rpath = path;
#endif
for (const char *dpath : DangerousIncludePaths) {
if (!strncmp(rpath, dpath, strlen(dpath))) {
warn << "possibly dangerous include path specified: '" << opt << " "
<< path << "'";
if (strcmp(path, rpath))
warn << " (" << rpath << ")";
warn << warn.endl();
warninfo << "you can silence this warning via "
<< "'OSXCROSS_NO_INCLUDE_PATH_WARNINGS=1' (env)"
<< warninfo.endl();
}
}
#else
(void)opt;
(void)path;
#endif
return true;
}
constexpr struct Opt {
const char *name;
const size_t namelen;
const optFun fun;
const bool valrequired;
const bool pusharg;
const char *valseparator;
const size_t valseparatorlen;
constexpr Opt(const char *name, optFun fun, const bool valrequired = false,
const bool pusharg = false, const char *valseparator = nullptr)
: name(name), namelen(slen(name)), fun(fun),
valrequired(valrequired), pusharg(pusharg),
valseparator(valseparator),
valseparatorlen(valseparator ? slen(valseparator) : 0) {}
} opts[] = {
{"-mmacosx-version-min", versionmin, true, false, "="},
{"-stdlib", stdlib, true, false, "="},
{"-arch", arch, true},
{"-m16", arch},
{"-m32", arch},
{"-mx32", arch},
{"-m64", arch},
{"-o", outputname, true, true},
{"-foc-use-gcc-libstdc++", usegcclibstdcxx},
{"-foc-run-prog", runprog, true, false, "="}, // for internal use only
{"-isystem", checkincludepath, true, true},
{"-icxx-isystem", checkincludepath, true, true},
{"-cxx-isystem", checkincludepath, true, true},
{"-I", checkincludepath, true, true},
};
bool parse(int argc, char **argv, Target &target) {
target.args.reserve(argc);
if (char *p = getenv("MACOSX_DEPLOYMENT_TARGET")) {
target.OSNum = parseOSVersion(p);
unsetenv("MACOSX_DEPLOYMENT_TARGET");
}
for (int i = 1; i < argc; ++i) {
char *arg = argv[i];
if (*arg != '-') {
target.args.push_back(arg);
continue;
}
bool pusharg = true;
int j = i;
for (const Opt &opt : opts) {
if (strncmp(arg, opt.name, opt.namelen))
continue;
pusharg = opt.pusharg;
const char *val = nullptr;
if (opt.valrequired) {
val = arg + opt.namelen;
if (opt.valseparator &&
strncmp(val, opt.valseparator, opt.valseparatorlen)) {
err << "expected '" << opt.name << opt.valseparator << "<val>' "
<< "instead of '" << arg << "'" << err.endl();
return false;
} else {
val += opt.valseparatorlen;
}
if (!opt.valseparator && !*val && i < argc - 1)
val = argv[++i];
if (!*val) {
err << "missing argument for '" << opt.name << "'" << err.endl();
return false;
}
}
if (opt.fun && !opt.fun(target, opt.name, val, &argv[i + 1]))
return false;
break;
}
if (pusharg) {
for (; j <= i; ++j)
target.args.push_back(argv[j]);
}
}
return true;
}
} // namespace commandopts
void detectCXXLib(Target &target) {
if (target.compilername.size() <= 7)
return;
StdLib prevstdlib = target.stdlib;
if (endsWith(target.compilername, "-stdc++")) {
target.stdlib = StdLib::libstdcxx;
target.compilername.resize(target.compilername.size() - 7);
} else if (endsWith(target.compilername, "-gstdc++")) {
target.stdlib = StdLib::libstdcxx;
target.usegcclibs = true;
target.compilername.resize(target.compilername.size() - 8);
} else if (endsWith(target.compilername, "-libc++")) {
target.stdlib = StdLib::libcxx;
target.compilername.resize(target.compilername.size() - 7);
}
if (prevstdlib != StdLib::unset && prevstdlib != target.stdlib)
warn << "ignoring '-stdlib=" << getStdLibString(prevstdlib) << "'"
<< warn.endl();
}
//
// detectTarget():
// detect target and setup invocation command
//
bool detectTarget(int argc, char **argv, Target &target) {
const char *cmd = argv[0];
const char *p = strrchr(cmd, '/');
size_t len;
size_t i = 0;
if (p)
cmd = &p[1];
if (auto *prog = program::getprog(cmd))
(*prog)(argc, argv, target);
// -> x86_64 <- -apple-darwin13
p = strchr(cmd, '-');
len = (p ? p : cmd) - cmd;
for (auto arch : ArchNames) {
++i;
if (!strncmp(cmd, arch, len)) {
target.arch = static_cast<Arch>(i - 1);
cmd += len;
if (*cmd++ != '-')
return false;
if (strncmp(cmd, "apple-", 6))
return false;
cmd += 6;
if (strncmp(cmd, "darwin", 6))
return false;
if (!(p = strchr(cmd, '-')))
return false;
target.target = std::string(cmd, p - cmd);
target.compilername = &p[1];
if (target.compilername == "cc")
target.compilername = getDefaultCompiler();
else if (target.compilername == "c++")
target.compilername = getDefaultCXXCompiler();
else if (auto *prog = program::getprog(target.compilername))
(*prog)(argc, argv, target);
if (target.target != getDefaultTarget())
warn << "this wrapper was built for target "
<< "'" << getDefaultTarget() << "'" << warn.endl();
if (!commandopts::parse(argc, argv, target))
return false;
detectCXXLib(target);
return target.setup();
}
}
if (!strncmp(cmd, "o32", 3))
target.arch = Arch::i386;
else if (!strncmp(cmd, "o64h", 4))
target.arch = Arch::x86_64h;
else if (!strncmp(cmd, "o64", 3))
target.arch = Arch::x86_64;
else
return false;
if (const char *p = strchr(cmd, '-'))
target.compilername = &cmd[p - cmd + 1];
if (!commandopts::parse(argc, argv, target))
return false;
detectCXXLib(target);
return target.setup();
}
} // unnamed namespace
//
// Main routine
//
int main(int argc, char **argv) {
char bbuf[sizeof(benchmark)];
auto b = new (bbuf) benchmark;
Target target;
char **cargs = nullptr;
int debug = 0;
int rc = -1;
if (char *p = getenv("OCDEBUG"))
debug = atoi(p);
if (char *p = getenv("OSXCROSS_UNIT_TEST")) {
unittest = atoi(p);
if ((p = getenv("OSXCROSS_PROG_NAME")))
argv[0] = p;
}
if (!detectTarget(argc, argv, target)) {
err << "while detecting target" << err.endl();
return 1;
}
if (debug) {
b->halt();
if (debug >= 2) {
dbg << "detected target triple: " << target.getTriple() << dbg.endl();
dbg << "detected compiler: " << target.compilername << dbg.endl();
dbg << "detected stdlib: " << getStdLibString(target.stdlib)
<< dbg.endl();
b->resume();
}
}
#ifdef __DragonFly__
// Escape DragonFlyBSD's weird PFS paths.
std::string escapedexecpath;
escapePath(target.execpath, escapedexecpath);
concatEnvVariable("COMPILER_PATH", escapedexecpath);
#else
concatEnvVariable("COMPILER_PATH", target.execpath);
#endif
auto printCommand = [&]() {
std::string in;
std::string out;
for (int i = 0; i < argc; ++i) {
in += argv[i];
in += " ";
}
out += target.compilerpath;
if (target.compilerpath != target.fargs[0]) {
out += " (";
out += target.fargs[0];
out += ") ";
} else {
out += " ";
}
for (size_t i = 1; i < target.fargs.size(); ++i) {
out += target.fargs[i];
out += " ";
}
for (auto &arg : target.args) {
out += arg;
out += " ";
}
if (!unittest)
dbg << "--> " << in << dbg.endl();
dbg << "<-- " << out << dbg.endl();
};
if (rc == -1) {
cargs = new char *[target.fargs.size() + target.args.size() + 1];
size_t i = 0;
for (auto &arg : target.fargs)
cargs[i++] = const_cast<char *>(arg.c_str());
for (auto &arg : target.args)
cargs[i++] = const_cast<char *>(arg.c_str());
cargs[i] = nullptr;
}
if (debug) {
time_type diff = b->getDiff();
if (rc == -1)
printCommand();
dbg << "=== time spent in wrapper: " << diff / 1000000.0 << " ms"
<< dbg.endl();
}
if (unittest == 2)
return 0;
if (rc == -1 && execvp(target.compilerpath.c_str(), cargs)) {
err << "invoking compiler failed" << err.endl();
if (!debug)
printCommand();
return 1;
}
return rc;
}