Merge changes I6eb07d50,I15cc1568,I70b7132f
* changes: def-tool: Do not emit errors on public.libraries.txt def-tool: Support product and product_services partition def-tool: Remove a dead argument (cont)
This commit is contained in:
@@ -22,6 +22,8 @@ _TEST_DATA = '''Path,Tag
|
|||||||
/vendor/lib/lib_sp_hal.so,sp-hal
|
/vendor/lib/lib_sp_hal.so,sp-hal
|
||||||
/vendor/lib/lib_sp_hal_dep.so,sp-hal-dep
|
/vendor/lib/lib_sp_hal_dep.so,sp-hal-dep
|
||||||
/vendor/lib/lib_vendor_only.so,vendor-only
|
/vendor/lib/lib_vendor_only.so,vendor-only
|
||||||
|
/product/lib/lib_product_only.so,product-only
|
||||||
|
/product_services/lib/lib_product_services_only.so,product_services-only
|
||||||
/system/lib/lib_remove.so,remove
|
/system/lib/lib_remove.so,remove
|
||||||
/system/lib/lib_hl_ndk.so,hl-ndk
|
/system/lib/lib_hl_ndk.so,hl-ndk
|
||||||
/system/lib/lib_vndk_private.so,vndk-private
|
/system/lib/lib_vndk_private.so,vndk-private
|
||||||
@@ -58,6 +60,8 @@ class TaggedDictTest(unittest.TestCase):
|
|||||||
|
|
||||||
|
|
||||||
def _check_tag_visibility(self, d, from_tag, visible_tags):
|
def _check_tag_visibility(self, d, from_tag, visible_tags):
|
||||||
|
for to_tag in visible_tags:
|
||||||
|
self.assertTrue(d.is_tag_visible(from_tag, to_tag))
|
||||||
for to_tag in TaggedDict.TAGS:
|
for to_tag in TaggedDict.TAGS:
|
||||||
self.assertEqual(d.is_tag_visible(from_tag, to_tag),
|
self.assertEqual(d.is_tag_visible(from_tag, to_tag),
|
||||||
to_tag in visible_tags)
|
to_tag in visible_tags)
|
||||||
@@ -72,24 +76,30 @@ class TaggedDictTest(unittest.TestCase):
|
|||||||
self._check_tag_visibility(d, 'll_ndk_private', visible_tags)
|
self._check_tag_visibility(d, 'll_ndk_private', visible_tags)
|
||||||
|
|
||||||
# VNDK-SP
|
# VNDK-SP
|
||||||
visible_tags = {'ll_ndk', 'vndk_sp', 'vndk_sp_private',
|
visible_tags = {
|
||||||
'system_only_rs'}
|
'll_ndk', 'vndk_sp', 'vndk_sp_private', 'system_only_rs',
|
||||||
|
}
|
||||||
self._check_tag_visibility(d, 'vndk_sp', visible_tags)
|
self._check_tag_visibility(d, 'vndk_sp', visible_tags)
|
||||||
self._check_tag_visibility(d, 'vndk_sp_private', visible_tags)
|
self._check_tag_visibility(d, 'vndk_sp_private', visible_tags)
|
||||||
|
|
||||||
# VNDK
|
# VNDK
|
||||||
visible_tags = {'ll_ndk', 'vndk_sp', 'vndk_sp_private',
|
visible_tags = {
|
||||||
'vndk', 'vndk_private'}
|
'll_ndk', 'vndk_sp', 'vndk_sp_private', 'vndk', 'vndk_private',
|
||||||
|
}
|
||||||
self._check_tag_visibility(d, 'vndk', visible_tags)
|
self._check_tag_visibility(d, 'vndk', visible_tags)
|
||||||
|
|
||||||
# SYSTEM-ONLY
|
# SYSTEM-ONLY and PRODUCT_SERVICES-ONLY
|
||||||
visible_tags = {'ll_ndk', 'll_ndk_private',
|
visible_tags = {
|
||||||
'vndk_sp', 'vndk_sp_private',
|
'll_ndk', 'll_ndk_private',
|
||||||
'vndk', 'vndk_private',
|
'vndk_sp', 'vndk_sp_private',
|
||||||
'system_only', 'system_only_rs',
|
'vndk', 'vndk_private',
|
||||||
'sp_hal'}
|
'system_only', 'system_only_rs',
|
||||||
|
'product_services_only',
|
||||||
|
'sp_hal'
|
||||||
|
}
|
||||||
self._check_tag_visibility(d, 'system_only', visible_tags)
|
self._check_tag_visibility(d, 'system_only', visible_tags)
|
||||||
self._check_tag_visibility(d, 'system_only_rs', visible_tags)
|
self._check_tag_visibility(d, 'system_only_rs', visible_tags)
|
||||||
|
self._check_tag_visibility(d, 'product_services_only', visible_tags)
|
||||||
|
|
||||||
# SP-HAL
|
# SP-HAL
|
||||||
visible_tags = {'ll_ndk', 'vndk_sp', 'sp_hal', 'sp_hal_dep'}
|
visible_tags = {'ll_ndk', 'vndk_sp', 'sp_hal', 'sp_hal_dep'}
|
||||||
@@ -97,8 +107,17 @@ class TaggedDictTest(unittest.TestCase):
|
|||||||
self._check_tag_visibility(d, 'sp_hal_dep', visible_tags)
|
self._check_tag_visibility(d, 'sp_hal_dep', visible_tags)
|
||||||
|
|
||||||
# VENDOR-ONLY
|
# VENDOR-ONLY
|
||||||
visible_tags = {'ll_ndk', 'vndk_sp', 'vndk', 'sp_hal', 'sp_hal_dep',
|
visible_tags = {
|
||||||
'vendor_only'}
|
'll_ndk', 'vndk_sp', 'vndk', 'sp_hal', 'sp_hal_dep', 'vendor_only',
|
||||||
|
}
|
||||||
|
self._check_tag_visibility(d, 'vendor_only', visible_tags)
|
||||||
|
|
||||||
|
# PRODUCT-ONLY
|
||||||
|
visible_tags = {
|
||||||
|
'll_ndk', 'vndk_sp', 'vndk', 'sp_hal',
|
||||||
|
# Remove the following after VNDK-ext can be checked separately.
|
||||||
|
'sp_hal_dep', 'vendor_only',
|
||||||
|
}
|
||||||
self._check_tag_visibility(d, 'vendor_only', visible_tags)
|
self._check_tag_visibility(d, 'vendor_only', visible_tags)
|
||||||
|
|
||||||
# Remove
|
# Remove
|
||||||
@@ -182,6 +201,9 @@ class TaggedPathDictTest(unittest.TestCase):
|
|||||||
self.assertIn('/vendor/lib/lib_sp_hal.so', d.sp_hal)
|
self.assertIn('/vendor/lib/lib_sp_hal.so', d.sp_hal)
|
||||||
self.assertIn('/vendor/lib/lib_sp_hal_dep.so', d.sp_hal_dep)
|
self.assertIn('/vendor/lib/lib_sp_hal_dep.so', d.sp_hal_dep)
|
||||||
self.assertIn('/vendor/lib/lib_vendor_only.so', d.vendor_only)
|
self.assertIn('/vendor/lib/lib_vendor_only.so', d.vendor_only)
|
||||||
|
self.assertIn('/product_services/lib/lib_product_services_only.so',
|
||||||
|
d.product_services_only)
|
||||||
|
self.assertIn('/product/lib/lib_product_only.so', d.product_only)
|
||||||
self.assertIn('/system/lib/lib_remove.so', d.remove)
|
self.assertIn('/system/lib/lib_remove.so', d.remove)
|
||||||
|
|
||||||
# Aliases
|
# Aliases
|
||||||
@@ -339,6 +361,7 @@ class TaggedPathDictTest(unittest.TestCase):
|
|||||||
'/system/lib/lib_system_only.so',
|
'/system/lib/lib_system_only.so',
|
||||||
'/system/lib/lib_system_only_rs.so',
|
'/system/lib/lib_system_only_rs.so',
|
||||||
'/vendor/lib/lib_sp_hal.so',
|
'/vendor/lib/lib_sp_hal.so',
|
||||||
|
'/product_services/lib/lib_product_services_only.so',
|
||||||
}
|
}
|
||||||
self._check_path_visibility(d, all_paths, from_paths, visible_paths)
|
self._check_path_visibility(d, all_paths, from_paths, visible_paths)
|
||||||
|
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ class VNDKLibDirTest(unittest.TestCase):
|
|||||||
VNDKLibDir.is_in_vndk_dir('/system/lib/vndk-sp-28/liba.so'))
|
VNDKLibDir.is_in_vndk_dir('/system/lib/vndk-sp-28/liba.so'))
|
||||||
|
|
||||||
|
|
||||||
def test_create_vndk_search_paths(self):
|
def test_get_vndk_lib_dirs(self):
|
||||||
for version in ('current', '28'):
|
for version in ('current', '28'):
|
||||||
for lib_dir in ('lib', 'lib64'):
|
for lib_dir in ('lib', 'lib64'):
|
||||||
vndk_sp_name = VNDKLibDir.create_vndk_sp_dir_name(version)
|
vndk_sp_name = VNDKLibDir.create_vndk_sp_dir_name(version)
|
||||||
@@ -124,7 +124,7 @@ class VNDKLibDirTest(unittest.TestCase):
|
|||||||
]
|
]
|
||||||
|
|
||||||
vndk_sp_dirs, vndk_dirs = \
|
vndk_sp_dirs, vndk_dirs = \
|
||||||
VNDKLibDir.create_vndk_search_paths(lib_dir, version)
|
VNDKLibDir.get_vndk_lib_dirs(lib_dir, version)
|
||||||
self.assertEqual(expected_vndk_sp, vndk_sp_dirs)
|
self.assertEqual(expected_vndk_sp, vndk_sp_dirs)
|
||||||
self.assertEqual(expected_vndk, vndk_dirs)
|
self.assertEqual(expected_vndk, vndk_dirs)
|
||||||
|
|
||||||
|
|||||||
@@ -1190,6 +1190,16 @@ class DexFileReader(object):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
#------------------------------------------------------------------------------
|
||||||
|
# Path Functions
|
||||||
|
#------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def _is_under_dir(dir_path, path):
|
||||||
|
dir_path = os.path.abspath(dir_path)
|
||||||
|
path = os.path.abspath(path)
|
||||||
|
return path == dir_path or path.startswith(dir_path + os.path.sep)
|
||||||
|
|
||||||
|
|
||||||
#------------------------------------------------------------------------------
|
#------------------------------------------------------------------------------
|
||||||
# TaggedDict
|
# TaggedDict
|
||||||
#------------------------------------------------------------------------------
|
#------------------------------------------------------------------------------
|
||||||
@@ -1203,6 +1213,8 @@ class TaggedDict(object):
|
|||||||
'system_only', 'system_only_rs',
|
'system_only', 'system_only_rs',
|
||||||
'sp_hal', 'sp_hal_dep',
|
'sp_hal', 'sp_hal_dep',
|
||||||
'vendor_only',
|
'vendor_only',
|
||||||
|
'product_services_only',
|
||||||
|
'product_only',
|
||||||
'remove',
|
'remove',
|
||||||
]
|
]
|
||||||
assert len(tag_list) < 32
|
assert len(tag_list) < 32
|
||||||
@@ -1243,17 +1255,38 @@ class TaggedDict(object):
|
|||||||
return tag
|
return tag
|
||||||
|
|
||||||
|
|
||||||
_LL_NDK_VIS = {'ll_ndk', 'll_ndk_private'}
|
_LL_NDK_VIS = {
|
||||||
|
'll_ndk', 'll_ndk_private',
|
||||||
|
}
|
||||||
|
|
||||||
_VNDK_SP_VIS = {'ll_ndk', 'vndk_sp', 'vndk_sp_private', 'system_only_rs'}
|
_VNDK_SP_VIS = {
|
||||||
|
'll_ndk', 'vndk_sp', 'vndk_sp_private', 'system_only_rs',
|
||||||
|
}
|
||||||
|
|
||||||
_VNDK_VIS = {'ll_ndk', 'vndk_sp', 'vndk_sp_private', 'vndk', 'vndk_private'}
|
_VNDK_VIS = {
|
||||||
|
'll_ndk', 'vndk_sp', 'vndk_sp_private', 'vndk', 'vndk_private',
|
||||||
|
}
|
||||||
|
|
||||||
_SYSTEM_ONLY_VIS = {'ll_ndk', 'll_ndk_private',
|
_SYSTEM_ONLY_VIS = {
|
||||||
'vndk_sp', 'vndk_sp_private',
|
'll_ndk', 'll_ndk_private',
|
||||||
'vndk', 'vndk_private',
|
'vndk_sp', 'vndk_sp_private',
|
||||||
'system_only', 'system_only_rs',
|
'vndk', 'vndk_private',
|
||||||
'sp_hal'}
|
'system_only', 'system_only_rs',
|
||||||
|
'product_services_only',
|
||||||
|
'sp_hal',
|
||||||
|
}
|
||||||
|
|
||||||
|
_PRODUCT_ONLY_VIS = {
|
||||||
|
'll_ndk', 'vndk_sp', 'vndk', 'sp_hal',
|
||||||
|
|
||||||
|
# Remove the following after VNDK-ext can be checked separately.
|
||||||
|
'sp_hal_dep', 'vendor_only',
|
||||||
|
}
|
||||||
|
|
||||||
|
_VENDOR_ONLY_VIS = {
|
||||||
|
'll_ndk', 'vndk_sp', 'vndk', 'sp_hal', 'sp_hal_dep',
|
||||||
|
'vendor_only',
|
||||||
|
}
|
||||||
|
|
||||||
_SP_HAL_VIS = {'ll_ndk', 'vndk_sp', 'sp_hal', 'sp_hal_dep'}
|
_SP_HAL_VIS = {'ll_ndk', 'vndk_sp', 'sp_hal', 'sp_hal_dep'}
|
||||||
|
|
||||||
@@ -1269,17 +1302,19 @@ class TaggedDict(object):
|
|||||||
|
|
||||||
'system_only': _SYSTEM_ONLY_VIS,
|
'system_only': _SYSTEM_ONLY_VIS,
|
||||||
'system_only_rs': _SYSTEM_ONLY_VIS,
|
'system_only_rs': _SYSTEM_ONLY_VIS,
|
||||||
|
'product_services_only': _SYSTEM_ONLY_VIS,
|
||||||
|
|
||||||
'sp_hal': _SP_HAL_VIS,
|
'sp_hal': _SP_HAL_VIS,
|
||||||
'sp_hal_dep': _SP_HAL_VIS,
|
'sp_hal_dep': _SP_HAL_VIS,
|
||||||
|
|
||||||
'vendor_only': {'ll_ndk', 'vndk_sp', 'vndk', 'sp_hal', 'sp_hal_dep',
|
'vendor_only': _VENDOR_ONLY_VIS,
|
||||||
'vendor_only'},
|
'product_only': _PRODUCT_ONLY_VIS,
|
||||||
|
|
||||||
'remove': set(),
|
'remove': set(),
|
||||||
}
|
}
|
||||||
|
|
||||||
del _LL_NDK_VIS, _VNDK_SP_VIS, _VNDK_VIS, _SYSTEM_ONLY_VIS, _SP_HAL_VIS
|
del _LL_NDK_VIS, _VNDK_SP_VIS, _VNDK_VIS, _SYSTEM_ONLY_VIS, \
|
||||||
|
_PRODUCT_ONLY_VIS, _VENDOR_ONLY_VIS, _SP_HAL_VIS
|
||||||
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -1426,8 +1461,12 @@ class TaggedPathDict(TaggedDict):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_path_tag_default(path):
|
def get_path_tag_default(path):
|
||||||
if path.startswith('/vendor/'):
|
if _is_under_dir('/vendor', path):
|
||||||
return 'vendor_only'
|
return 'vendor_only'
|
||||||
|
if _is_under_dir('/product', path):
|
||||||
|
return 'product_only'
|
||||||
|
if _is_under_dir('/product_services', path):
|
||||||
|
return 'product_services_only'
|
||||||
return 'system_only'
|
return 'system_only'
|
||||||
|
|
||||||
|
|
||||||
@@ -1459,6 +1498,13 @@ class TaggedLibDict(object):
|
|||||||
d.add('sp_hal_dep', lib)
|
d.add('sp_hal_dep', lib)
|
||||||
else:
|
else:
|
||||||
d.add('vendor_only', lib)
|
d.add('vendor_only', lib)
|
||||||
|
|
||||||
|
for lib in graph.lib_pt[PT_PRODUCT].values():
|
||||||
|
d.add('vendor_only', lib)
|
||||||
|
|
||||||
|
for lib in graph.lib_pt[PT_PRODUCT_SERVICES].values():
|
||||||
|
d.add('vendor_only', lib)
|
||||||
|
|
||||||
return d
|
return d
|
||||||
|
|
||||||
|
|
||||||
@@ -1520,6 +1566,28 @@ class LibProperties(object):
|
|||||||
return root + '-properties' + ext
|
return root + '-properties' + ext
|
||||||
|
|
||||||
|
|
||||||
|
#------------------------------------------------------------------------------
|
||||||
|
# Public Libraries
|
||||||
|
#------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
class PublicLibSet(object):
|
||||||
|
def __init__(self):
|
||||||
|
self._lib_names = set()
|
||||||
|
|
||||||
|
|
||||||
|
def load_from_public_libraries_txt(self, config_path):
|
||||||
|
with open(config_path, 'r') as config_file:
|
||||||
|
for line in config_file:
|
||||||
|
line = line.strip()
|
||||||
|
if line and not line.startswith('#'):
|
||||||
|
self._lib_names.add(line)
|
||||||
|
|
||||||
|
|
||||||
|
def is_public_lib(self, path):
|
||||||
|
lib_name = os.path.basename(path)
|
||||||
|
return lib_name in self._lib_names
|
||||||
|
|
||||||
|
|
||||||
#------------------------------------------------------------------------------
|
#------------------------------------------------------------------------------
|
||||||
# ELF Linker
|
# ELF Linker
|
||||||
#------------------------------------------------------------------------------
|
#------------------------------------------------------------------------------
|
||||||
@@ -1679,7 +1747,9 @@ def scan_elf_files(root, mount_point=None, unzip_files=True):
|
|||||||
|
|
||||||
PT_SYSTEM = 0
|
PT_SYSTEM = 0
|
||||||
PT_VENDOR = 1
|
PT_VENDOR = 1
|
||||||
NUM_PARTITIONS = 2
|
PT_PRODUCT = 2
|
||||||
|
PT_PRODUCT_SERVICES = 3
|
||||||
|
NUM_PARTITIONS = 4
|
||||||
|
|
||||||
|
|
||||||
SPLibResult = collections.namedtuple(
|
SPLibResult = collections.namedtuple(
|
||||||
@@ -1771,7 +1841,7 @@ class VNDKLibDir(list):
|
|||||||
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def create_vndk_search_paths(cls, lib_dir, version):
|
def get_vndk_lib_dirs(cls, lib_dir, version):
|
||||||
"""Create VNDK/VNDK-SP search paths from lib_dir and version."""
|
"""Create VNDK/VNDK-SP search paths from lib_dir and version."""
|
||||||
vndk_sp_name = cls.create_vndk_sp_dir_name(version)
|
vndk_sp_name = cls.create_vndk_sp_dir_name(version)
|
||||||
vndk_name = cls.create_vndk_dir_name(version)
|
vndk_name = cls.create_vndk_dir_name(version)
|
||||||
@@ -2305,7 +2375,7 @@ class ELFLinker(object):
|
|||||||
|
|
||||||
def add_executables_in_dir(self, partition_name, partition, root,
|
def add_executables_in_dir(self, partition_name, partition, root,
|
||||||
alter_partition, alter_subdirs, ignored_subdirs,
|
alter_partition, alter_subdirs, ignored_subdirs,
|
||||||
scan_elf_files, unzip_files):
|
unzip_files):
|
||||||
root = os.path.abspath(root)
|
root = os.path.abspath(root)
|
||||||
prefix_len = len(root) + 1
|
prefix_len = len(root) + 1
|
||||||
|
|
||||||
@@ -2407,35 +2477,88 @@ class ELFLinker(object):
|
|||||||
self._resolve_lib_deps(lib, resolver, generic_refs)
|
self._resolve_lib_deps(lib, resolver, generic_refs)
|
||||||
|
|
||||||
|
|
||||||
def _get_apex_bionic_search_paths(self, lib_dir):
|
def _get_apex_bionic_lib_dirs(self, lib_dir):
|
||||||
return ['/apex/com.android.runtime/' + lib_dir + '/bionic']
|
return ['/apex/com.android.runtime/' + lib_dir + '/bionic']
|
||||||
|
|
||||||
|
|
||||||
def _get_apex_search_paths(self, lib_dir):
|
def _get_apex_lib_dirs(self, lib_dir):
|
||||||
return ['/apex/' + name + '/' + lib_dir
|
return ['/apex/' + name + '/' + lib_dir
|
||||||
for name in sorted(self.apex_module_names)]
|
for name in sorted(self.apex_module_names)]
|
||||||
|
|
||||||
|
|
||||||
def _get_system_search_paths(self, lib_dir):
|
def _get_system_lib_dirs(self, lib_dir):
|
||||||
apex_lib_dirs = (self._get_apex_search_paths(lib_dir) +
|
return ['/system/' + lib_dir]
|
||||||
self._get_apex_bionic_search_paths(lib_dir))
|
|
||||||
system_lib_dirs = ['/system/' + lib_dir, '/system/product/' + lib_dir]
|
|
||||||
vendor_lib_dirs = ['/vendor/' + lib_dir]
|
|
||||||
return apex_lib_dirs + system_lib_dirs + vendor_lib_dirs
|
|
||||||
|
|
||||||
|
|
||||||
def _get_vendor_search_paths(self, lib_dir, vndk_sp_dirs, vndk_dirs):
|
def _get_product_services_lib_dirs(self, lib_dir):
|
||||||
vendor_lib_dirs = [
|
return [
|
||||||
|
'/product_services/' + lib_dir,
|
||||||
|
'/system/product_services/' + lib_dir,
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def _get_vendor_lib_dirs(self, lib_dir):
|
||||||
|
return [
|
||||||
'/vendor/' + lib_dir + '/hw',
|
'/vendor/' + lib_dir + '/hw',
|
||||||
'/vendor/' + lib_dir + '/egl',
|
'/vendor/' + lib_dir + '/egl',
|
||||||
'/vendor/' + lib_dir,
|
'/vendor/' + lib_dir,
|
||||||
]
|
]
|
||||||
# For degenerated VNDK libs.
|
|
||||||
apex_lib_dirs = (self._get_apex_search_paths(lib_dir) +
|
|
||||||
self._get_apex_bionic_search_paths(lib_dir))
|
def _get_product_lib_dirs(self, lib_dir):
|
||||||
system_lib_dirs = ['/system/' + lib_dir]
|
return [
|
||||||
return (vendor_lib_dirs + vndk_sp_dirs + vndk_dirs + apex_lib_dirs +
|
'/product/' + lib_dir,
|
||||||
system_lib_dirs)
|
'/system/product/' + lib_dir,
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def _get_system_search_paths(self, lib_dir):
|
||||||
|
return (
|
||||||
|
self._get_apex_bionic_lib_dirs(lib_dir) +
|
||||||
|
self._get_apex_lib_dirs(lib_dir) +
|
||||||
|
self._get_product_services_lib_dirs(lib_dir) +
|
||||||
|
self._get_system_lib_dirs(lib_dir) +
|
||||||
|
|
||||||
|
# Search '/vendor/${LIB}' and '/product/${LIB}' to detect
|
||||||
|
# violations.
|
||||||
|
self._get_product_lib_dirs(lib_dir) +
|
||||||
|
self._get_vendor_lib_dirs(lib_dir)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _get_vendor_search_paths(self, lib_dir, vndk_sp_dirs, vndk_dirs):
|
||||||
|
return (
|
||||||
|
self._get_vendor_lib_dirs(lib_dir) +
|
||||||
|
vndk_sp_dirs +
|
||||||
|
vndk_dirs +
|
||||||
|
|
||||||
|
# Search '/apex/*/${LIB}' and '/system/${LIB}' for degenerated VNDK
|
||||||
|
# and LL-NDK or to detect violations.
|
||||||
|
self._get_apex_bionic_lib_dirs(lib_dir) +
|
||||||
|
self._get_apex_lib_dirs(lib_dir) +
|
||||||
|
self._get_system_lib_dirs(lib_dir)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _get_product_search_paths(self, lib_dir, vndk_sp_dirs, vndk_dirs):
|
||||||
|
return (
|
||||||
|
self._get_product_lib_dirs(lib_dir) +
|
||||||
|
vndk_sp_dirs +
|
||||||
|
vndk_dirs +
|
||||||
|
|
||||||
|
# Search '/vendor/${LIB}', '/system/${LIB}', and '/apex/*/${LIB}'
|
||||||
|
# for degenerated VNDK and LL-NDK or to detect violations.
|
||||||
|
self._get_vendor_lib_dirs(lib_dir) +
|
||||||
|
self._get_apex_bionic_lib_dirs(lib_dir) +
|
||||||
|
self._get_apex_lib_dirs(lib_dir) +
|
||||||
|
self._get_system_lib_dirs(lib_dir)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _get_product_services_search_paths(self, lib_dir):
|
||||||
|
# Delegate to _get_system_search_paths() because there is no ABI
|
||||||
|
# boundary between system and product_services partition.
|
||||||
|
return self._get_system_search_paths(lib_dir)
|
||||||
|
|
||||||
|
|
||||||
def _get_vndk_sp_search_paths(self, lib_dir, vndk_sp_dirs):
|
def _get_vndk_sp_search_paths(self, lib_dir, vndk_sp_dirs):
|
||||||
@@ -2444,16 +2567,21 @@ class ELFLinker(object):
|
|||||||
'/vendor/' + lib_dir,
|
'/vendor/' + lib_dir,
|
||||||
'/system/' + lib_dir,
|
'/system/' + lib_dir,
|
||||||
]
|
]
|
||||||
fallback_lib_dirs += self._get_apex_search_paths(lib_dir)
|
fallback_lib_dirs += self._get_apex_bionic_lib_dirs(lib_dir)
|
||||||
fallback_lib_dirs += self._get_apex_bionic_search_paths(lib_dir)
|
fallback_lib_dirs += self._get_apex_lib_dirs(lib_dir)
|
||||||
|
|
||||||
return vndk_sp_dirs + fallback_lib_dirs
|
return vndk_sp_dirs + fallback_lib_dirs
|
||||||
|
|
||||||
|
|
||||||
def _get_vndk_search_paths(self, lib_dir, vndk_sp_dirs, vndk_dirs):
|
def _get_vndk_search_paths(self, lib_dir, vndk_sp_dirs, vndk_dirs):
|
||||||
# To find missing dependencies or LL-NDK.
|
# To find missing dependencies or LL-NDK.
|
||||||
fallback_lib_dirs = ['/system/' + lib_dir]
|
fallback_lib_dirs = [
|
||||||
fallback_lib_dirs += self._get_apex_search_paths(lib_dir)
|
'/vendor/' + lib_dir,
|
||||||
fallback_lib_dirs += self._get_apex_bionic_search_paths(lib_dir)
|
'/system/' + lib_dir,
|
||||||
|
]
|
||||||
|
fallback_lib_dirs += self._get_apex_bionic_lib_dirs(lib_dir)
|
||||||
|
fallback_lib_dirs += self._get_apex_lib_dirs(lib_dir)
|
||||||
|
|
||||||
return vndk_sp_dirs + vndk_dirs + fallback_lib_dirs
|
return vndk_sp_dirs + vndk_dirs + fallback_lib_dirs
|
||||||
|
|
||||||
|
|
||||||
@@ -2478,7 +2606,7 @@ class ELFLinker(object):
|
|||||||
# Resolve vndk-sp libs
|
# Resolve vndk-sp libs
|
||||||
for version in vndk_lib_dirs:
|
for version in vndk_lib_dirs:
|
||||||
vndk_sp_dirs, vndk_dirs = \
|
vndk_sp_dirs, vndk_dirs = \
|
||||||
vndk_lib_dirs.create_vndk_search_paths(lib_dir, version)
|
vndk_lib_dirs.get_vndk_lib_dirs(lib_dir, version)
|
||||||
vndk_sp_libs = \
|
vndk_sp_libs = \
|
||||||
system_vndk_sp_libs[version] | vendor_vndk_sp_libs[version]
|
system_vndk_sp_libs[version] | vendor_vndk_sp_libs[version]
|
||||||
search_paths = self._get_vndk_sp_search_paths(
|
search_paths = self._get_vndk_sp_search_paths(
|
||||||
@@ -2489,7 +2617,7 @@ class ELFLinker(object):
|
|||||||
# Resolve vndk libs
|
# Resolve vndk libs
|
||||||
for version in vndk_lib_dirs:
|
for version in vndk_lib_dirs:
|
||||||
vndk_sp_dirs, vndk_dirs = \
|
vndk_sp_dirs, vndk_dirs = \
|
||||||
vndk_lib_dirs.create_vndk_search_paths(lib_dir, version)
|
vndk_lib_dirs.get_vndk_lib_dirs(lib_dir, version)
|
||||||
vndk_libs = system_vndk_libs[version] | vendor_vndk_libs[version]
|
vndk_libs = system_vndk_libs[version] | vendor_vndk_libs[version]
|
||||||
search_paths = self._get_vndk_search_paths(
|
search_paths = self._get_vndk_search_paths(
|
||||||
lib_dir, vndk_sp_dirs, vndk_dirs)
|
lib_dir, vndk_sp_dirs, vndk_dirs)
|
||||||
@@ -2497,13 +2625,30 @@ class ELFLinker(object):
|
|||||||
self._resolve_lib_set_deps(vndk_libs, resolver, generic_refs)
|
self._resolve_lib_set_deps(vndk_libs, resolver, generic_refs)
|
||||||
|
|
||||||
# Resolve vendor libs.
|
# Resolve vendor libs.
|
||||||
vndk_sp_dirs, vndk_dirs = vndk_lib_dirs.create_vndk_search_paths(
|
vndk_sp_dirs, vndk_dirs = vndk_lib_dirs.get_vndk_lib_dirs(
|
||||||
lib_dir, self.ro_vndk_version)
|
lib_dir, self.ro_vndk_version)
|
||||||
search_paths = self._get_vendor_search_paths(
|
search_paths = self._get_vendor_search_paths(
|
||||||
lib_dir, vndk_sp_dirs, vndk_dirs)
|
lib_dir, vndk_sp_dirs, vndk_dirs)
|
||||||
resolver = ELFResolver(lib_dict, search_paths)
|
resolver = ELFResolver(lib_dict, search_paths)
|
||||||
self._resolve_lib_set_deps(vendor_libs, resolver, generic_refs)
|
self._resolve_lib_set_deps(vendor_libs, resolver, generic_refs)
|
||||||
|
|
||||||
|
# Resolve product libs
|
||||||
|
product_lib_dict = self.lib_pt[PT_PRODUCT].get_lib_dict(elf_class)
|
||||||
|
product_libs = set(product_lib_dict.values())
|
||||||
|
search_paths = self._get_product_search_paths(
|
||||||
|
lib_dir, vndk_sp_dirs, vndk_dirs)
|
||||||
|
resolver = ELFResolver(lib_dict, search_paths)
|
||||||
|
self._resolve_lib_set_deps(product_libs, resolver, generic_refs)
|
||||||
|
|
||||||
|
# Resolve product_services libs
|
||||||
|
product_services_lib_dict = \
|
||||||
|
self.lib_pt[PT_PRODUCT_SERVICES].get_lib_dict(elf_class)
|
||||||
|
product_services_libs = set(product_services_lib_dict.values())
|
||||||
|
search_paths = self._get_product_services_search_paths(lib_dir)
|
||||||
|
resolver = ELFResolver(lib_dict, search_paths)
|
||||||
|
self._resolve_lib_set_deps(
|
||||||
|
product_services_libs, resolver, generic_refs)
|
||||||
|
|
||||||
|
|
||||||
def resolve_deps(self, generic_refs=None):
|
def resolve_deps(self, generic_refs=None):
|
||||||
self._resolve_elf_class_deps('lib', ELF.ELFCLASS32, generic_refs)
|
self._resolve_elf_class_deps('lib', ELF.ELFCLASS32, generic_refs)
|
||||||
@@ -2911,11 +3056,12 @@ class ELFLinker(object):
|
|||||||
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _create_internal(system_dirs, system_dirs_as_vendor,
|
def create(system_dirs=None, system_dirs_as_vendor=None,
|
||||||
system_dirs_ignored, vendor_dirs,
|
system_dirs_ignored=None, vendor_dirs=None,
|
||||||
vendor_dirs_as_system, vendor_dirs_ignored,
|
vendor_dirs_as_system=None, vendor_dirs_ignored=None,
|
||||||
extra_deps, generic_refs, tagged_paths,
|
product_dirs=None, product_services_dirs=None,
|
||||||
vndk_lib_dirs, unzip_files):
|
extra_deps=None, generic_refs=None, tagged_paths=None,
|
||||||
|
vndk_lib_dirs=None, unzip_files=True):
|
||||||
if vndk_lib_dirs is None:
|
if vndk_lib_dirs is None:
|
||||||
vndk_lib_dirs = VNDKLibDir.create_from_dirs(
|
vndk_lib_dirs = VNDKLibDir.create_from_dirs(
|
||||||
system_dirs, vendor_dirs)
|
system_dirs, vendor_dirs)
|
||||||
@@ -2936,6 +3082,18 @@ class ELFLinker(object):
|
|||||||
vendor_dirs_as_system, vendor_dirs_ignored,
|
vendor_dirs_as_system, vendor_dirs_ignored,
|
||||||
unzip_files)
|
unzip_files)
|
||||||
|
|
||||||
|
if product_dirs:
|
||||||
|
for path in product_dirs:
|
||||||
|
graph.add_executables_in_dir(
|
||||||
|
'product', PT_PRODUCT, path, None, None, None,
|
||||||
|
unzip_files)
|
||||||
|
|
||||||
|
if product_services_dirs:
|
||||||
|
for path in product_services_dirs:
|
||||||
|
graph.add_executables_in_dir(
|
||||||
|
'product_services', PT_PRODUCT_SERVICES, path, None,
|
||||||
|
None, None, unzip_files)
|
||||||
|
|
||||||
if extra_deps:
|
if extra_deps:
|
||||||
for path in extra_deps:
|
for path in extra_deps:
|
||||||
graph.add_dlopen_deps(path)
|
graph.add_dlopen_deps(path)
|
||||||
@@ -2946,19 +3104,6 @@ class ELFLinker(object):
|
|||||||
return graph
|
return graph
|
||||||
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def create(system_dirs=None, system_dirs_as_vendor=None,
|
|
||||||
system_dirs_ignored=None, vendor_dirs=None,
|
|
||||||
vendor_dirs_as_system=None, vendor_dirs_ignored=None,
|
|
||||||
extra_deps=None, generic_refs=None, tagged_paths=None,
|
|
||||||
vndk_lib_dirs=None, unzip_files=True):
|
|
||||||
return ELFLinker._create_internal(
|
|
||||||
system_dirs, system_dirs_as_vendor,
|
|
||||||
system_dirs_ignored, vendor_dirs, vendor_dirs_as_system,
|
|
||||||
vendor_dirs_ignored, extra_deps, generic_refs, tagged_paths,
|
|
||||||
vndk_lib_dirs, unzip_files)
|
|
||||||
|
|
||||||
|
|
||||||
#------------------------------------------------------------------------------
|
#------------------------------------------------------------------------------
|
||||||
# Generic Reference
|
# Generic Reference
|
||||||
#------------------------------------------------------------------------------
|
#------------------------------------------------------------------------------
|
||||||
@@ -3074,16 +3219,24 @@ def _enumerate_partition_paths(partition, root):
|
|||||||
yield (android_path, path)
|
yield (android_path, path)
|
||||||
|
|
||||||
|
|
||||||
def _enumerate_paths(system_dirs, vendor_dirs):
|
def _enumerate_paths(system_dirs, vendor_dirs, product_dirs,
|
||||||
|
product_services_dirs):
|
||||||
for root in system_dirs:
|
for root in system_dirs:
|
||||||
for ap, path in _enumerate_partition_paths('system', root):
|
for ap, path in _enumerate_partition_paths('system', root):
|
||||||
yield (ap, path)
|
yield (ap, path)
|
||||||
for root in vendor_dirs:
|
for root in vendor_dirs:
|
||||||
for ap, path in _enumerate_partition_paths('vendor', root):
|
for ap, path in _enumerate_partition_paths('vendor', root):
|
||||||
yield (ap, path)
|
yield (ap, path)
|
||||||
|
for root in product_dirs:
|
||||||
|
for ap, path in _enumerate_partition_paths('product', root):
|
||||||
|
yield (ap, path)
|
||||||
|
for root in product_services_dirs:
|
||||||
|
for ap, path in _enumerate_partition_paths('product_services', root):
|
||||||
|
yield (ap, path)
|
||||||
|
|
||||||
|
|
||||||
def scan_apk_dep(graph, system_dirs, vendor_dirs):
|
def scan_apk_dep(graph, system_dirs, vendor_dirs, product_dirs,
|
||||||
|
product_services_dirs):
|
||||||
libnames = _build_lib_names_dict(graph)
|
libnames = _build_lib_names_dict(graph)
|
||||||
results = []
|
results = []
|
||||||
|
|
||||||
@@ -3094,7 +3247,8 @@ def scan_apk_dep(graph, system_dirs, vendor_dirs):
|
|||||||
def decode(string): # PY3
|
def decode(string): # PY3
|
||||||
return string.decode('mutf-8')
|
return string.decode('mutf-8')
|
||||||
|
|
||||||
for ap, path in _enumerate_paths(system_dirs, vendor_dirs):
|
for ap, path in _enumerate_paths(system_dirs, vendor_dirs,
|
||||||
|
product_dirs, product_services_dirs):
|
||||||
# Read the dex file from various file formats
|
# Read the dex file from various file formats
|
||||||
try:
|
try:
|
||||||
dex_string_iter = DexFileReader.enumerate_dex_strings(path)
|
dex_string_iter = DexFileReader.enumerate_dex_strings(path)
|
||||||
@@ -3254,13 +3408,22 @@ class ELFGraphCommand(Command):
|
|||||||
help='load extra module dependencies')
|
help='load extra module dependencies')
|
||||||
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--system', action='append',
|
'--system', action='append', default=[],
|
||||||
help='path to system partition contents')
|
help='path to system partition contents')
|
||||||
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--vendor', action='append',
|
'--vendor', action='append', default=[],
|
||||||
help='path to vendor partition contents')
|
help='path to vendor partition contents')
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
'--product', action='append', default=[],
|
||||||
|
help='path to product partition contents')
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
'--product-services', action='append', default=[],
|
||||||
|
help='path to product_services partition contents')
|
||||||
|
|
||||||
|
# XXX: BEGIN: Remove these options
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--system-dir-as-vendor', action='append',
|
'--system-dir-as-vendor', action='append',
|
||||||
help='sub directory of system partition that has vendor files')
|
help='sub directory of system partition that has vendor files')
|
||||||
@@ -3276,6 +3439,7 @@ class ELFGraphCommand(Command):
|
|||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--vendor-dir-ignored', action='append',
|
'--vendor-dir-ignored', action='append',
|
||||||
help='sub directory of vendor partition that must be ignored')
|
help='sub directory of vendor partition that must be ignored')
|
||||||
|
# XXX: END: Remove these options
|
||||||
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--load-generic-refs',
|
'--load-generic-refs',
|
||||||
@@ -3322,6 +3486,8 @@ class ELFGraphCommand(Command):
|
|||||||
def check_dirs_from_args(self, args):
|
def check_dirs_from_args(self, args):
|
||||||
self._check_arg_dir_exists('--system', args.system)
|
self._check_arg_dir_exists('--system', args.system)
|
||||||
self._check_arg_dir_exists('--vendor', args.vendor)
|
self._check_arg_dir_exists('--vendor', args.vendor)
|
||||||
|
self._check_arg_dir_exists('--product', args.product)
|
||||||
|
self._check_arg_dir_exists('--product-services', args.product_services)
|
||||||
|
|
||||||
|
|
||||||
def create_from_args(self, args):
|
def create_from_args(self, args):
|
||||||
@@ -3341,6 +3507,8 @@ class ELFGraphCommand(Command):
|
|||||||
args.system_dir_ignored,
|
args.system_dir_ignored,
|
||||||
args.vendor, args.vendor_dir_as_system,
|
args.vendor, args.vendor_dir_as_system,
|
||||||
args.vendor_dir_ignored,
|
args.vendor_dir_ignored,
|
||||||
|
args.product,
|
||||||
|
args.product_services,
|
||||||
args.load_extra_deps,
|
args.load_extra_deps,
|
||||||
generic_refs=generic_refs,
|
generic_refs=generic_refs,
|
||||||
tagged_paths=tagged_paths,
|
tagged_paths=tagged_paths,
|
||||||
@@ -3899,7 +4067,8 @@ class ApkDepsCommand(ELFGraphCommand):
|
|||||||
def main(self, args):
|
def main(self, args):
|
||||||
_, graph, _, _ = self.create_from_args(args)
|
_, graph, _, _ = self.create_from_args(args)
|
||||||
|
|
||||||
apk_deps = scan_apk_dep(graph, args.system, args.vendor)
|
apk_deps = scan_apk_dep(graph, args.system, args.vendor, args.product,
|
||||||
|
args.product_services)
|
||||||
|
|
||||||
for apk_path, dep_paths in apk_deps:
|
for apk_path, dep_paths in apk_deps:
|
||||||
print(apk_path)
|
print(apk_path)
|
||||||
@@ -3979,16 +4148,39 @@ class CheckDepCommand(CheckDepCommandBase):
|
|||||||
help='Do not check ordering of DT_NEEDED entries')
|
help='Do not check ordering of DT_NEEDED entries')
|
||||||
|
|
||||||
|
|
||||||
|
def _load_public_lib_names(self, system_dirs, vendor_dirs):
|
||||||
|
names = PublicLibSet()
|
||||||
|
for base in itertools.chain(system_dirs, vendor_dirs):
|
||||||
|
config_path = os.path.join(base, 'etc', 'public.libraries.txt')
|
||||||
|
try:
|
||||||
|
names.load_from_public_libraries_txt(config_path)
|
||||||
|
except FileNotFoundError:
|
||||||
|
pass
|
||||||
|
return names
|
||||||
|
|
||||||
|
|
||||||
def _check_vendor_dep(self, graph, tagged_libs, lib_properties,
|
def _check_vendor_dep(self, graph, tagged_libs, lib_properties,
|
||||||
module_info):
|
module_info, public_libs):
|
||||||
"""Check whether vendor libs are depending on non-eligible libs."""
|
"""Check whether vendor libs are depending on non-eligible libs."""
|
||||||
num_errors = 0
|
num_errors = 0
|
||||||
|
|
||||||
vendor_libs = set(graph.lib_pt[PT_VENDOR].values())
|
vendor_libs = set(graph.lib_pt[PT_VENDOR].values())
|
||||||
|
vendor_libs.update(graph.lib_pt[PT_PRODUCT].values())
|
||||||
|
|
||||||
eligible_libs = (tagged_libs.ll_ndk | tagged_libs.vndk_sp |
|
eligible_libs = (tagged_libs.ll_ndk | tagged_libs.vndk_sp |
|
||||||
tagged_libs.vndk_sp_private | tagged_libs.vndk)
|
tagged_libs.vndk_sp_private | tagged_libs.vndk)
|
||||||
|
|
||||||
|
def _is_app_lib(lib):
|
||||||
|
app_dirs = [
|
||||||
|
'/product/app',
|
||||||
|
'/product/priv-app',
|
||||||
|
'/product_services/app',
|
||||||
|
'/product_services/priv-app',
|
||||||
|
'/vendor/app',
|
||||||
|
'/vendor/priv-app',
|
||||||
|
]
|
||||||
|
return any(_is_under_dir(d, lib.path) for d in app_dirs)
|
||||||
|
|
||||||
for lib in sorted(vendor_libs):
|
for lib in sorted(vendor_libs):
|
||||||
bad_deps = set()
|
bad_deps = set()
|
||||||
|
|
||||||
@@ -4006,6 +4198,12 @@ class CheckDepCommand(CheckDepCommandBase):
|
|||||||
# Check whether vendor modules depend on ineligible libs.
|
# Check whether vendor modules depend on ineligible libs.
|
||||||
for dep in lib.deps_all:
|
for dep in lib.deps_all:
|
||||||
if dep not in vendor_libs and dep not in eligible_libs:
|
if dep not in vendor_libs and dep not in eligible_libs:
|
||||||
|
if _is_app_lib(lib) and public_libs.is_public_lib(dep.path):
|
||||||
|
# It is fine for APK files to depend on public
|
||||||
|
# libraries (including NDK or other explicitly exposed
|
||||||
|
# libs).
|
||||||
|
continue
|
||||||
|
|
||||||
num_errors += 1
|
num_errors += 1
|
||||||
bad_deps.add(dep)
|
bad_deps.add(dep)
|
||||||
|
|
||||||
@@ -4064,7 +4262,8 @@ class CheckDepCommand(CheckDepCommandBase):
|
|||||||
return num_errors
|
return num_errors
|
||||||
|
|
||||||
|
|
||||||
def _check_apk_dep(self, graph, system_dirs, vendor_dirs, module_info):
|
def _check_apk_dep(self, graph, system_dirs, vendor_dirs, product_dirs,
|
||||||
|
product_services_dirs, module_info):
|
||||||
num_errors = 0
|
num_errors = 0
|
||||||
|
|
||||||
def is_in_system_partition(path):
|
def is_in_system_partition(path):
|
||||||
@@ -4072,7 +4271,8 @@ class CheckDepCommand(CheckDepCommandBase):
|
|||||||
path.startswith('/product/') or \
|
path.startswith('/product/') or \
|
||||||
path.startswith('/oem/')
|
path.startswith('/oem/')
|
||||||
|
|
||||||
apk_deps = scan_apk_dep(graph, system_dirs, vendor_dirs)
|
apk_deps = scan_apk_dep(graph, system_dirs, vendor_dirs, product_dirs,
|
||||||
|
product_services_dirs)
|
||||||
|
|
||||||
for apk_path, dep_paths in apk_deps:
|
for apk_path, dep_paths in apk_deps:
|
||||||
apk_in_system = is_in_system_partition(apk_path)
|
apk_in_system = is_in_system_partition(apk_path)
|
||||||
@@ -4106,15 +4306,18 @@ class CheckDepCommand(CheckDepCommandBase):
|
|||||||
lib_properties = \
|
lib_properties = \
|
||||||
LibProperties.load_from_path_or_default(lib_properties_path)
|
LibProperties.load_from_path_or_default(lib_properties_path)
|
||||||
|
|
||||||
|
public_libs = self._load_public_lib_names(args.system, args.vendor)
|
||||||
|
|
||||||
num_errors = self._check_vendor_dep(graph, tagged_libs, lib_properties,
|
num_errors = self._check_vendor_dep(graph, tagged_libs, lib_properties,
|
||||||
module_info)
|
module_info, public_libs)
|
||||||
|
|
||||||
if args.check_dt_needed_ordering:
|
if args.check_dt_needed_ordering:
|
||||||
num_errors += self._check_dt_needed_ordering(graph)
|
num_errors += self._check_dt_needed_ordering(graph)
|
||||||
|
|
||||||
if args.check_apk:
|
if args.check_apk:
|
||||||
num_errors += self._check_apk_dep(graph, args.system, args.vendor,
|
num_errors += self._check_apk_dep(
|
||||||
module_info)
|
graph, args.system, args.vendor, args.product,
|
||||||
|
args.product_services, module_info)
|
||||||
|
|
||||||
return 0 if num_errors == 0 else 1
|
return 0 if num_errors == 0 else 1
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user