#!/bin/bash -e
#
# Author:: Lance Albertson <lance@osuosl.org>
# Copyright:: Copyright 2024-2026, Cinc Project
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# Windows build entry point.
#
# Same shape as build/build-macos.sh and build/build-freebsd.sh:
#
#   1. Preflight: confirm a non-toolchain Ruby is on PATH. We need an
#      independent interpreter to drive omnibus — Ruby installed inside
#      c:\cinc-project\omnibus-toolchain would disappear when we rename
#      that dir aside in step 2, and Ruby installed inside the Chef seed
#      at c:\opscode\omnibus-toolchain is an acceptable fallback but the
#      operator-documented default is RubyInstaller-DevKit at
#      C:\Ruby34-x64-toolchain (distinct from C:\Ruby34-x64 used by
#      omnibus-software builds on the same runner so the two do not
#      clobber each other). See CINC_MAINTENANCE_PLAN.md §3.1.
#   2. If a prior Cinc install exists at C:\cinc-project\omnibus-toolchain,
#      rename it to .bootstrap and set a trap that restores it on script
#      exit — runner is rolled back on either success or failure so the
#      operator's working install survives broken builds.
#   3. The build writes a fresh C:\cinc-project\omnibus-toolchain and
#      packages it into an MSI; the trap then restores the bootstrap.

# --- Step 1: ensure RubyInstaller-DevKit is present ----------------------
#
# Install to a dedicated toolchain path, not the standard C:\Ruby34-x64
# (which omnibus-software builds on the same runner own and routinely
# remove). Override the path via the RUBYINSTALLER_BIN CI/CD variable
# if installed elsewhere. Prepend to PATH for this script only — do
# NOT pollute the runner's system PATH.
#
# Pinned version, bumped deliberately. Other jobs on this runner share
# the install path, so silently chasing "latest" upstream would mask
# real breakage when an upgrade lands mid-build.
RUBYINSTALLER_VERSION="3.4.9-1"
# SHA256 of rubyinstaller-devkit-${RUBYINSTALLER_VERSION}-x64.exe from
# the GitHub release. Pinned alongside the version so corruption (or
# a tampered cache .exe) is caught before we shell out to it. To bump
# the version, also update this hash — compute via:
#   curl -fsSL https://github.com/oneclick/rubyinstaller2/releases/\
#     download/RubyInstaller-X.Y.Z-N/rubyinstaller-devkit-X.Y.Z-N-x64.exe \
#     | sha256sum
RUBYINSTALLER_SHA256="39632975220ff43133244d8b8b8774feb0d301b33d11394513df28cb1853c36d"
RUBYINSTALLER_BIN="${RUBYINSTALLER_BIN:-/c/Ruby34-x64-toolchain/bin}"
RUBYINSTALLER_DIR="${RUBYINSTALLER_BIN%/bin}"

# Auto-install if missing. Other jobs on the shared windows-2016
# runner have been observed wiping the RubyInstaller path, so a static
# operator install is not enough — make every build self-heal. The
# installer .exe lives in ${TOP_DIR}/cache/, which is GitLab-cached
# via S3, so we only re-download when the cache is cold.
if [ ! -x "${RUBYINSTALLER_BIN}/ruby.exe" ]; then
  echo "RubyInstaller-DevKit missing at ${RUBYINSTALLER_DIR}; installing ${RUBYINSTALLER_VERSION}..."
  INSTALLER_CACHE="${PWD}/cache/rubyinstaller"
  INSTALLER="${INSTALLER_CACHE}/rubyinstaller-devkit-${RUBYINSTALLER_VERSION}-x64.exe"
  mkdir -p "${INSTALLER_CACHE}"

  # Re-download if missing OR if the cached file does not match the
  # pinned hash (corruption / partial download / tampering).
  if [ ! -s "${INSTALLER}" ] || ! echo "${RUBYINSTALLER_SHA256}  ${INSTALLER}" | sha256sum -c --quiet; then
    echo "Downloading from GitHub releases..."
    rm -f "${INSTALLER}"
    curl -fsSL -o "${INSTALLER}" \
      "https://github.com/oneclick/rubyinstaller2/releases/download/RubyInstaller-${RUBYINSTALLER_VERSION}/rubyinstaller-devkit-${RUBYINSTALLER_VERSION}-x64.exe"
    echo "${RUBYINSTALLER_SHA256}  ${INSTALLER}" | sha256sum -c -
  fi

  # Inno Setup silent install. MSYS2_ARG_CONV_EXCL stops MSYS bash from
  # rewriting the /-prefixed args into Windows paths.
  RUBYINSTALLER_DIR_WIN="$(cygpath -w "${RUBYINSTALLER_DIR}")"
  MSYS2_ARG_CONV_EXCL='*' "${INSTALLER}" \
    /VERYSILENT /NORESTART /SUPPRESSMSGBOXES \
    /DIR="${RUBYINSTALLER_DIR_WIN}" \
    /LOG="$(cygpath -w "${INSTALLER_CACHE}/install.log")"

  if [ ! -x "${RUBYINSTALLER_BIN}/ruby.exe" ]; then
    echo "ERROR: RubyInstaller-DevKit install failed; ruby.exe still missing at ${RUBYINSTALLER_BIN}." >&2
    echo "See ${INSTALLER_CACHE}/install.log. Override the install path with" >&2
    echo "the RUBYINSTALLER_BIN CI/CD variable if needed." >&2
    echo "See CINC_MAINTENANCE_PLAN.md §3.1." >&2
    exit 1
  fi
