#!/usr/bin/env python3

# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to you 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.

# Check for programs linked with libraries they don't in fact use.
# For example, if a program was linked with -lfoo but doesn't use any
# symbols from libfoo.
#
# The list of programs is gathered by scraping Automake files, which are
# themselves gathered from Autoconf. ldd does the actual business of
# checking for unused dependencies.
#
# There are a couple of manual exceptions listed below, either because
# we deliberately link with an unused library -- possibly for
# convenience -- or because I haven't figured out how to fix it yet.
#
# For now, this only checks the programs that we install,
# but it could potentially check our libraries as well.

import os.path
import re
import subprocess
import sys

config_files_re = re.compile(r'(?<=config_files=").*(?=")')
programs_re = re.compile(r'([^\n ]*_)PROGRAMS \+?= (.*)')


def get_dependencies(program):
    args = [
        './libtool', '--mode=execute', 'ldd', '--unused', '--function-relocs',
        program
    ]
    for dependency in subprocess.Popen(args, stdout=subprocess.PIPE).stdout:
        dependency = dependency.decode('utf-8')[:-1]
        if any(
            map(
                os.path.basename(dependency).startswith,
                [
                    'libdl.so.',      # Because we add -ldl to LIBS
                    'libgcc_s.so.',
                    'libm.so.',       # Why does Libtool call ld with -lm?
                    'libpthread.so.', # Because we add -lpthread to LIBS
                ])):
            continue

        progbase = os.path.basename(program)

        # Why does Libtool call ld with -lcrypto -lresolv -lssl?
        if progbase == 'traffic_manager':
            if any(
                map(
                    os.path.basename(dependency).startswith, [
                        'libcrypto.so.',
                        'libresolv.so.',
                        'libssl.so.',
                    ])):
                continue

        # clang+asan pulls in dependencies for these specific tools:
        if any(
            map(
                progbase.__eq__,
                [
                    'jtest',
                    'http_load',
                    'escape_mapper',
                ])):
            if any(
                map(
                    os.path.basename(dependency).startswith,
                    [
                        'librt.so',
                        'libresolv.so',
                    ])):
                continue


        if re.sub(r'\s+', '', dependency):
            yield dependency

success = True
filename = 'config.status'
contents = open(filename).read()
config_files = config_files_re.search(contents).group(0)
for filename in config_files.split():
    filename = filename + '.am'
    if os.path.exists(filename):
        contents = open(filename).read()
        contents = contents.replace('\\\n', '')
        for prefix, programs in programs_re.findall(contents):
            if prefix not in [
                'EXTRA_',
                'check_',
                'noinst_'
            ]:
                for program in programs.split():
                    program = os.path.join(os.path.dirname(filename), program)
                    if os.path.exists(program):
                        dependencies = list(get_dependencies(program))
                        if len(dependencies) > 1:
                            success = False
                            print(program)
                            for dependency in dependencies:
                                print(dependency)

if not success:
    sys.exit(1)
