• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python3
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#
17"""Unzips and installs the vendor snapshot."""
18
19import argparse
20import glob
21import logging
22import os
23import re
24import shutil
25import subprocess
26import sys
27import tempfile
28import textwrap
29import json
30
31INDENT = ' ' * 4
32
33
34def get_notice_path(module_name):
35    return os.path.join('NOTICE_FILES', module_name + '.txt')
36
37
38def get_target_arch(json_rel_path):
39    return json_rel_path.split('/')[0]
40
41
42def get_arch(json_rel_path):
43    return json_rel_path.split('/')[1].split('-')[1]
44
45
46def get_variation(json_rel_path):
47    return json_rel_path.split('/')[2]
48
49# convert .bp prop dictionary to .bp prop string
50def gen_bp_prop(prop, ind):
51    bp = ''
52    for key in sorted(prop):
53        val = prop[key]
54
55        # Skip empty list or dict, rather than printing empty prop like
56        # "key: []," or "key: {},"
57        if type(val) == list and len(val) == 0:
58            continue
59        if type(val) == dict and gen_bp_prop(val, '') == '':
60            continue
61
62        bp += ind + key + ': '
63        if type(val) == bool:
64            bp += 'true,\n' if val else 'false,\n'
65        elif type(val) == str:
66            bp += '"%s",\n' % val
67        elif type(val) == list:
68            bp += '[\n'
69            for elem in val:
70                bp += ind + INDENT + '"%s",\n' % elem
71            bp += ind + '],\n'
72        elif type(val) == dict:
73            bp += '{\n'
74            bp += gen_bp_prop(val, ind + INDENT)
75            bp += ind + '},\n'
76        else:
77            raise TypeError('unsupported type %s for gen_bp_prop' % type(val))
78    return bp
79
80
81# Remove non-existent dirs from given list. Emits warning for such dirs.
82def remove_invalid_dirs(paths, bp_dir, module_name):
83    ret = []
84    for path in paths:
85        if os.path.isdir(os.path.join(bp_dir, path)):
86            ret.append(path)
87        else:
88            logging.warning('Dir "%s" of module "%s" does not exist', path,
89                            module_name)
90    return ret
91
92
93JSON_TO_BP = {
94    'ModuleName': 'name',
95    'RelativeInstallPath': 'relative_install_path',
96    'ExportedDirs': 'export_include_dirs',
97    'ExportedSystemDirs': 'export_system_include_dirs',
98    'ExportedFlags': 'export_flags',
99    'Sanitize': 'sanitize',
100    'SanitizeMinimalDep': 'sanitize_minimal_dep',
101    'SanitizeUbsanDep': 'sanitize_ubsan_dep',
102    'Symlinks': 'symlinks',
103    'InitRc': 'init_rc',
104    'VintfFragments': 'vintf_fragments',
105    'SharedLibs': 'shared_libs',
106    'RuntimeLibs': 'runtime_libs',
107    'Required': 'required',
108}
109
110SANITIZER_VARIANT_PROPS = {
111    'export_include_dirs',
112    'export_system_include_dirs',
113    'export_flags',
114    'sanitize_minimal_dep',
115    'sanitize_ubsan_dep',
116    'src',
117}
118
119EXPORTED_FLAGS_PROPS = {
120    'export_include_dirs',
121    'export_system_include_dirs',
122    'export_flags',
123}
124
125
126# Converts parsed json dictionary (which is intermediate) to Android.bp prop
127# dictionary. This validates paths such as include directories and init_rc
128# files while converting.
129def convert_json_to_bp_prop(json_path, bp_dir):
130    prop = json.load(json_path)
131    ret = {}
132
133    module_name = prop['ModuleName']
134    ret['name'] = module_name
135
136    # Soong will complain about non-existing paths on Android.bp. There might
137    # be missing files among generated header files, so check all exported
138    # directories and filter out invalid ones. Emits warning for such dirs.
139    # TODO: fix soong to track all generated headers correctly
140    for key in {'ExportedDirs', 'ExportedSystemDirs'}:
141        if key in prop:
142            prop[key] = remove_invalid_dirs(prop[key], bp_dir, module_name)
143
144    for key in prop:
145        if key in JSON_TO_BP:
146            ret[JSON_TO_BP[key]] = prop[key]
147        else:
148            logging.warning('Unknown prop "%s" of module "%s"', key,
149                            module_name)
150
151    return ret
152
153def is_64bit_arch(arch):
154    return '64' in arch # arm64, x86_64
155
156def remove_keys_from_dict(keys, d):
157    # May contain subdictionaries (e.g. cfi), so recursively erase
158    for k in list(d.keys()):
159        if k in keys:
160            del d[k]
161        elif type(d[k]) == dict:
162            remove_keys_from_dict(keys, d[k])
163
164def reexport_vndk_header(name, arch_props):
165    remove_keys_from_dict(EXPORTED_FLAGS_PROPS, arch_props)
166    for arch in arch_props:
167        arch_props[arch]['shared_libs'] = [name]
168        arch_props[arch]['export_shared_lib_headers'] = [name]
169
170def gen_bp_module(image, variation, name, version, target_arch, vndk_list, arch_props, bp_dir):
171    # Generate Android.bp module for given snapshot.
172    # If a vndk library with the same name exists, reuses exported flags of the vndk library,
173    # instead of the snapshot's own flags.
174    prop = {
175        # These three are common for all snapshot modules.
176        'version': str(version),
177        'target_arch': target_arch,
178        image: True,
179        'arch': {},
180    }
181
182    reexport_vndk_name = name
183    if reexport_vndk_name == "libc++_static":
184        reexport_vndk_name = "libc++"
185
186    if reexport_vndk_name in vndk_list:
187        if variation == 'shared':
188            logging.error("Module %s is both vendor snapshot shared and vndk" % name)
189        reexport_vndk_header(reexport_vndk_name, arch_props)
190
191    # Factor out common prop among architectures to minimize Android.bp.
192    common_prop = None
193    for arch in arch_props:
194        if common_prop is None:
195            common_prop = dict()
196            for k in arch_props[arch]:
197                common_prop[k] = arch_props[arch][k]
198            continue
199        for k in list(common_prop.keys()):
200            if k not in arch_props[arch] or common_prop[k] != arch_props[arch][k]:
201                del common_prop[k]
202
203    # Some keys has to be arch_props to prevent 32-bit only modules from being
204    # used as 64-bit modules, and vice versa.
205    for arch_prop_key in ['src', 'cfi']:
206        if arch_prop_key in common_prop:
207            del common_prop[arch_prop_key]
208    prop.update(common_prop)
209
210    has32 = has64 = False
211    stem32 = stem64 = ''
212
213    for arch in arch_props:
214        for k in common_prop:
215            if k in arch_props[arch]:
216                del arch_props[arch][k]
217        prop['arch'][arch] = arch_props[arch]
218
219        has64 |= is_64bit_arch(arch)
220        has32 |= not is_64bit_arch(arch)
221
222        # Record stem for snapshots.
223        # We don't check existence of 'src'; src must exist for executables
224        if variation == 'binary':
225            if is_64bit_arch(arch):
226                stem64 = os.path.basename(arch_props[arch]['src'])
227            else:
228                stem32 = os.path.basename(arch_props[arch]['src'])
229
230    # header snapshots doesn't need compile_multilib. The other snapshots,
231    # shared/static/object/binary snapshots, do need them
232    if variation != 'header':
233        if has32 and has64:
234            prop['compile_multilib'] = 'both'
235        elif has32:
236            prop['compile_multilib'] = '32'
237        elif has64:
238            prop['compile_multilib'] = '64'
239        else:
240            raise RuntimeError("Module %s doesn't have prebuilts." % name)
241
242    # For binary snapshots, prefer 64bit if their stem collide and installing
243    # both is impossible
244    if variation == 'binary' and stem32 == stem64:
245        prop['compile_multilib'] = 'first'
246
247    bp = '%s_snapshot_%s {\n' % (image, variation)
248    bp += gen_bp_prop(prop, INDENT)
249    bp += '}\n\n'
250    return bp
251
252def get_vndk_list(vndk_dir, target_arch):
253    """Generates vndk_libs list, e.g. ['libbase', 'libc++', ...]
254    This list is retrieved from vndk_dir/target_arch/configs/module_names.txt.
255    If it doesn't exist, print an error message and return an empty list.
256    """
257
258    module_names_path = os.path.join(vndk_dir, target_arch, 'configs/module_names.txt')
259
260    try:
261        with open(module_names_path, 'r') as f:
262            """The format of module_names.txt is a list of "{so_name} {module_name}", e.g.
263
264                lib1.so lib1
265                lib2.so lib2
266                ...
267
268            We extract the module name part.
269            """
270            return [l.split()[1] for l in f.read().strip('\n').split('\n')]
271    except IOError as e:
272        logging.error('Failed to read %s: %s' % (module_names_path, e.strerror))
273    except IndexError as e:
274        logging.error('Failed to parse %s: invalid format' % module_names_path)
275
276    return []
277
278def gen_bp_list_module(image, snapshot_version, vndk_list, target_arch, arch_props):
279    """Generates a {image}_snapshot module which contains lists of snapshots.
280    For vendor snapshot, vndk list is also included, extracted from vndk_dir.
281    """
282
283    bp = '%s_snapshot {\n' % image
284
285    bp_props = dict()
286    bp_props['name'] = '%s_snapshot' % image
287    bp_props['version'] = str(snapshot_version)
288    if image == 'vendor':
289        bp_props['vndk_libs'] = vndk_list
290
291    variant_to_property = {
292        'shared': 'shared_libs',
293        'static': 'static_libs',
294        'header': 'header_libs',
295        'binary': 'binaries',
296        'object': 'objects',
297    }
298
299    # arch_bp_prop[arch][variant_prop] = list
300    # e.g. arch_bp_prop['x86']['shared_libs'] == ['libfoo', 'libbar', ...]
301    arch_bp_prop = dict()
302
303    # Gather module lists per arch.
304    # arch_props structure: arch_props[variant][module_name][arch]
305    # e.g. arch_props['shared']['libc++']['x86']
306    for variant in arch_props:
307        variant_name = variant_to_property[variant]
308        for name in arch_props[variant]:
309            for arch in arch_props[variant][name]:
310                if arch not in arch_bp_prop:
311                    arch_bp_prop[arch] = dict()
312                if variant_name not in arch_bp_prop[arch]:
313                    arch_bp_prop[arch][variant_name] = []
314                arch_bp_prop[arch][variant_name].append(name)
315
316    bp_props['arch'] = arch_bp_prop
317    bp += gen_bp_prop(bp_props, INDENT)
318
319    bp += '}\n\n'
320    return bp
321
322def build_props(install_dir):
323    # props[target_arch]["static"|"shared"|"binary"|"header"][name][arch] : json
324    props = dict()
325
326    # {target_arch}/{arch}/{variation}/{module}.json
327    for root, _, files in os.walk(install_dir, followlinks = True):
328        for file_name in sorted(files):
329            if not file_name.endswith('.json'):
330                continue
331            full_path = os.path.join(root, file_name)
332            rel_path = os.path.relpath(full_path, install_dir)
333
334            target_arch = get_target_arch(rel_path)
335            arch = get_arch(rel_path)
336            variation = get_variation(rel_path)
337            bp_dir = os.path.join(install_dir, target_arch)
338
339            if not target_arch in props:
340                props[target_arch] = dict()
341            if not variation in props[target_arch]:
342                props[target_arch][variation] = dict()
343
344            with open(full_path, 'r') as f:
345                prop = convert_json_to_bp_prop(f, bp_dir)
346                # Remove .json after parsing?
347                # os.unlink(full_path)
348
349            if variation != 'header':
350                prop['src'] = os.path.relpath(
351                    rel_path[:-5],  # removing .json
352                    target_arch)
353
354            module_name = prop['name']
355
356            # Is this sanitized variant?
357            if 'sanitize' in prop:
358                sanitizer_type = prop['sanitize']
359                # module_name is {name}.{sanitizer_type}; trim sanitizer_type
360                module_name = module_name[:-len(sanitizer_type) - 1]
361                # Only leave props for the sanitize variant
362                for k in list(prop.keys()):
363                    if not k in SANITIZER_VARIANT_PROPS:
364                        del prop[k]
365                prop = {'name': module_name, sanitizer_type: prop}
366
367            notice_path = 'NOTICE_FILES/' + module_name + '.txt'
368            if os.path.exists(os.path.join(bp_dir, notice_path)):
369                prop['notice'] = notice_path
370
371            variation_dict = props[target_arch][variation]
372            if not module_name in variation_dict:
373                variation_dict[module_name] = dict()
374            if not arch in variation_dict[module_name]:
375                variation_dict[module_name][arch] = prop
376            else:
377                variation_dict[module_name][arch].update(prop)
378
379    return props
380
381
382def gen_bp_files(image, vndk_dir, install_dir, snapshot_version):
383    """Generates Android.bp for each archtecture.
384    Android.bp will contain a {image}_snapshot module having lists of VNDK and
385    vendor snapshot libraries, and {image}_snapshot_{variant} modules which are
386    prebuilt libraries of the snapshot.
387
388    Args:
389      image: string, name of partition (e.g. 'vendor', 'recovery')
390      vndk_dir: string, directory to which vndk snapshot is installed
391      install_dir: string, directory to which the snapshot will be installed
392      snapshot_version: int, version of the snapshot
393    """
394    props = build_props(install_dir)
395
396    for target_arch in sorted(props):
397        androidbp = ''
398        bp_dir = os.path.join(install_dir, target_arch)
399        vndk_list = []
400        if image == 'vendor':
401            vndk_list = get_vndk_list(vndk_dir, target_arch)
402
403        # Generate snapshot modules.
404        for variation in sorted(props[target_arch]):
405            for name in sorted(props[target_arch][variation]):
406                androidbp += gen_bp_module(image, variation, name,
407                                           snapshot_version, target_arch,
408                                           vndk_list,
409                                           props[target_arch][variation][name],
410                                           bp_dir)
411
412        # Generate {image}_snapshot module which contains the list of modules.
413        androidbp += gen_bp_list_module(image, snapshot_version, vndk_list,
414                                        target_arch, props[target_arch])
415        with open(os.path.join(bp_dir, 'Android.bp'), 'w') as f:
416            logging.info('Generating Android.bp to: {}'.format(f.name))
417            f.write(androidbp)
418
419
420def find_all_installed_files(install_dir):
421    installed_files = dict()
422    for root, _, files in os.walk(install_dir, followlinks = True):
423        for file_name in sorted(files):
424            if file_name.endswith('.json'):
425                continue
426            if file_name.endswith('Android.bp'):
427                continue
428            full_path = os.path.join(root, file_name)
429            size = os.stat(full_path).st_size
430            installed_files[full_path] = size
431
432    logging.debug('')
433    for f in sorted(installed_files.keys()):
434        logging.debug(f)
435    logging.debug('')
436    logging.debug('found {} installed files'.format(len(installed_files)))
437    logging.debug('')
438    return installed_files
439
440
441def find_files_in_props(target_arch, arch_install_dir, variation, name, props, file_to_info):
442    logging.debug('{} {} {} {} {}'.format(
443        target_arch, arch_install_dir, variation, name, props))
444
445    def add_info(file, name, variation, arch, is_cfi, is_header):
446        info = (name, variation, arch, is_cfi, is_header)
447        info_list = file_to_info.get(file)
448        if not info_list:
449            info_list = []
450            file_to_info[file] = info_list
451        info_list.append(info)
452
453    def find_file_in_list(dict, key, is_cfi):
454        list = dict.get(key)
455        logging.debug('    {} {}'.format(key, list))
456        if list:
457            for item in list:
458                item_path = os.path.join(arch_install_dir, item)
459                add_info(item_path, name, variation, arch, is_cfi, False)
460
461    def find_file_in_dirs(dict, key, is_cfi, is_header):
462        dirs = dict.get(key)
463        logging.debug('    {} {}'.format(key, dirs))
464        if dirs:
465            for dir in dirs:
466                dir_path = os.path.join(arch_install_dir, dir)
467                logging.debug('        scanning {}'.format(dir_path))
468                for root, _, files in os.walk(dir_path, followlinks = True):
469                    for file_name in sorted(files):
470                        item_path = os.path.join(root, file_name)
471                        add_info(item_path, name, variation, arch, is_cfi, is_header)
472
473    def find_file_in_dict(dict, is_cfi):
474        logging.debug('    arch {}'.format(arch))
475        logging.debug('    name {}'.format( name))
476        logging.debug('    is_cfi {}'.format(is_cfi))
477
478        src = dict.get('src')
479        logging.debug('    src {}'.format(src))
480        if src:
481            src_path = os.path.join(arch_install_dir, src)
482            add_info(src_path, name, variation, arch, is_cfi, False)
483
484        notice = dict.get('notice')
485        logging.debug('    notice {}'.format(notice))
486        if notice:
487            notice_path = os.path.join(arch_install_dir, notice)
488            add_info(notice_path, name, variation, arch, is_cfi, False)
489
490        find_file_in_list(dict, 'init_rc', is_cfi)
491        find_file_in_list(dict, 'vintf_fragments', is_cfi)
492
493        find_file_in_dirs(dict, 'export_include_dirs', is_cfi, True)
494        find_file_in_dirs(dict, 'export_system_include_dirs', is_cfi, True)
495
496    for arch in sorted(props):
497        name = props[arch]['name']
498        find_file_in_dict(props[arch], False)
499        cfi = props[arch].get('cfi')
500        if cfi:
501            find_file_in_dict(cfi, True)
502
503
504def find_all_props_files(install_dir):
505
506    # This function builds a database of filename to module. This means that we
507    # need to dive into the json to find the files that the vendor snapshot
508    # provides, and link these back to modules that provide them.
509
510    file_to_info = dict()
511
512    props = build_props(install_dir)
513    for target_arch in sorted(props):
514        arch_install_dir = os.path.join(install_dir, target_arch)
515        for variation in sorted(props[target_arch]):
516            for name in sorted(props[target_arch][variation]):
517                find_files_in_props(
518                    target_arch,
519                    arch_install_dir,
520                    variation,
521                    name,
522                    props[target_arch][variation][name],
523                    file_to_info)
524
525    logging.debug('')
526    for f in sorted(file_to_info.keys()):
527        logging.debug(f)
528    logging.debug('')
529    logging.debug('found {} props files'.format(len(file_to_info)))
530    logging.debug('')
531    return file_to_info
532
533
534def get_ninja_inputs(ninja_binary, ninja_build_file, modules):
535    """Returns the set of input file path strings for the given modules.
536
537    Uses the `ninja -t inputs` tool.
538
539    Args:
540        ninja_binary: The path to a ninja binary.
541        ninja_build_file: The path to a .ninja file from a build.
542        modules: The list of modules to scan for inputs.
543    """
544    inputs = set()
545    cmd = [
546        ninja_binary,
547        "-f",
548        ninja_build_file,
549        "-t",
550        "inputs",
551        "-d",
552    ] + list(modules)
553    logging.debug('invoke ninja {}'.format(cmd))
554    inputs = inputs.union(set(
555        subprocess.check_output(cmd).decode().strip("\n").split("\n")))
556
557    return inputs
558
559
560def check_module_usage(install_dir, ninja_binary, image, ninja_file, goals,
561                       output):
562    all_installed_files = find_all_installed_files(install_dir)
563    all_props_files = find_all_props_files(install_dir)
564
565    ninja_inputs = get_ninja_inputs(ninja_binary, ninja_file, goals)
566    logging.debug('')
567    logging.debug('ninja inputs')
568    for ni in ninja_inputs:
569        logging.debug(ni)
570
571    logging.debug('found {} ninja_inputs for goals {}'.format(
572        len(ninja_inputs), goals))
573
574    # Intersect the file_to_info dict with the ninja_inputs to determine
575    # which items from the vendor snapshot are actually used by the goals.
576
577    total_size = 0
578    used_size = 0
579    used_file_to_info = dict()
580
581    for file, size in all_installed_files.items():
582        total_size += size
583        if file in ninja_inputs:
584            logging.debug('used: {}'.format(file))
585            used_size += size
586            info = all_props_files.get(file)
587
588            if info:
589                used_file_to_info[file] = info
590            else:
591                logging.warning('No info for file {}'.format(file))
592                used_file_to_info[file] = 'no info'
593
594    logging.debug('Total size {}'.format(total_size))
595    logging.debug('Used size {}'.format(used_size))
596    logging.debug('')
597    logging.debug('used items')
598
599    used_modules = set()
600
601    for f, i in sorted(used_file_to_info.items()):
602        logging.debug('{} {}'.format(f, i))
603        for m in i:
604            (name, variation, arch, is_cfi, is_header) = m
605            if not is_header:
606                used_modules.add(name)
607
608    with open(output, 'w') as f:
609        f.write('%s_SNAPSHOT_MODULES := \\\n' % image.upper())
610        for m in sorted(used_modules):
611            f.write('  %s \\\n' % m)
612
613def check_call(cmd):
614    logging.debug('Running `{}`'.format(' '.join(cmd)))
615    subprocess.check_call(cmd)
616
617
618def fetch_artifact(branch, build, target, pattern, destination):
619    """Fetches build artifacts from Android Build server.
620
621    Args:
622      branch: string, branch to pull build artifacts from
623      build: string, build number to pull build artifacts from
624      target: string, target name to pull build artifacts from
625      pattern: string, pattern of build artifact file name
626      destination: string, destination to pull build artifact to
627    """
628    fetch_artifact_path = '/google/data/ro/projects/android/fetch_artifact'
629    cmd = [
630        fetch_artifact_path, '--branch', branch, '--target', target, '--bid',
631        build, pattern, destination
632    ]
633    check_call(cmd)
634
635def install_artifacts(image, branch, build, target, local_dir, symlink,
636                      install_dir):
637    """Installs vendor snapshot build artifacts to {install_dir}/v{version}.
638
639    1) Fetch build artifacts from Android Build server or from local_dir
640    2) Unzip or create symlinks to build artifacts
641
642    Args:
643      image: string, img file for which the snapshot was created (vendor,
644             recovery, etc.)
645      branch: string or None, branch name of build artifacts
646      build: string or None, build number of build artifacts
647      target: string or None, target name of build artifacts
648      local_dir: string or None, local dir to pull artifacts from
649      symlink: boolean, whether to use symlinks instead of unzipping the
650        vendor snapshot zip
651      install_dir: string, directory to install vendor snapshot
652      temp_artifact_dir: string, temp directory to hold build artifacts fetched
653        from Android Build server. For 'local' option, is set to None.
654    """
655    artifact_pattern = image + '-*.zip'
656
657    def unzip_artifacts(artifact_dir):
658        artifacts = glob.glob(os.path.join(artifact_dir, artifact_pattern))
659        for artifact in artifacts:
660            logging.info('Unzipping Vendor snapshot: {}'.format(artifact))
661            check_call(['unzip', '-qn', artifact, '-d', install_dir])
662
663    if branch and build and target:
664        with tempfile.TemporaryDirectory() as tmpdir:
665            logging.info(
666                'Fetching {pattern} from {branch} (bid: {build}, target: {target})'
667                .format(
668                    pattern=artifact_pattern,
669                    branch=branch,
670                    build=build,
671                    target=target))
672            fetch_artifact(branch, build, target, artifact_pattern, tmpdir)
673            unzip_artifacts(tmpdir)
674    elif local_dir:
675        if symlink:
676            # This assumes local_dir is the location of vendor-snapshot in the
677            # build (e.g., out/soong/vendor-snapshot).
678            #
679            # Create the first level as proper directories and the next level
680            # as symlinks.
681            for item1 in os.listdir(local_dir):
682                dest_dir = os.path.join(install_dir, item1)
683                src_dir = os.path.join(local_dir, item1)
684                if os.path.isdir(src_dir):
685                    check_call(['mkdir', '-p', dest_dir])
686                    # Create symlinks.
687                    for item2 in os.listdir(src_dir):
688                        src_item = os.path.join(src_dir, item2)
689                        logging.info('Creating symlink from {} in {}'.format(
690                            src_item, dest_dir))
691                        os.symlink(src_item, os.path.join(dest_dir, item2))
692        else:
693            logging.info('Fetching local VNDK snapshot from {}'.format(
694                local_dir))
695            unzip_artifacts(local_dir)
696    else:
697        raise RuntimeError('Neither local nor remote fetch information given.')
698
699def get_args():
700    parser = argparse.ArgumentParser()
701    parser.add_argument(
702        'snapshot_version',
703        type=int,
704        help='Vendor snapshot version to install, e.g. "30".')
705    parser.add_argument(
706        '--image',
707        help=('Image whose snapshot is being updated (e.g., vendor, '
708              'recovery , ramdisk, etc.)'),
709        default='vendor')
710    parser.add_argument('--branch', help='Branch to pull build from.')
711    parser.add_argument('--build', help='Build number to pull.')
712    parser.add_argument('--target', help='Target to pull.')
713    parser.add_argument(
714        '--local',
715        help=('Fetch local vendor snapshot artifacts from specified local '
716              'directory instead of Android Build server. '
717              'Example: --local /path/to/local/dir'))
718    parser.add_argument(
719        '--symlink',
720        action='store_true',
721        help='Use symlinks instead of unzipping vendor snapshot zip')
722    parser.add_argument(
723        '--install-dir',
724        required=True,
725        help=(
726            'Base directory to which vendor snapshot artifacts are installed. '
727            'Example: --install-dir vendor/<company name>/vendor_snapshot/v30'))
728    parser.add_argument(
729        '--overwrite',
730        action='store_true',
731        help=(
732            'If provided, does not ask before overwriting the install-dir.'))
733    parser.add_argument(
734        '--check-module-usage',
735        action='store_true',
736        help='Check which modules are used.')
737    parser.add_argument(
738        '--check-module-usage-goal',
739        action='append',
740        help='Goal(s) for which --check-module-usage is calculated.')
741    parser.add_argument(
742        '--check-module-usage-ninja-file',
743        help='Ninja file for which --check-module-usage is calculated.')
744    parser.add_argument(
745        '--check-module-usage-output',
746        help='File to which to write the check-module-usage results.')
747    parser.add_argument(
748        '--vndk-dir',
749        help='Path to installed vndk snapshot directory. Needed to retrieve '
750             'the list of VNDK. prebuilts/vndk/v{ver} will be used by default.')
751
752    parser.add_argument(
753        '-v',
754        '--verbose',
755        action='count',
756        default=0,
757        help='Increase output verbosity, e.g. "-v", "-vv".')
758    return parser.parse_args()
759
760
761def main():
762    """Program entry point."""
763    args = get_args()
764
765    verbose_map = (logging.WARNING, logging.INFO, logging.DEBUG)
766    verbosity = min(args.verbose, 2)
767    logging.basicConfig(
768        format='%(levelname)-8s [%(filename)s:%(lineno)d] %(message)s',
769        level=verbose_map[verbosity])
770
771    if not args.install_dir:
772        raise ValueError('Please provide --install-dir option.')
773    install_dir = os.path.expanduser(args.install_dir)
774
775    if args.check_module_usage:
776        ninja_binary = './prebuilts/build-tools/linux-x86/bin/ninja'
777
778        if not args.check_module_usage_goal:
779            raise ValueError('Please provide --check-module-usage-goal option.')
780        if not args.check_module_usage_ninja_file:
781            raise ValueError(
782                'Please provide --check-module-usage-ninja-file option.')
783        if not args.check_module_usage_output:
784            raise ValueError(
785                'Please provide --check-module-usage-output option.')
786
787        check_module_usage(install_dir, ninja_binary, args.image,
788                           args.check_module_usage_ninja_file,
789                           args.check_module_usage_goal,
790                           args.check_module_usage_output)
791        return
792
793    local = None
794    if args.local:
795        local = os.path.expanduser(args.local)
796
797    if local:
798        if args.build or args.branch or args.target:
799            raise ValueError(
800                'When --local option is set, --branch, --build or --target cannot be '
801                'specified.')
802        elif not os.path.isdir(local):
803            raise RuntimeError(
804                'The specified local directory, {}, does not exist.'.format(
805                    local))
806    else:
807        if not (args.build and args.branch and args.target):
808            raise ValueError(
809                'Please provide --branch, --build and --target. Or set --local '
810                'option.')
811
812    snapshot_version = args.snapshot_version
813
814    if os.path.exists(install_dir):
815        def remove_dir():
816            logging.info('Removing {}'.format(install_dir))
817            check_call(['rm', '-rf', install_dir])
818        if args.overwrite:
819            remove_dir()
820        else:
821            resp = input('Directory {} already exists. IT WILL BE REMOVED.\n'
822                         'Are you sure? (yes/no): '.format(install_dir))
823            if resp == 'yes':
824                remove_dir()
825            elif resp == 'no':
826                logging.info('Cancelled snapshot install.')
827                return
828            else:
829                raise ValueError('Did not understand: ' + resp)
830    check_call(['mkdir', '-p', install_dir])
831
832    if args.vndk_dir:
833        vndk_dir = os.path.expanduser(args.vndk_dir)
834    else:
835        vndk_dir = 'prebuilts/vndk/v%d' % snapshot_version
836        logging.debug('Using %s for vndk directory' % vndk_dir)
837
838    install_artifacts(
839        image=args.image,
840        branch=args.branch,
841        build=args.build,
842        target=args.target,
843        local_dir=local,
844        symlink=args.symlink,
845        install_dir=install_dir)
846    gen_bp_files(args.image, vndk_dir, install_dir, snapshot_version)
847
848if __name__ == '__main__':
849    main()
850