• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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	locationFlag := flags.String("location", "", "an override for the value of the location field in the proto. If not specified, the filename will be used")
76	merge := flags.String("merge", "", "merge multiple identifier protos")
77
78	writeIfChanged := flags.Bool("write_if_changed", false, "only write output file if it is modified")
79	ignoreMissingFiles := flags.Bool("ignore_missing_files", false, "ignore missing input files in merge mode")
80	stripPrefix := flags.String("strip_prefix", "", "prefix to strip off of the location field in merge mode")
81
82	flags.Parse(expandedArgs)
83
84	if *merge != "" {
85		// If merge mode was requested perform the merge and exit early.
86		err := mergeProtos(*merge, flags.Args(), *stripPrefix, *writeIfChanged, *ignoreMissingFiles)
87		if err != nil {
88			fmt.Fprintf(os.Stderr, "failed to merge protos: %s", err)
89			os.Exit(1)
90		}
91		os.Exit(0)
92	}
93
94	if *elfFile == "" && *r8File == "" {
95		fmt.Fprintf(os.Stderr, "-elf or -r8 argument is required\n")
96		flags.Usage()
97		os.Exit(1)
98	}
99
100	if *elfFile != "" && *r8File != "" {
101		fmt.Fprintf(os.Stderr, "only one of -elf or -r8 argument is allowed\n")
102		flags.Usage()
103		os.Exit(1)
104	}
105
106	if flags.NArg() != 1 {
107		flags.Usage()
108		os.Exit(1)
109	}
110
111	output := flags.Arg(0)
112
113	var identifier string
114	var location string
115	var typ symbols_map_proto.Mapping_Type
116	var err error
117
118	if *elfFile != "" {
119		typ = symbols_map_proto.Mapping_ELF
120		location = *elfFile
121		identifier, err = elf.Identifier(*elfFile, true)
122		if err != nil {
123			fmt.Fprintf(os.Stderr, "error reading elf identifier: %s\n", err)
124			os.Exit(1)
125		}
126	} else if *r8File != "" {
127		typ = symbols_map_proto.Mapping_R8
128		identifier, err = r8Identifier(*r8File)
129		location = *r8File
130		if err != nil {
131			fmt.Fprintf(os.Stderr, "error reading r8 identifier: %s\n", err)
132			os.Exit(1)
133		}
134	} else {
135		panic("shouldn't get here")
136	}
137
138	if *locationFlag != "" {
139		location = *locationFlag
140	}
141
142	mapping := symbols_map_proto.Mapping{
143		Identifier: proto.String(identifier),
144		Location:   proto.String(location),
145		Type:       typ.Enum(),
146	}
147
148	err = writeTextProto(output, &mapping, *writeIfChanged)
149	if err != nil {
150		fmt.Fprintf(os.Stderr, "error writing output: %s\n", err)
151		os.Exit(1)
152	}
153}
154
155// writeTextProto writes a proto to an output file as a textproto, optionally leaving the file
156// unmodified if it was already up to date.
157func writeTextProto(output string, message proto.Message, writeIfChanged bool) error {
158	marshaller := prototext.MarshalOptions{Multiline: true}
159	data, err := marshaller.Marshal(message)
160	if err != nil {
161		return fmt.Errorf("error marshalling textproto: %w", err)
162	}
163
164	if writeIfChanged {
165		err = pathtools.WriteFileIfChanged(output, data, 0666)
166	} else {
167		err = ioutil.WriteFile(output, data, 0666)
168	}
169
170	if err != nil {
171		return fmt.Errorf("error writing to %s: %w\n", output, err)
172	}
173
174	return nil
175}
176
177// mergeProtos merges a list of textproto files containing Mapping messages into a single textproto
178// containing a Mappings message.
179func mergeProtos(output string, inputs []string, stripPrefix string, writeIfChanged bool, ignoreMissingFiles bool) error {
180	mappings := symbols_map_proto.Mappings{}
181	for _, input := range inputs {
182		mapping := symbols_map_proto.Mapping{}
183		data, err := ioutil.ReadFile(input)
184		if err != nil {
185			if ignoreMissingFiles && os.IsNotExist(err) {
186				// Merge mode is used on a list of files in the packaging directory.  If multiple
187				// goals are included on the build command line, for example `dist` and `tests`,
188				// then the symbols packaging rule for `dist` can run while a dependency of `tests`
189				// is modifying the symbols packaging directory.  That can result in a file that
190				// existed when the file list was generated being deleted as part of updating it,
191				// resulting in sporadic ENOENT errors.  Ignore them if -ignore_missing_files
192				// was passed on the command line.
193				continue
194			}
195			return fmt.Errorf("failed to read %s: %w", input, err)
196		}
197		err = prototext.Unmarshal(data, &mapping)
198		if err != nil {
199			return fmt.Errorf("failed to parse textproto %s: %w", input, err)
200		}
201		if stripPrefix != "" && mapping.Location != nil {
202			mapping.Location = proto.String(strings.TrimPrefix(*mapping.Location, stripPrefix))
203		}
204		mappings.Mappings = append(mappings.Mappings, &mapping)
205	}
206
207	return writeTextProto(output, &mappings, writeIfChanged)
208}
209