/*********************************************************************** * 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. * ***********************************************************************/ #include "compat.h" #include #include #include #include #include #include #include #include #include #include #include "tools.h" #include "target.h" namespace target { OSVersion Target::getSDKOSNum() const { if (target.size() < 7) return OSVersion(); int n = atoi(target.c_str() + 6); return OSVersion(10, 4 + (n - 8)); } bool Target::getSDKPath(std::string &path) const { OSVersion SDKVer = getSDKOSNum(); path = execpath; path += "/../SDK/MacOSX"; path += SDKVer.shortStr(); if (SDKVer <= OSVersion(10, 4)) path += "u"; path += ".sdk"; return dirExists(path); } bool Target::getMacPortsDir(std::string &path) const { path = execpath; path += "/../macports"; return dirExists(path); } bool Target::getMacPortsSysRootDir(std::string &path) const { if (!getMacPortsDir(path)) return false; path += "/pkgs"; return dirExists(path); } bool Target::getMacPortsPkgConfigDir(std::string &path) const { if (!getMacPortsDir(path)) return false; path += "/pkgs/opt/local/lib/pkgconfig"; return dirExists(path); } bool Target::getMacPortsIncludeDir(std::string &path) const { if (!getMacPortsDir(path)) return false; path += "/pkgs/opt/local/include"; return dirExists(path); } bool Target::getMacPortsLibDir(std::string &path) const { if (!getMacPortsDir(path)) return false; path += "/pkgs/opt/local/lib"; return dirExists(path); } bool Target::getMacPortsFrameworksDir(std::string &path) const { if (!getMacPortsDir(path)) return false; path += "/pkgs/opt/local/Library/Frameworks"; return dirExists(path); } void Target::addArch(const Arch arch) { auto &v = targetarch; for (size_t i = 0; i < v.size(); ++i) { if (v[i] == arch) { v.erase(v.begin() + i); addArch(arch); return; } } v.push_back(arch); } bool Target::haveArch(const Arch arch) { for (auto a : targetarch) { if (arch == a) return true; } return false; } bool Target::hasLibCXX() const { return getSDKOSNum() >= OSVersion(10, 7); } bool Target::libCXXIsDefaultCXXLib() const { OSVersion OSNum = this->OSNum; if (!OSNum.Num()) OSNum = getSDKOSNum(); return stdlib != libstdcxx && hasLibCXX() && !isGCC() && OSNum >= OSVersion(10, 9); } bool Target::isLibCXX() const { return stdlib == StdLib::libcxx || libCXXIsDefaultCXXLib(); } bool Target::isLibSTDCXX() const { return stdlib == StdLib::libstdcxx; } bool Target::isC(bool r) { if (!r && isCXX()) return false; if (langGiven() && lang[0] != 'o' && (!strcmp(lang, "c") || !strcmp(lang, "c-header"))) return true; if (haveSourceFile()) { if (!strcmp(getFileExtension(sourcefile), ".c")) return true; } return compilername.find("++") == std::string::npos && !isObjC(true); } bool Target::isCXX() { bool CXXCompiler = compilername.find("++") != std::string::npos; if (!langGiven() && CXXCompiler && !isObjC(true)) return true; if (langGiven() && !strncmp(lang, "c++", 3)) return true; constexpr const char *CXXFileExts[] = { ".C", ".cc", ".cpp", ".CPP", ".c++", ".cp", ".cxx" }; if (haveSourceFile()) { const char *ext = getFileExtension(sourcefile); if (*ext) { for (auto &cxxfe : CXXFileExts) { if (!strcmp(ext, cxxfe)) return true; } } } return CXXCompiler && !isC(true) && !isObjC(true); } bool Target::isObjC(bool r) { if (!r && isCXX()) return false; if (langGiven() && lang[0] == 'o') return true; if (haveSourceFile()) { const char *ext = getFileExtension(sourcefile); if (!strcmp(ext, ".m") || !strcmp(ext, ".mm")) return true; } return false; } const char *Target::getLangName() { if (isC()) return "C"; else if (isCXX()) return "C++"; else if (isObjC()) return "Obj-C"; else return "unknown"; } bool Target::isCXX11orNewer() const { if (!langStdGiven()) return false; constexpr const char *STD[] = { "c++0x", "gnu++0x", "c++11", "gnu++11", "c++1y", "gnu++1y", "c++14", "gnu++14", "c++1z", "gnu++1z" }; for (auto std : STD) { if (!strcmp(langstd, std)) return true; } return false; } void Target::setCompilerPath() { if (isGCC()) { compilerpath = execpath; compilerpath += "/"; compilerpath += getTriple(); compilerpath += "-"; compilerpath += "base-"; compilerpath += compilername; compilerexecname = getTriple(); compilerexecname += "-"; compilerexecname += compilername; } else { if (!realPath(compilername.c_str(), compilerpath, ignoreCCACHE)) compilerpath = compilername; compilerexecname += compilername; } } bool Target::findClangIntrinsicHeaders(std::string &path) { static std::stringstream dir; assert(isClang()); if (compilerpath.empty()) return false; std::string clangbindir = compilerpath; stripFileName(clangbindir); static ClangVersion *clangversion; static std::string pathtmp; clangversion = &this->clangversion; clear(dir); *clangversion = ClangVersion(); pathtmp.clear(); auto tryDir = [&]()->bool { listFiles(dir.str().c_str(), nullptr, [](const char *file) { if (file[0] != '.' && isDirectory(file, dir.str().c_str())) { ClangVersion cv = parseClangVersion(file); if (cv != ClangVersion()) { static std::stringstream tmp; clear(tmp); auto checkDir = [&](std::stringstream &dir) { static std::string intrindir; auto &file = dir; intrindir = dir.str(); file << "/xmmintrin.h"; if (fileExists(file.str())) { if (cv > *clangversion) { *clangversion = cv; pathtmp.swap(intrindir); } return true; } return false; }; tmp << dir.str() << "/" << file << "/include"; if (!checkDir(tmp)) { clear(tmp); tmp << dir.str() << "/" << file; checkDir(tmp); } } return true; } return true; }); return *clangversion != ClangVersion(); }; #define TRYDIR(basedir, subdir) \ do { \ dir << basedir << subdir; \ if (tryDir()) { \ path.swap(pathtmp); \ return true; \ } \ clear(dir); \ } while (0) #define TRYDIR2(libdir) TRYDIR(clangbindir, libdir) #define TRYDIR3(libdir) TRYDIR(std::string(), libdir) TRYDIR2("/../lib/clang"); #ifdef __linux__ #ifdef __x86_64__ // opensuse uses lib64 instead of lib on x86_64 TRYDIR2("/../lib64/clang"); #elif __i386__ TRYDIR2("/../lib32/clang"); #endif #endif #ifdef __APPLE__ constexpr const char *OSXIntrinDirs[] = { "/Library/Developer/CommandLineTools/usr/lib/clang", "/Applications/Contents/Developer/Toolchains/" "XcodeDefault.xctoolchain/usr/lib/clang" }; for (auto intrindir : OSXIntrinDirs) TRYDIR3(intrindir); #endif TRYDIR2("/../include/clang"); TRYDIR2("/usr/include/clang"); return false; #undef TRYDIR #undef TRYDIR2 #undef TRYDIR3 } void Target::setupGCCLibs(Arch arch) { assert(stdlib == StdLib::libstdcxx); fargs.push_back("-nodefaultlibs"); std::string SDKPath; std::stringstream GCCLibSTDCXXPath; std::stringstream GCCLibPath; const bool dynamic = !!getenv("OSXCROSS_GCC_NO_STATIC_RUNTIME"); getSDKPath(SDKPath); GCCLibPath << SDKPath << "/../../lib/gcc/" << otriple << "/" << gccversion.Str(); GCCLibSTDCXXPath << SDKPath << "/../../" << otriple << "/lib"; switch (arch) { case Arch::i386: case Arch::i486: case Arch::i586: case Arch::i686: GCCLibPath << "/" << getArchName(Arch::i386); GCCLibSTDCXXPath << "/" << getArchName(i386); default: ; } if (dynamic) { fargs.push_back("-L"); fargs.push_back(GCCLibPath.str()); fargs.push_back("-L"); fargs.push_back(GCCLibSTDCXXPath.str()); } auto addLib = [&](const std::stringstream &path, const char *lib) { if (dynamic) { fargs.push_back("-l"); fargs.push_back(lib); } else { static std::stringstream tmp; clear(tmp); tmp << path.str() << "/lib" << lib << ".a"; fargs.push_back(tmp.str()); } }; fargs.push_back("-Qunused-arguments"); addLib(GCCLibSTDCXXPath, "stdc++"); addLib(GCCLibSTDCXXPath, "supc++"); addLib(GCCLibPath, "gcc"); addLib(GCCLibPath, "gcc_eh"); fargs.push_back("-lc"); fargs.push_back("-Wl,-no_compact_unwind"); } bool Target::setup() { std::string SDKPath; OSVersion SDKOSNum = getSDKOSNum(); if (!isKnownCompiler()) warn << "unknown compiler '" << compilername << "'" << warn.endl(); if (!getSDKPath(SDKPath)) { err << "cannot find Mac OS X SDK (expected in: " << SDKPath << ")" << err.endl(); return false; } if (targetarch.empty()) targetarch.push_back(arch); if (!langStdGiven()) { if (isC()) langstd = getDefaultCStandard(); else if (isCXX()) langstd = getDefaultCXXStandard(); } triple = getArchName(arch); triple += "-"; triple += vendor; triple += "-"; triple += target; otriple = getArchName(Arch::x86_64); otriple += "-"; otriple += vendor; otriple += "-"; otriple += target; setCompilerPath(); if (!OSNum.Num()) { if (haveArch(Arch::x86_64h)) { OSNum = OSVersion(10, 8); // Default to 10.8 for x86_64h if (SDKOSNum < OSNum) { err << "'" << getArchName(arch) << "' requires the SDK from " << OSNum.Str() << " (or later)" << err.endl(); return false; } } else if (stdlib == StdLib::libcxx) { OSNum = OSVersion(10, 7); // Default to 10.7 for libc++ } else { OSNum = getDefaultMinTarget(); } } if (OSNum > SDKOSNum) { err << "targeted OS X version must be <= " << SDKOSNum.Str() << " (SDK)" << err.endl(); return false; } else if (OSNum < OSVersion(10, 4)) { err << "targeted OS X version must be >= 10.4" << err.endl(); return false; } if (haveArch(Arch::x86_64h) && OSNum < OSVersion(10, 8)) { err << "'" << getArchName(Arch::x86_64h) << "' requires " << "'-mmacosx-version-min=10.8' (or later)" << err.endl(); return false; } if (stdlib == StdLib::unset) { if (libCXXIsDefaultCXXLib()) { stdlib = StdLib::libcxx; } else { stdlib = StdLib::libstdcxx; } } else if (stdlib == StdLib::libcxx) { if (!hasLibCXX()) { err << "libc++ requires the SDK from 10.7 (or later)" << err.endl(); return false; } if (OSNum.Num() && OSNum < OSVersion(10, 7)) { err << "libc++ requires '-mmacosx-version-min=10.7' (or later)" << err.endl(); return false; } } std::string CXXHeaderPath = SDKPath; string_vector AdditionalCXXHeaderPaths; auto addCXXPath = [&](const std::string &path) { std::string tmp; tmp = CXXHeaderPath; tmp += "/"; tmp += path; AdditionalCXXHeaderPaths.push_back(tmp); }; switch (stdlib) { case StdLib::libcxx: { CXXHeaderPath += "/usr/include/c++/v1"; if (!dirExists(CXXHeaderPath)) { err << "cannot find " << getStdLibString(stdlib) << " headers" << err.endl(); return false; } break; } case StdLib::libstdcxx: { if (isGCC() && /*isCXX11orNewer()*/ true) break; if (usegcclibs) { // Use libs from './build_gcc.sh' installation CXXHeaderPath += "/../../"; CXXHeaderPath += otriple; CXXHeaderPath += "/include/c++"; static std::vector v; v.clear(); listFiles(CXXHeaderPath.c_str(), nullptr, [](const char *path) { if (path[0] != '.') v.push_back(parseGCCVersion(path)); return false; }); if (v.empty()) { err << "'-oc-use-gcc-libs' requires gcc to be installed " "(./build_gcc.sh)" << err.endl(); return false; } std::sort(v.begin(), v.end()); gccversion = v[v.size() - 1]; CXXHeaderPath += "/"; CXXHeaderPath += gccversion.Str(); addCXXPath(otriple); } else { // Use SDK libs std::string tmp; if (SDKOSNum <= OSVersion(10, 5)) CXXHeaderPath += "/usr/include/c++/4.0.0"; else CXXHeaderPath += "/usr/include/c++/4.2.1"; tmp = getArchName(arch); tmp += "-apple-"; tmp += target; addCXXPath(tmp); } if (!dirExists(CXXHeaderPath)) { err << "cannot find " << getStdLibString(stdlib) << " headers" << err.endl(); return false; } break; } case StdLib::unset: abort(); } fargs.push_back(compilerexecname); if (isClang()) { std::string tmp; fargs.push_back("-target"); fargs.push_back(getTriple()); tmp = "-mlinker-version="; tmp += getLinkerVersion(); fargs.push_back(tmp); tmp.clear(); #ifndef __APPLE__ if (!findClangIntrinsicHeaders(tmp)) { warn << "cannot find clang intrinsic headers; please report this " "issue to the OSXCross project" << warn.endl(); } else { fargs.push_back("-isystem"); fargs.push_back(tmp); } tmp.clear(); #endif fargs.push_back("-isysroot"); fargs.push_back(SDKPath); if (isCXX()) { tmp = "-stdlib="; tmp += getStdLibString(stdlib); fargs.push_back(tmp); if (stdlib == StdLib::libcxx || (stdlib == StdLib::libstdcxx && usegcclibs)) { fargs.push_back("-nostdinc++"); fargs.push_back("-Qunused-arguments"); } if (stdlib == StdLib::libstdcxx && usegcclibs && targetarch.size() < 2) { // Use libs from './build_gcc' installation setupGCCLibs(targetarch[0]); } } } else if (isGCC()) { if (isLibCXX()) { if (!langStdGiven()) langstd = "c++0x"; else if (!isCXX11orNewer()) warn << "libc++ requires -std=c++11 (or later) with gcc" << warn.endl(); } if (isCXX() && isLibCXX()) { fargs.push_back("-nostdinc++"); fargs.push_back("-nodefaultlibs"); if (!isGCH()) { fargs.push_back("-lc"); fargs.push_back("-lc++"); fargs.push_back(OSNum <= OSVersion(10, 4) ? "-lgcc_s.10.4" : "-lgcc_s.10.5"); } } else if (!isLibCXX() && !isGCH() && !getenv("OSXCROSS_GCC_NO_STATIC_RUNTIME")) { fargs.push_back("-static-libgcc"); fargs.push_back("-static-libstdc++"); } fargs.push_back("-Wl,-no_compact_unwind"); } auto addCXXHeaderPath = [&](const std::string &path) { fargs.push_back(isClang() ? "-cxx-isystem" : "-isystem"); fargs.push_back(path); }; addCXXHeaderPath(CXXHeaderPath); for (auto &path : AdditionalCXXHeaderPaths) addCXXHeaderPath(path); if (getenv("OSXCROSS_MP_INC")) { std::string MacPortsIncludeDir; std::string MacPortsLibraryDir; std::string MacPortsFrameworksDir; // Add them to args (instead of fargs), // so the user's -I / -L / -F is prefered. if (getMacPortsIncludeDir(MacPortsIncludeDir)) { args.push_back("-isystem"); args.push_back(MacPortsIncludeDir); if (getMacPortsLibDir(MacPortsLibraryDir)) { if (isClang()) args.push_back("-Qunused-arguments"); args.push_back("-L"); args.push_back(MacPortsLibraryDir); } if (getMacPortsFrameworksDir(MacPortsFrameworksDir)) { args.push_back("-iframework"); args.push_back(MacPortsFrameworksDir); } } } if (langGiven() && !usegcclibs) { // usegcclibs: delay it to later fargs.push_back("-x"); fargs.push_back(lang); } if (langStdGiven()) { std::string tmp; tmp = "-std="; tmp += langstd; fargs.push_back(tmp); } if (OSNum.Num()) { std::string tmp; tmp = "-mmacosx-version-min="; tmp += OSNum.Str(); fargs.push_back(tmp); } for (auto arch : targetarch) { bool is32bit = false; switch (arch) { case Arch::i386: case Arch::i486: case Arch::i586: case Arch::i686: is32bit = true; case Arch::x86_64: case Arch::x86_64h: if (isGCC()) { if (arch == Arch::x86_64h) { err << "gcc does not support architecture '" << getArchName(arch) << "'" << err.endl(); return false; } if (targetarch.size() > 1) break; fargs.push_back(is32bit ? "-m32" : "-m64"); } else if (isClang()) { if (usegcclibs && targetarch.size() > 1) break; fargs.push_back("-arch"); fargs.push_back(getArchName(arch)); } break; default: err << "unsupported architecture: '" << getArchName(arch) << "'" << err.endl(); return false; } } if (haveOutputName() && (targetarch.size() <= 1 || (!isGCC() && !usegcclibs))) { fargs.push_back("-o"); fargs.push_back(outputname); } return true; } } // namespace target