xref: /aosp_15_r20/build/soong/scripts/conv_linker_config.py (revision 333d2b3687b3a337dbcca9d65000bca186795e39)
1#!/usr/bin/env python
2#
3# Copyright (C) 2020 The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9#      http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16""" A tool to convert json file into pb with linker config format."""
17
18import argparse
19import collections
20import json
21import os
22import sys
23
24import linker_config_pb2 #pylint: disable=import-error
25from google.protobuf.descriptor import FieldDescriptor
26from google.protobuf.json_format import ParseDict
27from google.protobuf.text_format import MessageToString
28
29
30def LoadJsonMessage(path):
31    """
32    Loads a message from a .json file with `//` comments strippedfor convenience.
33    """
34    json_content = ''
35    with open(path) as f:
36        for line in f:
37            if not line.lstrip().startswith('//'):
38                json_content += line
39    obj = json.loads(json_content, object_pairs_hook=collections.OrderedDict)
40    return ParseDict(obj, linker_config_pb2.LinkerConfig())
41
42
43def Proto(args):
44    """
45    Merges input json files (--source) into a protobuf message (--output).
46    Fails if the output file exists. Set --force or --append to deal with the existing
47    output file.
48    --force to overwrite the output file with the input (.json files).
49    --append to append the input to the output file.
50    """
51    pb = linker_config_pb2.LinkerConfig()
52    if os.path.isfile(args.output):
53        if args.force:
54            pass
55        elif args.append:
56            with open(args.output, 'rb') as f:
57                pb.ParseFromString(f.read())
58        else:
59            sys.stderr.write(f'Error: {args.output} exists. Use --force or --append.\n')
60            sys.exit(1)
61
62    if args.source:
63        for input in args.source.split(':'):
64            pb.MergeFrom(LoadJsonMessage(input))
65
66    ValidateAndWriteAsPbFile(pb, args.output)
67
68
69def Print(args):
70    with open(args.source, 'rb') as f:
71        pb = linker_config_pb2.LinkerConfig()
72        pb.ParseFromString(f.read())
73    print(MessageToString(pb))
74
75
76def SystemProvide(args):
77    pb = linker_config_pb2.LinkerConfig()
78    with open(args.source, 'rb') as f:
79        pb.ParseFromString(f.read())
80    libraries = args.value.split()
81
82    def IsInLibPath(lib_name):
83        lib_path = os.path.join(args.system, 'lib', lib_name)
84        lib64_path = os.path.join(args.system, 'lib64', lib_name)
85        return os.path.exists(lib_path) or os.path.islink(
86            lib_path) or os.path.exists(lib64_path) or os.path.islink(
87                lib64_path)
88
89    installed_libraries = [lib for lib in libraries if IsInLibPath(lib)]
90    for item in installed_libraries:
91        if item not in getattr(pb, 'provideLibs'):
92            getattr(pb, 'provideLibs').append(item)
93
94    ValidateAndWriteAsPbFile(pb, args.output)
95
96
97def Append(args):
98    pb = linker_config_pb2.LinkerConfig()
99    with open(args.source, 'rb') as f:
100        pb.ParseFromString(f.read())
101
102    if getattr(type(pb),
103               args.key).DESCRIPTOR.label == FieldDescriptor.LABEL_REPEATED:
104        for value in args.value.split():
105            getattr(pb, args.key).append(value)
106    else:
107        setattr(pb, args.key, args.value)
108
109    ValidateAndWriteAsPbFile(pb, args.output)
110
111
112
113def Merge(args):
114    pb = linker_config_pb2.LinkerConfig()
115    for other in args.input:
116        with open(other, 'rb') as f:
117            pb.MergeFromString(f.read())
118
119    ValidateAndWriteAsPbFile(pb, args.output)
120
121
122def Validate(args):
123    if os.path.isdir(args.input):
124        config_file = os.path.join(args.input, 'etc/linker.config.pb')
125        if os.path.exists(config_file):
126            args.input = config_file
127            Validate(args)
128        # OK if there's no linker config file.
129        return
130
131    if not os.path.isfile(args.input):
132        sys.exit(f"{args.input} is not a file")
133
134    pb = linker_config_pb2.LinkerConfig()
135    with open(args.input, 'rb') as f:
136        pb.ParseFromString(f.read())
137
138    if args.type == 'apex':
139        # Shouldn't use provideLibs/requireLibs in APEX linker.config.pb
140        if getattr(pb, 'provideLibs'):
141            sys.exit(f'{args.input}: provideLibs is set. Use provideSharedLibs in apex_manifest')
142        if getattr(pb, 'requireLibs'):
143            sys.exit(f'{args.input}: requireLibs is set. Use requireSharedLibs in apex_manifest')
144    elif args.type == 'system':
145        if getattr(pb, 'visible'):
146            sys.exit(f'{args.input}: do not use visible, which is for APEX')
147        if getattr(pb, 'permittedPaths'):
148            sys.exit(f'{args.input}: do not use permittedPaths, which is for APEX')
149    else:
150        sys.exit(f'Unknown type: {args.type}')
151
152    # Reject contributions field at build time while keeping the runtime behavior for GRF.
153    if getattr(pb, 'contributions'):
154        sys.exit(f"{args.input}: 'contributions' is set. "
155                 "It's deprecated. Instead, make the APEX 'visible' and use android_dlopen_ext().")
156
157
158def ValidateAndWriteAsPbFile(pb, output_path):
159    ValidateConfiguration(pb)
160    with open(output_path, 'wb') as f:
161        f.write(pb.SerializeToString())
162
163
164def ValidateConfiguration(pb):
165    """
166    Validate if the configuration is valid to be used as linker configuration
167    """
168
169    # Validate if provideLibs and requireLibs have common module
170    provideLibs = set(getattr(pb, 'provideLibs'))
171    requireLibs = set(getattr(pb, 'requireLibs'))
172
173    intersectLibs = provideLibs.intersection(requireLibs)
174
175    if intersectLibs:
176        for lib in intersectLibs:
177            print(f'{lib} exists both in requireLibs and provideLibs', file=sys.stderr)
178        sys.exit(1)
179
180
181def GetArgParser():
182    parser = argparse.ArgumentParser()
183    subparsers = parser.add_subparsers()
184
185    parser_proto = subparsers.add_parser(
186        'proto',
187        help='Convert the input JSON configuration file into protobuf.')
188    parser_proto.add_argument(
189        '-s',
190        '--source',
191        nargs='?',
192        type=str,
193        help='Colon-separated list of linker configuration files in JSON.')
194    parser_proto.add_argument(
195        '-o',
196        '--output',
197        required=True,
198        type=str,
199        help='Target path to create protobuf file.')
200    option_for_existing_output = parser_proto.add_mutually_exclusive_group()
201    option_for_existing_output.add_argument(
202        '-f',
203        '--force',
204        action='store_true',
205        help='Overwrite if the output file exists.')
206    option_for_existing_output.add_argument(
207        '-a',
208        '--append',
209        action='store_true',
210        help='Append the input to the output file if the output file exists.')
211    parser_proto.set_defaults(func=Proto)
212
213    print_proto = subparsers.add_parser(
214        'print', help='Print configuration in human-readable text format.')
215    print_proto.add_argument(
216        '-s',
217        '--source',
218        required=True,
219        type=str,
220        help='Source linker configuration file in protobuf.')
221    print_proto.set_defaults(func=Print)
222
223    system_provide_libs = subparsers.add_parser(
224        'systemprovide',
225        help='Append system provide libraries into the configuration.')
226    system_provide_libs.add_argument(
227        '-s',
228        '--source',
229        required=True,
230        type=str,
231        help='Source linker configuration file in protobuf.')
232    system_provide_libs.add_argument(
233        '-o',
234        '--output',
235        required=True,
236        type=str,
237        help='Target linker configuration file to write in protobuf.')
238    system_provide_libs.add_argument(
239        '--value',
240        required=True,
241        type=str,
242        help='Values of the libraries to append. If there are more than one '
243        'it should be separated by empty space'
244    )
245    system_provide_libs.add_argument(
246        '--system', required=True, type=str, help='Path of the system image.')
247    system_provide_libs.set_defaults(func=SystemProvide)
248
249    append = subparsers.add_parser(
250        'append', help='Append value(s) to given key.')
251    append.add_argument(
252        '-s',
253        '--source',
254        required=True,
255        type=str,
256        help='Source linker configuration file in protobuf.')
257    append.add_argument(
258        '-o',
259        '--output',
260        required=True,
261        type=str,
262        help='Target linker configuration file to write in protobuf.')
263    append.add_argument('--key', required=True, type=str, help='.')
264    append.add_argument(
265        '--value',
266        required=True,
267        type=str,
268        help='Values of the libraries to append. If there are more than one'
269        'it should be separated by empty space'
270    )
271    append.set_defaults(func=Append)
272
273    append = subparsers.add_parser('merge', help='Merge configurations')
274    append.add_argument(
275        '-o',
276        '--out',
277        required=True,
278        type=str,
279        help='Output linker configuration file to write in protobuf.')
280    append.add_argument(
281        '-i',
282        '--input',
283        nargs='+',
284        type=str,
285        help='Linker configuration files to merge.')
286    append.set_defaults(func=Merge)
287
288    validate = subparsers.add_parser('validate', help='Validate configuration')
289    validate.add_argument(
290        '--type',
291        required=True,
292        choices=['apex', 'system'],
293        help='Type of linker configuration')
294    validate.add_argument(
295        'input',
296        help='Input can be a directory which has etc/linker.config.pb or a path'
297        ' to the linker config file')
298    validate.set_defaults(func=Validate)
299
300    return parser
301
302
303def main():
304    parser = GetArgParser()
305    args = parser.parse_args()
306    if 'func' in args:
307        args.func(args)
308    else:
309        parser.print_help()
310
311
312if __name__ == '__main__':
313    main()
314