#!/usr/bin/env bash
set -euo pipefail

PROGNAME=$(basename "$0")
SCRIPT_DIR=$(cd "$(dirname "$0")" ; pwd)
ROOT_DIR=$(cd "$SCRIPT_DIR/../.." ; pwd)

DEFAULT_STEPS="req build copy_files test sign create_archive"

# Intermediate build artifacts (PyInstaller bundle, package staging, scratch)
# live under build/; only the final shippable packages go to dist/.
BUILD_DIR=$ROOT_DIR/build
DIST_DIR=$ROOT_DIR/dist

PYINSTALLER_OUTPUT_DIR=$BUILD_DIR/ggshield

# `cargo` is deliberately absent: build_dispatcher provisions it on demand.
REQUIREMENTS="pyinstaller"

# Whether we want a signed binary or not
DO_SIGN=0

VERSION_SUFFIX=""

# Colors
C_RED="\e[31;1m"
C_GREEN="\e[32;1m"
C_RESET="\e[0m"

err() {
    echo "$@" >&2
}

info() {
    printf "$C_GREEN%s$C_RESET\n" "$PROGNAME: [INFO] $*" >&2
}

die() {
    printf "$C_RED%s$C_RESET\n" "$PROGNAME: [ERROR] $*" >&2
    exit 1
}

check_var() {
    local name="$1"
    set +u
    if [ -z "${!name}" ] ; then
        die "\$$name must be set"
    fi
    set -u
}

usage() {
    if [ "$*" != "" ] ; then
        err "Error: $*"
        err
    fi

    cat << EOF
Usage: $PROGNAME [OPTION ...] [STEPS]
Build OS specific packages for ggshield.

Default steps are: $DEFAULT_STEPS

Options:
  -h, --help      Display this usage message and exit.
  --sign          Sign the binary, on supported OSes.
  --suffix SUFFIX Append "SUFFIX" to the version number.

For more details, see doc/dev/os-packages.md.
EOF

    exit 1
}

read_version() {
    # Anchor on the `__version__` assignment, not just any version-like
    # substring: other lines (type hints, examples in messages, ...) can hold
    # one and would corrupt VERSION. The whole quoted value is kept, so a
    # pre-release suffix survives into the package name.
    VERSION=$(sed -n 's/^__version__ *= *"\([0-9][^"]*\)".*/\1/p' \
        "$ROOT_DIR/ggshield/__init__.py")
    if [ -n "$VERSION_SUFFIX" ] ; then
        VERSION="${VERSION}${VERSION_SUFFIX}"
    fi
    info "VERSION=$VERSION"
}

init_system_vars() {
    local arch
    arch=$(uname -m)

    case "$arch" in
    arm64|aarch64)
        HUMAN_ARCH=ARM-based
        # Debian-style architecture name, used by nfpm
        NFPM_ARCH=arm64
        ;;
    x86_64)
        HUMAN_ARCH=Intel-based
        NFPM_ARCH=amd64
        ;;
    *)
        die "Unsupported architecture '$arch'"
        ;;
    esac

    local out
    out=$(uname)

    # directory containing ggshield executable, inside the archive
    INSTALL_PREFIX=""

    case "$out" in
    Linux)
        EXE_EXT=""
        TARGET="$arch-unknown-linux-gnu"
        HUMAN_OS=Linux
        REQUIREMENTS="$REQUIREMENTS nfpm"
        ;;
    Darwin)
        EXE_EXT=""
        HUMAN_OS=macOS
        TARGET="$arch-apple-darwin"
        INSTALL_PREFIX=opt/gitguardian/ggshield-$VERSION
        ;;
    MINGW*|MSYS*)
        EXE_EXT=".exe"
        HUMAN_OS=Windows
        TARGET="$arch-pc-windows-msvc"
        REQUIREMENTS="$REQUIREMENTS choco wix"
        ;;
    *)
        die "Unknown OS. uname printed '$out'"
        ;;
    esac
    ARCHIVE_DIR_NAME=ggshield-$VERSION-$TARGET
}

load_os_specific_code() {
    case "$HUMAN_OS" in
    macOS)
        . "$SCRIPT_DIR/macos-functions.bash"
        ;;
    Windows)
        . "$SCRIPT_DIR/windows-functions.bash"
        ;;
    *)
        ;;
    esac
}

add_os_specific_sign_requirements() {
    case "$HUMAN_OS" in
    macOS)
        macos_add_sign_dependencies
        ;;
    Windows)
        windows_add_sign_dependencies
        ;;
    *)
        ;;
    esac
}

