1// Copyright 2024 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 find_input_delta_lib 16 17import ( 18 "fmt" 19 "io" 20 "os" 21 "path/filepath" 22 "slices" 23 "text/template" 24 25 fid_exp "android/soong/cmd/find_input_delta/find_input_delta_proto" 26 "google.golang.org/protobuf/encoding/protowire" 27 "google.golang.org/protobuf/proto" 28) 29 30var DefaultTemplate = ` 31 {{- define "contents"}} 32 {{- range .Deletions}}-{{.}} {{end}} 33 {{- range .Additions}}+{{.}} {{end}} 34 {{- range .Changes}}+{{- .Name}} {{end}} 35 {{- range .Changes}} 36 {{- if or .Additions .Deletions .Changes}}--file {{.Name}} {{template "contents" .}}--endfile {{end}} 37 {{- end}} 38 {{- end}} 39 {{- template "contents" .}}` 40 41type FileList struct { 42 // The name of the parent for the list of file differences. 43 // For the outermost FileList, this is the name of the ninja target. 44 // Under `Changes`, it is the name of the changed file. 45 Name string 46 47 // The added files 48 Additions []string 49 50 // The deleted files 51 Deletions []string 52 53 // The modified files 54 Changes []FileList 55 56 // Map of file_extension:counts 57 ExtCountMap map[string]*FileCounts 58 59 // Total number of added/changed/deleted files. 60 TotalDelta uint32 61} 62 63// The maximum number of files that will be recorded by name. 64var MaxFilesRecorded uint32 = 50 65 66type FileCounts struct { 67 Additions uint32 68 Deletions uint32 69 Changes uint32 70} 71 72func FileListFactory(name string) *FileList { 73 return &FileList{ 74 Name: name, 75 ExtCountMap: make(map[string]*FileCounts), 76 } 77} 78 79func (fl *FileList) addFile(name string) { 80 fl.Additions = append(fl.Additions, name) 81 fl.TotalDelta += 1 82 ext := filepath.Ext(name) 83 if _, ok := fl.ExtCountMap[ext]; !ok { 84 fl.ExtCountMap[ext] = &FileCounts{} 85 } 86 fl.ExtCountMap[ext].Additions += 1 87} 88 89func (fl *FileList) deleteFile(name string) { 90 fl.Deletions = append(fl.Deletions, name) 91 fl.TotalDelta += 1 92 ext := filepath.Ext(name) 93 if _, ok := fl.ExtCountMap[ext]; !ok { 94 fl.ExtCountMap[ext] = &FileCounts{} 95 } 96 fl.ExtCountMap[ext].Deletions += 1 97} 98 99func (fl *FileList) changeFile(name string, ch *FileList) { 100 fl.Changes = append(fl.Changes, *ch) 101 fl.TotalDelta += 1 102 ext := filepath.Ext(name) 103 if _, ok := fl.ExtCountMap[ext]; !ok { 104 fl.ExtCountMap[ext] = &FileCounts{} 105 } 106 fl.ExtCountMap[ext].Changes += 1 107} 108 109func (fl FileList) ToProto() (*fid_exp.FileList, error) { 110 var count uint32 111 return fl.toProto(&count) 112} 113 114func (fl FileList) toProto(count *uint32) (*fid_exp.FileList, error) { 115 ret := &fid_exp.FileList{ 116 Name: proto.String(fl.Name), 117 } 118 for _, a := range fl.Additions { 119 if *count >= MaxFilesRecorded { 120 break 121 } 122 ret.Additions = append(ret.Additions, a) 123 *count += 1 124 } 125 for _, ch := range fl.Changes { 126 if *count >= MaxFilesRecorded { 127 break 128 } else { 129 // Pre-increment to limit what the call adds. 130 *count += 1 131 change, err := ch.toProto(count) 132 if err != nil { 133 return nil, err 134 } 135 ret.Changes = append(ret.Changes, change) 136 } 137 } 138 for _, d := range fl.Deletions { 139 if *count >= MaxFilesRecorded { 140 break 141 } 142 ret.Deletions = append(ret.Deletions, d) 143 } 144 ret.TotalDelta = proto.Uint32(*count) 145 exts := []string{} 146 for k := range fl.ExtCountMap { 147 exts = append(exts, k) 148 } 149 slices.Sort(exts) 150 for _, k := range exts { 151 v := fl.ExtCountMap[k] 152 ret.Counts = append(ret.Counts, &fid_exp.FileCount{ 153 Extension: proto.String(k), 154 Additions: proto.Uint32(v.Additions), 155 Deletions: proto.Uint32(v.Deletions), 156 Modifications: proto.Uint32(v.Changes), 157 }) 158 } 159 return ret, nil 160} 161 162func (fl FileList) SendMetrics(path string) error { 163 if path == "" { 164 return fmt.Errorf("No path given") 165 } 166 message, err := fl.ToProto() 167 if err != nil { 168 return err 169 } 170 171 // Marshal the message wrapped in SoongCombinedMetrics. 172 data := protowire.AppendVarint( 173 []byte{}, 174 protowire.EncodeTag( 175 protowire.Number(fid_exp.FieldNumbers_FIELD_NUMBERS_FILE_LIST), 176 protowire.BytesType)) 177 size := uint64(proto.Size(message)) 178 data = protowire.AppendVarint(data, size) 179 data, err = proto.MarshalOptions{UseCachedSize: true}.MarshalAppend(data, message) 180 if err != nil { 181 return err 182 } 183 184 out, err := os.Create(path) 185 if err != nil { 186 return err 187 } 188 defer func() { 189 if err := out.Close(); err != nil { 190 fmt.Fprintf(os.Stderr, "Failed to close %s: %v\n", path, err) 191 } 192 }() 193 _, err = out.Write(data) 194 return err 195} 196 197func (fl FileList) Format(wr io.Writer, format string) error { 198 tmpl, err := template.New("filelist").Parse(format) 199 if err != nil { 200 return err 201 } 202 return tmpl.Execute(wr, fl) 203} 204