# Copyright 2021 Pants project contributors (see CONTRIBUTORS.md).
# Licensed under the Apache License, Version 2.0 (see LICENSE).

from __future__ import annotations

import argparse
import os
from pathlib import Path
from textwrap import dedent
from typing import Any, Dict, Sequence, cast

import toml
import yaml
from common import die

HEADER = dedent(
    """\
    # GENERATED, DO NOT EDIT!
    # To change, edit `build-support/bin/generate_github_workflows.py` and run:
    #   ./pants run build-support/bin/generate_github_workflows.py
    """
)


Step = Dict[str, Any]
Jobs = Dict[str, Any]
Env = Dict[str, str]


# ----------------------------------------------------------------------
# Constants
# ----------------------------------------------------------------------


NATIVE_FILES = [
    ".pants",
    "src/python/pants/engine/internals/native_engine.so",
    "src/python/pants/engine/internals/native_engine_pyo3.so",
    "src/python/pants/engine/internals/native_engine.so.metadata",
]

# We don't specify patch versions so that we get the latest, which comes pre-installed:
#  https://github.com/actions/setup-python#available-versions-of-python
PYTHON37_VERSION = "3.7"
PYTHON38_VERSION = "3.8"
PYTHON39_VERSION = "3.9"

LINUX_VERSION = "ubuntu-20.04"
MACOS_VERSION = "macos-10.15"

DONT_SKIP_RUST = "!contains(env.COMMIT_MESSAGE, '[ci skip-rust]')"
DONT_SKIP_WHEELS = (
    "github.event_name == 'push' || !contains(env.COMMIT_MESSAGE, '[ci skip-build-wheels]')"
)


# NB: This overrides `pants.ci.toml`.
DISABLE_REMOTE_CACHE_ENV = {"PANTS_REMOTE_CACHE_READ": "false", "PANTS_REMOTE_CACHE_WRITE": "false"}
# Works around bad `-arch arm64` flag embedded in Xcode 12.x Python interpreters on
# intel machines. See: https://github.com/giampaolo/psutil/issues/1832
MACOS_ENV = {"ARCHFLAGS": "-arch x86_64"}


IS_PANTS_OWNER = "${{ github.repository_owner == 'pantsbuild' }}"

# ----------------------------------------------------------------------
# Actions
# ----------------------------------------------------------------------


def checkout() -> Sequence[Step]:
    """Get prior commits and the commit message."""
    return [
        # See https://github.community/t/accessing-commit-message-in-pull-request-event/17158/8
        # for details on how we get the commit message here.
        # We need to fetch a few commits back, to be able to access HEAD^2 in the PR case.
        {
            "name": "Check out code",
            "uses": "actions/checkout@v2",
            "with": {"fetch-depth": 10},
        },
        # For a push event, the commit we care about is HEAD itself.
        # This CI currently only runs on PRs, so this is future-proofing.
        {
            "name": "Get commit message for branch builds",
            "if": "github.event_name == 'push'",
            "run": dedent(
                """\
                echo "COMMIT_MESSAGE<<EOF" >> $GITHUB_ENV
                echo "$(git log --format=%B -n 1 HEAD)" >> $GITHUB_ENV
                echo "EOF" >> $GITHUB_ENV
                """
            ),
        },
        # For a pull_request event, the commit we care about is the second parent of the merge
        # commit. This CI currently only runs on PRs, so this is future-proofing.
        {
            "name": "Get commit message for PR builds",
            "if": "github.event_name == 'pull_request'",
            "run": dedent(
                """\
                echo "COMMIT_MESSAGE<<EOF" >> $GITHUB_ENV
                echo "$(git log --format=%B -n 1 HEAD^2)" >> $GITHUB_ENV
                echo "EOF" >> $GITHUB_ENV
                """
            ),
        },
    ]


def setup_toolchain_auth() -> Step:
    return {
        "name": "Setup toolchain auth",
        "if": "github.event_name != 'pull_request'",
        "run": dedent(
            """\
            echo TOOLCHAIN_AUTH_TOKEN="${{ secrets.TOOLCHAIN_AUTH_TOKEN }}" >> $GITHUB_ENV
            """
        ),
    }


def pants_virtualenv_cache() -> Step:
    return {
        "name": "Cache Pants Virtualenv",
        "uses": "actions/cache@v2",
        "with": {
            "path": "~/.cache/pants/pants_dev_deps\n",
            "key": "${{ runner.os }}-pants-venv-${{ matrix.python-version }}-${{ hashFiles('pants/3rdparty/python/**', 'pants.toml') }}\n",
        },
    }