step_req() {
    local fail=0
    info "Checking requirements"
    local requirements=$REQUIREMENTS
    for exe in $requirements ; do
        err -n "$exe: "
        if command -v "$exe" > /dev/null ; then
            err OK
        else
            err FAIL
            fail=1
        fi
    done
    if [ $fail -ne 0 ] ; then
        die "Not all requirements are installed"
    fi
}

step_build() {
    # PyInstaller's analysis cache (workpath) and the onedir bundle (distpath)
    # both default to a "ggshield" subdir, so give the workpath its own home to
    # avoid colliding with the bundle now that both live under build/.
    local pyinstaller_workpath=$BUILD_DIR/.pyinstaller
    rm -rf "$pyinstaller_workpath"
    rm -rf "$PYINSTALLER_OUTPUT_DIR"

    local extra_args=""
    if [ "$HUMAN_OS" != Windows ] ; then
        # Only strip on Linux and macOS: pyinstaller docs says it's not
        # recommended on Windows.
        extra_args="--strip"
    fi

    # sigstore loads its TUF bootstrap roots with
    # importlib.resources.files("sigstore._store"). PyInstaller does not see
    # that package/data reference through static analysis, so collect the data
    # files and force the resource package itself in as a hidden import.
    # When adding a similar dependency, append it here AND extend
    # smoke_check_bundle() with a structural assertion.
    local collect_args=(
        --collect-data sigstore
        --hidden-import sigstore._store
        # ggshield's command groups are declared as "module:attr" strings and
        # imported on lookup (see ggshield/cmd/utils/lazy_group.py), which
        # PyInstaller's static analysis cannot follow. Bundle everything under
        # the package; frozen imports are pay-per-use, so this costs nothing at
        # runtime. smoke_check_bundle() walks the whole command tree to prove
        # each one still resolves from the bundle.
        --collect-submodules ggshield
    )

    pyinstaller ggshield/__main__.py --name ggshield --exclude-module pkg_resources --noupx \
        --workpath "$pyinstaller_workpath" --distpath "$BUILD_DIR" \
        "${collect_args[@]}" $extra_args

    if [ "$HUMAN_OS" != Windows ] ; then
        # Libraries do not need to be executable
        find "$PYINSTALLER_OUTPUT_DIR" \( -name "*.so.*" -o -name "*.so" -o -name "*.dylib" \) \
            -exec chmod -x '{}' ';'
    fi

    build_dispatcher
    smoke_check_bundle
}

# Version and checksums of the `rustup-init` the release build installs when the
# container has no cargo. Pinned like everything else in a release: it runs as
# root in the build container, before signing. This pins the installer only; the
# toolchain is pinned by rust/rust-toolchain.toml.
#
# To bump: pick a version from https://static.rust-lang.org/rustup/release-stable.toml
# and read each sha256 from
# https://static.rust-lang.org/rustup/archive/$RUSTUP_VERSION/$triple/rustup-init.sha256
RUSTUP_VERSION=1.29.0
# Read through indirect expansion in install_rustup, hence "unused" to shellcheck.
# shellcheck disable=SC2034
RUSTUP_SHA256_x86_64_unknown_linux_gnu=4acc9acc76d5079515b46346a485974457b5a79893cfb01112423c89aeb5aa10
# shellcheck disable=SC2034
RUSTUP_SHA256_aarch64_unknown_linux_gnu=9732d6c5e2a098d3521fca8145d826ae0aaa067ef2385ead08e6feac88fa5792

# Install rustup from a pinned, checksum-verified `rustup-init`, rather than
# piping https://sh.rustup.rs into a shell.
install_rustup() {
    local triple sha expected tmp
    case "$(uname -m)" in
        x86_64|amd64)  triple=x86_64-unknown-linux-gnu ;;
        aarch64|arm64) triple=aarch64-unknown-linux-gnu ;;
        *) die "no pinned rustup-init for machine '$(uname -m)'" ;;
    esac
    # Indirect expansion: the constants above are named after the triple.
    local var="RUSTUP_SHA256_${triple//-/_}"
    expected="${!var}"

    tmp="$(mktemp -d)"
    info "Installing rustup $RUSTUP_VERSION ($triple)"
    curl --proto '=https' --tlsv1.2 -sSf --retry 3 -o "$tmp/rustup-init" \
        "https://static.rust-lang.org/rustup/archive/$RUSTUP_VERSION/$triple/rustup-init"

    sha="$(sha256sum "$tmp/rustup-init" | cut -d' ' -f1)"
    if [ "$sha" != "$expected" ] ; then
        rm -rf "$tmp"
        die "rustup-init checksum mismatch: got $sha, expected $expected"
    fi

    chmod +x "$tmp/rustup-init"
    "$tmp/rustup-init" -y --profile minimal --default-toolchain none --no-modify-path
    rm -rf "$tmp"
}

