blob: d06a6dc992426ecc7631d36b152593e2849af3f2 [file] [log] [blame]
Colin Cross7592d5a2023-07-18 15:57:09 -07001// Copyright 2023 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 jar
16
17import (
18 "android/soong/third_party/zip"
19 "bufio"
20 "hash/crc32"
21 "sort"
22 "strings"
23)
24
25const servicesPrefix = "META-INF/services/"
26
27// Services is used to collect service files from multiple zip files and produce a list of ServiceFiles containing
28// the unique lines from all the input zip entries with the same name.
29type Services struct {
30 services map[string]*ServiceFile
31}
32
33// ServiceFile contains the combined contents of all input zip entries with a single name.
34type ServiceFile struct {
35 Name string
36 FileHeader *zip.FileHeader
37 Contents []byte
38 Lines []string
39}
40
41// IsServiceFile returns true if the zip entry is in the META-INF/services/ directory.
42func (Services) IsServiceFile(entry *zip.File) bool {
43 return strings.HasPrefix(entry.Name, servicesPrefix)
44}
45
46// AddServiceFile adds a zip entry in the META-INF/services/ directory to the list of service files that need
47// to be combined.
48func (j *Services) AddServiceFile(entry *zip.File) error {
49 if j.services == nil {
50 j.services = map[string]*ServiceFile{}
51 }
52
53 service := entry.Name
54 serviceFile := j.services[service]
55 fh := entry.FileHeader
56 if serviceFile == nil {
57 serviceFile = &ServiceFile{
58 Name: service,
59 FileHeader: &fh,
60 }
61 j.services[service] = serviceFile
62 }
63
64 f, err := entry.Open()
65 if err != nil {
66 return err
67 }
68 defer f.Close()
69
70 scanner := bufio.NewScanner(f)
71 for scanner.Scan() {
72 line := scanner.Text()
73 if line != "" {
74 serviceFile.Lines = append(serviceFile.Lines, line)
75 }
76 }
77
78 if err := scanner.Err(); err != nil {
79 return err
80 }
81
82 return nil
83}
84
85// ServiceFiles returns the list of combined service files, each containing all the unique lines from the
86// corresponding service files in the input zip entries.
87func (j *Services) ServiceFiles() []ServiceFile {
88 services := make([]ServiceFile, 0, len(j.services))
89
90 for _, serviceFile := range j.services {
91 serviceFile.Lines = dedupServicesLines(serviceFile.Lines)
92 serviceFile.Lines = append(serviceFile.Lines, "")
93 serviceFile.Contents = []byte(strings.Join(serviceFile.Lines, "\n"))
94
95 serviceFile.FileHeader.UncompressedSize64 = uint64(len(serviceFile.Contents))
96 serviceFile.FileHeader.CRC32 = crc32.ChecksumIEEE(serviceFile.Contents)
97 if serviceFile.FileHeader.Method == zip.Store {
98 serviceFile.FileHeader.CompressedSize64 = serviceFile.FileHeader.UncompressedSize64
99 }
100
101 services = append(services, *serviceFile)
102 }
103
104 sort.Slice(services, func(i, j int) bool {
105 return services[i].Name < services[j].Name
106 })
107
108 return services
109}
110
111func dedupServicesLines(in []string) []string {
112 writeIndex := 0
113outer:
114 for readIndex := 0; readIndex < len(in); readIndex++ {
115 for compareIndex := 0; compareIndex < writeIndex; compareIndex++ {
116 if interface{}(in[readIndex]) == interface{}(in[compareIndex]) {
117 // The value at readIndex already exists somewhere in the output region
118 // of the slice before writeIndex, skip it.
119 continue outer
120 }
121 }
122 if readIndex != writeIndex {
123 in[writeIndex] = in[readIndex]
124 }
125 writeIndex++
126 }
127 return in[0:writeIndex]
128}