# Copyright 2021 The IREE Authors
#
# Licensed under the Apache License v2.0 with LLVM Exceptions.
# See https://llvm.org/LICENSE.txt for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
"""Builds the main distribution package.

This script runs as the CIBW_BEFORE_BUILD command within cibuildwheel:
  - Main distribution .tar.bz2 file (the result of `ninja install`).
  - The python_packages/iree_base_compiler wheel.
  - Installable tests.

It uses cibuildwheel for all of this as a convenience since it already knows
how to arrange for the cross platform part of the build, including using
an appropriate manylinux image, etc.

This is expected to be run from the project directory, containing the
following sub-directories:
  - c/ : Main IREE repository checkout.
  - bindist/ : Directory where binary distribution artifacts are written.
  - c/version_info.json : Version config information (legacy).
  - c/version_local.json : Version config information.

Within the build environment (which may be the naked runner or a docker image):
  - iree-build/ : The build tree.
  - iree-install/ : The install tree.

Environment variables:
  - BINDIST_DIR : If set, then this overrides the default bindist/ directory.
    Should be set if running in a mapped context like a docker container.

Testing this script:
It is not recommended to run cibuildwheel locally. However, this script can
be executed as if running within such an environment. To do so, create
a directory and:
  ln -s /path/to/iree c
  python -m venv .venv
  source .venv/bin/activate

  python ./c/build_tools/github_actions/build_dist.py main-dist
  python ./c/build_tools/github_actions/build_dist.py py-tflite-compiler-tools-pkg
  python ./c/build_tools/github_actions/build_dist.py py-tf-compiler-tools-pkg


That is not a perfect approximation but is close.
"""

import json
import os
import platform
import shutil
import subprocess
import sys
import sysconfig
import tarfile

# Setup.
WORK_DIR = os.path.realpath(os.path.curdir)
BUILD_DIR = os.path.join(WORK_DIR, "iree-build")
INSTALL_DIR = os.path.join(WORK_DIR, "iree-install")
IREESRC_DIR = os.path.join(WORK_DIR, "c")
TF_INTEGRATIONS_DIR = os.path.join(IREESRC_DIR, "integrations/tensorflow")
BINDIST_DIR = os.environ.get("BINDIST_DIR")
if BINDIST_DIR is None:
    BINDIST_DIR = os.path.join(WORK_DIR, "bindist")
THIS_DIR = os.path.realpath(os.path.dirname(__file__))
CMAKE_CI_SCRIPT = os.path.join(THIS_DIR, "cmake_ci.py")
BUILD_REQUIREMENTS_TXT = os.path.join(
    IREESRC_DIR,
    "runtime",
    "bindings",
    "python",
    "iree",
    "runtime",
    "build_requirements.txt",
)
CI_REQUIREMENTS_TXT = os.path.join(THIS_DIR, "ci_requirements.txt")
CONFIGURE_BAZEL_PY = os.path.join(IREESRC_DIR, "configure_bazel.py")


# Load version info.
def load_version_info():
    with open(os.path.join(IREESRC_DIR, "version_local.json"), "rt") as f:
        return json.load(f)


try:
    version_info = load_version_info()
except FileNotFoundError:
    print("version_local.json not found. Using version.json defaults")
    version_info = {
        "package-version": "0.1dev1",
        "package-suffix": "-dev",
    }


def remove_cmake_cache():
    cache_file = os.path.join(BUILD_DIR, "CMakeCache.txt")
    if os.path.exists(cache_file):
        print(f"Removing {cache_file}")
        os.remove(cache_file)
    else:
        print(f"Not removing cache file (does not exist): {cache_file}")


def install_python_requirements():
    print("Installing python requirements...")
    subprocess.check_call(
        [sys.executable, "-m", "pip", "install", "-r", BUILD_REQUIREMENTS_TXT]
    )
    subprocess.check_call(
        [sys.executable, "-m", "pip", "install", "-r", CI_REQUIREMENTS_TXT]
    )


def configure_bazel():
    print("Generating configured.bazelrc...")
    subprocess.check_call([sys.executable, CONFIGURE_BAZEL_PY])


def build_main_dist():
    """Builds the main distribution binaries.

    Additional packages that are installable as part of a full build and do not
    benefit from a more restricted build can be added here.
    """
    install_python_requirements()

    # Clean up install and build trees.
    shutil.rmtree(INSTALL_DIR, ignore_errors=True)
    remove_cmake_cache()

    # CMake configure.
    print("*** Configuring ***")
    subprocess.run(
        [
            sys.executable,
            CMAKE_CI_SCRIPT,
            f"-B{BUILD_DIR}",
            "--log-level=VERBOSE",
            f"-DCMAKE_INSTALL_PREFIX={INSTALL_DIR}",
            # On some distributions, this will install to lib64. We would like
            # consistency in built packages, so hard-code it.
            "-DCMAKE_INSTALL_LIBDIR=lib",
            f"-DCMAKE_BUILD_TYPE=Release",
            f"-DIREE_BUILD_COMPILER=ON",
            f"-DIREE_BUILD_PYTHON_BINDINGS=OFF",
            f"-DIREE_BUILD_SAMPLES=OFF",
            # cpuinfo is set to be removed and is problematic from an
            # installed/bundled library perspective.
            f"-DIREE_ENABLE_CPUINFO=OFF",
        ],
        check=True,
    )

    print("*** Building ***")
    subprocess.run(
        [
            sys.executable,
            CMAKE_CI_SCRIPT,
            "--build",
            BUILD_DIR,
            "--target",
            "all",
        ],
        check=True,
    )

    # TODO: Get proper dependency management on install targets so we don't
    # have to build all first.
    subprocess.run(
        [
            sys.executable,
            CMAKE_CI_SCRIPT,
            "--build",
            BUILD_DIR,
            "--target",
            "iree-install-dist-stripped",
        ],
        check=True,
    )

    print("*** Packaging ***")
    dist_entries = [
        "bin",
        "lib",
        "include",
    ]
    dist_archive = os.path.join(
        BINDIST_DIR,
        f"iree-dist{version_info['package-suffix']}"
        f"-{version_info['package-version']}"
        f"-{sysconfig.get_platform()}.tar.xz",
    )
    print(f"Creating archive {dist_archive}")
    os.makedirs(os.path.dirname(dist_archive), exist_ok=True)
    with tarfile.open(dist_archive, mode="w:xz") as tf:
        for entry in dist_entries:
            print(f"Adding entry: {entry}")
            tf.add(os.path.join(INSTALL_DIR, entry), arcname=entry, recursive=True)


def build_py_tf_compiler_tools_pkg():
    """Builds iree-install/python_packages/iree_tools_[tf, tflite] packages."""
    install_python_requirements()
    configure_bazel()

    # Clean up install and build trees.
    shutil.rmtree(INSTALL_DIR, ignore_errors=True)
    remove_cmake_cache()

    os.makedirs(BINDIST_DIR, exist_ok=True)

    for project in ["iree_tflite", "iree_tf"]:
        print(f"*** Building wheel for {project} ***")
        subprocess.run(
            [
                sys.executable,
                "-m",
                "pip",
                "wheel",
                os.path.join(TF_INTEGRATIONS_DIR, "python_projects", project),
            ],
            cwd=BINDIST_DIR,
            check=True,
        )


command = sys.argv[1]
if command == "main-dist":
    build_main_dist()
elif command == "py-tf-compiler-tools-pkg":
    build_py_tf_compiler_tools_pkg()
else:
    print(f"Unrecognized command: {command}")
