github.com/distbuild/reclient@v0.0.0-20240401075343-3de72e395564/internal/pkg/inputprocessor/action/metalava/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 metalava 16 17 import ( 18 "context" 19 "fmt" 20 "path/filepath" 21 "strings" 22 23 "github.com/bazelbuild/reclient/internal/pkg/inputprocessor/args" 24 "github.com/bazelbuild/reclient/internal/pkg/inputprocessor/flags" 25 "github.com/bazelbuild/reclient/internal/pkg/rsp" 26 ) 27 28 var ( 29 depFlags = map[string]bool{ 30 "--source-files": true, 31 "--source-path": true, 32 "-sourcepath": true, 33 "-classpath": true, 34 "-bootclasspath": true, 35 "--merge-qualifier-annotations": true, 36 "--merge-inclusion-annotations": true, 37 "--validate-nullability-from-list": true, 38 "--input-api-jar": true, 39 "--manifest": true, 40 "--subtract-api": true, 41 "--register-artifact": true, 42 "--check-compatibility:api:current": true, 43 "--check-compatibility:removed:current": true, 44 "--check-compatibility:api:released": true, 45 "--check-compatibility:removed:released": true, 46 "--api-lint": true, 47 "--migrate-nullness": true, 48 "--annotation-coverage-of": true, 49 "--include-annotation-classes": true, 50 "--rewrite-annotations": true, 51 "--apply-api-levels": true, 52 "--baseline:api-lint": true, 53 "--baseline:compatibility:released": true, 54 } 55 56 outFileFlags = map[string]bool{ 57 "--api": true, 58 "--private-api": true, 59 "--dex-api": true, 60 "--private-dex-api": true, 61 "--dex-api-mapping": true, 62 "--removed-api": true, 63 "--removed-dex-api": true, 64 "--proguard": true, 65 "--write-doc-stubs-source-list": true, 66 "--report-even-if-suppressed": true, 67 "--api-xml": true, 68 "--write-class-coverage-to": true, 69 "--write-member-coverage-to": true, 70 "--extract-annotations": true, 71 "--generate-api-levels": true, 72 "--nullability-warnings-txt": true, 73 "--update-baseline:compatibility:released": true, 74 "--update-baseline:api-lint": true, 75 } 76 77 outDirFlags = map[string]bool{ 78 "--sdk-values": true, 79 "--stubs": true, 80 "--doc-stubs": true, 81 "--rewrite-annotations": true, 82 } 83 84 inOutFlags = map[string]bool{ 85 "--baseline": true, 86 "--update-baseline": true, 87 "--merge-baseline": true, 88 } 89 90 srcDestFlags = map[string]bool{ 91 "--convert-to-jdiff": true, 92 "--convert-to-v1": true, 93 "--convert-to-v2": true, 94 "--copy-annotations": true, 95 } 96 97 srcSrcDestFlags = map[string]bool{ 98 "--convert-new-to-jdiff": true, 99 "--convert-new-to-v2": true, 100 } 101 102 metavalaFlags = map[string]int{} 103 ) 104 105 func init() { 106 // depFlags has no flag value. 107 108 // srcDestFlags takes 2 values. 109 for f := range srcDestFlags { 110 metavalaFlags[f] = 2 111 } 112 // srcSrcDestFlags takes 3 values. 113 for f := range srcSrcDestFlags { 114 metavalaFlags[f] = 3 115 } 116 117 // inOutFlags, outFileFlags, outDirFlags take value 118 for f := range inOutFlags { 119 metavalaFlags[f] = 1 120 } 121 for f := range outFileFlags { 122 metavalaFlags[f] = 1 123 } 124 for f := range outDirFlags { 125 metavalaFlags[f] = 1 126 } 127 metavalaFlags["--android-jar-pattern"] = 1 128 metavalaFlags["--strict-input-files"] = 1 129 metavalaFlags["--strict-input-files:warn"] = 1 130 } 131 132 // parseFlags is used to transform a metalava command into a CommandFlags structure. 133 func parseFlags(ctx context.Context, command []string, workingDir, execRoot string) (*flags.CommandFlags, error) { 134 numArgs := len(command) 135 if numArgs < 2 { 136 return nil, fmt.Errorf("insufficient number of arguments in command: %v", command) 137 } 138 139 res := &flags.CommandFlags{ 140 ExecutablePath: command[0], 141 WorkingDirectory: workingDir, 142 ExecRoot: execRoot, 143 } 144 s := args.Scanner{ 145 Args: command[1:], 146 Flags: metavalaFlags, 147 } 148 for s.HasNext() { 149 flag, args, values, _ := s.Next() 150 switch { 151 case depFlags[flag] && s.HasNext() && s.LookAhead() == "": 152 _, args, _, _ = s.Next() 153 if flag == "-sourcepath" { 154 res.VirtualDirectories = append(res.VirtualDirectories, args...) 155 continue 156 } 157 value := args[0] 158 var deps []string 159 switch { 160 case strings.Contains(value, ":"): 161 deps = strings.Split(value, ":") 162 case strings.Contains(value, ","): 163 deps = strings.Split(value, ",") 164 case strings.HasPrefix(value, "@"): 165 deps = []string{value[1:]} 166 rspDeps, err := rsp.Parse(filepath.Join(execRoot, workingDir, deps[0])) 167 if err != nil { 168 return nil, err 169 } 170 deps = append(deps, rspDeps...) 171 default: 172 deps = []string{value} 173 } 174 for _, d := range deps { 175 // Exclude empty strings and . strings from dependencies. 176 if d == "" || d == "." { 177 continue 178 } 179 res.Dependencies = append(res.Dependencies, d) 180 } 181 continue 182 case inOutFlags[flag]: 183 res.Dependencies = append(res.Dependencies, values[0]) 184 res.OutputFilePaths = append(res.OutputFilePaths, values[0]) 185 continue 186 case outFileFlags[flag]: 187 res.OutputFilePaths = append(res.OutputFilePaths, values[0]) 188 continue 189 case outDirFlags[flag]: 190 res.OutputDirPaths = append(res.OutputDirPaths, values[0]) 191 continue 192 case srcDestFlags[flag]: 193 res.Dependencies = append(res.Dependencies, values[0]) 194 res.OutputFilePaths = append(res.OutputFilePaths, values[1]) 195 continue 196 case srcSrcDestFlags[flag]: 197 res.Dependencies = append(res.Dependencies, values[0], values[1]) 198 res.OutputFilePaths = append(res.OutputFilePaths, values[2]) 199 continue 200 case flag == "--android-jar-pattern": 201 res.Dependencies = append(res.Dependencies, strings.SplitN(values[0], "%", 2)[0]) 202 continue 203 case flag == "--strict-input-files", flag == "--strict-input-files:warn": 204 res.EmittedDependencyFile = values[0] 205 res.OutputFilePaths = append(res.OutputFilePaths, values[0]) 206 continue 207 case flag == "": 208 if strings.HasPrefix(args[0], "@") { 209 rspFile := args[0][1:] 210 res.TargetFilePaths = append(res.TargetFilePaths, rspFile) 211 rspDeps, err := rsp.Parse(filepath.Join(execRoot, workingDir, rspFile)) 212 if err != nil { 213 return nil, err 214 } 215 res.Dependencies = append(res.Dependencies, rspDeps...) 216 continue 217 } 218 } 219 res.Flags = append(res.Flags, &flags.Flag{Value: args[0]}) 220 221 } 222 return res, nil 223 }