def global_env() -> Env:
    return {
        "PANTS_CONFIG_FILES": "+['pants.ci.toml']",
        "RUST_BACKTRACE": "all",
    }


def rust_channel() -> str:
    with open("rust-toolchain") as fp:
        rust_toolchain = toml.load(fp)
    return cast(str, rust_toolchain["toolchain"]["channel"])


def install_rustup() -> Step:
    return {
        "name": "Install rustup",
        "run": dedent(
            """\
            curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -v -y --default-toolchain none
            echo "PATH=${PATH}:${HOME}/.cargo/bin" >> $GITHUB_ENV
            """
        ),
    }


def rust_caches() -> Sequence[Step]:
    return [
        {
            "name": "Cache Rust toolchain",
            "uses": "actions/cache@v2",
            "with": {
                "path": f"~/.rustup/toolchains/{rust_channel()}-*\n~/.rustup/update-hashes\n~/.rustup/settings.toml\n",
                "key": "${{ runner.os }}-rustup-${{ hashFiles('rust-toolchain') }}",
            },
        },
        {
            "name": "Cache Cargo",
            "uses": "actions/cache@v2",
            "with": {
                "path": "~/.cargo/registry\n~/.cargo/git\n",
                "key": "${{ runner.os }}-cargo-${{ hashFiles('rust-toolchain') }}-${{ hashFiles('src/rust/engine/Cargo.*') }}\n",
                "restore-keys": "${{ runner.os }}-cargo-${{ hashFiles('rust-toolchain') }}-\n",
            },
        },
    ]


def install_jdk() -> Step:
    return {
        "name": "Install AdoptJDK",
        "uses": "actions/setup-java@v2",
        "with": {
            "distribution": "adopt",
            "java-version": "11",
        },
    }


def install_go() -> Step:
    return {
        "name": "Install Go",
        "uses": "actions/setup-go@v2",
        "with": {"go-version": "1.17.1"},
    }


def bootstrap_caches() -> Sequence[Step]:
    return [
        *rust_caches(),
        pants_virtualenv_cache(),
        # NB: This caching is only intended for the bootstrap jobs to avoid them needing to
        # re-compile when possible. Compare to the upload-artifact and download-artifact actions,
        # which are how the bootstrap jobs share the compiled binaries with the other jobs like
        # `lint` and `test`.
        {
            "name": "Get native engine hash",
            "id": "get-engine-hash",
            "run": 'echo "::set-output name=hash::$(./build-support/bin/rust/print_engine_hash.sh)"\n',
            "shell": "bash",
        },
        {
            "name": "Cache native engine",
            "uses": "actions/cache@v2",
            "with": {
                "path": "\n".join(NATIVE_FILES),
                "key": "${{ runner.os }}-engine-${{ steps.get-engine-hash.outputs.hash }}\n",
            },
        },
    ]


def native_binaries_upload() -> Step:
    return {
        "name": "Upload native binaries",
        "uses": "actions/upload-artifact@v2",
        "with": {
            "name": "native_binaries.${{ matrix.python-version }}.${{ runner.os }}",
            "path": "\n".join(NATIVE_FILES),
        },
    }


def native_binaries_download() -> Step:
    return {
        "name": "Download native binaries",
        "uses": "actions/download-artifact@v2",
        "with": {"name": "native_binaries.${{ matrix.python-version }}.${{ runner.os }}"},
    }


def setup_primary_python() -> Sequence[Step]:
    return [
        {
            "name": "Set up Python ${{ matrix.python-version }}",
            "uses": "actions/setup-python@v2",
            "with": {"python-version": "${{ matrix.python-version }}"},
        },
        {
            "name": "Tell Pants to use Python ${{ matrix.python-version }}",
            "run": dedent(
                """\
                echo "PY=python${{ matrix.python-version }}" >> $GITHUB_ENV
                echo "PANTS_PYTHON_SETUP_INTERPRETER_CONSTRAINTS=['==${{ matrix.python-version }}.*']" >> $GITHUB_ENV
                """
            ),
        },
    ]


def expose_all_pythons() -> Step:
    return {
        "name": "Expose Pythons",
        "uses": "pantsbuild/actions/expose-pythons@627a8ce25d972afa03da1641be9261bbbe0e3ffe",