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