1// Copyright 2021 Google Inc. All rights reserved. 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 15package main 16 17import ( 18 "flag" 19 "fmt" 20 "io/ioutil" 21 "os" 22 "path/filepath" 23 "strings" 24 25 "google.golang.org/protobuf/encoding/prototext" 26 "google.golang.org/protobuf/proto" 27 28 "android/soong/compliance/license_metadata_proto" 29 "android/soong/response" 30) 31 32func newMultiString(flags *flag.FlagSet, name, usage string) *multiString { 33 var f multiString 34 flags.Var(&f, name, usage) 35 return &f 36} 37 38type multiString []string 39 40func (ms *multiString) String() string { return strings.Join(*ms, ", ") } 41func (ms *multiString) Set(s string) error { *ms = append(*ms, s); return nil } 42 43func main() { 44 var expandedArgs []string 45 for _, arg := range os.Args[1:] { 46 if strings.HasPrefix(arg, "@") { 47 f, err := os.Open(strings.TrimPrefix(arg, "@")) 48 if err != nil { 49 fmt.Fprintln(os.Stderr, err.Error()) 50 os.Exit(1) 51 } 52 53 respArgs, err := response.ReadRspFile(f) 54 f.Close() 55 if err != nil { 56 fmt.Fprintln(os.Stderr, err.Error()) 57 os.Exit(1) 58 } 59 expandedArgs = append(expandedArgs, respArgs...) 60 } else { 61 expandedArgs = append(expandedArgs, arg) 62 } 63 } 64 65 flags := flag.NewFlagSet("flags", flag.ExitOnError) 66 67 packageName := flags.String("p", "", "license package name") 68 moduleType := newMultiString(flags, "mt", "module type") 69 moduleName := flags.String("mn", "", "module name") 70 kinds := newMultiString(flags, "k", "license kinds") 71 moduleClass := newMultiString(flags, "mc", "module class") 72 conditions := newMultiString(flags, "c", "license conditions") 73 notices := newMultiString(flags, "n", "license notice file") 74 deps := newMultiString(flags, "d", "license metadata file dependency") 75 sources := newMultiString(flags, "s", "source (input) dependency") 76 built := newMultiString(flags, "t", "built targets") 77 installed := newMultiString(flags, "i", "installed targets") 78 roots := newMultiString(flags, "r", "root directory of project") 79 installedMap := newMultiString(flags, "m", "map dependent targets to their installed names") 80 isContainer := flags.Bool("is_container", false, "preserved dependent target name when given") 81 outFile := flags.String("o", "", "output file") 82 83 flags.Parse(expandedArgs) 84 85 metadata := license_metadata_proto.LicenseMetadata{} 86 metadata.PackageName = proto.String(*packageName) 87 metadata.ModuleName = proto.String(*moduleName) 88 metadata.ModuleTypes = *moduleType 89 metadata.ModuleClasses = *moduleClass 90 metadata.IsContainer = proto.Bool(*isContainer) 91 metadata.Projects = findGitRoots(*roots) 92 metadata.LicenseKinds = *kinds 93 metadata.LicenseConditions = *conditions 94 metadata.LicenseTexts = *notices 95 metadata.Built = *built 96 metadata.Installed = *installed 97 metadata.InstallMap = convertInstalledMap(*installedMap) 98 metadata.Sources = *sources 99 metadata.Deps = convertDependencies(*deps) 100 101 err := writeMetadata(*outFile, &metadata) 102 if err != nil { 103 fmt.Fprintf(os.Stderr, "error: %s\n", err.Error()) 104 os.Exit(2) 105 } 106} 107 108func findGitRoots(dirs []string) []string { 109 ret := make([]string, len(dirs)) 110 for i, dir := range dirs { 111 ret[i] = findGitRoot(dir) 112 } 113 return ret 114} 115 116// findGitRoot finds the directory at or above dir that contains a ".git" directory. This isn't 117// guaranteed to exist, for example during remote execution, when sandboxed, when building from 118// infrastructure that doesn't use git, or when the .git directory has been removed to save space, 119// but it should be good enough for local builds. If no .git directory is found the original value 120// is returned. 121func findGitRoot(dir string) string { 122 orig := dir 123 for dir != "" && dir != "." && dir != "/" { 124 _, err := os.Stat(filepath.Join(dir, ".git")) 125 if err == nil { 126 // Found dir/.git, return dir. 127 return dir 128 } else if !os.IsNotExist(err) { 129 // Error finding .git, return original input. 130 return orig 131 } 132 dir, _ = filepath.Split(dir) 133 dir = strings.TrimSuffix(dir, "/") 134 } 135 return orig 136} 137 138// convertInstalledMap converts a list of colon-separated from:to pairs into InstallMap proto 139// messages. 140func convertInstalledMap(installMaps []string) []*license_metadata_proto.InstallMap { 141 var ret []*license_metadata_proto.InstallMap 142 143 for _, installMap := range installMaps { 144 components := strings.Split(installMap, ":") 145 if len(components) != 2 { 146 panic(fmt.Errorf("install map entry %q contains %d colons, expected 1", installMap, len(components)-1)) 147 } 148 ret = append(ret, &license_metadata_proto.InstallMap{ 149 FromPath: proto.String(components[0]), 150 ContainerPath: proto.String(components[1]), 151 }) 152 } 153 154 return ret 155} 156 157// convertDependencies converts a colon-separated tuple of dependency:annotation:annotation... 158// into AnnotatedDependency proto messages. 159func convertDependencies(deps []string) []*license_metadata_proto.AnnotatedDependency { 160 var ret []*license_metadata_proto.AnnotatedDependency 161 162 for _, d := range deps { 163 components := strings.Split(d, ":") 164 dep := components[0] 165 components = components[1:] 166 ad := &license_metadata_proto.AnnotatedDependency{ 167 File: proto.String(dep), 168 Annotations: make([]string, 0, len(components)), 169 } 170 for _, ann := range components { 171 if len(ann) == 0 { 172 continue 173 } 174 ad.Annotations = append(ad.Annotations, ann) 175 } 176 ret = append(ret, ad) 177 } 178 179 return ret 180} 181 182func writeMetadata(file string, metadata *license_metadata_proto.LicenseMetadata) error { 183 buf, err := prototext.MarshalOptions{Multiline: true}.Marshal(metadata) 184 if err != nil { 185 return fmt.Errorf("error marshalling textproto: %w", err) 186 } 187 188 if file != "" { 189 err = ioutil.WriteFile(file, buf, 0666) 190 if err != nil { 191 return fmt.Errorf("error writing textproto %q: %w", file, err) 192 } 193 } else { 194 _, _ = os.Stdout.Write(buf) 195 } 196 197 return nil 198} 199