• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python3
2
3from __future__ import print_function
4
5import argparse
6import collections
7import copy
8import csv
9import itertools
10import json
11import os
12import re
13import shutil
14import stat
15import struct
16import sys
17
18
19#------------------------------------------------------------------------------
20# Python 2 and 3 Compatibility Layer
21#------------------------------------------------------------------------------
22
23if sys.version_info >= (3, 0):
24    from os import makedirs
25    from mmap import ACCESS_READ, mmap
26else:
27    from mmap import ACCESS_READ, mmap
28
29    def makedirs(path, exist_ok):
30        if exist_ok and os.path.isdir(path):
31            return
32        return os.makedirs(path)
33
34    class mmap(mmap):
35        def __enter__(self):
36            return self
37
38        def __exit__(self, exc, value, tb):
39            self.close()
40
41        def __getitem__(self, key):
42            res = super(mmap, self).__getitem__(key)
43            if type(key) == int:
44                return ord(res)
45            return res
46
47    FileNotFoundError = OSError
48
49try:
50    from sys import intern
51except ImportError:
52    pass
53
54
55#------------------------------------------------------------------------------
56# Collections
57#------------------------------------------------------------------------------
58
59def defaultnamedtuple(typename, field_names, default):
60    """Create a namedtuple type with default values.
61
62    This function creates a namedtuple type which will fill in default value
63    when actual arguments to the constructor were omitted.
64
65    >>> Point = defaultnamedtuple('Point', ['x', 'y'], 0)
66    >>> Point()
67    Point(x=0, y=0)
68    >>> Point(1)
69    Point(x=1, y=0)
70    >>> Point(1, 2)
71    Point(x=1, y=2)
72    >>> Point(x=1, y=2)
73    Point(x=1, y=2)
74    >>> Point(y=2, x=1)
75    Point(x=1, y=2)
76
77    >>> PermSet = defaultnamedtuple('PermSet', 'allowed disallowed', set())
78    >>> s = PermSet()
79    >>> s
80    PermSet(allowed=set(), disallowed=set())
81    >>> s.allowed is not s.disallowed
82    True
83    >>> PermSet({1})
84    PermSet(allowed={1}, disallowed=set())
85    >>> PermSet({1}, {2})
86    PermSet(allowed={1}, disallowed={2})
87    """
88
89    if isinstance(field_names, str):
90        field_names = field_names.replace(',', ' ').split()
91    field_names = list(map(str, field_names))
92    num_fields = len(field_names)
93
94    base_cls = collections.namedtuple(typename, field_names)
95    def __new__(cls, *args, **kwargs):
96        args = list(args)
97        for i in range(len(args), num_fields):
98            arg = kwargs.get(field_names[i])
99            if arg:
100                args.append(arg)
101            else:
102                args.append(copy.copy(default))
103        return base_cls.__new__(cls, *args)
104    return type(typename, (base_cls,), {'__new__': __new__})
105
106
107#------------------------------------------------------------------------------
108# ELF Parser
109#------------------------------------------------------------------------------
110
111Elf_Hdr = collections.namedtuple(
112        'Elf_Hdr',
113        'ei_class ei_data ei_version ei_osabi e_type e_machine e_version '
114        'e_entry e_phoff e_shoff e_flags e_ehsize e_phentsize e_phnum '
115        'e_shentsize e_shnum e_shstridx')
116
117
118Elf_Shdr = collections.namedtuple(
119        'Elf_Shdr',
120        'sh_name sh_type sh_flags sh_addr sh_offset sh_size sh_link sh_info '
121        'sh_addralign sh_entsize')
122
123
124Elf_Dyn = collections.namedtuple('Elf_Dyn', 'd_tag d_val')
125
126
127class Elf_Sym(collections.namedtuple(
128    'ELF_Sym', 'st_name st_value st_size st_info st_other st_shndx')):
129
130    STB_LOCAL = 0
131    STB_GLOBAL = 1
132    STB_WEAK = 2
133
134    SHN_UNDEF = 0
135
136    @property
137    def st_bind(self):
138        return (self.st_info >> 4)
139
140    @property
141    def is_local(self):
142        return self.st_bind == Elf_Sym.STB_LOCAL
143
144    @property
145    def is_global(self):
146        return self.st_bind == Elf_Sym.STB_GLOBAL
147
148    @property
149    def is_weak(self):
150        return self.st_bind == Elf_Sym.STB_WEAK
151
152    @property
153    def is_undef(self):
154        return self.st_shndx == Elf_Sym.SHN_UNDEF
155
156
157class ELFError(ValueError):
158    pass
159
160
161class ELF(object):
162    # ELF file format constants.
163    ELF_MAGIC = b'\x7fELF'
164
165    EI_CLASS = 4
166    EI_DATA = 5
167
168    ELFCLASSNONE = 0
169    ELFCLASS32 = 1
170    ELFCLASS64 = 2
171
172    ELFDATANONE = 0
173    ELFDATA2LSB = 1
174    ELFDATA2MSB = 2
175
176    DT_NEEDED = 1
177    DT_RPATH = 15
178    DT_RUNPATH = 29
179
180    _ELF_CLASS_NAMES = {
181        ELFCLASS32: '32',
182        ELFCLASS64: '64',
183    }
184
185    _ELF_DATA_NAMES = {
186        ELFDATA2LSB: 'Little-Endian',
187        ELFDATA2MSB: 'Big-Endian',
188    }
189
190    EM_NONE = 0
191    EM_386 = 3
192    EM_MIPS = 8
193    EM_ARM = 40
194    EM_X86_64 = 62
195    EM_AARCH64 = 183
196
197    def _create_elf_machines(d):
198        elf_machine_ids = {}
199        for key, value in d.items():
200            if key.startswith('EM_'):
201                elf_machine_ids[value] = key
202        return elf_machine_ids
203
204    ELF_MACHINES = _create_elf_machines(locals())
205
206    del _create_elf_machines
207
208
209    @staticmethod
210    def _dict_find_key_by_value(d, dst):
211        for key, value in d.items():
212            if value == dst:
213                return key
214        raise KeyError(dst)
215
216    @staticmethod
217    def get_ei_class_from_name(name):
218        return ELF._dict_find_key_by_value(ELF._ELF_CLASS_NAMES, name)
219
220    @staticmethod
221    def get_ei_data_from_name(name):
222        return ELF._dict_find_key_by_value(ELF._ELF_DATA_NAMES, name)
223
224    @staticmethod
225    def get_e_machine_from_name(name):
226        return ELF._dict_find_key_by_value(ELF.ELF_MACHINES, name)
227
228
229    __slots__ = ('ei_class', 'ei_data', 'e_machine', 'dt_rpath', 'dt_runpath',
230                 'dt_needed', 'exported_symbols', 'imported_symbols',)
231
232
233    def __init__(self, ei_class=ELFCLASSNONE, ei_data=ELFDATANONE, e_machine=0,
234                 dt_rpath=None, dt_runpath=None, dt_needed=None,
235                 exported_symbols=None, imported_symbols=None):
236        self.ei_class = ei_class
237        self.ei_data = ei_data
238        self.e_machine = e_machine
239        self.dt_rpath = dt_rpath if dt_rpath is not None else []
240        self.dt_runpath = dt_runpath if dt_runpath is not None else []
241        self.dt_needed = dt_needed if dt_needed is not None else []
242        self.exported_symbols = \
243                exported_symbols if exported_symbols is not None else set()
244        self.imported_symbols = \
245                imported_symbols if imported_symbols is not None else set()
246
247    def __repr__(self):
248        args = (a + '=' + repr(getattr(self, a)) for a in self.__slots__)
249        return 'ELF(' + ', '.join(args) + ')'
250
251    def __eq__(self, rhs):
252        return all(getattr(self, a) == getattr(rhs, a) for a in self.__slots__)
253
254    @property
255    def elf_class_name(self):
256        return self._ELF_CLASS_NAMES.get(self.ei_class, 'None')
257
258    @property
259    def elf_data_name(self):
260        return self._ELF_DATA_NAMES.get(self.ei_data, 'None')
261
262    @property
263    def elf_machine_name(self):
264        return self.ELF_MACHINES.get(self.e_machine, str(self.e_machine))
265
266    @property
267    def is_32bit(self):
268        return self.ei_class == ELF.ELFCLASS32
269
270    @property
271    def is_64bit(self):
272        return self.ei_class == ELF.ELFCLASS64
273
274    @property
275    def sorted_exported_symbols(self):
276        return sorted(list(self.exported_symbols))
277
278    @property
279    def sorted_imported_symbols(self):
280        return sorted(list(self.imported_symbols))
281
282    def dump(self, file=None):
283        """Print parsed ELF information to the file"""
284        file = file if file is not None else sys.stdout
285
286        print('EI_CLASS\t' + self.elf_class_name, file=file)
287        print('EI_DATA\t\t' + self.elf_data_name, file=file)
288        print('E_MACHINE\t' + self.elf_machine_name, file=file)
289        for dt_rpath in self.dt_rpath:
290            print('DT_RPATH\t' + dt_rpath, file=file)
291        for dt_runpath in self.dt_runpath:
292            print('DT_RUNPATH\t' + dt_runpath, file=file)
293        for dt_needed in self.dt_needed:
294            print('DT_NEEDED\t' + dt_needed, file=file)
295        for symbol in self.sorted_exported_symbols:
296            print('EXP_SYMBOL\t' + symbol, file=file)
297        for symbol in self.sorted_imported_symbols:
298            print('IMP_SYMBOL\t' + symbol, file=file)
299
300    # Extract zero-terminated buffer slice.
301    def _extract_zero_terminated_buf_slice(self, buf, offset):
302        """Extract a zero-terminated buffer slice from the given offset"""
303        end = buf.find(b'\0', offset)
304        if end == -1:
305            return buf[offset:]
306        return buf[offset:end]
307
308    # Extract c-style interned string from the buffer.
309    if sys.version_info >= (3, 0):
310        def _extract_zero_terminated_str(self, buf, offset):
311            """Extract a c-style string from the given buffer and offset"""
312            buf_slice = self._extract_zero_terminated_buf_slice(buf, offset)
313            return intern(buf_slice.decode('utf-8'))
314    else:
315        def _extract_zero_terminated_str(self, buf, offset):
316            """Extract a c-style string from the given buffer and offset"""
317            return intern(self._extract_zero_terminated_buf_slice(buf, offset))
318
319    def _parse_from_buf_internal(self, buf):
320        """Parse ELF image resides in the buffer"""
321
322        # Check ELF ident.
323        if buf.size() < 8:
324            raise ELFError('bad ident')
325
326        if buf[0:4] != ELF.ELF_MAGIC:
327            raise ELFError('bad magic')
328
329        self.ei_class = buf[ELF.EI_CLASS]
330        if self.ei_class not in (ELF.ELFCLASS32, ELF.ELFCLASS64):
331            raise ELFError('unknown word size')
332
333        self.ei_data = buf[ELF.EI_DATA]
334        if self.ei_data not in (ELF.ELFDATA2LSB, ELF.ELFDATA2MSB):
335            raise ELFError('unknown endianness')
336
337        # ELF structure definitions.
338        endian_fmt = '<' if self.ei_data == ELF.ELFDATA2LSB else '>'
339
340        if self.is_32bit:
341            elf_hdr_fmt = endian_fmt + '4x4B8xHHLLLLLHHHHHH'
342            elf_shdr_fmt = endian_fmt + 'LLLLLLLLLL'
343            elf_dyn_fmt = endian_fmt + 'lL'
344            elf_sym_fmt = endian_fmt + 'LLLBBH'
345        else:
346            elf_hdr_fmt = endian_fmt + '4x4B8xHHLQQQLHHHHHH'
347            elf_shdr_fmt = endian_fmt + 'LLQQQQLLQQ'
348            elf_dyn_fmt = endian_fmt + 'QQ'
349            elf_sym_fmt = endian_fmt + 'LBBHQQ'
350
351        def parse_struct(cls, fmt, offset, error_msg):
352            try:
353                return cls._make(struct.unpack_from(fmt, buf, offset))
354            except struct.error:
355                raise ELFError(error_msg)
356
357        def parse_elf_hdr(offset):
358            return parse_struct(Elf_Hdr, elf_hdr_fmt, offset, 'bad elf header')
359
360        def parse_elf_shdr(offset):
361            return parse_struct(Elf_Shdr, elf_shdr_fmt, offset,
362                                'bad section header')
363
364        def parse_elf_dyn(offset):
365            return parse_struct(Elf_Dyn, elf_dyn_fmt, offset,
366                                'bad .dynamic entry')
367
368        if self.is_32bit:
369            def parse_elf_sym(offset):
370                return parse_struct(Elf_Sym, elf_sym_fmt, offset, 'bad elf sym')
371        else:
372            def parse_elf_sym(offset):
373                try:
374                    p = struct.unpack_from(elf_sym_fmt, buf, offset)
375                    return Elf_Sym(p[0], p[4], p[5], p[1], p[2], p[3])
376                except struct.error:
377                    raise ELFError('bad elf sym')
378
379        def extract_str(offset):
380            return self._extract_zero_terminated_str(buf, offset)
381
382        # Parse ELF header.
383        header = parse_elf_hdr(0)
384        self.e_machine = header.e_machine
385
386        # Check section header size.
387        if header.e_shentsize == 0:
388            raise ELFError('no section header')
389
390        # Find .shstrtab section.
391        shstrtab_shdr_off = \
392                header.e_shoff + header.e_shstridx * header.e_shentsize
393        shstrtab_shdr = parse_elf_shdr(shstrtab_shdr_off)
394        shstrtab_off = shstrtab_shdr.sh_offset
395
396        # Parse ELF section header.
397        sections = dict()
398        header_end = header.e_shoff + header.e_shnum * header.e_shentsize
399        for shdr_off in range(header.e_shoff, header_end, header.e_shentsize):
400            shdr = parse_elf_shdr(shdr_off)
401            name = extract_str(shstrtab_off + shdr.sh_name)
402            sections[name] = shdr
403
404        # Find .dynamic and .dynstr section header.
405        dynamic_shdr = sections.get('.dynamic')
406        if not dynamic_shdr:
407            raise ELFError('no .dynamic section')
408
409        dynstr_shdr = sections.get('.dynstr')
410        if not dynstr_shdr:
411            raise ELFError('no .dynstr section')
412
413        dynamic_off = dynamic_shdr.sh_offset
414        dynstr_off = dynstr_shdr.sh_offset
415
416        # Parse entries in .dynamic section.
417        assert struct.calcsize(elf_dyn_fmt) == dynamic_shdr.sh_entsize
418        dynamic_end = dynamic_off + dynamic_shdr.sh_size
419        for ent_off in range(dynamic_off, dynamic_end, dynamic_shdr.sh_entsize):
420            ent = parse_elf_dyn(ent_off)
421            if ent.d_tag == ELF.DT_NEEDED:
422                self.dt_needed.append(extract_str(dynstr_off + ent.d_val))
423            elif ent.d_tag == ELF.DT_RPATH:
424                self.dt_rpath.extend(
425                        extract_str(dynstr_off + ent.d_val).split(':'))
426            elif ent.d_tag == ELF.DT_RUNPATH:
427                self.dt_runpath.extend(
428                        extract_str(dynstr_off + ent.d_val).split(':'))
429
430        # Parse exported symbols in .dynsym section.
431        dynsym_shdr = sections.get('.dynsym')
432        if dynsym_shdr:
433            exp_symbols = self.exported_symbols
434            imp_symbols = self.imported_symbols
435
436            dynsym_off = dynsym_shdr.sh_offset
437            dynsym_end = dynsym_off + dynsym_shdr.sh_size
438            dynsym_entsize = dynsym_shdr.sh_entsize
439
440            # Skip first symbol entry (null symbol).
441            dynsym_off += dynsym_entsize
442
443            for ent_off in range(dynsym_off, dynsym_end, dynsym_entsize):
444                ent = parse_elf_sym(ent_off)
445                symbol_name = extract_str(dynstr_off + ent.st_name)
446                if ent.is_undef:
447                    imp_symbols.add(symbol_name)
448                elif not ent.is_local:
449                    exp_symbols.add(symbol_name)
450
451    def _parse_from_buf(self, buf):
452        """Parse ELF image resides in the buffer"""
453        try:
454            self._parse_from_buf_internal(buf)
455        except IndexError:
456            raise ELFError('bad offset')
457
458    def _parse_from_file(self, path):
459        """Parse ELF image from the file path"""
460        with open(path, 'rb') as f:
461            st = os.fstat(f.fileno())
462            if not st.st_size:
463                raise ELFError('empty file')
464            with mmap(f.fileno(), st.st_size, access=ACCESS_READ) as image:
465                self._parse_from_buf(image)
466
467    def _parse_from_dump_lines(self, path, lines):
468        patt = re.compile('^([A-Za-z_]+)\t+(.*)$')
469        for line_no, line in enumerate(lines):
470            match = patt.match(line)
471            if not match:
472                print('error: {}: {}: failed to parse'
473                        .format(path, line_no + 1), file=sys.stderr)
474                continue
475            key = match.group(1)
476            value = match.group(2)
477
478            if key == 'EI_CLASS':
479                self.ei_class = ELF.get_ei_class_from_name(value)
480            elif key == 'EI_DATA':
481                self.ei_data = ELF.get_ei_data_from_name(value)
482            elif key == 'E_MACHINE':
483                self.e_machine = ELF.get_e_machine_from_name(value)
484            elif key == 'DT_RPATH':
485                self.dt_rpath.append(intern(value))
486            elif key == 'DT_RUNPATH':
487                self.dt_runpath.append(intern(value))
488            elif key == 'DT_NEEDED':
489                self.dt_needed.append(intern(value))
490            elif key == 'EXP_SYMBOL':
491                self.exported_symbols.add(intern(value))
492            elif key == 'IMP_SYMBOL':
493                self.imported_symbols.add(intern(value))
494            else:
495                print('error: {}: {}: unknown tag name: {}'
496                        .format(path, line_no + 1, key), file=sys.stderr)
497
498    def _parse_from_dump_file(self, path):
499        """Load information from ELF dump file."""
500        with open(path, 'r') as f:
501            self._parse_from_dump_lines(path, f)
502
503    def _parse_from_dump_buf(self, buf):
504        """Load information from ELF dump buffer."""
505        self._parse_from_dump_lines('<str:0x{:x}>'.format(id(buf)),
506                                    buf.splitlines())
507
508    @staticmethod
509    def load(path):
510        """Create an ELF instance from the file path"""
511        elf = ELF()
512        elf._parse_from_file(path)
513        return elf
514
515    @staticmethod
516    def loads(buf):
517        """Create an ELF instance from the buffer"""
518        elf = ELF()
519        elf._parse_from_buf(buf)
520        return elf
521
522    @staticmethod
523    def load_dump(path):
524        """Create an ELF instance from a dump file path"""
525        elf = ELF()
526        elf._parse_from_dump_file(path)
527        return elf
528
529    @staticmethod
530    def load_dumps(buf):
531        """Create an ELF instance from a dump file buffer"""
532        elf = ELF()
533        elf._parse_from_dump_buf(buf)
534        return elf
535
536
537#------------------------------------------------------------------------------
538# TaggedDict
539#------------------------------------------------------------------------------
540
541class TaggedDict(object):
542    def _define_tag_constants(local_ns):
543        tag_list = [
544            'll_ndk', 'll_ndk_indirect', 'sp_ndk', 'sp_ndk_indirect',
545            'vndk_sp', 'vndk_sp_indirect', 'vndk_sp_indirect_private',
546            'vndk',
547            'fwk_only', 'fwk_only_rs',
548            'sp_hal', 'sp_hal_dep',
549            'vnd_only',
550            'remove',
551        ]
552        assert len(tag_list) < 32
553
554        tags = {}
555        for i, tag in enumerate(tag_list):
556            local_ns[tag.upper()] = 1 << i
557            tags[tag] = 1 << i
558
559        local_ns['TAGS'] = tags
560
561    _define_tag_constants(locals())
562    del _define_tag_constants
563
564    NDK = LL_NDK | SP_NDK
565
566    _TAG_ALIASES = {
567        'hl_ndk': 'fwk_only',  # Treat HL-NDK as FWK-ONLY.
568        'vndk_indirect': 'vndk',  # Legacy
569        'vndk_sp_hal': 'vndk_sp',  # Legacy
570        'vndk_sp_both': 'vndk_sp',  # Legacy
571    }
572
573    @classmethod
574    def _normalize_tag(cls, tag):
575        tag = tag.lower().replace('-', '_')
576        tag = cls._TAG_ALIASES.get(tag, tag)
577        if tag not in cls.TAGS:
578            raise ValueError('unknown lib tag ' + tag)
579        return tag
580
581    _LL_NDK_VIS = {'ll_ndk', 'll_ndk_indirect'}
582    _SP_NDK_VIS = {'ll_ndk', 'll_ndk_indirect', 'sp_ndk', 'sp_ndk_indirect'}
583    _VNDK_SP_VIS = {'ll_ndk', 'sp_ndk', 'vndk_sp', 'vndk_sp_indirect',
584                    'vndk_sp_indirect_private', 'fwk_only_rs'}
585    _FWK_ONLY_VIS = {'ll_ndk', 'll_ndk_indirect', 'sp_ndk', 'sp_ndk_indirect',
586                     'vndk_sp', 'vndk_sp_indirect', 'vndk_sp_indirect_private',
587                     'vndk', 'fwk_only', 'fwk_only_rs', 'sp_hal'}
588    _SP_HAL_VIS = {'ll_ndk', 'sp_ndk', 'vndk_sp', 'sp_hal', 'sp_hal_dep'}
589
590    _TAG_VISIBILITY = {
591        'll_ndk': _LL_NDK_VIS,
592        'll_ndk_indirect': _LL_NDK_VIS,
593        'sp_ndk': _SP_NDK_VIS,
594        'sp_ndk_indirect': _SP_NDK_VIS,
595
596        'vndk_sp': _VNDK_SP_VIS,
597        'vndk_sp_indirect': _VNDK_SP_VIS,
598        'vndk_sp_indirect_private': _VNDK_SP_VIS,
599
600        'vndk': {'ll_ndk', 'sp_ndk', 'vndk_sp', 'vndk_sp_indirect', 'vndk'},
601
602        'fwk_only': _FWK_ONLY_VIS,
603        'fwk_only_rs': _FWK_ONLY_VIS,
604
605        'sp_hal': _SP_HAL_VIS,
606        'sp_hal_dep': _SP_HAL_VIS,
607
608        'vnd_only': {'ll_ndk', 'sp_ndk', 'vndk_sp', 'vndk_sp_indirect',
609                     'vndk', 'sp_hal', 'sp_hal_dep', 'vnd_only'},
610
611        'remove': set(),
612    }
613
614    del _LL_NDK_VIS, _SP_NDK_VIS, _VNDK_SP_VIS, _FWK_ONLY_VIS, _SP_HAL_VIS
615
616    @classmethod
617    def is_tag_visible(cls, from_tag, to_tag):
618        return to_tag in cls._TAG_VISIBILITY[from_tag]
619
620    def __init__(self):
621        self._path_tag = dict()
622        for tag in self.TAGS:
623            setattr(self, tag, set())
624
625    def add(self, tag, lib):
626        lib_set = getattr(self, tag)
627        lib_set.add(lib)
628        self._path_tag[lib] = tag
629
630    def get_path_tag(self, lib):
631        try:
632            return self._path_tag[lib]
633        except KeyError:
634            return self.get_path_tag_default(lib)
635
636    def get_path_tag_default(self, lib):
637        raise NotImplementedError()
638
639    def get_path_tag_bit(self, lib):
640        return self.TAGS[self.get_path_tag(lib)]
641
642    def is_path_visible(self, from_lib, to_lib):
643        return self.is_tag_visible(self.get_path_tag(from_lib),
644                                   self.get_path_tag(to_lib))
645
646    @staticmethod
647    def is_ndk(tag_bit):
648        return bool(tag_bit & TaggedDict.NDK)
649
650    @staticmethod
651    def is_ll_ndk(tag_bit):
652        return bool(tag_bit & TaggedDict.LL_NDK)
653
654    @staticmethod
655    def is_sp_ndk(tag_bit):
656        return bool(tag_bit & TaggedDict.SP_NDK)
657
658    @staticmethod
659    def is_vndk_sp(tag_bit):
660        return bool(tag_bit & TaggedDict.VNDK_SP)
661
662    @staticmethod
663    def is_vndk_sp_indirect(tag_bit):
664        return bool(tag_bit & TaggedDict.VNDK_SP_INDIRECT)
665
666    @staticmethod
667    def is_vndk_sp_indirect_private(tag_bit):
668        return bool(tag_bit & TaggedDict.VNDK_SP_INDIRECT_PRIVATE)
669
670    @staticmethod
671    def is_fwk_only_rs(tag_bit):
672        return bool(tag_bit & TaggedDict.FWK_ONLY_RS)
673
674
675class TaggedPathDict(TaggedDict):
676    def load_from_csv(self, fp):
677        reader = csv.reader(fp)
678
679        # Read first row and check the existence of the header.
680        try:
681            row = next(reader)
682        except StopIteration:
683            return
684
685        try:
686            path_col = row.index('Path')
687            tag_col = row.index('Tag')
688        except ValueError:
689            path_col = 0
690            tag_col = 1
691            self.add(self._normalize_tag(row[tag_col]), row[path_col])
692
693        # Read the rest of rows.
694        for row in reader:
695            self.add(self._normalize_tag(row[tag_col]), row[path_col])
696
697    @staticmethod
698    def create_from_csv(fp):
699        d = TaggedPathDict()
700        d.load_from_csv(fp)
701        return d
702
703    @staticmethod
704    def create_from_csv_path(path):
705        with open(path, 'r') as fp:
706            return TaggedPathDict.create_from_csv(fp)
707
708    @staticmethod
709    def _enumerate_paths(pattern):
710        if '${LIB}' in pattern:
711            yield pattern.replace('${LIB}', 'lib')
712            yield pattern.replace('${LIB}', 'lib64')
713        else:
714            yield pattern
715
716    def add(self, tag, path):
717        for path in self._enumerate_paths(path):
718            super(TaggedPathDict, self).add(tag, path)
719
720    def get_path_tag_default(self, path):
721        return 'vnd_only' if path.startswith('/vendor') else 'fwk_only'
722
723
724class TaggedLibDict(TaggedDict):
725    @staticmethod
726    def create_from_graph(graph, tagged_paths, generic_refs=None):
727        d = TaggedLibDict()
728
729        for lib in graph.lib_pt[PT_SYSTEM].values():
730            d.add(tagged_paths.get_path_tag(lib.path), lib)
731
732        sp_lib = graph.compute_sp_lib(generic_refs)
733        for lib in graph.lib_pt[PT_VENDOR].values():
734            if lib in sp_lib.sp_hal:
735                d.add('sp_hal', lib)
736            elif lib in sp_lib.sp_hal_dep:
737                d.add('sp_hal_dep', lib)
738            else:
739                d.add('vnd_only', lib)
740        return d
741
742    def get_path_tag_default(self, lib):
743        return 'vnd_only' if lib.path.startswith('/vendor') else 'fwk_only'
744
745
746#------------------------------------------------------------------------------
747# ELF Linker
748#------------------------------------------------------------------------------
749
750def is_accessible(path):
751    try:
752        mode = os.stat(path).st_mode
753        return (mode & (stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH)) != 0
754    except FileNotFoundError:
755        return False
756
757
758def scan_accessible_files(root):
759    for base, dirs, files in os.walk(root):
760        for filename in files:
761            path = os.path.join(base, filename)
762            if is_accessible(path):
763                yield path
764
765
766def scan_elf_files(root):
767    for path in scan_accessible_files(root):
768        try:
769            yield (path, ELF.load(path))
770        except ELFError:
771            pass
772
773
774def scan_elf_dump_files(root):
775    for path in scan_accessible_files(root):
776        if not path.endswith('.sym'):
777            continue
778        yield (path[0:-4], ELF.load_dump(path))
779
780
781PT_SYSTEM = 0
782PT_VENDOR = 1
783NUM_PARTITIONS = 2
784
785
786SPLibResult = collections.namedtuple(
787        'SPLibResult',
788        'sp_hal sp_hal_dep vndk_sp_hal sp_ndk sp_ndk_indirect '
789        'vndk_sp_both')
790
791
792class ELFResolver(object):
793    def __init__(self, lib_set, default_search_path):
794        self.lib_set = lib_set
795        self.default_search_path = default_search_path
796
797    def get_candidates(self, name, dt_rpath=None, dt_runpath=None):
798        if dt_rpath:
799            for d in dt_rpath:
800                yield os.path.join(d, name)
801        if dt_runpath:
802            for d in dt_runpath:
803                yield os.path.join(d, name)
804        for d in self.default_search_path:
805            yield os.path.join(d, name)
806
807    def resolve(self, name, dt_rpath=None, dt_runpath=None):
808        for path in self.get_candidates(name, dt_rpath, dt_runpath):
809            try:
810                return self.lib_set[path]
811            except KeyError:
812                continue
813        return None
814
815
816class ELFLinkData(object):
817    NEEDED = 0  # Dependencies recorded in DT_NEEDED entries.
818    DLOPEN = 1  # Dependencies introduced by dlopen().
819
820    def __init__(self, partition, path, elf, tag_bit):
821        self.partition = partition
822        self.path = path
823        self.elf = elf
824        self._deps = (set(), set())
825        self._users = (set(), set())
826        self.imported_ext_symbols = collections.defaultdict(set)
827        self._tag_bit = tag_bit
828        self.unresolved_symbols = set()
829        self.unresolved_dt_needed = []
830        self.linked_symbols = dict()
831
832    @property
833    def is_ndk(self):
834        return TaggedDict.is_ndk(self._tag_bit)
835
836    @property
837    def is_ll_ndk(self):
838        return TaggedDict.is_ll_ndk(self._tag_bit)
839
840    @property
841    def is_sp_ndk(self):
842        return TaggedDict.is_sp_ndk(self._tag_bit)
843
844    @property
845    def is_vndk_sp(self):
846        return TaggedDict.is_vndk_sp(self._tag_bit)
847
848    @property
849    def is_vndk_sp_indirect(self):
850        return TaggedDict.is_vndk_sp_indirect(self._tag_bit)
851
852    @property
853    def is_vndk_sp_indirect_private(self):
854        return TaggedDict.is_vndk_sp_indirect_private(self._tag_bit)
855
856    @property
857    def is_fwk_only_rs(self):
858        return TaggedDict.is_fwk_only_rs(self._tag_bit)
859
860    def add_dep(self, dst, ty):
861        self._deps[ty].add(dst)
862        dst._users[ty].add(self)
863
864    def remove_dep(self, dst, ty):
865        self._deps[ty].remove(dst)
866        dst._users[ty].remove(self)
867
868    @property
869    def num_deps(self):
870        """Get the number of dependencies.  If a library is linked by both
871        NEEDED and DLOPEN relationship, then it will be counted twice."""
872        return sum(len(deps) for deps in self._deps)
873
874    @property
875    def deps(self):
876        return itertools.chain.from_iterable(self._deps)
877
878    @property
879    def deps_with_type(self):
880        dt_deps = zip(self._deps[self.NEEDED], itertools.repeat(self.NEEDED))
881        dl_deps = zip(self._deps[self.DLOPEN], itertools.repeat(self.DLOPEN))
882        return itertools.chain(dt_deps, dl_deps)
883
884    @property
885    def dt_deps(self):
886        return self._deps[self.NEEDED]
887
888    @property
889    def dl_deps(self):
890        return self._deps[self.DLOPEN]
891
892    @property
893    def num_users(self):
894        """Get the number of users.  If a library is linked by both NEEDED and
895        DLOPEN relationship, then it will be counted twice."""
896        return sum(len(users) for users in self._users)
897
898    @property
899    def users(self):
900        return itertools.chain.from_iterable(self._users)
901
902    @property
903    def users_with_type(self):
904        dt_users = zip(self._users[self.NEEDED], itertools.repeat(self.NEEDED))
905        dl_users = zip(self._users[self.DLOPEN], itertools.repeat(self.DLOPEN))
906        return itertools.chain(dt_users, dl_users)
907
908    @property
909    def dt_users(self):
910        return self._users[self.NEEDED]
911
912    @property
913    def dl_users(self):
914        return self._users[self.DLOPEN]
915
916    def has_dep(self, dst):
917        return any(dst in deps for deps in self._deps)
918
919    def has_user(self, dst):
920        return any(dst in users for users in self._users)
921
922    def is_system_lib(self):
923        return self.partition == PT_SYSTEM
924
925    def get_dep_linked_symbols(self, dep):
926        symbols = set()
927        for symbol, exp_lib in self.linked_symbols.items():
928            if exp_lib == dep:
929                symbols.add(symbol)
930        return sorted(symbols)
931
932    def __lt__(self, rhs):
933        return self.path < rhs.path
934
935
936def sorted_lib_path_list(libs):
937    libs = [lib.path for lib in libs]
938    libs.sort()
939    return libs
940
941_VNDK_RESULT_FIELD_NAMES = (
942        'll_ndk', 'll_ndk_indirect', 'sp_ndk', 'sp_ndk_indirect',
943        'vndk_sp', 'vndk_sp_unused', 'vndk_sp_indirect',
944        'vndk_sp_indirect_unused', 'vndk_sp_indirect_private', 'vndk',
945        'vndk_indirect', 'fwk_only', 'fwk_only_rs', 'sp_hal', 'sp_hal_dep',
946        'vnd_only', 'vndk_ext', 'vndk_sp_ext', 'vndk_sp_indirect_ext',
947        'extra_vendor_libs')
948
949VNDKResult = defaultnamedtuple('VNDKResult', _VNDK_RESULT_FIELD_NAMES, set())
950
951_SIMPLE_VNDK_RESULT_FIELD_NAMES = (
952        'vndk_sp', 'vndk_sp_ext', 'extra_vendor_libs')
953
954SimpleVNDKResult = defaultnamedtuple(
955        'SimpleVNDKResult', _SIMPLE_VNDK_RESULT_FIELD_NAMES, set())
956
957
958class ELFLibDict(defaultnamedtuple('ELFLibDict', ('lib32', 'lib64'), {})):
959    def get_lib_dict(self, elf_class):
960        return self[elf_class - 1]
961
962    def add(self, path, lib):
963        self.get_lib_dict(lib.elf.ei_class)[path] = lib
964
965    def remove(self, lib):
966        del self.get_lib_dict(lib.elf.ei_class)[lib.path]
967
968    def get(self, path, default=None):
969        for lib_set in self:
970            res = lib_set.get(path, None)
971            if res:
972                return res
973        return default
974
975    def keys(self):
976        return itertools.chain(self.lib32.keys(), self.lib64.keys())
977
978    def values(self):
979        return itertools.chain(self.lib32.values(), self.lib64.values())
980
981    def items(self):
982        return itertools.chain(self.lib32.items(), self.lib64.items())
983
984
985class ELFLinker(object):
986    def __init__(self, tagged_paths=None):
987        self.lib_pt = [ELFLibDict() for i in range(NUM_PARTITIONS)]
988
989        if tagged_paths is None:
990            script_dir = os.path.dirname(os.path.abspath(__file__))
991            dataset_path = os.path.join(
992                    script_dir, 'datasets', 'minimum_tag_file.csv')
993            tagged_paths = TaggedPathDict.create_from_csv_path(dataset_path)
994
995        self.tagged_paths = tagged_paths
996
997    def _add_lib_to_lookup_dict(self, lib):
998        self.lib_pt[lib.partition].add(lib.path, lib)
999
1000    def _remove_lib_from_lookup_dict(self, lib):
1001        self.lib_pt[lib.partition].remove(lib)
1002
1003    def add_lib(self, partition, path, elf):
1004        lib = ELFLinkData(partition, path, elf,
1005                          self.tagged_paths.get_path_tag_bit(path))
1006        self._add_lib_to_lookup_dict(lib)
1007        return lib
1008
1009    def rename_lib(self, lib, new_partition, new_path):
1010        self._remove_lib_from_lookup_dict(lib)
1011        lib.path = new_path
1012        lib.partition = new_partition
1013        self._add_lib_to_lookup_dict(lib)
1014
1015    def add_dep(self, src_path, dst_path, ty):
1016        for elf_class in (ELF.ELFCLASS32, ELF.ELFCLASS64):
1017            src = self.get_lib_in_elf_class(elf_class, src_path)
1018            dst = self.get_lib_in_elf_class(elf_class, dst_path)
1019            if src and dst:
1020                src.add_dep(dst, ty)
1021                return
1022        print('error: cannot add dependency from {} to {}.'
1023              .format(src_path, dst_path), file=sys.stderr)
1024
1025    def get_lib_in_elf_class(self, elf_class, path, default=None):
1026        for partition in range(NUM_PARTITIONS):
1027            res = self.lib_pt[partition].get_lib_dict(elf_class).get(path)
1028            if res:
1029                return res
1030        return default
1031
1032    def get_lib(self, path):
1033        for lib_set in self.lib_pt:
1034            lib = lib_set.get(path)
1035            if lib:
1036                return lib
1037        return None
1038
1039    def get_libs(self, paths, report_error=None):
1040        result = set()
1041        for path in paths:
1042            lib = self.get_lib(path)
1043            if not lib:
1044                if report_error is None:
1045                    raise ValueError('path not found ' + path)
1046                report_error(path)
1047                continue
1048            result.add(lib)
1049        return result
1050
1051    def all_libs(self):
1052        for lib_set in self.lib_pt:
1053            for lib in lib_set.values():
1054                yield lib
1055
1056    def _compute_lib_dict(self, elf_class):
1057        res = dict()
1058        for lib_pt in self.lib_pt:
1059            res.update(lib_pt.get_lib_dict(elf_class))
1060        return res
1061
1062    @staticmethod
1063    def _compile_path_matcher(root, subdirs):
1064        dirs = [os.path.normpath(os.path.join(root, i)) for i in subdirs]
1065        patts = ['(?:' + re.escape(i) + os.sep + ')' for i in dirs]
1066        return re.compile('|'.join(patts))
1067
1068    def add_executables_in_dir(self, partition_name, partition, root,
1069                               alter_partition, alter_subdirs, ignored_subdirs,
1070                               scan_elf_files):
1071        root = os.path.abspath(root)
1072        prefix_len = len(root) + 1
1073
1074        if alter_subdirs:
1075            alter_patt = ELFLinker._compile_path_matcher(root, alter_subdirs)
1076        if ignored_subdirs:
1077            ignored_patt = ELFLinker._compile_path_matcher(root, ignored_subdirs)
1078
1079        for path, elf in scan_elf_files(root):
1080            # Ignore ELF files with unknown machine ID (eg. DSP).
1081            if elf.e_machine not in ELF.ELF_MACHINES:
1082                continue
1083
1084            # Ignore ELF files with matched path.
1085            short_path = os.path.join('/', partition_name, path[prefix_len:])
1086            if ignored_subdirs and ignored_patt.match(path):
1087                continue
1088
1089            if alter_subdirs and alter_patt.match(path):
1090                self.add_lib(alter_partition, short_path, elf)
1091            else:
1092                self.add_lib(partition, short_path, elf)
1093
1094    def load_extra_deps(self, path):
1095        patt = re.compile('([^:]*):\\s*(.*)')
1096        with open(path, 'r') as f:
1097            for line in f:
1098                match = patt.match(line)
1099                if match:
1100                    self.add_dep(match.group(1), match.group(2),
1101                                 ELFLinkData.DLOPEN)
1102
1103    def _find_exported_symbol(self, symbol, libs):
1104        """Find the shared library with the exported symbol."""
1105        for lib in libs:
1106            if symbol in lib.elf.exported_symbols:
1107                return lib
1108        return None
1109
1110    def _resolve_lib_imported_symbols(self, lib, imported_libs, generic_refs):
1111        """Resolve the imported symbols in a library."""
1112        for symbol in lib.elf.imported_symbols:
1113            imported_lib = self._find_exported_symbol(symbol, imported_libs)
1114            if not imported_lib:
1115                lib.unresolved_symbols.add(symbol)
1116            else:
1117                lib.linked_symbols[symbol] = imported_lib
1118                if generic_refs:
1119                    ref_lib = generic_refs.refs.get(imported_lib.path)
1120                    if not ref_lib or not symbol in ref_lib.exported_symbols:
1121                        lib.imported_ext_symbols[imported_lib].add(symbol)
1122
1123    def _resolve_lib_dt_needed(self, lib, resolver):
1124        imported_libs = []
1125        for dt_needed in lib.elf.dt_needed:
1126            dep = resolver.resolve(dt_needed, lib.elf.dt_rpath,
1127                                   lib.elf.dt_runpath)
1128            if not dep:
1129                candidates = list(resolver.get_candidates(
1130                    dt_needed, lib.elf.dt_rpath, lib.elf.dt_runpath))
1131                print('warning: {}: Missing needed library: {}  Tried: {}'
1132                      .format(lib.path, dt_needed, candidates), file=sys.stderr)
1133                lib.unresolved_dt_needed.append(dt_needed)
1134                continue
1135            lib.add_dep(dep, ELFLinkData.NEEDED)
1136            imported_libs.append(dep)
1137        return imported_libs
1138
1139    def _resolve_lib_deps(self, lib, resolver, generic_refs):
1140        # Resolve DT_NEEDED entries.
1141        imported_libs = self._resolve_lib_dt_needed(lib, resolver)
1142
1143        if generic_refs:
1144            for imported_lib in imported_libs:
1145                if imported_lib.path not in generic_refs.refs:
1146                    # Add imported_lib to imported_ext_symbols to make sure
1147                    # non-AOSP libraries are in the imported_ext_symbols key
1148                    # set.
1149                    lib.imported_ext_symbols[imported_lib].update()
1150
1151        # Resolve imported symbols.
1152        self._resolve_lib_imported_symbols(lib, imported_libs, generic_refs)
1153
1154    def _resolve_lib_set_deps(self, lib_set, resolver, generic_refs):
1155        for lib in lib_set:
1156            self._resolve_lib_deps(lib, resolver, generic_refs)
1157
1158    SYSTEM_SEARCH_PATH = (
1159        '/system/${LIB}',
1160        '/vendor/${LIB}',
1161    )
1162
1163    VENDOR_SEARCH_PATH = (
1164        '/vendor/${LIB}',
1165        '/vendor/${LIB}/vndk-sp',
1166        '/system/${LIB}/vndk-sp',
1167        '/vendor/${LIB}/vndk',
1168        '/system/${LIB}/vndk',
1169        '/system/${LIB}',  # For degenerated VNDK libs.
1170    )
1171
1172    VNDK_SP_SEARCH_PATH = (
1173        '/vendor/${LIB}/vndk-sp',
1174        '/system/${LIB}/vndk-sp',
1175        '/vendor/${LIB}',  # To discover missing VNDK-SP dependencies.
1176        '/system/${LIB}',  # To discover missing VNDK-SP dependencies or LL-NDK.
1177    )
1178
1179    VNDK_SEARCH_PATH = (
1180        '/vendor/${LIB}/vndk-sp',
1181        '/system/${LIB}/vndk-sp',
1182        '/vendor/${LIB}/vndk',
1183        '/system/${LIB}/vndk',
1184        '/system/${LIB}',  # To discover missing VNDK dependencies or LL-NDK.
1185    )
1186
1187
1188    @staticmethod
1189    def _subst_search_path(search_path, elf_class):
1190        lib_dir_name = 'lib' if elf_class == ELF.ELFCLASS32 else 'lib64'
1191        return [path.replace('${LIB}', lib_dir_name) for path in search_path]
1192
1193    @classmethod
1194    def _get_dirname(cls, path):
1195        return os.path.basename(os.path.dirname(path))
1196
1197    @classmethod
1198    def _is_in_vndk_dir(cls, path):
1199        return cls._get_dirname(path) == 'vndk'
1200
1201    @classmethod
1202    def _is_in_vndk_sp_dir(cls, path):
1203        return cls._get_dirname(path) == 'vndk-sp'
1204
1205    @classmethod
1206    def _classify_vndk_libs(cls, libs):
1207        vndk_libs = set()
1208        vndk_sp_libs = set()
1209        other_libs = set()
1210        for lib in libs:
1211            dirname = cls._get_dirname(lib.path)
1212            if dirname == 'vndk':
1213                vndk_libs.add(lib)
1214            elif dirname == 'vndk-sp':
1215                vndk_sp_libs.add(lib)
1216            else:
1217                other_libs.add(lib)
1218        return (vndk_libs, vndk_sp_libs, other_libs)
1219
1220    def _resolve_elf_class_deps(self, elf_class, generic_refs):
1221        # Classify libs.
1222        lib_dict = self._compute_lib_dict(elf_class)
1223        system_lib_dict = self.lib_pt[PT_SYSTEM].get_lib_dict(elf_class)
1224        system_vndk_libs, system_vndk_sp_libs, system_libs = \
1225                self._classify_vndk_libs(system_lib_dict.values())
1226
1227        vendor_lib_dict = self.lib_pt[PT_VENDOR].get_lib_dict(elf_class)
1228        vendor_vndk_libs, vendor_vndk_sp_libs, vendor_libs = \
1229                self._classify_vndk_libs(vendor_lib_dict.values())
1230
1231        vndk_libs = system_vndk_libs | vendor_vndk_libs
1232        vndk_sp_libs = system_vndk_sp_libs | vendor_vndk_sp_libs
1233
1234        # Resolve system libs.
1235        search_path = self._subst_search_path(
1236                self.SYSTEM_SEARCH_PATH, elf_class)
1237        resolver = ELFResolver(lib_dict, search_path)
1238        self._resolve_lib_set_deps(system_libs, resolver, generic_refs)
1239
1240        # Resolve vndk-sp libs
1241        search_path = self._subst_search_path(
1242                self.VNDK_SP_SEARCH_PATH, elf_class)
1243        resolver = ELFResolver(lib_dict, search_path)
1244        self._resolve_lib_set_deps(vndk_sp_libs, resolver, generic_refs)
1245
1246        # Resolve vndk libs
1247        search_path = self._subst_search_path(self.VNDK_SEARCH_PATH, elf_class)
1248        resolver = ELFResolver(lib_dict, search_path)
1249        self._resolve_lib_set_deps(vndk_libs, resolver, generic_refs)
1250
1251        # Resolve vendor libs.
1252        search_path = self._subst_search_path(
1253                self.VENDOR_SEARCH_PATH, elf_class)
1254        resolver = ELFResolver(lib_dict, search_path)
1255        self._resolve_lib_set_deps(vendor_libs, resolver, generic_refs)
1256
1257    def resolve_deps(self, generic_refs=None):
1258        self._resolve_elf_class_deps(ELF.ELFCLASS32, generic_refs)
1259        self._resolve_elf_class_deps(ELF.ELFCLASS64, generic_refs)
1260
1261    def compute_path_matched_lib(self, path_patterns):
1262        patt = re.compile('|'.join('(?:' + p + ')' for p in path_patterns))
1263        return set(lib for lib in self.all_libs() if patt.match(lib.path))
1264
1265    def compute_predefined_sp_hal(self):
1266        """Find all same-process HALs."""
1267        path_patterns = (
1268            # OpenGL-related
1269            '^/vendor/.*/libEGL_.*\\.so$',
1270            '^/vendor/.*/libGLES_.*\\.so$',
1271            '^/vendor/.*/libGLESv1_CM_.*\\.so$',
1272            '^/vendor/.*/libGLESv2_.*\\.so$',
1273            '^/vendor/.*/libGLESv3_.*\\.so$',
1274            # Vulkan
1275            '^/vendor/.*/vulkan.*\\.so$',
1276            # libRSDriver
1277            '^.*/android\\.hardware\\.renderscript@1\\.0-impl\\.so$',
1278            '^/vendor/.*/libPVRRS\\.so$',
1279            '^/vendor/.*/libRSDriver.*\\.so$',
1280            # Gralloc mapper
1281            '^.*/gralloc\\..*\\.so$',
1282            '^.*/android\\.hardware\\.graphics\\.mapper@\\d+\\.\\d+-impl\\.so$',
1283        )
1284        return self.compute_path_matched_lib(path_patterns)
1285
1286    def compute_sp_ndk(self):
1287        """Find all SP-NDK libraries."""
1288        return set(lib for lib in self.all_libs() if lib.is_sp_ndk)
1289
1290    def compute_sp_lib(self, generic_refs):
1291        def is_ndk(lib):
1292            return lib.is_ndk
1293
1294        sp_ndk = self.compute_sp_ndk()
1295        sp_ndk_closure = self.compute_closure(sp_ndk, is_ndk)
1296        sp_ndk_indirect = sp_ndk_closure - sp_ndk
1297
1298        sp_hal = self.compute_predefined_sp_hal()
1299        sp_hal_closure = self.compute_closure(sp_hal, is_ndk)
1300
1301        def is_aosp_lib(lib):
1302            return (not generic_refs or \
1303                    generic_refs.classify_lib(lib) != GenericRefs.NEW_LIB)
1304
1305        vndk_sp_hal = set()
1306        sp_hal_dep = set()
1307        for lib in sp_hal_closure - sp_hal:
1308            if is_aosp_lib(lib):
1309                vndk_sp_hal.add(lib)
1310            else:
1311                sp_hal_dep.add(lib)
1312
1313        vndk_sp_both = sp_ndk_indirect & vndk_sp_hal
1314        sp_ndk_indirect -= vndk_sp_both
1315        vndk_sp_hal -= vndk_sp_both
1316
1317        return SPLibResult(sp_hal, sp_hal_dep, vndk_sp_hal, sp_ndk,
1318                           sp_ndk_indirect, vndk_sp_both)
1319
1320    def _po_sorted(self, lib_set, get_successors):
1321        result = []
1322        visited = set()
1323        def traverse(lib):
1324            for succ in get_successors(lib):
1325                if succ in lib_set and succ not in visited:
1326                    visited.add(succ)
1327                    traverse(succ)
1328            result.append(lib)
1329        for lib in lib_set:
1330            if lib not in visited:
1331                visited.add(lib)
1332                traverse(lib)
1333        return result
1334
1335    def _deps_po_sorted(self, lib_set):
1336        return self._po_sorted(lib_set, lambda x: x.deps)
1337
1338    def _users_po_sorted(self, lib_set):
1339        return self._po_sorted(lib_set, lambda x: x.users)
1340
1341    def normalize_partition_tags(self, sp_hals, generic_refs):
1342        system_libs = set(self.lib_pt[PT_SYSTEM].values())
1343        system_libs_po = self._deps_po_sorted(system_libs)
1344
1345        def is_system_lib_or_sp_hal(lib):
1346            return lib.is_system_lib() or lib in sp_hals
1347
1348        for lib in system_libs_po:
1349            if all(is_system_lib_or_sp_hal(dep) for dep in lib.deps):
1350                # Good system lib.  Do nothing.
1351                continue
1352            if not generic_refs or generic_refs.refs.get(lib.path):
1353                # If lib is in AOSP generic reference, then we assume that the
1354                # non-SP-HAL dependencies are errors.  Emit errors and remove
1355                # the dependencies.
1356                for dep in list(lib.dt_deps):
1357                    if not is_system_lib_or_sp_hal(dep):
1358                        print('error: {}: system exe/lib must not depend on '
1359                              'vendor lib {}.  Assume such dependency does '
1360                              'not exist.'.format(lib.path, dep.path),
1361                              file=sys.stderr)
1362                        lib.remove_dep(dep, ELFLinkData.NEEDED)
1363                for dep in list(lib.dl_deps):
1364                    if not is_system_lib_or_sp_hal(dep):
1365                        print('error: {}: system exe/lib must not dlopen() '
1366                              'vendor lib {}.  Assume such dependency does '
1367                              'not exist.'.format(lib.path, dep.path),
1368                              file=sys.stderr)
1369                        lib.remove_dep(dep, ELFLinkData.DLOPEN)
1370            else:
1371                # If lib is not in AOSP generic reference, then we assume that
1372                # lib must be moved to vendor partition.
1373                for dep in lib.deps:
1374                    if not is_system_lib_or_sp_hal(dep):
1375                        print('warning: {}: system exe/lib must not depend on '
1376                              'vendor lib {}.  Assuming {} should be placed in '
1377                              'vendor partition.'
1378                              .format(lib.path, dep.path, lib.path),
1379                              file=sys.stderr)
1380                new_path = lib.path.replace('/system/', '/vendor/')
1381                self.rename_lib(lib, PT_VENDOR, new_path)
1382
1383    @staticmethod
1384    def _parse_action_on_ineligible_lib(arg):
1385        follow = False
1386        warn = False
1387        for flag in arg.split(','):
1388            if flag == 'follow':
1389                follow = True
1390            elif flag == 'warn':
1391                warn = True
1392            elif flag == 'ignore':
1393                continue
1394            else:
1395                raise ValueError('unknown action \"{}\"'.format(flag))
1396        return (follow, warn)
1397
1398    def compute_degenerated_vndk(self, generic_refs, tagged_paths=None,
1399                                 action_ineligible_vndk_sp='warn',
1400                                 action_ineligible_vndk='warn'):
1401        # Find LL-NDK and SP-NDK libs.
1402        ll_ndk = set(lib for lib in self.all_libs() if lib.is_ll_ndk)
1403        sp_ndk = set(lib for lib in self.all_libs() if lib.is_sp_ndk)
1404
1405        # Find pre-defined libs.
1406        fwk_only_rs = set(lib for lib in self.all_libs() if lib.is_fwk_only_rs)
1407        predefined_vndk_sp = set(
1408                lib for lib in self.all_libs() if lib.is_vndk_sp)
1409        predefined_vndk_sp_indirect = set(
1410                lib for lib in self.all_libs() if lib.is_vndk_sp_indirect)
1411        predefined_vndk_sp_indirect_private = set(
1412                lib for lib in self.all_libs()
1413                if lib.is_vndk_sp_indirect_private)
1414
1415        # FIXME: Don't squash VNDK-SP-Indirect-Private into VNDK-SP-Indirect.
1416        predefined_vndk_sp_indirect |= predefined_vndk_sp_indirect_private
1417
1418        # Find SP-HAL libs.
1419        sp_hal = self.compute_predefined_sp_hal()
1420
1421        # Normalize partition tags.  We expect many violations from the
1422        # pre-Treble world.  Guess a resolution for the incorrect partition
1423        # tag.
1424        self.normalize_partition_tags(sp_hal, generic_refs)
1425
1426        # Find SP-HAL-Dep libs.
1427        def is_aosp_lib(lib):
1428            if not generic_refs:
1429                # If generic reference is not available, then assume all system
1430                # libs are AOSP libs.
1431                return lib.partition == PT_SYSTEM
1432            return generic_refs.has_same_name_lib(lib)
1433
1434        def is_not_sp_hal_dep(lib):
1435            if lib.is_ll_ndk or lib.is_sp_ndk or lib in sp_hal:
1436                return True
1437            return is_aosp_lib(lib)
1438
1439        sp_hal_dep = self.compute_closure(sp_hal, is_not_sp_hal_dep)
1440        sp_hal_dep -= sp_hal
1441
1442        # Find VNDK-SP libs.
1443        def is_not_vndk_sp(lib):
1444            return lib.is_ll_ndk or lib.is_sp_ndk or lib in sp_hal or \
1445                   lib in sp_hal_dep
1446
1447        follow_ineligible_vndk_sp, warn_ineligible_vndk_sp = \
1448                self._parse_action_on_ineligible_lib(action_ineligible_vndk_sp)
1449        vndk_sp = set()
1450        for lib in itertools.chain(sp_hal, sp_hal_dep):
1451            for dep in lib.deps:
1452                if is_not_vndk_sp(dep):
1453                    continue
1454                if dep in predefined_vndk_sp:
1455                    vndk_sp.add(dep)
1456                    continue
1457                if warn_ineligible_vndk_sp:
1458                    print('error: SP-HAL {} depends on non vndk-sp '
1459                          'library {}.'.format(lib.path, dep.path),
1460                          file=sys.stderr)
1461                if follow_ineligible_vndk_sp:
1462                    vndk_sp.add(dep)
1463
1464        # Find VNDK-SP-Indirect libs.
1465        def is_not_vndk_sp_indirect(lib):
1466            return lib.is_ll_ndk or lib.is_sp_ndk or lib in vndk_sp or \
1467                   lib in fwk_only_rs
1468
1469        vndk_sp_indirect = self.compute_closure(
1470                vndk_sp, is_not_vndk_sp_indirect)
1471        vndk_sp_indirect -= vndk_sp
1472
1473        # Find unused predefined VNDK-SP libs.
1474        vndk_sp_unused = set(lib for lib in predefined_vndk_sp
1475                             if self._is_in_vndk_sp_dir(lib.path))
1476        vndk_sp_unused -= vndk_sp
1477        vndk_sp_unused -= vndk_sp_indirect
1478
1479        # Find dependencies of unused predefined VNDK-SP libs.
1480        def is_not_vndk_sp_indirect_unused(lib):
1481            return is_not_vndk_sp_indirect(lib) or lib in vndk_sp_indirect
1482        vndk_sp_unused_deps = self.compute_closure(
1483                vndk_sp_unused, is_not_vndk_sp_indirect_unused)
1484        vndk_sp_unused_deps -= vndk_sp_unused
1485
1486        vndk_sp_indirect_unused = set(lib for lib in predefined_vndk_sp_indirect
1487                                      if self._is_in_vndk_sp_dir(lib.path))
1488        vndk_sp_indirect_unused -= vndk_sp_indirect
1489        vndk_sp_indirect_unused -= vndk_sp_unused
1490        vndk_sp_indirect_unused |= vndk_sp_unused_deps
1491
1492        # TODO: Compute VNDK-SP-Indirect-Private.
1493        vndk_sp_indirect_private = set()
1494
1495        assert not (vndk_sp & vndk_sp_indirect)
1496        assert not (vndk_sp_unused & vndk_sp_indirect_unused)
1497
1498        # Define helper functions for vndk_sp sets.
1499        def is_vndk_sp_public(lib):
1500            return lib in vndk_sp or lib in vndk_sp_unused or \
1501                   lib in vndk_sp_indirect or \
1502                   lib in vndk_sp_indirect_unused
1503
1504        def is_vndk_sp(lib):
1505            return is_vndk_sp_public(lib) or lib in vndk_sp_indirect_private
1506
1507        def is_vndk_sp_unused(lib):
1508            return lib in vndk_sp_unused or lib in vndk_sp_indirect_unused
1509
1510        def relabel_vndk_sp_as_used(lib):
1511            assert is_vndk_sp_unused(lib)
1512
1513            if lib in vndk_sp_unused:
1514                vndk_sp_unused.remove(lib)
1515                vndk_sp.add(lib)
1516            else:
1517                vndk_sp_indirect_unused.remove(lib)
1518                vndk_sp_indirect.add(lib)
1519
1520            # Add the dependencies to vndk_sp_indirect if they are not vndk_sp.
1521            closure = self.compute_closure(
1522                    {lib}, lambda lib: lib not in vndk_sp_indirect_unused)
1523            closure.remove(lib)
1524            vndk_sp_indirect_unused.difference_update(closure)
1525            vndk_sp_indirect.update(closure)
1526
1527        # Find VNDK-SP-Ext libs.
1528        vndk_sp_ext = set()
1529        def collect_vndk_ext(libs):
1530            result = set()
1531            for lib in libs:
1532                for dep in lib.imported_ext_symbols:
1533                    if dep in vndk_sp and dep not in vndk_sp_ext:
1534                        result.add(dep)
1535            return result
1536
1537        candidates = collect_vndk_ext(self.lib_pt[PT_VENDOR].values())
1538        while candidates:
1539            vndk_sp_ext |= candidates
1540            candidates = collect_vndk_ext(candidates)
1541
1542        # Find VNDK-SP-Indirect-Ext libs.
1543        vndk_sp_indirect_ext = set()
1544        def collect_vndk_sp_indirect_ext(libs):
1545            result = set()
1546            for lib in libs:
1547                exts = set(lib.imported_ext_symbols.keys())
1548                for dep in lib.deps:
1549                    if not is_vndk_sp_public(dep):
1550                        continue
1551                    if dep in vndk_sp_ext or dep in vndk_sp_indirect_ext:
1552                        continue
1553                    # If lib is using extended definition from deps, then we
1554                    # have to make a copy of dep.
1555                    if dep in exts:
1556                        result.add(dep)
1557                        continue
1558                    # If lib is using non-predefined VNDK-SP-Indirect, then we
1559                    # have to make a copy of dep.
1560                    if dep not in predefined_vndk_sp and \
1561                            dep not in predefined_vndk_sp_indirect:
1562                        result.add(dep)
1563                        continue
1564            return result
1565
1566        def is_not_vndk_sp_indirect(lib):
1567            return lib.is_ll_ndk or lib.is_sp_ndk or lib in vndk_sp or \
1568                   lib in fwk_only_rs
1569
1570        candidates = collect_vndk_sp_indirect_ext(vndk_sp_ext)
1571        while candidates:
1572            vndk_sp_indirect_ext |= candidates
1573            candidates = collect_vndk_sp_indirect_ext(candidates)
1574
1575        # Find VNDK libs (a.k.a. system shared libs directly used by vendor
1576        # partition.)
1577        def is_not_vndk(lib):
1578            if lib.is_ll_ndk or lib.is_sp_ndk or is_vndk_sp_public(lib) or \
1579               lib in fwk_only_rs:
1580                return True
1581            return lib.partition != PT_SYSTEM
1582
1583        def is_eligible_lib_access(lib, dep):
1584            return not tagged_paths or \
1585                    tagged_paths.is_path_visible(lib.path, dep.path)
1586
1587        follow_ineligible_vndk, warn_ineligible_vndk = \
1588                self._parse_action_on_ineligible_lib(action_ineligible_vndk)
1589        vndk = set()
1590        extra_vendor_libs = set()
1591        def collect_vndk(vendor_libs):
1592            next_vendor_libs = set()
1593            for lib in vendor_libs:
1594                for dep in lib.deps:
1595                    if is_vndk_sp_unused(dep):
1596                        relabel_vndk_sp_as_used(dep)
1597                        continue
1598                    if is_not_vndk(dep):
1599                        continue
1600                    if not is_aosp_lib(dep):
1601                        # The dependency should be copied into vendor partition
1602                        # as an extra vendor lib.
1603                        if dep not in extra_vendor_libs:
1604                            next_vendor_libs.add(dep)
1605                            extra_vendor_libs.add(dep)
1606                        continue
1607                    if is_eligible_lib_access(lib, dep):
1608                        vndk.add(dep)
1609                        continue
1610                    if warn_ineligible_vndk:
1611                        print('warning: vendor lib/exe {} depends on '
1612                              'ineligible framework shared lib {}.'
1613                              .format(lib.path, dep.path), file=sys.stderr)
1614                    if follow_ineligible_vndk:
1615                        vndk.add(dep)
1616            return next_vendor_libs
1617
1618        candidates = collect_vndk(self.lib_pt[PT_VENDOR].values())
1619        while candidates:
1620            candidates = collect_vndk(candidates)
1621
1622        vndk_indirect = self.compute_closure(vndk, is_not_vndk)
1623        vndk_indirect -= vndk
1624
1625        def is_vndk(lib):
1626            return lib in vndk or lib in vndk_indirect
1627
1628        # Find VNDK-EXT libs (VNDK libs with extended definitions and the
1629        # extended definitions are used by the vendor modules (including
1630        # extra_vendor_libs).
1631
1632        # FIXME: DAUX libraries won't be found by the following algorithm.
1633        vndk_ext = set()
1634
1635        def collect_vndk_ext(libs):
1636            result = set()
1637            for lib in libs:
1638                for dep in lib.imported_ext_symbols:
1639                    if dep in vndk and dep not in vndk_ext:
1640                        result.add(dep)
1641            return result
1642
1643        candidates = collect_vndk_ext(self.lib_pt[PT_VENDOR].values())
1644        candidates |= collect_vndk_ext(extra_vendor_libs)
1645
1646        while candidates:
1647            vndk_ext |= candidates
1648            candidates = collect_vndk_ext(candidates)
1649
1650        # Compute LL-NDK-Indirect and SP-NDK-Indirect.
1651        def is_not_ll_ndk_indirect(lib):
1652            return lib.is_ll_ndk or is_vndk_sp(lib) or is_vndk(lib)
1653
1654        ll_ndk_indirect = self.compute_closure(ll_ndk, is_not_ll_ndk_indirect)
1655        ll_ndk_indirect -= ll_ndk
1656
1657        def is_not_sp_ndk_indirect(lib):
1658            return lib.is_ll_ndk or lib.is_sp_ndk or lib in ll_ndk_indirect or \
1659                   is_vndk_sp(lib) or is_vndk(lib)
1660
1661        sp_ndk_indirect = self.compute_closure(sp_ndk, is_not_sp_ndk_indirect)
1662        sp_ndk_indirect -= sp_ndk
1663
1664        # Return the VNDK classifications.
1665        return VNDKResult(
1666                ll_ndk=ll_ndk,
1667                ll_ndk_indirect=ll_ndk_indirect,
1668                sp_ndk=sp_ndk,
1669                sp_ndk_indirect=sp_ndk_indirect,
1670                vndk_sp=vndk_sp,
1671                vndk_sp_indirect=vndk_sp_indirect,
1672                # vndk_sp_indirect_private=vndk_sp_indirect_private,
1673                vndk_sp_unused=vndk_sp_unused,
1674                vndk_sp_indirect_unused=vndk_sp_indirect_unused,
1675                vndk=vndk,
1676                vndk_indirect=vndk_indirect,
1677                # fwk_only=fwk_only,
1678                fwk_only_rs=fwk_only_rs,
1679                sp_hal=sp_hal,
1680                sp_hal_dep=sp_hal_dep,
1681                # vnd_only=vnd_only,
1682                vndk_ext=vndk_ext,
1683                vndk_sp_ext=vndk_sp_ext,
1684                vndk_sp_indirect_ext=vndk_sp_indirect_ext,
1685                extra_vendor_libs=extra_vendor_libs)
1686
1687    @staticmethod
1688    def _compute_closure(root_set, is_excluded, get_successors):
1689        closure = set(root_set)
1690        stack = list(root_set)
1691        while stack:
1692            lib = stack.pop()
1693            for succ in get_successors(lib):
1694                if is_excluded(succ):
1695                    continue
1696                if succ not in closure:
1697                    closure.add(succ)
1698                    stack.append(succ)
1699        return closure
1700
1701    @classmethod
1702    def compute_deps_closure(cls, root_set, is_excluded):
1703        return cls._compute_closure(root_set, is_excluded, lambda x: x.deps)
1704
1705    compute_closure = compute_deps_closure
1706
1707    @classmethod
1708    def compute_users_closure(cls, root_set, is_excluded):
1709        return cls._compute_closure(root_set, is_excluded, lambda x: x.users)
1710
1711    @staticmethod
1712    def _create_internal(scan_elf_files, system_dirs, system_dirs_as_vendor,
1713                         system_dirs_ignored, vendor_dirs,
1714                         vendor_dirs_as_system, vendor_dirs_ignored,
1715                         extra_deps, generic_refs, tagged_paths):
1716        graph = ELFLinker(tagged_paths)
1717
1718        if system_dirs:
1719            for path in system_dirs:
1720                graph.add_executables_in_dir('system', PT_SYSTEM, path,
1721                                             PT_VENDOR, system_dirs_as_vendor,
1722                                             system_dirs_ignored,
1723                                             scan_elf_files)
1724
1725        if vendor_dirs:
1726            for path in vendor_dirs:
1727                graph.add_executables_in_dir('vendor', PT_VENDOR, path,
1728                                             PT_SYSTEM, vendor_dirs_as_system,
1729                                             vendor_dirs_ignored,
1730                                             scan_elf_files)
1731
1732        if extra_deps:
1733            for path in extra_deps:
1734                graph.load_extra_deps(path)
1735
1736        graph.resolve_deps(generic_refs)
1737
1738        return graph
1739
1740    @staticmethod
1741    def create(system_dirs=None, system_dirs_as_vendor=None,
1742               system_dirs_ignored=None, vendor_dirs=None,
1743               vendor_dirs_as_system=None, vendor_dirs_ignored=None,
1744               extra_deps=None, generic_refs=None, tagged_paths=None):
1745        return ELFLinker._create_internal(
1746                scan_elf_files, system_dirs, system_dirs_as_vendor,
1747                system_dirs_ignored, vendor_dirs, vendor_dirs_as_system,
1748                vendor_dirs_ignored, extra_deps, generic_refs, tagged_paths)
1749
1750    @staticmethod
1751    def create_from_dump(system_dirs=None, system_dirs_as_vendor=None,
1752                         vendor_dirs=None, vendor_dirs_as_system=None,
1753                         extra_deps=None, generic_refs=None, tagged_paths=None):
1754        return ELFLinker._create_internal(
1755                scan_elf_dump_files, system_dirs, system_dirs_as_vendor,
1756                vendor_dirs, vendor_dirs_as_system, extra_deps, generic_refs,
1757                tagged_paths)
1758
1759
1760#------------------------------------------------------------------------------
1761# Generic Reference
1762#------------------------------------------------------------------------------
1763
1764class GenericRefs(object):
1765    NEW_LIB = 0
1766    EXPORT_EQUAL = 1
1767    EXPORT_SUPER_SET = 2
1768    MODIFIED = 3
1769
1770    def __init__(self):
1771        self.refs = dict()
1772        self._lib_names = set()
1773
1774    def add(self, path, elf):
1775        self.refs[path] = elf
1776        self._lib_names.add(os.path.basename(path))
1777
1778    def _load_from_sym_dir(self, root):
1779        root = os.path.abspath(root)
1780        prefix_len = len(root) + 1
1781        for base, dirnames, filenames in os.walk(root):
1782            for filename in filenames:
1783                if not filename.endswith('.sym'):
1784                    continue
1785                path = os.path.join(base, filename)
1786                lib_path = '/' + path[prefix_len:-4]
1787                with open(path, 'r') as f:
1788                    self.add(lib_path, ELF.load_dump(path))
1789
1790    @staticmethod
1791    def create_from_sym_dir(root):
1792        result = GenericRefs()
1793        result._load_from_sym_dir(root)
1794        return result
1795
1796    def _load_from_image_dir(self, root, prefix):
1797        root = os.path.abspath(root)
1798        root_len = len(root) + 1
1799        for path, elf in scan_elf_files(root):
1800            self.add(os.path.join(prefix, path[root_len:]), elf)
1801
1802    @staticmethod
1803    def create_from_image_dir(root, prefix):
1804        result = GenericRefs()
1805        result._load_from_image_dir(root, prefix)
1806        return result
1807
1808    def classify_lib(self, lib):
1809        ref_lib = self.refs.get(lib.path)
1810        if not ref_lib:
1811            return GenericRefs.NEW_LIB
1812        exported_symbols = lib.elf.exported_symbols
1813        if exported_symbols == ref_lib.exported_symbols:
1814            return GenericRefs.EXPORT_EQUAL
1815        if exported_symbols > ref_lib.exported_symbols:
1816            return GenericRefs.EXPORT_SUPER_SET
1817        return GenericRefs.MODIFIED
1818
1819    def is_equivalent_lib(self, lib):
1820        return self.classify_lib(lib) == GenericRefs.EXPORT_EQUAL
1821
1822    def has_same_name_lib(self, lib):
1823        return os.path.basename(lib.path) in self._lib_names
1824
1825
1826#------------------------------------------------------------------------------
1827# Module Info
1828#------------------------------------------------------------------------------
1829
1830class ModuleInfo(object):
1831    def __init__(self, json=None):
1832        if not json:
1833            self._mods = dict()
1834            return
1835
1836        mods = collections.defaultdict(set)
1837        installed_path_patt = re.compile(
1838                '.*[\\\\/]target[\\\\/]product[\\\\/][^\\\\/]+([\\\\/].*)$')
1839        for name, module in json.items():
1840            for path in module['installed']:
1841                match = installed_path_patt.match(path)
1842                if match:
1843                    for path in module['path']:
1844                        mods[match.group(1)].add(path)
1845        self._mods = { installed_path: sorted(src_dirs)
1846                       for installed_path, src_dirs in mods.items() }
1847
1848    def get_module_path(self, installed_path):
1849        return self._mods.get(installed_path, [])
1850
1851    @staticmethod
1852    def load(f):
1853        return ModuleInfo(json.load(f))
1854
1855    @staticmethod
1856    def load_from_path_or_default(path):
1857        if not path:
1858            return ModuleInfo()
1859        with open(path, 'r') as f:
1860            return ModuleInfo.load(f)
1861
1862
1863#------------------------------------------------------------------------------
1864# Commands
1865#------------------------------------------------------------------------------
1866
1867class Command(object):
1868    def __init__(self, name, help):
1869        self.name = name
1870        self.help = help
1871
1872    def add_argparser_options(self, parser):
1873        pass
1874
1875    def main(self, args):
1876        return 0
1877
1878
1879class ELFDumpCommand(Command):
1880    def __init__(self):
1881        super(ELFDumpCommand, self).__init__(
1882                'elfdump', help='Dump ELF .dynamic section')
1883
1884    def add_argparser_options(self, parser):
1885        parser.add_argument('path', help='path to an ELF file')
1886
1887    def main(self, args):
1888        try:
1889            ELF.load(args.path).dump()
1890        except ELFError as e:
1891            print('error: {}: Bad ELF file ({})'.format(args.path, e),
1892                  file=sys.stderr)
1893            sys.exit(1)
1894        return 0
1895
1896
1897class CreateGenericRefCommand(Command):
1898    def __init__(self):
1899        super(CreateGenericRefCommand, self).__init__(
1900                'create-generic-ref', help='Create generic references')
1901
1902    def add_argparser_options(self, parser):
1903        parser.add_argument('dir')
1904
1905        parser.add_argument(
1906                '--output', '-o', metavar='PATH', required=True,
1907                help='output directory')
1908
1909    def main(self, args):
1910        root = os.path.abspath(args.dir)
1911        print(root)
1912        prefix_len = len(root) + 1
1913        for path, elf in scan_elf_files(root):
1914            name = path[prefix_len:]
1915            print('Processing:', name, file=sys.stderr)
1916            out = os.path.join(args.output, name) + '.sym'
1917            makedirs(os.path.dirname(out), exist_ok=True)
1918            with open(out, 'w') as f:
1919                elf.dump(f)
1920        return 0
1921
1922
1923class ELFGraphCommand(Command):
1924    def add_argparser_options(self, parser):
1925        parser.add_argument(
1926                '--load-extra-deps', action='append',
1927                help='load extra module dependencies')
1928
1929        parser.add_argument(
1930                '--system', action='append',
1931                help='path to system partition contents')
1932
1933        parser.add_argument(
1934                '--vendor', action='append',
1935                help='path to vendor partition contents')
1936
1937        parser.add_argument(
1938                '--system-dir-as-vendor', action='append',
1939                help='sub directory of system partition that has vendor files')
1940
1941        parser.add_argument(
1942                '--system-dir-ignored', action='append',
1943                help='sub directory of system partition that must be ignored')
1944
1945        parser.add_argument(
1946                '--vendor-dir-as-system', action='append',
1947                help='sub directory of vendor partition that has system files')
1948
1949        parser.add_argument(
1950                '--vendor-dir-ignored', action='append',
1951                help='sub directory of vendor partition that must be ignored')
1952
1953        parser.add_argument(
1954                '--load-generic-refs',
1955                help='compare with generic reference symbols')
1956
1957        parser.add_argument(
1958                '--aosp-system',
1959                help='compare with AOSP generic system image directory')
1960
1961        parser.add_argument('--tag-file', help='lib tag file')
1962
1963    def get_generic_refs_from_args(self, args):
1964        if args.load_generic_refs:
1965            return GenericRefs.create_from_sym_dir(args.load_generic_refs)
1966        if args.aosp_system:
1967            return GenericRefs.create_from_image_dir(args.aosp_system,
1968                                                     '/system')
1969        return None
1970
1971    def _check_arg_dir_exists(self, arg_name, dirs):
1972        for path in dirs:
1973            if not os.path.exists(path):
1974                print('error: Failed to find the directory "{}" specified in {}'
1975                        .format(path, arg_name), file=sys.stderr)
1976                sys.exit(1)
1977            if not os.path.isdir(path):
1978                print('error: Path "{}" specified in {} is not a directory'
1979                        .format(path, arg_name), file=sys.stderr)
1980                sys.exit(1)
1981
1982    def check_dirs_from_args(self, args):
1983        self._check_arg_dir_exists('--system', args.system)
1984        self._check_arg_dir_exists('--vendor', args.vendor)
1985
1986    def create_from_args(self, args):
1987        self.check_dirs_from_args(args)
1988
1989        generic_refs = self.get_generic_refs_from_args(args)
1990
1991        if args.tag_file:
1992            tagged_paths = TaggedPathDict.create_from_csv_path(args.tag_file)
1993        else:
1994            tagged_paths = None
1995
1996        graph = ELFLinker.create(args.system, args.system_dir_as_vendor,
1997                                 args.system_dir_ignored,
1998                                 args.vendor, args.vendor_dir_as_system,
1999                                 args.vendor_dir_ignored,
2000                                 args.load_extra_deps,
2001                                 generic_refs=generic_refs,
2002                                 tagged_paths=tagged_paths)
2003
2004        return (generic_refs, graph, tagged_paths)
2005
2006
2007class VNDKCommandBase(ELFGraphCommand):
2008    def add_argparser_options(self, parser):
2009        super(VNDKCommandBase, self).add_argparser_options(parser)
2010
2011        parser.add_argument('--no-default-dlopen-deps', action='store_true',
2012                help='do not add default dlopen dependencies')
2013
2014        parser.add_argument(
2015                '--action-ineligible-vndk-sp', default='warn',
2016                help='action when a sp-hal uses non-vndk-sp libs '
2017                     '(option: follow,warn,ignore)')
2018
2019        parser.add_argument(
2020                '--action-ineligible-vndk', default='warn',
2021                help='action when a vendor lib/exe uses fwk-only libs '
2022                     '(option: follow,warn,ignore)')
2023
2024    def create_from_args(self, args):
2025        """Create all essential data structures for VNDK computation."""
2026
2027        generic_refs, graph, tagged_paths = \
2028                super(VNDKCommandBase, self).create_from_args(args)
2029
2030        if not args.no_default_dlopen_deps:
2031            script_dir = os.path.dirname(os.path.abspath(__file__))
2032            minimum_dlopen_deps = os.path.join(script_dir, 'datasets',
2033                                               'minimum_dlopen_deps.txt')
2034            graph.load_extra_deps(minimum_dlopen_deps)
2035
2036        return (generic_refs, graph, tagged_paths)
2037
2038
2039class VNDKCommand(VNDKCommandBase):
2040    def __init__(self):
2041        super(VNDKCommand, self).__init__(
2042                'vndk', help='Compute VNDK libraries set')
2043
2044    def add_argparser_options(self, parser):
2045        super(VNDKCommand, self).add_argparser_options(parser)
2046
2047        parser.add_argument(
2048                '--warn-incorrect-partition', action='store_true',
2049                help='warn about libraries only have cross partition linkages')
2050
2051        parser.add_argument(
2052                '--full', action='store_true',
2053                help='print all classification')
2054
2055        parser.add_argument(
2056                '--output-format', default='tag',
2057                help='output format for vndk classification')
2058
2059    def _warn_incorrect_partition_lib_set(self, lib_set, partition, error_msg):
2060        for lib in lib_set.values():
2061            if not lib.num_users:
2062                continue
2063            if all((user.partition != partition for user in lib.users)):
2064                print(error_msg.format(lib.path), file=sys.stderr)
2065
2066    def _warn_incorrect_partition(self, graph):
2067        self._warn_incorrect_partition_lib_set(
2068                graph.lib_pt[PT_VENDOR], PT_VENDOR,
2069                'warning: {}: This is a vendor library with framework-only '
2070                'usages.')
2071
2072        self._warn_incorrect_partition_lib_set(
2073                graph.lib_pt[PT_SYSTEM], PT_SYSTEM,
2074                'warning: {}: This is a framework library with vendor-only '
2075                'usages.')
2076
2077    @staticmethod
2078    def _extract_simple_vndk_result(vndk_result):
2079        field_name_tags = [
2080            ('vndk_sp', 'vndk_sp'),
2081            ('vndk_sp_unused', 'vndk_sp'),
2082            ('vndk_sp_indirect', 'vndk_sp'),
2083            ('vndk_sp_indirect_unused', 'vndk_sp'),
2084            ('vndk_sp_indirect_private', 'vndk_sp'),
2085
2086            ('vndk_sp_ext', 'vndk_sp_ext'),
2087            ('vndk_sp_indirect_ext', 'vndk_sp_ext'),
2088
2089            ('vndk_ext', 'extra_vendor_libs'),
2090            ('extra_vendor_libs', 'extra_vendor_libs'),
2091        ]
2092        results = SimpleVNDKResult()
2093        for field_name, tag in field_name_tags:
2094            getattr(results, tag).update(getattr(vndk_result, field_name))
2095        return results
2096
2097    def _print_tags(self, vndk_lib, full, file=sys.stdout):
2098        if full:
2099            result_tags = _VNDK_RESULT_FIELD_NAMES
2100            results = vndk_lib
2101        else:
2102            # Simplified VNDK output with only three sets.
2103            result_tags = _SIMPLE_VNDK_RESULT_FIELD_NAMES
2104            results = self._extract_simple_vndk_result(vndk_lib)
2105
2106        for tag in result_tags:
2107            libs = getattr(results, tag)
2108            tag += ':'
2109            for lib in sorted_lib_path_list(libs):
2110                print(tag, lib, file=file)
2111
2112    def _print_make(self, vndk_lib, file=sys.stdout):
2113        def get_module_name(path):
2114            name = os.path.basename(path)
2115            root, ext = os.path.splitext(name)
2116            return root
2117
2118        def get_module_names(lib_set):
2119            return sorted({ get_module_name(lib.path) for lib in lib_set })
2120
2121        results = self._extract_simple_vndk_result(vndk_lib)
2122        vndk_sp = get_module_names(results.vndk_sp)
2123        vndk_sp_ext = get_module_names(results.vndk_sp_ext)
2124        extra_vendor_libs= get_module_names(results.extra_vendor_libs)
2125
2126        def format_module_names(module_names):
2127            return '\\\n    ' +  ' \\\n    '.join(module_names)
2128
2129        script_dir = os.path.dirname(os.path.abspath(__file__))
2130        template_path = os.path.join(script_dir, 'templates', 'vndk.txt')
2131        with open(template_path, 'r') as f:
2132            template = f.read()
2133
2134        template = template.replace('##_VNDK_SP_##',
2135                                    format_module_names(vndk_sp))
2136        template = template.replace('##_VNDK_SP_EXT_##',
2137                                    format_module_names(vndk_sp_ext))
2138        template = template.replace('##_EXTRA_VENDOR_LIBS_##',
2139                                    format_module_names(extra_vendor_libs))
2140
2141        file.write(template)
2142
2143    def main(self, args):
2144        generic_refs, graph, tagged_paths = self.create_from_args(args)
2145
2146        if args.warn_incorrect_partition:
2147            self._warn_incorrect_partition(graph)
2148
2149        # Compute vndk heuristics.
2150        vndk_lib = graph.compute_degenerated_vndk(
2151                generic_refs, tagged_paths, args.action_ineligible_vndk_sp,
2152                args.action_ineligible_vndk)
2153
2154        # Print results.
2155        if args.output_format == 'make':
2156            self._print_make(vndk_lib)
2157        else:
2158            self._print_tags(vndk_lib, args.full)
2159
2160        return 0
2161
2162
2163class DepsInsightCommand(VNDKCommandBase):
2164    def __init__(self):
2165        super(DepsInsightCommand, self).__init__(
2166                'deps-insight', help='Generate HTML to show dependencies')
2167
2168    def add_argparser_options(self, parser):
2169        super(DepsInsightCommand, self).add_argparser_options(parser)
2170
2171        parser.add_argument('--module-info')
2172
2173        parser.add_argument(
2174                '--output', '-o', help='output directory')
2175
2176    def main(self, args):
2177        generic_refs, graph, tagged_paths = self.create_from_args(args)
2178
2179        module_info = ModuleInfo.load_from_path_or_default(args.module_info)
2180
2181        # Compute vndk heuristics.
2182        vndk_lib = graph.compute_degenerated_vndk(
2183                generic_refs, tagged_paths, args.action_ineligible_vndk_sp,
2184                args.action_ineligible_vndk)
2185
2186        # Serialize data.
2187        strs = []
2188        strs_dict = dict()
2189
2190        libs = list(graph.all_libs())
2191        libs.sort(key=lambda lib: lib.path)
2192        libs_dict = {lib: i for i, lib in enumerate(libs)}
2193
2194        def get_str_idx(s):
2195            try:
2196                return strs_dict[s]
2197            except KeyError:
2198                idx = len(strs)
2199                strs_dict[s] = idx
2200                strs.append(s)
2201                return idx
2202
2203        def collect_path_sorted_lib_idxs(libs):
2204            return [libs_dict[lib] for lib in sorted(libs)]
2205
2206        def collect_deps(lib):
2207            queue = list(lib.deps)
2208            visited = set(queue)
2209            visited.add(lib)
2210            deps = []
2211
2212            # Traverse dependencies with breadth-first search.
2213            while queue:
2214                # Collect dependencies for next queue.
2215                next_queue = []
2216                for lib in queue:
2217                    for dep in lib.deps:
2218                        if dep not in visited:
2219                            next_queue.append(dep)
2220                            visited.add(dep)
2221
2222                # Append current queue to result.
2223                deps.append(collect_path_sorted_lib_idxs(queue))
2224
2225                queue = next_queue
2226
2227            return deps
2228
2229        def collect_source_dir_paths(lib):
2230            return [get_str_idx(path)
2231                    for path in module_info.get_module_path(lib.path)]
2232
2233        def collect_tags(lib):
2234            tags = []
2235            for field_name in _VNDK_RESULT_FIELD_NAMES:
2236                if lib in getattr(vndk_lib, field_name):
2237                    tags.append(get_str_idx(field_name))
2238            return tags
2239
2240        mods = []
2241        for lib in libs:
2242            mods.append([get_str_idx(lib.path),
2243                         32 if lib.elf.is_32bit else 64,
2244                         collect_tags(lib),
2245                         collect_deps(lib),
2246                         collect_path_sorted_lib_idxs(lib.users),
2247                         collect_source_dir_paths(lib)])
2248
2249        # Generate output files.
2250        makedirs(args.output, exist_ok=True)
2251        script_dir = os.path.dirname(os.path.abspath(__file__))
2252        for name in ('index.html', 'insight.css', 'insight.js'):
2253            shutil.copyfile(os.path.join(script_dir, 'assets', 'insight', name),
2254                            os.path.join(args.output, name))
2255
2256        with open(os.path.join(args.output, 'insight-data.js'), 'w') as f:
2257            f.write('''(function () {
2258    var strs = ''' + json.dumps(strs) + ''';
2259    var mods = ''' + json.dumps(mods) + ''';
2260    insight.init(document, strs, mods);
2261})();''')
2262
2263        return 0
2264
2265
2266class DepsCommand(ELFGraphCommand):
2267    def __init__(self):
2268        super(DepsCommand, self).__init__(
2269                'deps', help='Print binary dependencies for debugging')
2270
2271    def add_argparser_options(self, parser):
2272        super(DepsCommand, self).add_argparser_options(parser)
2273
2274        parser.add_argument(
2275                '--revert', action='store_true',
2276                help='print usage dependency')
2277
2278        parser.add_argument(
2279                '--leaf', action='store_true',
2280                help='print binaries without dependencies or usages')
2281
2282        parser.add_argument(
2283                '--symbols', action='store_true',
2284                help='print symbols')
2285
2286        parser.add_argument('--module-info')
2287
2288    def main(self, args):
2289        generic_refs, graph, tagged_paths = self.create_from_args(args)
2290
2291        module_info = ModuleInfo.load_from_path_or_default(args.module_info)
2292
2293        results = []
2294        for partition in range(NUM_PARTITIONS):
2295            for name, lib in graph.lib_pt[partition].items():
2296                if args.symbols:
2297                    def collect_symbols(user, definer):
2298                        return user.get_dep_linked_symbols(definer)
2299                else:
2300                    def collect_symbols(user, definer):
2301                        return ()
2302
2303                data = []
2304                if args.revert:
2305                    for assoc_lib in sorted(lib.users):
2306                        data.append((assoc_lib.path,
2307                                     collect_symbols(assoc_lib, lib)))
2308                else:
2309                    for assoc_lib in sorted(lib.deps):
2310                        data.append((assoc_lib.path,
2311                                     collect_symbols(lib, assoc_lib)))
2312                results.append((name, data))
2313        results.sort()
2314
2315        if args.leaf:
2316            for name, deps in results:
2317                if not deps:
2318                    print(name)
2319        else:
2320            for name, assoc_libs in results:
2321                print(name)
2322                for module_path in module_info.get_module_path(name):
2323                    print('\tMODULE_PATH:', module_path)
2324                for assoc_lib, symbols in assoc_libs:
2325                    print('\t' + assoc_lib)
2326                    for module_path in module_info.get_module_path(assoc_lib):
2327                        print('\t\tMODULE_PATH:', module_path)
2328                    for symbol in symbols:
2329                        print('\t\t' + symbol)
2330        return 0
2331
2332
2333class DepsClosureCommand(ELFGraphCommand):
2334    def __init__(self):
2335        super(DepsClosureCommand, self).__init__(
2336                'deps-closure', help='Find transitive closure of dependencies')
2337
2338    def add_argparser_options(self, parser):
2339        super(DepsClosureCommand, self).add_argparser_options(parser)
2340
2341        parser.add_argument('lib', nargs='*',
2342                            help='root set of the shared libraries')
2343
2344        parser.add_argument('--exclude-lib', action='append', default=[],
2345                            help='libraries to be excluded')
2346
2347        parser.add_argument('--exclude-ndk', action='store_true',
2348                            help='exclude ndk libraries')
2349
2350        parser.add_argument('--revert', action='store_true',
2351                            help='print usage dependency')
2352
2353        parser.add_argument('--enumerate', action='store_true',
2354                            help='print closure for each lib instead of union')
2355
2356    def print_deps_closure(self, root_libs, graph, is_excluded_libs,
2357                           is_reverted, indent):
2358        if is_reverted:
2359            closure = graph.compute_users_closure(root_libs, is_excluded_libs)
2360        else:
2361            closure = graph.compute_deps_closure(root_libs, is_excluded_libs)
2362
2363        for lib in sorted_lib_path_list(closure):
2364            print(indent + lib)
2365
2366
2367    def main(self, args):
2368        generic_refs, graph, tagged_paths = self.create_from_args(args)
2369
2370        # Find root/excluded libraries by their paths.
2371        def report_error(path):
2372            print('error: no such lib: {}'.format(path), file=sys.stderr)
2373        root_libs = graph.get_libs(args.lib, report_error)
2374        excluded_libs = graph.get_libs(args.exclude_lib, report_error)
2375
2376        # Define the exclusion filter.
2377        if args.exclude_ndk:
2378            def is_excluded_libs(lib):
2379                return lib.is_ndk or lib in excluded_libs
2380        else:
2381            def is_excluded_libs(lib):
2382                return lib in excluded_libs
2383
2384        if not args.enumerate:
2385            self.print_deps_closure(root_libs, graph, is_excluded_libs,
2386                                    args.revert, '')
2387        else:
2388            if not root_libs:
2389                root_libs = list(graph.all_libs())
2390            for lib in sorted(root_libs):
2391                print(lib.path)
2392                self.print_deps_closure({lib}, graph, is_excluded_libs,
2393                                        args.revert, '\t')
2394        return 0
2395
2396
2397class DepsUnresolvedCommand(ELFGraphCommand):
2398    def __init__(self):
2399        super(DepsUnresolvedCommand, self).__init__(
2400                'deps-unresolved',
2401                help='Show unresolved dt_needed entries or symbols')
2402
2403    def add_argparser_options(self, parser):
2404        super(DepsUnresolvedCommand, self).add_argparser_options(parser)
2405        parser.add_argument('--module-info')
2406        parser.add_argument('--path-filter')
2407
2408    def _dump_unresolved(self, lib, module_info):
2409        if not lib.unresolved_symbols and not lib.unresolved_dt_needed:
2410            return
2411
2412        print(lib.path)
2413        for module_path in module_info.get_module_path(lib.path):
2414            print('\tMODULE_PATH:', module_path)
2415        for dt_needed in sorted(lib.unresolved_dt_needed):
2416            print('\tUNRESOLVED_DT_NEEDED:', dt_needed)
2417        for symbol in sorted(lib.unresolved_symbols):
2418            print('\tUNRESOLVED_SYMBOL:', symbol)
2419
2420    def main(self, args):
2421        generic_refs, graph, tagged_paths = self.create_from_args(args)
2422        module_info = ModuleInfo.load_from_path_or_default(args.module_info)
2423
2424        libs = graph.all_libs()
2425        if args.path_filter:
2426            path_filter = re.compile(args.path_filter)
2427            libs = [lib for lib in libs if path_filter.match(lib.path)]
2428
2429        for lib in sorted(libs):
2430            self._dump_unresolved(lib, module_info)
2431
2432class CheckDepCommandBase(ELFGraphCommand):
2433    def add_argparser_options(self, parser):
2434        super(CheckDepCommandBase, self).add_argparser_options(parser)
2435
2436        parser.add_argument('--module-info')
2437
2438    @staticmethod
2439    def _dump_dep(lib, bad_deps, module_info):
2440        print(lib.path)
2441        for module_path in module_info.get_module_path(lib.path):
2442            print('\tMODULE_PATH:', module_path)
2443        for dep in sorted(bad_deps):
2444            print('\t' + dep.path)
2445            for module_path in module_info.get_module_path(dep.path):
2446                print('\t\tMODULE_PATH:', module_path)
2447            for symbol in lib.get_dep_linked_symbols(dep):
2448                print('\t\t' + symbol)
2449
2450
2451class CheckDepCommand(CheckDepCommandBase):
2452    def __init__(self):
2453        super(CheckDepCommand, self).__init__(
2454                'check-dep', help='Check the eligible dependencies')
2455
2456    def _check_vendor_dep(self, graph, tagged_libs, module_info):
2457        """Check whether vendor libs are depending on non-eligible libs."""
2458        num_errors = 0
2459
2460        vendor_libs = set(graph.lib_pt[PT_VENDOR].values())
2461
2462        eligible_libs = (tagged_libs.ll_ndk | tagged_libs.sp_ndk | \
2463                         tagged_libs.vndk_sp | tagged_libs.vndk_sp_indirect | \
2464                         tagged_libs.vndk)
2465
2466        for lib in sorted(vendor_libs):
2467            bad_deps = set()
2468
2469            # Check whether vendor modules depend on extended NDK symbols.
2470            for dep, symbols in lib.imported_ext_symbols.items():
2471                if dep.is_ndk:
2472                    num_errors += 1
2473                    bad_deps.add(dep)
2474                    for symbol in symbols:
2475                        print('error: vendor lib "{}" depends on extended '
2476                              'NDK symbol "{}" from "{}".'
2477                              .format(lib.path, symbol, dep.path),
2478                              file=sys.stderr)
2479
2480            # Check whether vendor modules depend on ineligible libs.
2481            for dep in lib.deps:
2482                if dep not in vendor_libs and dep not in eligible_libs:
2483                    num_errors += 1
2484                    bad_deps.add(dep)
2485                    print('error: vendor lib "{}" depends on non-eligible '
2486                          'lib "{}".'.format(lib.path, dep.path),
2487                          file=sys.stderr)
2488
2489            if bad_deps:
2490                self._dump_dep(lib, bad_deps, module_info)
2491
2492        return num_errors
2493
2494    def main(self, args):
2495        generic_refs, graph, tagged_paths = self.create_from_args(args)
2496
2497        tagged_paths = TaggedPathDict.create_from_csv_path(args.tag_file)
2498        tagged_libs = TaggedLibDict.create_from_graph(graph, tagged_paths)
2499
2500        module_info = ModuleInfo.load_from_path_or_default(args.module_info)
2501
2502        num_errors = self._check_vendor_dep(graph, tagged_libs, module_info)
2503
2504        return 0 if num_errors == 0 else 1
2505
2506
2507class CheckEligibleListCommand(CheckDepCommandBase):
2508    def __init__(self):
2509        super(CheckEligibleListCommand, self).__init__(
2510                'check-eligible-list', help='Check the eligible list')
2511
2512    def _check_eligible_vndk_dep(self, graph, tagged_libs, module_info):
2513        """Check whether eligible sets are self-contained."""
2514        num_errors = 0
2515
2516        indirect_libs = (tagged_libs.ll_ndk_indirect | \
2517                         tagged_libs.sp_ndk_indirect | \
2518                         tagged_libs.vndk_sp_indirect_private | \
2519                         tagged_libs.fwk_only_rs)
2520
2521        eligible_libs = (tagged_libs.ll_ndk | tagged_libs.sp_ndk | \
2522                         tagged_libs.vndk_sp | tagged_libs.vndk_sp_indirect | \
2523                         tagged_libs.vndk)
2524
2525        # Check eligible vndk is self-contained.
2526        for lib in sorted(eligible_libs):
2527            bad_deps = []
2528            for dep in lib.deps:
2529                if dep not in eligible_libs and dep not in indirect_libs:
2530                    print('error: eligible lib "{}" should not depend on '
2531                          'non-eligible lib "{}".'.format(lib.path, dep.path),
2532                          file=sys.stderr)
2533                    bad_deps.append(dep)
2534                    num_errors += 1
2535            if bad_deps:
2536                self._dump_dep(lib, bad_deps, module_info)
2537
2538        # Check the libbinder dependencies.
2539        for lib in sorted(eligible_libs):
2540            bad_deps = []
2541            for dep in lib.deps:
2542                if os.path.basename(dep.path) == 'libbinder.so':
2543                    print('error: eligible lib "{}" should not depend on '
2544                          'libbinder.so.'.format(lib.path), file=sys.stderr)
2545                    bad_deps.append(dep)
2546                    num_errors += 1
2547            if bad_deps:
2548                self._dump_dep(lib, bad_deps, module_info)
2549
2550        return num_errors
2551
2552    def main(self, args):
2553        generic_refs, graph, tagged_paths = self.create_from_args(args)
2554
2555        tagged_paths = TaggedPathDict.create_from_csv_path(args.tag_file)
2556        tagged_libs = TaggedLibDict.create_from_graph(graph, tagged_paths)
2557
2558        module_info = ModuleInfo.load_from_path_or_default(args.module_info)
2559
2560        num_errors = self._check_eligible_vndk_dep(graph, tagged_libs,
2561                                                   module_info)
2562        return 0 if num_errors == 0 else 1
2563
2564
2565class DepGraphCommand(ELFGraphCommand):
2566    def __init__(self):
2567        super(DepGraphCommand, self).__init__(
2568                'dep-graph', help='Show the eligible dependencies graph')
2569
2570    def add_argparser_options(self, parser):
2571        super(DepGraphCommand, self).add_argparser_options(parser)
2572
2573        parser.add_argument('--output', '-o', help='output directory')
2574
2575    def _get_tag_from_lib(self, lib, tagged_paths):
2576        tag_hierarchy = dict()
2577        for tag in TaggedPathDict.TAGS:
2578            if tag in {'sp_hal', 'sp_hal_dep', 'vnd_only'}:
2579                tag_hierarchy[tag] = 'vendor.private.{}'.format(tag)
2580            else:
2581                vendor_visible = TaggedPathDict.is_tag_visible('vnd_only', tag)
2582                pub = 'public' if vendor_visible else 'private'
2583                tag_hierarchy[tag] = 'system.{}.{}'.format(pub, tag)
2584
2585        return tag_hierarchy[tagged_paths.get_path_tag(lib.path)]
2586
2587    def _check_if_allowed(self, my_tag, other_tag):
2588        my = my_tag.split('.')
2589        other = other_tag.split('.')
2590        if my[0] == 'system' and other[0] == 'vendor':
2591            return False
2592        if my[0] == 'vendor' and other[0] == 'system' \
2593                             and other[1] == 'private':
2594            return False
2595        return True
2596
2597    def _get_dep_graph(self, graph, tagged_paths):
2598        data = []
2599        violate_libs = dict()
2600        system_libs = graph.lib_pt[PT_SYSTEM].values()
2601        vendor_libs = graph.lib_pt[PT_VENDOR].values()
2602        for lib in itertools.chain(system_libs, vendor_libs):
2603            tag = self._get_tag_from_lib(lib, tagged_paths)
2604            violate_count = 0
2605            lib_item = {
2606                'name': lib.path,
2607                'tag': tag,
2608                'depends': [],
2609                'violates': [],
2610            }
2611            for dep in lib.deps:
2612                if self._check_if_allowed(tag,
2613                        self._get_tag_from_lib(dep, tagged_paths)):
2614                    lib_item['depends'].append(dep.path)
2615                else:
2616                    lib_item['violates'].append([dep.path, lib.get_dep_linked_symbols(dep)])
2617                    violate_count += 1;
2618            lib_item['violate_count'] = violate_count
2619            if violate_count > 0:
2620                if not tag in violate_libs:
2621                    violate_libs[tag] = []
2622                violate_libs[tag].append((lib.path, violate_count))
2623            data.append(lib_item)
2624        return data, violate_libs
2625
2626    def main(self, args):
2627        generic_refs, graph, tagged_paths = self.create_from_args(args)
2628
2629        tagged_paths = TaggedPathDict.create_from_csv_path(args.tag_file)
2630        data, violate_libs = self._get_dep_graph(graph, tagged_paths)
2631        data.sort(key=lambda lib_item: (lib_item['tag'],
2632                                        lib_item['violate_count']))
2633        for libs in violate_libs.values():
2634            libs.sort(key=lambda libs: libs[1], reverse=True)
2635
2636        makedirs(args.output, exist_ok=True)
2637        script_dir = os.path.dirname(os.path.abspath(__file__))
2638        for name in ('index.html', 'dep-graph.js', 'dep-graph.css'):
2639            shutil.copyfile(os.path.join(script_dir, 'assets', 'visual', name),
2640                            os.path.join(args.output, name))
2641        with open(os.path.join(args.output, 'dep-data.js'), 'w') as f:
2642            f.write('var violatedLibs = ' + json.dumps(violate_libs) +
2643                    '\nvar depData = ' + json.dumps(data) + ';')
2644
2645        return 0
2646
2647
2648def main():
2649    parser = argparse.ArgumentParser()
2650    subparsers = parser.add_subparsers(dest='subcmd')
2651    subcmds = dict()
2652
2653    def register_subcmd(cmd):
2654        subcmds[cmd.name] = cmd
2655        cmd.add_argparser_options(
2656                subparsers.add_parser(cmd.name, help=cmd.help))
2657
2658    register_subcmd(ELFDumpCommand())
2659    register_subcmd(CreateGenericRefCommand())
2660    register_subcmd(VNDKCommand())
2661    register_subcmd(DepsCommand())
2662    register_subcmd(DepsClosureCommand())
2663    register_subcmd(DepsInsightCommand())
2664    register_subcmd(DepsUnresolvedCommand())
2665    register_subcmd(CheckDepCommand())
2666    register_subcmd(CheckEligibleListCommand())
2667    register_subcmd(DepGraphCommand())
2668
2669    args = parser.parse_args()
2670    if not args.subcmd:
2671        parser.print_help()
2672        sys.exit(1)
2673    return subcmds[args.subcmd].main(args)
2674
2675if __name__ == '__main__':
2676    sys.exit(main())
2677