fi
# Prepend the build tools that the operator's
# C:\omnibus\load-omnibus-toolchain.ps1 shim would normally provide.
# We can't source the shim because it also prepends
# C:\opscode\omnibus-toolchain\embedded\bin (the Chef seed toolchain),
# which would conflict with the install_dir we are about to rename
# aside. But we still need its non-conflicting bits: WiX (heat.exe,
# candle.exe, light.exe) for MSI packaging, 7-Zip for some omnibus
# extraction steps, and the Windows SDK for native tools.
#
# Override individual paths via CI/CD vars if the runner installs
# them elsewhere; the defaults match the current windows-2016 runner.
WIX_BIN="${WIX_BIN:-/c/Program Files (x86)/WiX Toolset v3.14/bin}"
SEVENZIP_BIN="${SEVENZIP_BIN:-/c/Program Files/7-zip}"
WINSDK_BIN="${WINSDK_BIN:-/c/Program Files (x86)/Windows Kits/8.1/bin/x64}"
export PATH="${RUBYINSTALLER_BIN}:${WIX_BIN}:${SEVENZIP_BIN}:${WINSDK_BIN}:${PATH}"

# omnibus-software's ruby.rb (line 217ish) calls ENV["MSYSTEM"].downcase
# and ENV["MSYS2_INSTALL_DIR"] to locate MSYS2-bundled DLLs that the
# embedded Ruby needs to copy. Those env vars are normally set by an
# MSYS2 shell or the load-omnibus-toolchain.ps1 shim — neither runs
# here, so set them explicitly. RubyInstaller-DevKit bundles MSYS2
# alongside the Ruby install (msys64/ subdir under the install root)
# and Ruby 3.4 uses the UCRT64 toolchain.
export MSYS2_INSTALL_DIR="${RUBYINSTALLER_BIN%/bin}/msys64"
export MSYSTEM="UCRT64"

# Default omnibus windows_arch is :x86 (see omnibus.rb), which makes
# omnibus-software's ruby.rb look for the 32-bit libgcc_s_dw2-1.dll
# from MSYS2. We are building a 64-bit toolchain on top of 64-bit
# RubyInstaller / UCRT64 MSYS2, so flip to x64. omnibus then expects
# libgcc_s_seh-1.dll which is what UCRT64 actually ships.
export OMNIBUS_WINDOWS_ARCH="x64"

RUBY_PATH="$(command -v ruby)"
case "${RUBY_PATH}" in
  ${RUBYINSTALLER_BIN}/ruby*) ;;
  *)
    echo "ERROR: ruby on PATH is ${RUBY_PATH}, not ${RUBYINSTALLER_BIN}/ruby." >&2
    echo "Something else on PATH is shadowing RubyInstaller. Check the" >&2
    echo "runner's system PATH for stray Ruby installs and remove them." >&2
    echo "See CINC_MAINTENANCE_PLAN.md §3.1." >&2
    exit 1
    ;;
esac

echo "Using ruby from ${RUBY_PATH}:"
ruby --version

TOP_DIR="$(pwd)"
export CI_PROJECT_DIR=${CI_PROJECT_DIR:-${TOP_DIR}}

# Put Bundler's user-level cache + config inside the workspace
# instead of $HOME/.bundle, so the build does not depend on the
# gitlab-runner user's profile dir being writable.
export BUNDLE_USER_HOME="${TOP_DIR}/.bundle-user"
mkdir -p "${BUNDLE_USER_HOME}"

INSTALL_DIR="/c/cinc-project/omnibus-toolchain"
BOOTSTRAP_DIR="/c/cinc-project/omnibus-toolchain.bootstrap"

