#!/usr/bin/env python
#
# Copyright (C) 2020 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
"""A tool for constructing class loader context."""
import argparse
import json
import sys
from manifest
import compare_version_gt
def parse_args(args):
"""Parse commandline arguments."""
parser = argparse.ArgumentParser()
parser.add_argument(
'--target-sdk-version',
default=
'',
dest=
'sdk',
help=
'specify target SDK version (as it appears in the manifest)')
parser.add_argument(
'--context-json',
default=
'',
dest=
'context_json',
)
parser.add_argument(
'--product-packages',
default=
'',
dest=
'product_packages_file',
)
return parser.parse_args(args)
# Special keyword that means that the context should be added to class loader
# context regardless of the target SDK version.
any_sdk =
'any'
context_sep =
'#'
def encode_class_loader(context, product_packages):
host_sub_contexts, target_sub_contexts = encode_class_loaders(
context[
'Subcontexts'], product_packages)
return (
'PCL[%s]%s' % (context[
'Host'], host_sub_contexts),
'PCL[%s]%s' % (context[
'Device'], target_sub_contexts))
def encode_class_loaders(contexts, product_packages):
host_contexts = []
target_contexts = []
for context
in contexts:
if not context[
'Optional']
or context[
'Name']
in product_packages:
host_context, target_context = encode_class_loader(
context, product_packages)
host_contexts.append(host_context)
target_contexts.append(target_context)
if host_contexts:
return (
'{%s}' % context_sep.join(host_contexts),
'{%s}' % context_sep.join(target_contexts))
else:
return '',
''
def construct_context_args(target_sdk, context_json, product_packages):
all_contexts = []
# CLC for different SDK versions should come in specific order that agrees
# with PackageManager. Since PackageManager processes SDK versions in
# ascending order and prepends compatibility libraries at the front, the
# required order is descending, except for any_sdk that has numerically
# the largest order, but must be the last one. Example of correct order:
# [30, 29, 28, any_sdk]. There are Python tests to ensure that someone
# doesn't change this by accident, but there is no way to guard against
# changes in the PackageManager, except for grepping logcat on the first
# boot for absence of the following messages:
#
# `logcat | grep -E 'ClassLoaderContext [a-z ]+ mismatch`
for sdk, contexts
in sorted(
((sdk, contexts)
for sdk, contexts
in context_json.items()
if sdk != any_sdk
and compare_version_gt(sdk, target_sdk)),
key=
lambda item: int(item[
0]), reverse=
True):
all_contexts += contexts
if any_sdk
in context_json:
all_contexts += context_json[any_sdk]
host_contexts, target_contexts = encode_class_loaders(
all_contexts, product_packages)
return (
'class_loader_context_arg=--class-loader-context=PCL[]%s ; ' %
host_contexts +
'stored_class_loader_context_arg='
'--stored-class-loader-context=PCL[]%s'
% target_contexts)
def main():
"""Program entry point."""
try:
args = parse_args(sys.argv[
1:])
if not args.sdk:
raise SystemExit(
'target sdk version is not set')
context_json = json.loads(args.context_json)
with open(args.product_packages_file,
'r')
as f:
product_packages = set(line.strip()
for line
in f
if line.strip())
print(construct_context_args(args.sdk, context_json, product_packages))
# pylint: disable=broad-except
except Exception
as err:
print(
'error: ' + str(err), file=sys.stderr)
sys.exit(-
1)
if __name__ ==
'__main__':
main()