blob: 9eb951ded2ed658e61390d58091b8919729ae038 [file] [log] [blame]
// Copyright 2019 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package main
import (
"bytes"
"os"
"path/filepath"
"strings"
)
func processClangFlags(builder *commandBuilder) error {
env := builder.env
clangDir, _ := env.getenv("CLANG")
if clangDir == "" {
if builder.cfg.isHostWrapper {
clangDir = filepath.Dir(builder.absWrapperPath)
} else {
clangDir = filepath.Join(builder.rootPath, "usr/bin/")
if !filepath.IsAbs(builder.path) {
// If sysroot_wrapper is invoked by relative path, call actual compiler in
// relative form. This is neccesary to remove absolute path from compile
// outputs.
var err error
clangDir, err = filepath.Rel(env.getwd(), clangDir)
if err != nil {
return wrapErrorwithSourceLocf(err, "failed to make clangDir %s relative to %s.", clangDir, env.getwd())
}
}
}
} else {
clangDir = filepath.Dir(clangDir)
}
clangBasename := "clang"
if strings.HasSuffix(builder.target.compiler, "++") {
clangBasename = "clang++"
}
// GCC flags to remove from the clang command line.
// TODO: Once clang supports GCC compatibility mode, remove
// these checks.
//
// Use of -Qunused-arguments allows this set to be small, just those
// that clang still warns about.
unsupported := map[string]bool{
"-pass-exit-codes": true,
}
unsupportedPrefixes := []string{"-Wstrict-aliasing=", "-finline-limit="}
// clang with '-ftrapv' generates 'call __mulodi4', which is only implemented
// in compiler-rt library. However compiler-rt library only has i386/x86_64
// backends (see '/usr/lib/clang/3.7.0/lib/linux/libclang_rt.*'). GCC, on the
// other hand, generate 'call __mulvdi3', which is implemented in libgcc. See
// bug chromium:503229.
armUnsupported := map[string]bool{"-ftrapv": true}
if builder.cfg.isHostWrapper {
unsupported["-ftrapv"] = true
}
// Clang may use different options for the same or similar functionality.
gccToClang := map[string]string{
"-Wno-error=cpp": "-Wno-#warnings",
"-Wno-error=maybe-uninitialized": "-Wno-error=uninitialized",
"-Wno-error=unused-but-set-variable": "-Wno-error=unused-variable",
"-Wno-unused-but-set-variable": "-Wno-unused-variable",
"-Wunused-but-set-variable": "-Wunused-variable",
}
// Note: not using builder.transformArgs as we need to add multiple arguments
// based on a single input argument, and also be able to return errors.
newArgs := []builderArg{}
for _, arg := range builder.args {
// Adds an argument with the given value, preserving the
// fromUser value of the original argument.
addNewArg := func(value string) {
newArgs = append(newArgs, builderArg{
fromUser: arg.fromUser,
value: value,
})
}
if mapped, ok := gccToClang[arg.value]; ok {
addNewArg(mapped)
continue
}
if unsupported[arg.value] {
continue
}
if hasAtLeastOnePrefix(arg.value, unsupportedPrefixes) {
continue
}
if builder.target.arch == "armv7a" && builder.target.sys == "linux" {
if armUnsupported[arg.value] {
continue
}
}
if clangOnly := "-Xclang-only="; strings.HasPrefix(arg.value, clangOnly) {
addNewArg(arg.value[len(clangOnly):])
continue
}
if clangPath := "-Xclang-path="; strings.HasPrefix(arg.value, clangPath) {
clangPathValue := arg.value[len(clangPath):]
resourceDir, err := getClangResourceDir(env, filepath.Join(clangDir, clangBasename))
if err != nil {
return err
}
clangDir = clangPathValue
addNewArg("-resource-dir=" + resourceDir)
addNewArg("--gcc-toolchain=/usr")
continue
}
addNewArg(arg.value)
}
builder.args = newArgs
builder.path = filepath.Join(clangDir, clangBasename)
// Specify the target for clang.
if !builder.cfg.isHostWrapper {
linkerPath := getLinkerPath(env, builder.target.target+"-ld", builder.rootPath)
relLinkerPath, err := filepath.Rel(env.getwd(), linkerPath)
if err != nil {
return wrapErrorwithSourceLocf(err, "failed to make linker path %s relative to %s",
linkerPath, env.getwd())
}
builder.addPostUserArgs("-B" + relLinkerPath)
if startswithI86(builder.target.arch) {
// TODO: -target i686-pc-linux-gnu causes clang to search for
// libclang_rt.asan-i686.a which doesn't exist because it's packaged
// as libclang_rt.asan-i386.a. We can't use -target i386-pc-linux-gnu
// because then it would try to run i386-pc-linux-gnu-ld which doesn't
// exist. Consider renaming the runtime library to use i686 in its name.
builder.addPostUserArgs("-m32")
// clang does not support -mno-movbe. This is the alternate way to do it.
builder.addPostUserArgs("-Xclang", "-target-feature", "-Xclang", "-movbe")
} else {
builder.addPostUserArgs("-target", builder.target.target)
}
}
return nil
}
func getClangResourceDir(env env, clangPath string) (string, error) {
readResourceCmd := &command{
Path: clangPath,
Args: []string{"--print-resource-dir"},
}
stdoutBuffer := bytes.Buffer{}
if err := env.run(readResourceCmd, nil, &stdoutBuffer, env.stderr()); err != nil {
return "", wrapErrorwithSourceLocf(err,
"failed to call clang to read the resouce-dir: %#v",
readResourceCmd)
}
resourceDir := strings.TrimRight(stdoutBuffer.String(), "\n")
return resourceDir, nil
}
// Return the a directory which contains an 'ld' that gcc is using.
func getLinkerPath(env env, linkerCmd string, rootPath string) string {
// We did not pass the tuple i686-pc-linux-gnu to x86-32 clang. Instead,
// we passed '-m32' to clang. As a result, clang does not want to use the
// i686-pc-linux-gnu-ld, so we need to add this to help clang find the right
// linker.
if linkerPath, err := resolveAgainstPathEnv(env, linkerCmd); err == nil {
// FIXME: We are not using filepath.EvalSymlinks to only unpack
// one layer of symlinks to match the old wrapper. Investigate
// why this is important or simplify to filepath.EvalSymlinks.
if fi, err := os.Lstat(linkerPath); err == nil {
if fi.Mode()&os.ModeSymlink != 0 {
if linkPath, err := os.Readlink(linkerPath); err == nil {
linkerPath = linkPath
}
}
return filepath.Dir(linkerPath)
}
}
// When using the sdk outside chroot, we need to provide the cross linker path
// to the compiler via -B ${linker_path}. This is because for gcc, it can
// find the right linker via searching its internal paths. Clang does not have
// such feature, and it falls back to $PATH search only. However, the path of
// ${SDK_LOCATION}/bin is not necessarily in the ${PATH}. To fix this, we
// provide the directory that contains the cross linker wrapper to clang.
// Outside chroot, it is the top bin directory form the sdk tarball.
return filepath.Join(rootPath, "bin")
}
func hasAtLeastOnePrefix(s string, prefixes []string) bool {
for _, prefix := range prefixes {
if strings.HasPrefix(s, prefix) {
return true
}
}
return false
}