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  }