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 "errors" 19 "fmt" 20 "io/fs" 21 "regexp" 22 "slices" 23 24 fid_proto "android/soong/cmd/find_input_delta/find_input_delta_proto_internal" 25 "android/soong/third_party/zip" 26 "github.com/google/blueprint/pathtools" 27 "google.golang.org/protobuf/proto" 28) 29 30// Load the internal state from a file. 31// If the file does not exist, an empty state is returned. 32func LoadState(filename string, fsys fs.ReadFileFS) (*fid_proto.PartialCompileInputs, error) { 33 var message = &fid_proto.PartialCompileInputs{} 34 data, err := fsys.ReadFile(filename) 35 if err != nil && !errors.Is(err, fs.ErrNotExist) { 36 return message, err 37 } 38 proto.Unmarshal(data, message) 39 return message, nil 40} 41 42type StatReadFileFS interface { 43 fs.StatFS 44 fs.ReadFileFS 45} 46 47// Create the internal state by examining the inputs. 48func CreateState(inputs []string, inspect_contents bool, fsys StatReadFileFS) (*fid_proto.PartialCompileInputs, error) { 49 ret := &fid_proto.PartialCompileInputs{} 50 slices.Sort(inputs) 51 for _, input := range inputs { 52 stat, err := fs.Stat(fsys, input) 53 if err != nil { 54 return ret, err 55 } 56 pci := &fid_proto.PartialCompileInput{ 57 Name: proto.String(input), 58 MtimeNsec: proto.Int64(stat.ModTime().UnixNano()), 59 // If we ever have an easy hash, assign it here. 60 } 61 if inspect_contents { 62 // NOTE: When we find it useful, we can parallelize the file inspection for speed. 63 contents, err := InspectFileContents(input) 64 if err != nil { 65 return ret, err 66 } 67 if contents != nil { 68 pci.Contents = contents 69 } 70 } 71 ret.InputFiles = append(ret.InputFiles, pci) 72 } 73 return ret, nil 74} 75 76// We ignore any suffix digit caused by sharding. 77var InspectExtsZipRegexp = regexp.MustCompile("\\.(jar|apex|apk)[0-9]*$") 78 79// Inspect the file and extract the state of the elements in the archive. 80// If this is not an archive of some sort, nil is returned. 81func InspectFileContents(name string) ([]*fid_proto.PartialCompileInput, error) { 82 if InspectExtsZipRegexp.Match([]byte(name)) { 83 return inspectZipFileContents(name) 84 } 85 return nil, nil 86} 87 88func inspectZipFileContents(name string) ([]*fid_proto.PartialCompileInput, error) { 89 rc, err := zip.OpenReader(name) 90 if err != nil { 91 return nil, err 92 } 93 ret := []*fid_proto.PartialCompileInput{} 94 for _, v := range rc.File { 95 pci := &fid_proto.PartialCompileInput{ 96 Name: proto.String(v.Name), 97 MtimeNsec: proto.Int64(v.ModTime().UnixNano()), 98 Hash: proto.String(fmt.Sprintf("%08x", v.CRC32)), 99 } 100 ret = append(ret, pci) 101 // We do not support nested inspection. 102 } 103 return ret, nil 104} 105 106func WriteState(s *fid_proto.PartialCompileInputs, path string) error { 107 data, err := proto.Marshal(s) 108 if err != nil { 109 return err 110 } 111 return pathtools.WriteFileIfChanged(path, data, 0644) 112} 113 114func CompareInternalState(prior, other *fid_proto.PartialCompileInputs, target string) *FileList { 115 return CompareInputFiles(prior.GetInputFiles(), other.GetInputFiles(), target) 116} 117 118func CompareInputFiles(prior, other []*fid_proto.PartialCompileInput, name string) *FileList { 119 fl := FileListFactory(name) 120 PriorMap := make(map[string]*fid_proto.PartialCompileInput, len(prior)) 121 // We know that the lists are properly sorted, so we can simply compare them. 122 for _, v := range prior { 123 PriorMap[v.GetName()] = v 124 } 125 otherMap := make(map[string]*fid_proto.PartialCompileInput, len(other)) 126 for _, v := range other { 127 name = v.GetName() 128 otherMap[name] = v 129 if _, ok := PriorMap[name]; !ok { 130 // Added file 131 fl.addFile(name) 132 } else if !proto.Equal(PriorMap[name], v) { 133 // Changed file 134 fl.changeFile(name, CompareInputFiles(PriorMap[name].GetContents(), v.GetContents(), name)) 135 } 136 } 137 for _, v := range prior { 138 name := v.GetName() 139 if _, ok := otherMap[name]; !ok { 140 // Deleted file 141 fl.deleteFile(name) 142 } 143 } 144 return fl 145} 146