blob: 938446d48fc0913d8a791ae9845a6b448f761946 [file] [log] [blame]
Colin Cross36f55aa2022-03-21 18:46:41 -07001// Copyright 2022 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 "strings"
23
24 "android/soong/cmd/symbols_map/symbols_map_proto"
25 "android/soong/response"
26
27 "github.com/google/blueprint/pathtools"
28 "google.golang.org/protobuf/encoding/prototext"
29 "google.golang.org/protobuf/proto"
30)
31
32// This tool is used to extract a hash from an elf file or an r8 dictionary and store it as a
33// textproto, or to merge multiple textprotos together.
34
35func main() {
36 var expandedArgs []string
37 for _, arg := range os.Args[1:] {
38 if strings.HasPrefix(arg, "@") {
39 f, err := os.Open(strings.TrimPrefix(arg, "@"))
40 if err != nil {
41 fmt.Fprintln(os.Stderr, err.Error())
42 os.Exit(1)
43 }
44
45 respArgs, err := response.ReadRspFile(f)
46 f.Close()
47 if err != nil {
48 fmt.Fprintln(os.Stderr, err.Error())
49 os.Exit(1)
50 }
51 expandedArgs = append(expandedArgs, respArgs...)
52 } else {
53 expandedArgs = append(expandedArgs, arg)
54 }
55 }
56
57 flags := flag.NewFlagSet("flags", flag.ExitOnError)
58
59 // Hide the flag package to prevent accidental references to flag instead of flags.
60 flag := struct{}{}
61 _ = flag
62
63 flags.Usage = func() {
64 fmt.Fprintf(flags.Output(), "Usage of %s:\n", os.Args[0])
65 fmt.Fprintf(flags.Output(), " %s -elf|-r8 <input file> [-write_if_changed] <output file>\n", os.Args[0])
66 fmt.Fprintf(flags.Output(), " %s -merge <output file> [-write_if_changed] [-ignore_missing_files] [-strip_prefix <prefix>] [<input file>...]\n", os.Args[0])
67 fmt.Fprintln(flags.Output())
68
69 flags.PrintDefaults()
70 }
71
72 elfFile := flags.String("elf", "", "extract identifier from an elf file")
73 r8File := flags.String("r8", "", "extract identifier from an r8 dictionary")
74 merge := flags.String("merge", "", "merge multiple identifier protos")
75
76 writeIfChanged := flags.Bool("write_if_changed", false, "only write output file if it is modified")
77 ignoreMissingFiles := flags.Bool("ignore_missing_files", false, "ignore missing input files in merge mode")
78 stripPrefix := flags.String("strip_prefix", "", "prefix to strip off of the location field in merge mode")
79
80 flags.Parse(expandedArgs)
81
82 if *merge != "" {
83 // If merge mode was requested perform the merge and exit early.
84 err := mergeProtos(*merge, flags.Args(), *stripPrefix, *writeIfChanged, *ignoreMissingFiles)
85 if err != nil {
86 fmt.Fprintf(os.Stderr, "failed to merge protos: %s", err)
87 os.Exit(1)
88 }
89 os.Exit(0)
90 }
91
92 if *elfFile == "" && *r8File == "" {
93 fmt.Fprintf(os.Stderr, "-elf or -r8 argument is required\n")
94 flags.Usage()
95 os.Exit(1)
96 }
97
98 if *elfFile != "" && *r8File != "" {
99 fmt.Fprintf(os.Stderr, "only one of -elf or -r8 argument is allowed\n")
100 flags.Usage()
101 os.Exit(1)
102 }
103
104 if flags.NArg() != 1 {
105 flags.Usage()
106 os.Exit(1)
107 }
108
109 output := flags.Arg(0)
110
111 var identifier string
112 var location string
113 var typ symbols_map_proto.Mapping_Type
114 var err error
115
116 if *elfFile != "" {
117 typ = symbols_map_proto.Mapping_ELF
118 location = *elfFile
119 identifier, err = elfIdentifier(*elfFile, true)
120 if err != nil {
121 fmt.Fprintf(os.Stderr, "error reading elf identifier: %s\n", err)
122 os.Exit(1)
123 }
124 } else if *r8File != "" {
125 typ = symbols_map_proto.Mapping_R8
126 identifier, err = r8Identifier(*r8File)
127 location = *r8File
128 if err != nil {
129 fmt.Fprintf(os.Stderr, "error reading r8 identifier: %s\n", err)
130 os.Exit(1)
131 }
132 } else {
133 panic("shouldn't get here")
134 }
135
136 mapping := symbols_map_proto.Mapping{
137 Identifier: proto.String(identifier),
138 Location: proto.String(location),
139 Type: typ.Enum(),
140 }
141
142 err = writeTextProto(output, &mapping, *writeIfChanged)
143 if err != nil {
144 fmt.Fprintf(os.Stderr, "error writing output: %s\n", err)
145 os.Exit(1)
146 }
147}
148
149// writeTextProto writes a proto to an output file as a textproto, optionally leaving the file
150// unmodified if it was already up to date.
151func writeTextProto(output string, message proto.Message, writeIfChanged bool) error {
152 marshaller := prototext.MarshalOptions{Multiline: true}
153 data, err := marshaller.Marshal(message)
154 if err != nil {
155 return fmt.Errorf("error marshalling textproto: %w", err)
156 }
157
158 if writeIfChanged {
159 err = pathtools.WriteFileIfChanged(output, data, 0666)
160 } else {
161 err = ioutil.WriteFile(output, data, 0666)
162 }
163
164 if err != nil {
165 return fmt.Errorf("error writing to %s: %w\n", output, err)
166 }
167
168 return nil
169}
170
171// mergeProtos merges a list of textproto files containing Mapping messages into a single textproto
172// containing a Mappings message.
173func mergeProtos(output string, inputs []string, stripPrefix string, writeIfChanged bool, ignoreMissingFiles bool) error {
174 mappings := symbols_map_proto.Mappings{}
175 for _, input := range inputs {
176 mapping := symbols_map_proto.Mapping{}
177 data, err := ioutil.ReadFile(input)
178 if err != nil {
179 if ignoreMissingFiles && os.IsNotExist(err) {
180 // Merge mode is used on a list of files in the packaging directory. If multiple
181 // goals are included on the build command line, for example `dist` and `tests`,
182 // then the symbols packaging rule for `dist` can run while a dependency of `tests`
183 // is modifying the symbols packaging directory. That can result in a file that
184 // existed when the file list was generated being deleted as part of updating it,
185 // resulting in sporadic ENOENT errors. Ignore them if -ignore_missing_files
186 // was passed on the command line.
187 continue
188 }
189 return fmt.Errorf("failed to read %s: %w", input, err)
190 }
191 err = prototext.Unmarshal(data, &mapping)
192 if err != nil {
193 return fmt.Errorf("failed to parse textproto %s: %w", input, err)
194 }
195 if stripPrefix != "" && mapping.Location != nil {
196 mapping.Location = proto.String(strings.TrimPrefix(*mapping.Location, stripPrefix))
197 }
198 mappings.Mappings = append(mappings.Mappings, &mapping)
199 }
200
201 return writeTextProto(output, &mappings, writeIfChanged)
202}