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  }