# Rename the PyInstaller launcher to `ggshield-py` and put the Rust dispatcher in
# its place as `ggshield`. Both names, in the same directory, are the contract the
# dispatcher relies on to find its sibling; see doc/dev/os-packages.md.
build_dispatcher() {
    info "Building the Rust dispatcher"
    local crate_dir="$ROOT_DIR/rust"

    # The Rocky 8 build container has neither rustup nor a C compiler (ring, the
    # TLS backend, compiles C); macOS and Windows runners ship rustup. Not done in
    # the CI's shared dependency step: doing it there broke the later `uv` step.
    # --default-toolchain none because rust/rust-toolchain.toml pins the version.
    if [ "$HUMAN_OS" = Linux ] ; then
        command -v cc > /dev/null 2>&1 || yum install -y gcc
    fi
    if ! command -v cargo > /dev/null 2>&1 ; then
        install_rustup
    fi
    if [ -f "$HOME/.cargo/env" ] ; then
        . "$HOME/.cargo/env"
    fi

    # No --target: every build_release_assets.yml job is native for its own
    # (OS, arch), so the host triple already *is* $TARGET. --locked so a release
    # builds the dependency versions we committed, not whatever resolves today.
    ( cd "$crate_dir" && cargo build --release --locked )

    local built="$crate_dir/target/release/ggshield$EXE_EXT"
    if ! [ -f "$built" ] ; then
        die "cargo did not produce '$built'"
    fi

    local launcher="$PYINSTALLER_OUTPUT_DIR/ggshield$EXE_EXT"
    if ! [ -f "$launcher" ] ; then
        die "PyInstaller did not produce '$launcher'"
    fi
    mv "$launcher" "$PYINSTALLER_OUTPUT_DIR/ggshield-py$EXE_EXT"
    cp "$built" "$launcher"
    chmod +x "$launcher"
}

