1#!/usr/bin/env python
2# Copyright 2017 The Chromium Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6"""Given a binary, uses dpkg-shlibdeps to check that its package dependencies
7are satisfiable on all supported debian-based distros.
8"""
9
10import argparse
11import json
12import os
13import re
14import subprocess
15import sys
16
17import deb_version
18import package_version_interval
19
20parser = argparse.ArgumentParser()
21parser.add_argument('binary')
22parser.add_argument('sysroot')
23parser.add_argument('arch')
24parser.add_argument('dep_filename')
25parser.add_argument('--distro-check', action='store_true')
26args = parser.parse_args()
27
28binary = os.path.abspath(args.binary)
29sysroot = os.path.abspath(args.sysroot)
30arch = args.arch
31dep_filename = os.path.abspath(args.dep_filename)
32distro_check = args.distro_check
33
34script_dir = os.path.dirname(os.path.realpath(__file__))
35dpkg_shlibdeps = os.path.join(script_dir, '..', '..', '..', '..', 'third_party',
36                              'dpkg-shlibdeps', 'dpkg-shlibdeps.pl')
37
38cmd = [dpkg_shlibdeps, '--ignore-weak-undefined']
39if arch == 'x64':
40  cmd.extend(['-l%s/usr/lib/x86_64-linux-gnu' % sysroot,
41              '-l%s/lib/x86_64-linux-gnu' % sysroot])
42elif arch == 'x86':
43  cmd.extend(['-l%s/usr/lib/i386-linux-gnu' % sysroot,
44              '-l%s/lib/i386-linux-gnu' % sysroot])
45elif arch == 'arm':
46  cmd.extend(['-l%s/usr/lib/arm-linux-gnueabihf' % sysroot,
47              '-l%s/lib/arm-linux-gnueabihf' % sysroot])
48elif arch == 'arm64':
49  cmd.extend(['-l%s/usr/lib/aarch64-linux-gnu' % sysroot,
50              '-l%s/lib/aarch64-linux-gnu' % sysroot])
51elif arch == 'mipsel':
52  cmd.extend(['-l%s/usr/lib/mipsel-linux-gnu' % sysroot,
53              '-l%s/lib/mipsel-linux-gnu' % sysroot])
54elif arch == 'mips64el':
55  cmd.extend(['-l%s/usr/lib/mips64el-linux-gnuabi64' % sysroot,
56              '-l%s/lib/mips64el-linux-gnuabi64' % sysroot])
57else:
58  print 'Unsupported architecture ' + arch
59  sys.exit(1)
60cmd.extend(['-l%s/usr/lib' % sysroot, '-O', '-e', binary])
61
62proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
63                        cwd=sysroot)
64(stdout, stderr) = proc.communicate()
65exit_code = proc.wait()
66if exit_code != 0:
67  print 'dpkg-shlibdeps failed with exit code ' + str(exit_code)
68  print 'stderr was ' + stderr
69  sys.exit(1)
70
71SHLIBS_DEPENDS_PREFIX = 'shlibs:Depends='
72deps_str = ''
73for line in stdout.split('\n'):
74  if line.startswith(SHLIBS_DEPENDS_PREFIX):
75    deps_str = line[len(SHLIBS_DEPENDS_PREFIX):]
76deps = deps_str.split(', ')
77interval_sets = []
78if deps_str != '':
79  for dep in deps:
80    interval_sets.append(package_version_interval.parse_interval_set(dep))
81
82script_dir = os.path.dirname(os.path.abspath(__file__))
83deps_file = os.path.join(script_dir, 'dist_package_versions.json')
84distro_package_versions = json.load(open(deps_file))
85
86ret_code = 0
87if distro_check:
88  for distro in distro_package_versions:
89    for interval_set in interval_sets:
90      dep_satisfiable = False
91      for interval in interval_set.intervals:
92        package = interval.package
93        if package not in distro_package_versions[distro]:
94          continue
95        distro_version = deb_version.DebVersion(
96            distro_package_versions[distro][package])
97        if interval.contains(distro_version):
98          dep_satisfiable = True
99          break
100      if not dep_satisfiable:
101        print >> sys.stderr, (
102            'Dependency %s not satisfiable on distro %s caused by binary %s' % (
103                interval_set.formatted(), distro, os.path.basename(binary)))
104        ret_code = 1
105if ret_code == 0:
106  with open(dep_filename, 'w') as dep_file:
107    lines = [interval_set.formatted() + '\n'
108           for interval_set in interval_sets]
109    dep_file.write(''.join(sorted(lines)))
110sys.exit(ret_code)
111