#!/usr/bin/env python3
# Copyright 2019 Collabora Ltd.
# SPDX-License-Identifier: BSD-3-Clause
# (See debian/copyright)

import logging
import os
import shutil
import subprocess
import sys

try:
    from typing import (
        Dict,
        IO,
        Set,
    )
    # placate pyflakes
    Dict, IO, Set
except ImportError:
    pass

import steamrt_abi


logger = logging.getLogger('generate-expectations')


def gensymbols(
    lib,            # type: steamrt_abi.Library
    architecture,   # type: steamrt_abi.Architecture
    package,        # type: str
    writer,         # type: IO[str]
) -> bool:
    if not lib.track_abi:
        return False

    for prefix in ('/usr', '/'):
        libpath = os.path.join(
            prefix,
            'lib',
            architecture.multiarch_tuple,
            lib.soname,
        )

        if os.path.exists(libpath):
            break
    else:
        return False

    version = '0'   # stub, we don't use this for anything yet

    argv = [
        'dpkg-gensymbols',
        '-p' + package,
        '-e' + libpath,
        '-v' + version,
        '-I/dev/null',
        '-q',
        '-O-',
    ]

    with subprocess.Popen(
        argv, stdout=subprocess.PIPE, universal_newlines=True,
    ) as dgs:
        stdout = dgs.stdout
        assert stdout is not None

        for line in stdout:
            if line.startswith(' '):
                assert not line[1:].startswith(' '), line
                assert ' ' in line[1:], line
                symbol, version = line[1:].split(None, 1)
                assert symbol, line
                assert version, line
                assert '@' in symbol, line
                symbol, symver = symbol.split('@', 1)

                if symbol == symver:
                    public = (
                        lib.public_symbol_versions
                        and lib.symbol_version_is_public(symver)
                    )
                else:
                    public = (
                        lib.public_symbols
                        and lib.symbol_is_public(symbol, symver)
                    )

                if public:
                    writer.write(line)
            else:
                writer.write(line)

    return True


def main() -> None:
    abi = steamrt_abi.AbiData(
        os.path.join(
            os.path.dirname(__file__),
            'steam-runtime-abi.yaml',
        )
    )
    failed = False

    for multiarch_tuple in abi.architectures:
        for lib in abi.shared_libraries.values():
            if (
                abi.architectures[multiarch_tuple].subset
                and not lib.multiarch
            ):
                continue

            if (
                lib.only_architectures
                and multiarch_tuple not in lib.only_architectures
            ):
                continue

            if (
                lib.not_architectures
                and multiarch_tuple in lib.not_architectures
            ):
                continue

            for libdir in (
                os.path.join('/lib', multiarch_tuple),
                os.path.join('/usr/lib', multiarch_tuple),
            ):
                if os.path.exists(os.path.join(libdir, lib.soname)):
                    break
            else:
                if not lib.from_host_system:
                    logger.error(
                        '%s/%s not found', multiarch_tuple, lib.soname,
                    )
                    failed = True

            for alias in lib.aliases:
                for libdir in (
                    os.path.join('/lib', multiarch_tuple),
                    os.path.join('/usr/lib', multiarch_tuple),
                ):
                    if os.path.exists(os.path.join(libdir, alias)):
                        break
                else:
                    logger.error(
                        '%s/%s not found (should be symlink to %s)',
                        multiarch_tuple, alias, lib.soname,
                    )
                    failed = True

    if failed:
        sys.exit(1)

    for multiarch_tuple in abi.architectures:
        # { 'zlib1g': set(['libz.so.1']) }
        packages = {}   # type: Dict[str, Set[str]]

        output_dir = os.path.join(
            os.path.dirname(__file__),
            'expectations',
            multiarch_tuple,
        )

        try:
            shutil.rmtree(output_dir)
        except OSError:
            pass

        os.makedirs(output_dir)

        for lib in abi.shared_libraries.values():
            packages.setdefault(lib.deb, set()).add(lib.soname)

        for package, sonames in sorted(packages.items()):
            with open(
                os.path.join(output_dir, package + '.symbols'),
                'w'
            ) as writer:
                done_any = False

                logger.info(
                    'Generating: %s/%s',
                    multiarch_tuple, package + '.symbols',
                )

                for soname in sorted(sonames):
                    lib = abi.shared_libraries[soname]

                    if not lib.check_loadable:
                        continue

                    if (
                        abi.architectures[multiarch_tuple].subset
                        and not lib.multiarch
                    ):
                        continue

                    if not gensymbols(
                        lib,
                        abi.architectures[multiarch_tuple],
                        package,
                        writer,
                    ):
                        writer.write('{} {} #MINVER#\n'.format(
                            soname,
                            package,
                        ))
                        writer.write('# No symbols listed here yet\n')

                    done_any = True

                if not done_any:
                    logger.info('(Skipped: no content)')
                    os.remove(
                        os.path.join(output_dir, package + '.symbols'),
                    )


if __name__ == '__main__':
    logging.basicConfig(level=logging.INFO)
    main()