# Walk the built bundle's Click command tree, running `--help` on every node.
# Resolving a name is what triggers its lazy "module:attr" import, so this is
# what proves the bundle contains every command's module. The tree is read from
# the binary's own `--help` output, so a command added later is covered without
# anyone updating a list here. Blind spot: `hidden=True` commands (e.g.
# `secret scan docker-archive`) are absent from --help, so they are not walked;
# they are covered by --collect-submodules and by the functional test suite,
# which runs against this same binary.
#
# Expects $bin and $smoke_env to be set by smoke_check_bundle (bash scoping) and
# counts leaves into the global $SMOKE_LEAF_COUNT (global, not local, because
# this function recurses). $1 is the command path, empty for the root. Command
# names never contain spaces or globs, so leaving $1 unquoted is the intended
# word-splitting.
smoke_check_command_tree() {
    local path="$1"
    local out
    # shellcheck disable=SC2086
    if ! out=$(env "${smoke_env[@]}" "$bin" $path --help 2>&1) ; then
        err "$out"
        die "Bundle smoke check FAILED for: ggshield $path --help. \
Likely a PyInstaller bundling gap — command groups are imported lazily from \
\"module:attr\" strings, so a dropped module only fails when that command is \
resolved. Check the --collect-submodules argument in step_build."
    fi

    # Sub-commands are the first word of each entry in the "Commands:" section.
    local subs
    subs=$(printf '%s\n' "$out" | awk '
        /^Commands:/ { in_cmds = 1; next }
        in_cmds && /^[^ ]/ { in_cmds = 0 }
        in_cmds && /^  [a-zA-Z][a-zA-Z0-9_-]*([ ]|$)/ { print $1 }
    ')

    if [ -z "$subs" ] ; then
        SMOKE_LEAF_COUNT=$((SMOKE_LEAF_COUNT + 1))
        return
    fi
    local sub
    for sub in $subs ; do
        smoke_check_command_tree "${path:+$path }$sub"
    done
}

# Run `secret scan ai-hook` end to end on a canned Claude Code payload and assert
# the contract a coding agent relies on: exit 0, parseable JSON on stdout. This is
# the one command whose failure is silent — agents read a non-zero exit as
# "allow". Called once per implementation, since both ship.
#
# Expects $smoke_env and $smoke_env_dir from smoke_check_bundle (bash scoping).
# $1 is the binary, $2 a label for messages.
smoke_check_ai_hook() {
    local hook_bin="$1"
    local what="$2"
    local hook_payload='{"session_id": "smoke-check", '
    hook_payload+='"transcript_path": "/tmp/claude/transcript.jsonl", '
    hook_payload+='"hook_event_name": "PreToolUse", "tool_name": "Bash", '
    hook_payload+='"tool_input": {"command": "echo smoke check"}}'
    local hook_out
    if ! hook_out=$(printf '%s' "$hook_payload" \
            | env "${smoke_env[@]}" "$hook_bin" secret scan ai-hook 2>/dev/null) ; then
        err "$hook_out"
        rm -rf "$smoke_env_dir"
        die "Bundle smoke check: '$what secret scan ai-hook' exited non-zero. \
Coding agents read a non-zero exit as 'allow', so this would silently disable \
secret scanning on every AI tool call."
    fi
    local py=python
    command -v "$py" > /dev/null 2>&1 || py=python3
    if ! printf '%s' "$hook_out" | "$py" -c "import json,sys; json.load(sys.stdin)" ; then
        err "stdout was: $hook_out"
        rm -rf "$smoke_env_dir"
        die "Bundle smoke check: '$what secret scan ai-hook' did not emit \
valid JSON on stdout. Coding agents cannot parse the verdict."
    fi
}

# Sanity-check the freshly built bundle. Catches "PyInstaller missed a
# dynamic import / data file" regressions before they ship to users, and that
# both of the dispatcher's paths work.
#
# Four layers:
#   1. Both binaries are present, so a half-applied rename in build_dispatcher
#      fails here instead of breaking every command for users.
#   2. Resolve every command in the tree from the bundle (see
#      smoke_check_command_tree), through the dispatcher. Any import failure
#      surfaces as a non-zero exit / ModuleNotFoundError; any delegation failure
#      surfaces on the very first one.
#   3. Run `secret scan ai-hook` twice: through the dispatcher, which answers it
#      natively, and directly against ggshield-py, which is the implementation
#      pip and Homebrew users get. See smoke_check_ai_hook.
#   4. Structurally assert that known package-data files are present in the
#      bundle. PyInstaller does not raise when it silently drops data files,
#      so a `--help` run can succeed while a runtime code path
#      (e.g. sigstore's Verifier.production() loading the TUF roots) fails.
smoke_check_bundle() {
    info "Smoke-checking bundle"
    local bin="$PYINSTALLER_OUTPUT_DIR/ggshield$EXE_EXT"
    local py_bin="$PYINSTALLER_OUTPUT_DIR/ggshield-py$EXE_EXT"
    local exe
    for exe in "$bin" "$py_bin" ; do
        if ! [ -x "$exe" ] ; then
            die "Bundle smoke check: '$exe' not found or not executable"
        fi
    done

    # Layers 1 and 2 run in an isolated config/data/cache directory, with
    # keyring and API key disabled, so the smoke test never touches a
    # developer's or CI runner's credential store and never hits the network.
    local smoke_env_dir
    smoke_env_dir=$(mktemp -d)
    mkdir -p "$smoke_env_dir/home"
    local smoke_env_dir_for_python="$smoke_env_dir"
    if [ "$HUMAN_OS" = Windows ] ; then
        smoke_env_dir_for_python=$(cygpath -w "$smoke_env_dir")
    fi
    local smoke_env=(
        "HOME=$smoke_env_dir_for_python/home"
        "XDG_CONFIG_HOME=$smoke_env_dir_for_python/xdg-config"
        "XDG_DATA_HOME=$smoke_env_dir_for_python/xdg-data"
        "XDG_CACHE_HOME=$smoke_env_dir_for_python/xdg-cache"
        "GG_USER_HOME_DIR=$smoke_env_dir_for_python/home"
        "GG_CONFIG_DIR=$smoke_env_dir_for_python/config"
        "GG_DATA_DIR=$smoke_env_dir_for_python/data"
        "GG_CACHE_DIR=$smoke_env_dir_for_python/cache"
        "GGSHIELD_NO_KEYRING=1"
        "GG_PLAINTEXT_OUTPUT=1"
        # Empty (not unset): ignore any key in the build environment, so the
        # ai-hook check below always takes the unauthenticated path.
        "GITGUARDIAN_API_KEY="
    )

    # `--version` is not the native hook, so it only succeeds if the dispatcher's
    # exec into ggshield-py worked.
    if ! env "${smoke_env[@]}" "$bin" --version > /dev/null 2>&1 ; then
        rm -rf "$smoke_env_dir"
        die "Bundle is broken: 'ggshield --version' exited non-zero. Either the \
PyInstaller bundle is broken, or the dispatcher could not exec '$py_bin'."
    fi

    SMOKE_LEAF_COUNT=0
    smoke_check_command_tree ""
    if [ "$SMOKE_LEAF_COUNT" -lt 20 ] ; then
        rm -rf "$smoke_env_dir"
        die "Bundle smoke check: only $SMOKE_LEAF_COUNT leaf commands found, \
expected at least 20. The command tree walk is probably not parsing \
'ggshield --help' output any more, so it is not checking anything."
    fi
    info "Resolved $SMOKE_LEAF_COUNT leaf commands from the bundle"

    # Layer 3: run the ai-hook end to end, on both implementations.
    # Unauthenticated, so each takes the fail-open path. This covers the
    # unauthenticated verdict only: a module reachable solely after a successful
    # auth (or imported inside a command body) is not exercised here.
    smoke_check_ai_hook "$bin" "ggshield"
    smoke_check_ai_hook "$py_bin" "ggshield-py"
    rm -rf "$smoke_env_dir"

    # Layer 4: structural assertions for known package-data files. Each
    # entry below was added because we caught (or want to prevent) a
    # silent-drop bug. Keep paths in sync with dependency package-data layouts.
    local required_paths=(
        # sigstore TUF bootstrap roots/config — missing these breaks Verifier.production()
        "_internal/sigstore/_store/https%3A%2F%2Ftuf-repo-cdn.sigstore.dev/root.json"
        "_internal/sigstore/_store/https%3A%2F%2Ftuf-repo-cdn.sigstore.dev/trusted_root.json"
        "_internal/sigstore/_store/https%3A%2F%2Ftuf-repo-cdn.sigstore.dev/signing_config.v0.2.json"
    )
    local rel
    for rel in "${required_paths[@]}" ; do
        if ! [ -e "$PYINSTALLER_OUTPUT_DIR/$rel" ] ; then
            die "Bundle smoke check: required file missing: $rel \
(PyInstaller did not bundle it — verify the matching collect/hidden-import \
argument is in step_build)"
        fi
    done

    info "Bundle smoke check OK"
}

step_copy_files() {
    if ! [ -d "$PYINSTALLER_OUTPUT_DIR" ] ; then
        die "$PYINSTALLER_OUTPUT_DIR does not exist"
    fi
    # Both halves: `cp -R` below copies whatever it finds without complaining.
    local exe
    for exe in ggshield ggshield-py ; do
        if ! [ -f "$PYINSTALLER_OUTPUT_DIR/$exe$EXE_EXT" ] ; then
            die "Can't find '$PYINSTALLER_OUTPUT_DIR/$exe$EXE_EXT', maybe 'build' step did not run?"
        fi
    done

    mkdir -p "$BUILD_DIR"
    case "$HUMAN_OS" in
    Linux|Windows)
        local output_dir="$BUILD_DIR/$ARCHIVE_DIR_NAME"
        info "Copying files to $output_dir"
        rm -rf "$output_dir"
        cp -R "$PYINSTALLER_OUTPUT_DIR" "$output_dir"

        info "Generating README.md"
        sed \
            -e "s/@HUMAN_OS@/$HUMAN_OS/" \
            -e "s/@HUMAN_ARCH@/$HUMAN_ARCH/" \
            "$SCRIPT_DIR/README.md.tmpl" \
            > "$output_dir/README.md"
        ;;
    macOS)
        local output_dir="$BUILD_DIR/$ARCHIVE_DIR_NAME/$INSTALL_PREFIX"
        local bin_dir="$BUILD_DIR/$ARCHIVE_DIR_NAME/usr/local/bin"
        info "Copying files to $output_dir"
        rm -rf "$output_dir" "$bin_dir"
        mkdir -p "$(dirname $output_dir)"
        cp -R "$PYINSTALLER_OUTPUT_DIR" "$output_dir"

        info "Creating launcher symlink"
        mkdir -p "$bin_dir"
        ln -s "/$INSTALL_PREFIX/ggshield" "$bin_dir/ggshield"
        ;;
    esac
}