# --- Step 2: rename-aside for rollback ----------------------------------
# Recover from a previous build that crashed before its trap could run.
if [ -d "${BOOTSTRAP_DIR}" ]; then
  if [ -x "${INSTALL_DIR}/embedded/bin/ruby.exe" ]; then
    rm -rf "${BOOTSTRAP_DIR}"
  else
    rm -rf "${INSTALL_DIR}"
    mv "${BOOTSTRAP_DIR}" "${INSTALL_DIR}"
  fi
fi

# First-ever Cinc build on this runner: no prior C:\cinc-project\
# omnibus-toolchain to rename aside. Subsequent builds rename-and-trap
# as designed.
if [ -d "${INSTALL_DIR}" ]; then
  mv "${INSTALL_DIR}" "${BOOTSTRAP_DIR}"
  trap 'rm -rf "${INSTALL_DIR}"; mv "${BOOTSTRAP_DIR}" "${INSTALL_DIR}"' EXIT
fi

# Pre-create the empty install_dir before omnibus runs. omnibus's
# git_cache invokes `git init -q --work-tree=/cinc-project/omnibus-
# toolchain` (drive letter stripped by GitCache#install_dir on Windows),
# and MSYS2's git resolves that against the current drive — if
# C:\cinc-project\ does not exist yet on this runner, git fails with
# "Invalid path '/cinc-project': No such file or directory". cinc-
# workstation never trips this because its install lives at the same
# C:\cinc-project\ parent and is always present.
mkdir -p "${INSTALL_DIR}"

set -e
mkdir -p ${TOP_DIR}/cache/git_cache

GIT_CACHE="${TOP_DIR}/cache/git_cache/cinc-project/omnibus-toolchain"
if [ -d "${GIT_CACHE}" ] && ! git --git-dir="${GIT_CACHE}" rev-parse --git-dir > /dev/null 2>&1; then
  echo "Git cache is corrupted, removing..."
  rm -rf "${GIT_CACHE}"
fi

# Convert TOP_DIR (MSYS2 style: /c/foo) to native Windows-with-forward-
# slashes style (C:/foo) so omnibus's git_cache does not blow up when
# File.join'ing it with install_dir. Mixing the two path flavors lets
# Ruby's Windows File.join strip drive letters in unexpected ways
# ("c:/cinc-project/foo" becomes "/cinc-project/foo" after a join),
# producing invalid --work-tree=/cinc-project/... git invocations.
TOP_DIR_WIN="$(cygpath -m "${TOP_DIR}")"
grep -q '^cache_dir' omnibus.rb || {
  echo "cache_dir '${TOP_DIR_WIN}/cache'" >> omnibus.rb
  echo "git_cache_dir '${TOP_DIR_WIN}/cache/git_cache'" >> omnibus.rb
  echo "use_git_caching true" >> omnibus.rb
}

# Pre-seed omnibus's git_cache repo so its own `git init -q` step does
# not run. omnibus's GitCache strips the drive letter from install_dir
# when building --work-tree (yielding "/cinc-project/omnibus-toolchain"),
# and `git init` rejects that path on Windows with "Invalid path
# '/cinc-project'". cinc-workstation never trips this because its cache
# is restored from S3 on every build, so init is skipped. For our
# first-ever build there is no S3 cache yet, so we init it here using
# fully drive-qualified paths that git accepts. Subsequent omnibus
# git_cmd calls (add/commit/tag) tolerate the drive-stripped work-tree
# because the install_dir directory exists.
INSTALL_DIR_WIN="$(cygpath -m "${INSTALL_DIR}")"
if [ ! -d "${GIT_CACHE}" ]; then
  mkdir -p "${GIT_CACHE}"
  git -c core.autocrlf=false -c core.ignorecase=false \
      --git-dir="${GIT_CACHE}" \
      --work-tree="${INSTALL_DIR_WIN}" \
      init -q
  git --git-dir="${GIT_CACHE}" config --local user.name "Omnibus Git Cache"
  git --git-dir="${GIT_CACHE}" config --local user.email "omnibus@localhost"
fi

gem sources --remove https://rubygems.org/ || true
gem sources --add https://rubygems.cinc.sh
gem sources --add https://rubygems.org

bundle config set --local path "${TOP_DIR}/vendor/bundle"
bundle config set --local without 'development'
bundle install

if [ -n "${AWS_ACCESS_KEY_ID:-}" ] ; then
  bundle exec omnibus cache populate
fi

bundle exec omnibus build omnibus-toolchain \
  -l ${OMNIBUS_LOG_LEVEL:-info} \
  --override append_timestamp:false

mkdir -p ${CI_PROJECT_DIR}/data
mv -v pkg/omnibus-toolchain* ${CI_PROJECT_DIR}/data/
