| // Copyright 2023 Google Inc. All rights reserved. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package jar |
| |
| import ( |
| "android/soong/third_party/zip" |
| "bufio" |
| "hash/crc32" |
| "sort" |
| "strings" |
| ) |
| |
| const servicesPrefix = "META-INF/services/" |
| |
| // Services is used to collect service files from multiple zip files and produce a list of ServiceFiles containing |
| // the unique lines from all the input zip entries with the same name. |
| type Services struct { |
| services map[string]*ServiceFile |
| } |
| |
| // ServiceFile contains the combined contents of all input zip entries with a single name. |
| type ServiceFile struct { |
| Name string |
| FileHeader *zip.FileHeader |
| Contents []byte |
| Lines []string |
| } |
| |
| // IsServiceFile returns true if the zip entry is in the META-INF/services/ directory. |
| func (Services) IsServiceFile(entry *zip.File) bool { |
| return strings.HasPrefix(entry.Name, servicesPrefix) |
| } |
| |
| // AddServiceFile adds a zip entry in the META-INF/services/ directory to the list of service files that need |
| // to be combined. |
| func (j *Services) AddServiceFile(entry *zip.File) error { |
| if j.services == nil { |
| j.services = map[string]*ServiceFile{} |
| } |
| |
| service := entry.Name |
| serviceFile := j.services[service] |
| fh := entry.FileHeader |
| if serviceFile == nil { |
| serviceFile = &ServiceFile{ |
| Name: service, |
| FileHeader: &fh, |
| } |
| j.services[service] = serviceFile |
| } |
| |
| f, err := entry.Open() |
| if err != nil { |
| return err |
| } |
| defer f.Close() |
| |
| scanner := bufio.NewScanner(f) |
| for scanner.Scan() { |
| line := scanner.Text() |
| if line != "" { |
| serviceFile.Lines = append(serviceFile.Lines, line) |
| } |
| } |
| |
| if err := scanner.Err(); err != nil { |
| return err |
| } |
| |
| return nil |
| } |
| |
| // ServiceFiles returns the list of combined service files, each containing all the unique lines from the |
| // corresponding service files in the input zip entries. |
| func (j *Services) ServiceFiles() []ServiceFile { |
| services := make([]ServiceFile, 0, len(j.services)) |
| |
| for _, serviceFile := range j.services { |
| serviceFile.Lines = dedupServicesLines(serviceFile.Lines) |
| serviceFile.Lines = append(serviceFile.Lines, "") |
| serviceFile.Contents = []byte(strings.Join(serviceFile.Lines, "\n")) |
| |
| serviceFile.FileHeader.UncompressedSize64 = uint64(len(serviceFile.Contents)) |
| serviceFile.FileHeader.CRC32 = crc32.ChecksumIEEE(serviceFile.Contents) |
| if serviceFile.FileHeader.Method == zip.Store { |
| serviceFile.FileHeader.CompressedSize64 = serviceFile.FileHeader.UncompressedSize64 |
| } |
| |
| services = append(services, *serviceFile) |
| } |
| |
| sort.Slice(services, func(i, j int) bool { |
| return services[i].Name < services[j].Name |
| }) |
| |
| return services |
| } |
| |
| func dedupServicesLines(in []string) []string { |
| writeIndex := 0 |
| outer: |
| for readIndex := 0; readIndex < len(in); readIndex++ { |
| for compareIndex := 0; compareIndex < writeIndex; compareIndex++ { |
| if interface{}(in[readIndex]) == interface{}(in[compareIndex]) { |
| // The value at readIndex already exists somewhere in the output region |
| // of the slice before writeIndex, skip it. |
| continue outer |
| } |
| } |
| if readIndex != writeIndex { |
| in[writeIndex] = in[readIndex] |
| } |
| writeIndex++ |
| } |
| return in[0:writeIndex] |
| } |