step_sign() {
    if [ "$DO_SIGN" -eq 0 ] ; then
        info "Skipping signing step"
        return
    fi
    case "$HUMAN_OS" in
    macOS)
        macos_sign
        ;;
    Windows)
        windows_sign
        ;;
    *)
        info "Signing not supported on $HUMAN_OS, skipping step"
        ;;
    esac
}

step_test() {
    for args in --help --version ; do
        info "test: running $args"
        "$BUILD_DIR/$ARCHIVE_DIR_NAME/$INSTALL_PREFIX/ggshield${EXE_EXT}" $args
        info "test: running $args: OK"
    done
}

step_functests() {
    PATH=$BUILD_DIR/$ARCHIVE_DIR_NAME/$INSTALL_PREFIX:$PATH pytest -n auto tests/functional
}

create_linux_packages() {
    for format in rpm deb ; do
        info "Building $format"

        PYINSTALLER_OUTPUT_DIR=$PYINSTALLER_OUTPUT_DIR \
        VERSION=$VERSION \
        NFPM_ARCH=$NFPM_ARCH \
            nfpm package \
                --packager $format \
                --config "$SCRIPT_DIR/nfpm.yaml" \
                --target "$DIST_DIR"
    done
}

step_create_archive() {
    local archive_path
    mkdir -p "$DIST_DIR"
    case "$HUMAN_OS" in
    Linux)
        archive_path="$DIST_DIR/$ARCHIVE_DIR_NAME.tar.gz"
        pushd "$BUILD_DIR"
        tar -czf "$archive_path" "$ARCHIVE_DIR_NAME"
        popd
        create_linux_packages
        info "Archive created in $archive_path"
        ;;
    macOS)
        # Create pkg file
        pkg_path="$DIST_DIR/$ARCHIVE_DIR_NAME.pkg"
        pushd "$BUILD_DIR"
        pkgbuild \
            --identifier com.gitguardian.ggshield \
            --version "$VERSION" \
            --root "$BUILD_DIR/$ARCHIVE_DIR_NAME" \
            "$pkg_path"
        popd

        if [ "$DO_SIGN" -eq 1 ] ; then
            macos_sign_file "$pkg_path"
        fi

        # Create tar.gz
        archive_path="$DIST_DIR/$ARCHIVE_DIR_NAME.tar.gz"

        # $BUILD_DIR/$ARCHIVE_DIR_NAME currently contains the following file tree:
        #
        #   $INSTALL_PREFIX
        #     ggshield
        #     internal/
        #   usr/local/bin
        #     ggshield -> /$INSTALL_PREFIX/ggshield
        #
        # We don't want the tar.gz to contain a file tree like this, it must contain a
        # tree similar to the Linux tar.gz, with a root dir called $ARCHIVE_DIR_NAME
        # containing what is currently in $INSTALL_PREFIX. To set this up, we move
        # $INSTALL_PREFIX to a temporary directory and create the tar.gz from there.
        # (we can't use `tar --transform`: it's not supported on macOS)
        rm -rf "$BUILD_DIR/tmp"
        mkdir "$BUILD_DIR/tmp"
        pushd "$BUILD_DIR/tmp"
        mv "$BUILD_DIR/$ARCHIVE_DIR_NAME/$INSTALL_PREFIX" "$ARCHIVE_DIR_NAME"
        tar -czf "$archive_path" "$ARCHIVE_DIR_NAME"
        popd

        info "Archive created in $pkg_path & $archive_path"
        ;;
    Windows)
        create_windows_packages
        test_chocolatey_package
        test_msi_package
        ;;
    esac
}

steps=""
while [ $# -gt 0 ] ; do
    case "$1" in
    -h|--help)
        usage
        ;;
    --sign)
        DO_SIGN=1
        ;;
    --suffix)
        VERSION_SUFFIX="$2"
        shift
        ;;
    -*)
        usage "Unknown option '$1'"
        ;;
    *)
        steps="$steps $1"
        ;;
    esac
    shift
done

cd "$ROOT_DIR"
read_version
init_system_vars
load_os_specific_code
if [ "$DO_SIGN" -eq 1 ] ; then
    add_os_specific_sign_requirements
fi

if [ -z "$steps" ] ; then
    steps=$DEFAULT_STEPS
fi
info "Steps: $steps"

for step in $steps ; do
    info "step $step"
    "step_$step"
done
info "Success!"
