github.com/distbuild/reclient@v0.0.0-20240401075343-3de72e395564/internal/pkg/inputprocessor/action/clanglink/flagsparser.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 clanglink 16 17 import ( 18 "context" 19 "fmt" 20 "path/filepath" 21 "strings" 22 23 "github.com/bazelbuild/reclient/internal/pkg/features" 24 "github.com/bazelbuild/reclient/internal/pkg/inputprocessor/args" 25 "github.com/bazelbuild/reclient/internal/pkg/inputprocessor/clangparser" 26 "github.com/bazelbuild/reclient/internal/pkg/inputprocessor/flags" 27 "github.com/bazelbuild/reclient/internal/pkg/rsp" 28 29 "github.com/bazelbuild/remote-apis-sdks/go/pkg/cache" 30 31 log "github.com/golang/glog" 32 ) 33 34 var ( 35 arCache = cache.SingleFlight{} 36 linkerFlags = []string{ 37 "--version-script", 38 "--symbol-ordering-file", 39 "--dynamic-list", 40 "-T", 41 "--retain-symbols-file", 42 "--script,", 43 } 44 ) 45 46 // parseFlags is used to translate the given action command into clang linker 47 // options, so that they can be used during input processing. 48 func parseFlags(ctx context.Context, command []string, workingDir, execRoot string, arDeepScan bool) (*flags.CommandFlags, error) { 49 numArgs := len(command) 50 if numArgs < 2 { 51 return nil, fmt.Errorf("insufficient number of arguments in command: %v", command) 52 } 53 54 res := &flags.CommandFlags{ 55 ExecutablePath: command[0], 56 WorkingDirectory: workingDir, 57 ExecRoot: execRoot, 58 } 59 var state clangLinkState 60 s := clangparser.New(command) 61 for s.HasNext() { 62 if err := state.handleClangLinkFlags(res, s, arDeepScan); err != nil { 63 return nil, err 64 } 65 } 66 state.Finalize(res) 67 return res, nil 68 } 69 70 type clangLinkState struct { 71 clangparser.State 72 } 73 74 func (s *clangLinkState) handleClangLinkFlags(cmdFlags *flags.CommandFlags, scanner *args.Scanner, arDeepScan bool) error { 75 nextRes := scanner.ReadNextFlag() 76 // A temporary CommandFlags structure is used to collect the various dependencies, flags and outputs in the handleArgFunc, 77 // and then the results of that are copied into the CommandFlags structure passed in (cmdFlags). 78 // This is done because if a flag is being processed that is in an rsp file, we don't want to add that flag to 79 // the Flags list. Perhaps a cleaner option is to add a flag to the handleArgFunc that indicates if the argument is from the 80 // command line or from inside an rsp file. 81 f := &flags.CommandFlags{ 82 ExecRoot: cmdFlags.ExecRoot, 83 WorkingDirectory: cmdFlags.WorkingDirectory, 84 } 85 86 handleArgFunc := func(sc *args.Scanner) error { 87 curr := sc.CurResult 88 flag, args, values := curr.NormalizedKey, curr.Args, curr.Values 89 flagSwitch: 90 switch flag { 91 case "--sysroot", "--sysroot=": 92 if !features.GetConfig().ExperimentalSysrootDoNotUpload { 93 f.Dependencies = append(f.Dependencies, values[0]) 94 } 95 case "-Wl,": 96 for _, prefix := range linkerFlags { 97 if strings.HasPrefix(values[0], prefix) { 98 value := strings.TrimPrefix(values[0], prefix) 99 // TODO(b/184928955): There's a few available options here, 100 // eg ',' or '='. Frustratingly, we have to support them all. 101 value = strings.TrimPrefix(value, "=") 102 value = strings.TrimPrefix(value, ",") 103 f.Dependencies = append(f.Dependencies, value) 104 break flagSwitch 105 } 106 } 107 if strings.HasPrefix(values[0], "--out-implib=") { 108 value := strings.TrimPrefix(values[0], "--out-implib=") 109 f.OutputFilePaths = append(f.OutputFilePaths, value) 110 } 111 case "-L": 112 f.Dependencies = append(f.Dependencies, values[0]) 113 case "": 114 switch { 115 case strings.HasSuffix(args[0], ".a"): 116 archdeps, err := processArchive(args[0], f, arDeepScan) 117 if err != nil { 118 log.Warningf("Unable to process archive %v, %v", args[0], err) 119 return nil 120 } 121 f.Dependencies = append(f.Dependencies, archdeps...) 122 default: 123 if !strings.HasPrefix(args[0], "-l") { 124 f.Dependencies = append(f.Dependencies, args[0]) 125 } 126 } 127 default: 128 return s.State.HandleClangFlags(nextRes, f, true) 129 } 130 131 for _, arg := range args { 132 f.Flags = append(f.Flags, &flags.Flag{Value: arg}) 133 } 134 return nil 135 } 136 137 // Check if this is a rsp file that needs processing or just a normal flag. 138 if strings.HasPrefix(nextRes.Args[0], "@") { 139 rspFile := nextRes.Args[0][1:] 140 cmdFlags.Dependencies = append(cmdFlags.Dependencies, rspFile) 141 if !filepath.IsAbs(rspFile) { 142 rspFile = filepath.Join(cmdFlags.ExecRoot, cmdFlags.WorkingDirectory, rspFile) 143 } 144 cmdFlags.Flags = append(cmdFlags.Flags, &flags.Flag{Value: nextRes.Args[0]}) 145 if err := rsp.ParseWithFunc(rspFile, *scanner, handleArgFunc); err != nil { 146 return err 147 } 148 // We don't want to pass along the flags that were in the rsp file, just the 149 // rsp file itself as a flag. 150 } else { 151 if err := handleArgFunc(scanner); err != nil { 152 return err 153 } 154 // We want to pass along flags that were on the command line. 155 cmdFlags.Flags = append(cmdFlags.Flags, f.Flags...) 156 } 157 cmdFlags.Dependencies = append(cmdFlags.Dependencies, f.Dependencies...) 158 cmdFlags.OutputDirPaths = append(cmdFlags.OutputDirPaths, f.OutputDirPaths...) 159 cmdFlags.OutputFilePaths = append(cmdFlags.OutputFilePaths, f.OutputFilePaths...) 160 return nil 161 } 162 163 func processArchive(archive string, f *flags.CommandFlags, arDeepScan bool) ([]string, error) { 164 arAbsName := filepath.Join(f.ExecRoot, f.WorkingDirectory, archive) 165 depsIntf, err := arCache.LoadOrStore(arAbsName, func() (interface{}, error) { 166 archinputs := []string{archive} 167 if arDeepScan { 168 archdeps, err := readArchive(arAbsName, archive) 169 if err != nil { 170 return nil, err 171 } 172 return append(archdeps, archive), nil 173 } 174 return archinputs, nil 175 }) 176 177 if err != nil { 178 return nil, err 179 } 180 deps := depsIntf.([]string) 181 return deps, nil 182 } 183 184 func readArchive(arPath string, archive string) ([]string, error) { 185 arReader, err := newArReader(arPath, filepath.Dir(archive)) 186 if err != nil { 187 return nil, err 188 } 189 defer arReader.Close() 190 return arReader.ReadFileNames() 191 }