github.com/distbuild/reclient@v0.0.0-20240401075343-3de72e395564/internal/pkg/inputprocessor/action/clanglint/preprocessor.go (about) 1 // Copyright 2023 Google LLC 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // Package clanglint performs include processing given a valid clang-tidy action. 16 package clanglint 17 18 import ( 19 "context" 20 "fmt" 21 "os" 22 "path/filepath" 23 "regexp" 24 "strings" 25 26 "github.com/bazelbuild/reclient/internal/pkg/inputprocessor/action/cppcompile" 27 "github.com/bazelbuild/reclient/internal/pkg/inputprocessor/flags" 28 29 "github.com/bazelbuild/remote-apis-sdks/go/pkg/cache" 30 "github.com/bazelbuild/remote-apis-sdks/go/pkg/command" 31 32 log "github.com/golang/glog" 33 ) 34 35 var ( 36 clangTidyResourceDirOutputParser = regexp.MustCompile(`^(.*)\n`) 37 rdCache = cache.SingleFlight{} 38 ) 39 40 type systemExecutor interface { 41 Execute(ctx context.Context, cmd *command.Command) (string, string, error) 42 } 43 44 // Preprocessor is the preprocessor of clang cpp lint actions. 45 type Preprocessor struct { 46 *cppcompile.Preprocessor 47 } 48 49 // ComputeSpec computes cpp header dependencies. 50 func (p *Preprocessor) ComputeSpec() error { 51 var err error 52 p.Flags, err = p.clangTidyToCPPFlags(p.Flags) 53 if err != nil { 54 p.Err = err 55 return p.Err 56 } 57 return p.Preprocessor.ComputeSpec() 58 } 59 60 // clangTidyToCPPFlags converts the given clang-tidy action to a cpp compile action 61 // so that we can run it by clang-scan-deps input processor. 62 func (p *Preprocessor) clangTidyToCPPFlags(f *flags.CommandFlags) (*flags.CommandFlags, error) { 63 res := f.Copy() 64 compilerFlags := []*flags.Flag{} 65 for i := 0; i < len(res.Flags); i++ { 66 if res.Flags[i].Key == "--" { 67 rd, err := p.resourceDir(f) 68 if err != nil { 69 return nil, err 70 } 71 compilerFlags = append(compilerFlags, &flags.Flag{Key: "-resource-dir", Value: rd}) 72 compilerFlags = append(compilerFlags, res.Flags[i+1:]...) 73 break 74 } 75 } 76 res.Flags = compilerFlags 77 return res, nil 78 } 79 80 // resourceDir is used to determine the resource directory used by the clang-tidy 81 // tool. It is explicitly passed into clang-scan-deps since scan-deps cannot find 82 // out the resource-dir if the clang-tidy executable does not support it via the 83 // "-print-resource-dir" option. 84 func (p *Preprocessor) resourceDir(f *flags.CommandFlags) (string, error) { 85 computeResourceDir := func() (interface{}, error) { 86 cmd := &command.Command{ 87 Args: []string{ 88 f.ExecutablePath, 89 "--version", 90 "--", 91 "-print-resource-dir", 92 }, 93 ExecRoot: f.ExecRoot, 94 WorkingDir: f.WorkingDirectory, 95 } 96 97 outStr, _, err := p.Executor.Execute(context.Background(), cmd) 98 if err != nil { 99 return nil, err 100 } 101 102 matches := clangTidyResourceDirOutputParser.FindStringSubmatch(outStr) 103 if len(matches) < 1 { 104 return nil, fmt.Errorf("unexpected clang-tidy output when trying to find resource directory: %v", outStr) 105 } 106 // The output of clang-tidy is this: 107 // ``` 108 // lib64/clang/11.0.2 109 // LLVM (http://llvm.org/): 110 // LLVM version 11.0.2git 111 // Optimized build. 112 // Default target: x86_64-unknown-linux-gnu 113 // Host CPU: broadwell 114 // ``` 115 // The resource dir is determined in clang as: 116 // `../../<check for lib64 / lib dir>/clang/<hard-coded-clang-version>`. 117 resourceDir := filepath.Join(f.WorkingDirectory, f.ExecutablePath, "../../", strings.TrimSuffix(matches[0], "\n")) 118 if _, err := os.Stat(resourceDir); os.IsNotExist(err) { 119 return nil, fmt.Errorf("resource-directory not found at: %v", resourceDir) 120 } 121 log.Infof("Determined resource-dir for clang-tidy action: %v", resourceDir) 122 return resourceDir, nil 123 } 124 125 rd, err := rdCache.LoadOrStore(f, computeResourceDir) 126 if err != nil { 127 return "", err 128 } 129 res, ok := rd.(string) 130 if !ok { 131 return "", fmt.Errorf("unexpected type stored in cache: %v", rd) 132 } 133 return res, nil 134 }