# Copyright 2017 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

"""Recursively create hardlinks to targets at output."""

import argparse
import os
import shutil
import sys


def CreateHardlinkHelper(target, output):
    """
    Creates hardlink to `target` at `output`.

    If `target` is a directory, the directory structure will be copied and
    each file will be hardlinked independently. If `target` is a symlink,
    a new symlink will be created.

    The parent directory of `output` must exists or the function will fail.
    """
    if os.path.islink(target):
        os.symlink(os.readlink(target), output)
    elif os.path.isfile(target):
        try:
            os.link(target, output)
        except:
            shutil.copy(target, output)
    else:
        os.mkdir(output)
        for name in os.listdir(target):
            CreateHardlinkHelper(
                os.path.join(target, name), os.path.join(output, name)
            )


def CreateHardlink(target, output):
    """
    Creates hardlink to `target` at `output`.

    If `target` is a directory, the directory structure will be copied and
    each file will be hardlinked independently. If `target` is a symlink,
    a new symlink will be created.

    If `output` already exists, it is first deleted. The parent directory
    of `output` is created if it does not exists.
    """
    if os.path.exists(output):
        if os.path.isdir(output):
            shutil.rmtree(output)
        else:
            os.unlink(output)
    dirname = os.path.dirname(output)
    if not os.path.isdir(dirname):
        os.makedirs(dirname)
    CreateHardlinkHelper(target, output)


def CreateHardlinks(output_dir, relative_to, targets):
    """
    Creates hardlinks to `targets` in `output_dir`.

    The `targets` should starts with `relative_to` and the hardlink will
    be created at `{output_dir}/{os.path.relpath(sources, relative_to)}`.

    Fails with an error if any file in `targets` not located inside the
    `relative_to` directory or if creating any of the hardlinks fails.
    """
    for target in targets:
        if not target.startswith(relative_to):
            print(
                f'error: "{target}" not relative to "{relative_to}',
                file=sys.stderr,
            )
            sys.exit(1)

    for target in targets:
        output = os.path.join(output_dir, os.path.relpath(target, relative_to))
        CreateHardlink(target, output)


def main(args):
    parser = argparse.ArgumentParser()

    parser.add_argument(
        '--output-dir',
        required=True,
        help='directory where the hardlinks should be created',
    )

    parser.add_argument(
        '--relative-to',
        required=True,
        help='sources file will be rebased to this directory',
    )

    parser.add_argument(
        'sources',
        nargs='+',
        help='files that should be hardlinked, must be below RELATIVE_TO',
    )

    parsed = parser.parse_args(args)
    CreateHardlinks(
        os.path.normpath(parsed.output_dir),
        os.path.normpath(parsed.relative_to) + os.sep,
        [os.path.normpath(source) for source in parsed.sources],
    )


if __name__ == '__main__':
    main(sys.argv[1:])
