1// 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/elf" 26 "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 120 identifier, err = elf.Identifier(*elfFile, true) 121 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} 204