1#!/usr/bin/env python3
2
3"""This is a wrapper function of apexer. It provides opportunity to do
4some artifact preprocessing before calling into apexer. Some of these
5artifact preprocessing are difficult or impossible to do in soong or
6bazel such as trimming the apex native shared libs. It is better to do
7these in a binary so that the preprocessing logic can be reused regardless
8of the build system
9"""
10
11import argparse
12from glob import glob
13import os
14import re
15import shutil
16import sys
17import tempfile
18
19import apex_manifest_pb2
20import apexer_wrapper_utils
21
22def ParseArgs(argv):
23  parser = argparse.ArgumentParser(
24      description='wrapper to run apexer with native shared lib trimming')
25  parser.add_argument(
26      '--apexer',
27      help='path to apexer binary')
28  parser.add_argument(
29      '--canned_fs_config',
30      help='path to canned_fs_config file')
31  parser.add_argument(
32      '--manifest',
33      help='path to apex_manifest.pb file')
34  parser.add_argument(
35      '--libs_to_trim',
36      help='native shared libraries to trim')
37  parser.add_argument(
38      'input_dir',
39      metavar='INPUT_DIR',
40      help='the directory having files to be packaged')
41  parser.add_argument(
42      'output',
43      metavar='OUTPUT',
44      help='name of the APEX file')
45  parser.add_argument(
46      'rest_args',
47      nargs='*',
48      help='remaining flags that will be passed as-is to apexer')
49  return parser.parse_args(argv)
50
51def TrimNativeSharedLibs(image_dir: str, canned_fs_config: str,
52                         manifest: str, libs_to_trim: list[str]):
53  """Place native shared libs for trimmed variant in a special way.
54
55  Traditional apex has native shared libs placed under /lib(64)? inside
56  the apex. However, for trimmed variant, for the libs to trim, they will
57  be replaced with a sym link to
58
59  /apex/sharedlibs/lib(64)?/foo.so/[sha512 foo.so]/foo.so
60  """
61
62  libs_trimmed = set()
63  with open(canned_fs_config, 'r') as f:
64    lines = f.readlines()
65  for line in lines:
66    segs = line.split(' ')
67    if segs[0].endswith('.so'):
68      if any(segs[0].endswith(v) for v in libs_to_trim):
69        lib_relative_path = segs[0][1:]
70        lib_absolute_path = os.path.join(image_dir, lib_relative_path)
71        lib_name = os.path.basename(lib_relative_path)
72        libs_trimmed.add(lib_name)
73        digest = apexer_wrapper_utils.GetDigest(lib_absolute_path)
74        os.remove(lib_absolute_path)
75        link = os.path.join('/apex/sharedlibs', lib_relative_path, digest, lib_name)
76        os.symlink(link, lib_absolute_path)
77
78  manifest_pb = apex_manifest_pb2.ApexManifest()
79  with open(manifest, 'rb') as f:
80    manifest_pb.ParseFromString(f.read())
81
82  # bump version code
83  # google mainline module logic, for trimmed variant, the variant digit is 2
84  manifest_pb.version = 10*(manifest_pb.version // 10) + 2
85
86  # setting requireSharedApexLibs
87  del manifest_pb.requireSharedApexLibs[:]
88  manifest_pb.requireSharedApexLibs.extend(
89    sorted(re.sub(r':.{128}$', ':sha-512', lib)
90           for lib in libs_trimmed))
91
92  # write back to root apex_manifest.pb
93  with open(manifest, 'wb') as f:
94    f.write(manifest_pb.SerializeToString())
95
96def main(argv):
97  args = ParseArgs(argv)
98  segs = args.libs_to_trim.split(',')
99  libs_to_trim = [v+'.so' for v in args.libs_to_trim.split(',')]
100  TrimNativeSharedLibs(args.input_dir, args.canned_fs_config, args.manifest,
101                       libs_to_trim)
102  cmd = [args.apexer, '--canned_fs_config', args.canned_fs_config]
103  cmd.extend(['--manifest', args.manifest])
104  cmd.extend(args.rest_args)
105  cmd.extend([args.input_dir, args.output])
106
107  apexer_wrapper_utils.RunCommand(cmd)
108
109if __name__ == "__main__":
110 main(sys.argv[1:])
111