#!/usr/bin/env python3
#
# OBS Source Service to vendor all crates.io and dependencies for a
# Rust project locally.
#
# (C) 2019 SUSE LLC
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
# See http://www.gnu.org/licenses/gpl-2.0.html for full license text.
#
# The following code is a derivative work of the code from obs-service-go_modules,
# available at: https://github.com/openSUSE/obs-service-go_modules
"""\
OBS Source Service to vendor all crates.io and dependencies for a
Rust project locally, by calling:

cargo vendor  <path/to/project/vendor>

This requires a decompressed version of you sources. Either you need to
provide this manually, or you can use obs_scm to generate this as part
of the osc services.

obs-service-cargo_vendor will a create vendor tarball, compressed with
the specified method (default to "gz"), containing the
vendor/ directory populated by cargo vendor.

See README.md for additional documentation.
"""

import logging
import argparse
import re
import tarfile
import os
import shutil

from pathlib import Path
from subprocess import check_output
from subprocess import CalledProcessError

service_name = "obs-service-cargo_vendor"

description = __doc__

logging.basicConfig(level=logging.DEBUG)
log = logging.getLogger(service_name)

parser = argparse.ArgumentParser(
    description=description, formatter_class=argparse.RawDescriptionHelpFormatter
)
parser.add_argument("--strategy", default="vendor")
parser.add_argument("--srcdir")
parser.add_argument("--outdir")
parser.add_argument("--compression", default="xz")
args = parser.parse_args()

outdir = args.outdir
srcdir = args.srcdir
compression = args.compression

vendor_example = """
Your spec file should be modified per the following example:

---BEGIN---
%global rustflags '-Clink-arg=-Wl,-z,relro,-z,now'

Source1:    vendor.tar.xz
Source2:    cargo_config

%prep
%setup -qa1
mkdir .cargo
cp %{SOURCE2} .cargo/config

%build
RUSTFLAGS=%{rustflags} cargo build --release

%install
RUSTFLAGS=%{rustflags} cargo install --root=%{buildroot}%{_prefix} --path .
---END---

WARNING: To avoid cargo install rebuilding the binary in the install stage
         all environment variables must be the same as in the build stage.
"""

vendor_tarname = f"vendor.tar.{compression}"


def find_file(path, filename):
    for root, dirs, files in os.walk(path):
        if filename in files:
            print(os.path.join(root, filename))
            return os.path.join(root, filename)


def run_cargo(runDirectory, command, argsList=[]):
    try:
        log.info(f"Running cargo {command} in directory: {runDirectory}")
        output = check_output(["cargo", command] + argsList, cwd=runDirectory).decode("utf-8").strip()
        if output:
            log.info(vendor_example)
            config_file_path = os.path.join(outdir, "cargo_config")
            config_file = open(config_file_path, 'w')
            config_file.write(output)
            config_file.close()
        return True
    except CalledProcessError as e:
        error = e.output.decode("utf-8").strip()
        if error:
            log.info(error)
        return False


def cargo_vendor(appDirectory, argsList=[]):
    vendor_dir = os.path.join(appDirectory, "vendor")
    log.info(f"Vendoring Cargo.toml deps to {vendor_dir}")
    run_cargo(appDirectory, "vendor", argsList + ["--", "vendor"])
    return vendor_dir


def main():
    log.info(f"Running OBS Source Service: {service_name}")

    log.info(f"Searching for Cargo.toml in {srcdir}")
    log.info(f"Current work dir {os.getcwd()}")

    cargo_toml_path = find_file(srcdir, "Cargo.toml")
    if cargo_toml_path:
        app_dir = os.path.dirname(cargo_toml_path)
        log.info(f"Detected Rust app directory: {app_dir}")
    else:
        log.error(f"No Rust app using Cargo.toml found under {outdir}")
        exit(1)

    if args.strategy == "vendor":
        vendor_dir = cargo_vendor(appDirectory=app_dir)
        vendor_tarfile = os.path.join(outdir, vendor_tarname)
        with tarfile.open(vendor_tarfile, f"w:{compression}") as tar:
            tar.add(vendor_dir, arcname=("vendor"))
    else:
        log.error(f"Not a valid strategy : \"{args.strategy}\"")
        exit(1)


if __name__ == "__main__":
    main()
