From 946918579413996845b77e146aa8eb3c6f40f7ab Mon Sep 17 00:00:00 2001 From: Taiki Endo Date: Fri, 17 Jan 2025 16:44:56 +0900 Subject: [PATCH] Format shell scripts with indent_size = 2 to match scripts in CI config --- .editorconfig | 2 +- main.sh | 1390 ++++++++++++++++++++--------------------- tools/ci/manifest.sh | 46 +- tools/ci/tool-list.sh | 254 ++++---- tools/manifest.sh | 10 +- tools/publish.sh | 138 ++-- tools/tidy.sh | 746 +++++++++++----------- 7 files changed, 1293 insertions(+), 1293 deletions(-) diff --git a/.editorconfig b/.editorconfig index 49a662440..3aa2b3802 100644 --- a/.editorconfig +++ b/.editorconfig @@ -11,7 +11,7 @@ indent_style = space insert_final_newline = true trim_trailing_whitespace = true -[*.{json,md,rb,yml,yaml}] +[*.{json,md,rb,sh,yml,yaml}] indent_size = 2 [*.{js,yml,yaml}] diff --git a/main.sh b/main.sh index 28487e4d8..e3e087b96 100755 --- a/main.sh +++ b/main.sh @@ -4,405 +4,405 @@ set -eEuo pipefail IFS=$'\n\t' rx() { - local cmd="$1" - shift - ( - set -x - "${cmd}" "$@" - ) + local cmd="$1" + shift + ( + set -x + "${cmd}" "$@" + ) } retry() { - for i in {1..10}; do - if "$@"; then - return 0 - else - sleep "${i}" - fi - done - "$@" + for i in {1..10}; do + if "$@"; then + return 0 + else + sleep "${i}" + fi + done + "$@" } bail() { - echo "::error::$*" - exit 1 + echo "::error::$*" + exit 1 } warn() { - echo "::warning::$*" + echo "::warning::$*" } info() { - echo "info: $*" + echo "info: $*" } _sudo() { - if type -P sudo &>/dev/null; then - sudo "$@" - else - "$@" - fi + if type -P sudo &>/dev/null; then + sudo "$@" + else + "$@" + fi } download_and_checksum() { - local url="$1" - local checksum="$2" - if [[ -z "${enable_checksum}" ]]; then - checksum="" - fi - info "downloading ${url}" - retry curl --proto '=https' --tlsv1.2 -fsSL --retry 10 "${url}" -o tmp - if [[ -n "${checksum}" ]]; then - info "verifying sha256 checksum for $(basename "${url}")" - if type -P sha256sum &>/dev/null; then - sha256sum -c - >/dev/null <<<"${checksum} *tmp" - elif type -P shasum &>/dev/null; then - # GitHub-hosted macOS runner does not install GNU Coreutils by default. - # https://github.com/actions/runner-images/issues/90 - shasum -a 256 -c - >/dev/null <<<"${checksum} *tmp" - else - bail "checksum requires 'sha256sum' or 'shasum' command; consider installing one of them or setting 'checksum' input option to 'false'" - fi + local url="$1" + local checksum="$2" + if [[ -z "${enable_checksum}" ]]; then + checksum="" + fi + info "downloading ${url}" + retry curl --proto '=https' --tlsv1.2 -fsSL --retry 10 "${url}" -o tmp + if [[ -n "${checksum}" ]]; then + info "verifying sha256 checksum for $(basename "${url}")" + if type -P sha256sum &>/dev/null; then + sha256sum -c - >/dev/null <<<"${checksum} *tmp" + elif type -P shasum &>/dev/null; then + # GitHub-hosted macOS runner does not install GNU Coreutils by default. + # https://github.com/actions/runner-images/issues/90 + shasum -a 256 -c - >/dev/null <<<"${checksum} *tmp" + else + bail "checksum requires 'sha256sum' or 'shasum' command; consider installing one of them or setting 'checksum' input option to 'false'" fi + fi } download_and_extract() { - local url="$1" - shift - local checksum="$1" - shift - local bin_dir="$1" - shift - local bin_in_archive=("$@") # path to bin in archive - if [[ "${bin_dir}" == "${install_action_dir}/bin" ]]; then - init_install_action_bin_dir - fi + local url="$1" + shift + local checksum="$1" + shift + local bin_dir="$1" + shift + local bin_in_archive=("$@") # path to bin in archive + if [[ "${bin_dir}" == "${install_action_dir}/bin" ]]; then + init_install_action_bin_dir + fi - installed_bin=() - local tmp - case "${tool}" in - # xbuild's binary name is "x", as opposed to the usual crate name - xbuild) installed_bin=("${bin_dir}/x${exe}") ;; - # editorconfig-checker's binary name is renamed below - editorconfig-checker) installed_bin=("${bin_dir}/${tool}${exe}") ;; - *) - for tmp in "${bin_in_archive[@]}"; do - installed_bin+=("${bin_dir}/$(basename "${tmp}")") - done - ;; - esac + installed_bin=() + local tmp + case "${tool}" in + # xbuild's binary name is "x", as opposed to the usual crate name + xbuild) installed_bin=("${bin_dir}/x${exe}") ;; + # editorconfig-checker's binary name is renamed below + editorconfig-checker) installed_bin=("${bin_dir}/${tool}${exe}") ;; + *) + for tmp in "${bin_in_archive[@]}"; do + installed_bin+=("${bin_dir}/$(basename "${tmp}")") + done + ;; + esac - local tar_args=() - case "${url}" in - *.tar.gz | *.tgz) - tar_args+=("xzf") - if ! type -P gzip &>/dev/null; then - case "${base_distro}" in - debian | fedora | suse | arch | alpine) - echo "::group::Install packages required for installation (gzip)" - sys_install gzip - echo "::endgroup::" - ;; - esac - fi + local tar_args=() + case "${url}" in + *.tar.gz | *.tgz) + tar_args+=("xzf") + if ! type -P gzip &>/dev/null; then + case "${base_distro}" in + debian | fedora | suse | arch | alpine) + echo "::group::Install packages required for installation (gzip)" + sys_install gzip + echo "::endgroup::" ;; - *.tar.bz2 | *.tbz2) - tar_args+=("xjf") - if ! type -P bzip2 &>/dev/null; then - case "${base_distro}" in - debian | fedora | suse | arch | alpine) - echo "::group::Install packages required for installation (bzip2)" - sys_install bzip2 - echo "::endgroup::" - ;; - esac - fi + esac + fi + ;; + *.tar.bz2 | *.tbz2) + tar_args+=("xjf") + if ! type -P bzip2 &>/dev/null; then + case "${base_distro}" in + debian | fedora | suse | arch | alpine) + echo "::group::Install packages required for installation (bzip2)" + sys_install bzip2 + echo "::endgroup::" ;; - *.tar.xz | *.txz) - tar_args+=("xJf") - if ! type -P xz &>/dev/null; then - case "${base_distro}" in - debian) - echo "::group::Install packages required for installation (xz-utils)" - sys_install xz-utils - echo "::endgroup::" - ;; - fedora | suse | arch | alpine) - echo "::group::Install packages required for installation (xz)" - sys_install xz - echo "::endgroup::" - ;; - esac - fi + esac + fi + ;; + *.tar.xz | *.txz) + tar_args+=("xJf") + if ! type -P xz &>/dev/null; then + case "${base_distro}" in + debian) + echo "::group::Install packages required for installation (xz-utils)" + sys_install xz-utils + echo "::endgroup::" ;; - *.zip) - if ! type -P unzip &>/dev/null; then - case "${base_distro}" in - debian | fedora | suse | arch | alpine) - echo "::group::Install packages required for installation (unzip)" - sys_install unzip - echo "::endgroup::" - ;; - esac - fi + fedora | suse | arch | alpine) + echo "::group::Install packages required for installation (xz)" + sys_install xz + echo "::endgroup::" ;; - esac - - mkdir -p "${tmp_dir}" - ( - cd "${tmp_dir}" - download_and_checksum "${url}" "${checksum}" - if [[ ${#tar_args[@]} -gt 0 ]]; then - tar_args+=("tmp") - tar "${tar_args[@]}" - for tmp in "${bin_in_archive[@]}"; do - case "${tool}" in - editorconfig-checker) mv "${tmp}" "${bin_dir}/${tool}${exe}" ;; - *) mv "${tmp}" "${bin_dir}/" ;; - esac - done - else - case "${url}" in - *.zip) - unzip -q tmp "${bin_in_archive#\./}" - for tmp in "${bin_in_archive[@]}"; do - case "${tool}" in - editorconfig-checker) mv "${tmp}" "${bin_dir}/${tool}${exe}" ;; - *) mv "${tmp}" "${bin_dir}/" ;; - esac - done - ;; - *) - for tmp in "${installed_bin[@]}"; do - mv tmp "${tmp}" - done - ;; - esac - fi - ) - rm -rf "${tmp_dir}" - - case "${host_os}" in - linux | macos) - for tmp in "${installed_bin[@]}"; do - if [[ ! -x "${tmp}" ]]; then - chmod +x "${tmp}" - fi - done + esac + fi + ;; + *.zip) + if ! type -P unzip &>/dev/null; then + case "${base_distro}" in + debian | fedora | suse | arch | alpine) + echo "::group::Install packages required for installation (unzip)" + sys_install unzip + echo "::endgroup::" ;; - esac -} -read_manifest() { - local tool="$1" - local version="$2" - local manifest - rust_crate=$(call_jq -r ".rust_crate" "${manifest_dir}/${tool}.json") - manifest=$(call_jq -r ".[\"${version}\"]" "${manifest_dir}/${tool}.json") - if [[ "${manifest}" == "null" ]]; then - download_info="null" - return 0 - fi - exact_version=$(call_jq <<<"${manifest}" -r '.version') - if [[ "${exact_version}" == "null" ]]; then - exact_version="${version}" + esac + fi + ;; + esac + + mkdir -p "${tmp_dir}" + ( + cd "${tmp_dir}" + download_and_checksum "${url}" "${checksum}" + if [[ ${#tar_args[@]} -gt 0 ]]; then + tar_args+=("tmp") + tar "${tar_args[@]}" + for tmp in "${bin_in_archive[@]}"; do + case "${tool}" in + editorconfig-checker) mv "${tmp}" "${bin_dir}/${tool}${exe}" ;; + *) mv "${tmp}" "${bin_dir}/" ;; + esac + done else - manifest=$(call_jq -r ".[\"${exact_version}\"]" "${manifest_dir}/${tool}.json") - if [[ "${rust_crate}" != "null" ]]; then - # TODO: don't hardcode tool name and use 'immediate_yank_reflection' field in base manifest. + case "${url}" in + *.zip) + unzip -q tmp "${bin_in_archive#\./}" + for tmp in "${bin_in_archive[@]}"; do case "${tool}" in - cargo-nextest | nextest) - crate_info=$(retry curl --proto '=https' --tlsv1.2 -fsSL --retry 10 "https://crates.io/api/v1/crates/${rust_crate}") - while true; do - yanked=$(jq <<<"${crate_info}" -r ".versions[] | select(.num == \"${exact_version}\") | .yanked") - if [[ "${yanked}" != "true" ]]; then - break - fi - previous_stable_version=$(jq <<<"${manifest}" -r '.previous_stable_version') - if [[ "${previous_stable_version}" == "null" ]]; then - break - fi - info "${tool}@${exact_version} is yanked; downgrade to ${previous_stable_version}" - exact_version="${previous_stable_version}" - manifest=$(jq -r ".[\"${exact_version}\"]" "${manifest_dir}/${tool}.json") - done - ;; + editorconfig-checker) mv "${tmp}" "${bin_dir}/${tool}${exe}" ;; + *) mv "${tmp}" "${bin_dir}/" ;; esac - fi + done + ;; + *) + for tmp in "${installed_bin[@]}"; do + mv tmp "${tmp}" + done + ;; + esac fi + ) + rm -rf "${tmp_dir}" - case "${host_os}" in - linux) - # Static-linked binaries compiled for linux-musl will also work on linux-gnu systems and are - # usually preferred over linux-gnu binaries because they can avoid glibc version issues. - # (rustc enables statically linking for linux-musl by default, except for mips.) - host_platform="${host_arch}_linux_musl" - download_info=$(call_jq <<<"${manifest}" -r ".${host_platform}") - if [[ "${download_info}" == "null" ]]; then - # Even if host_env is musl, we won't issue an error here because it seems that in - # some cases linux-gnu binaries will work on linux-musl hosts. - # https://wiki.alpinelinux.org/wiki/Running_glibc_programs - # TODO: However, a warning may make sense. - host_platform="${host_arch}_linux_gnu" - download_info=$(call_jq <<<"${manifest}" -r ".${host_platform}") - elif [[ "${host_env}" == "gnu" ]]; then - # TODO: don't hardcode tool name and use 'prefer_linux_gnu' field in base manifest. - case "${tool}" in - cargo-nextest | nextest) - # TODO: don't hardcode required glibc version - required_glibc_version=2.27 - higher_glibc_version=$(sort <<<"${required_glibc_version}"$'\n'"${host_glibc_version}" -Vu | tail -1) - if [[ "${higher_glibc_version}" == "${host_glibc_version}" ]]; then - # musl build of nextest is slow, so use glibc build if host_env is gnu. - # https://github.com/taiki-e/install-action/issues/13 - host_platform="${host_arch}_linux_gnu" - download_info=$(jq <<<"${manifest}" -r ".${host_platform}") - fi - ;; - esac + case "${host_os}" in + linux | macos) + for tmp in "${installed_bin[@]}"; do + if [[ ! -x "${tmp}" ]]; then + chmod +x "${tmp}" + fi + done + ;; + esac +} +read_manifest() { + local tool="$1" + local version="$2" + local manifest + rust_crate=$(call_jq -r ".rust_crate" "${manifest_dir}/${tool}.json") + manifest=$(call_jq -r ".[\"${version}\"]" "${manifest_dir}/${tool}.json") + if [[ "${manifest}" == "null" ]]; then + download_info="null" + return 0 + fi + exact_version=$(call_jq <<<"${manifest}" -r '.version') + if [[ "${exact_version}" == "null" ]]; then + exact_version="${version}" + else + manifest=$(call_jq -r ".[\"${exact_version}\"]" "${manifest_dir}/${tool}.json") + if [[ "${rust_crate}" != "null" ]]; then + # TODO: don't hardcode tool name and use 'immediate_yank_reflection' field in base manifest. + case "${tool}" in + cargo-nextest | nextest) + crate_info=$(retry curl --proto '=https' --tlsv1.2 -fsSL --retry 10 "https://crates.io/api/v1/crates/${rust_crate}") + while true; do + yanked=$(jq <<<"${crate_info}" -r ".versions[] | select(.num == \"${exact_version}\") | .yanked") + if [[ "${yanked}" != "true" ]]; then + break fi - ;; - macos | windows) - # Binaries compiled for x86_64 macOS will usually also work on AArch64 macOS. - # Binaries compiled for x86_64 Windows will usually also work on AArch64 Windows 11+. - host_platform="${host_arch}_${host_os}" - download_info=$(call_jq <<<"${manifest}" -r ".${host_platform}") - if [[ "${download_info}" == "null" ]] && [[ "${host_arch}" != "x86_64" ]]; then - host_platform="x86_64_${host_os}" - download_info=$(call_jq <<<"${manifest}" -r ".${host_platform}") + previous_stable_version=$(jq <<<"${manifest}" -r '.previous_stable_version') + if [[ "${previous_stable_version}" == "null" ]]; then + break + fi + info "${tool}@${exact_version} is yanked; downgrade to ${previous_stable_version}" + exact_version="${previous_stable_version}" + manifest=$(jq -r ".[\"${exact_version}\"]" "${manifest_dir}/${tool}.json") + done + ;; + esac + fi + fi + + case "${host_os}" in + linux) + # Static-linked binaries compiled for linux-musl will also work on linux-gnu systems and are + # usually preferred over linux-gnu binaries because they can avoid glibc version issues. + # (rustc enables statically linking for linux-musl by default, except for mips.) + host_platform="${host_arch}_linux_musl" + download_info=$(call_jq <<<"${manifest}" -r ".${host_platform}") + if [[ "${download_info}" == "null" ]]; then + # Even if host_env is musl, we won't issue an error here because it seems that in + # some cases linux-gnu binaries will work on linux-musl hosts. + # https://wiki.alpinelinux.org/wiki/Running_glibc_programs + # TODO: However, a warning may make sense. + host_platform="${host_arch}_linux_gnu" + download_info=$(call_jq <<<"${manifest}" -r ".${host_platform}") + elif [[ "${host_env}" == "gnu" ]]; then + # TODO: don't hardcode tool name and use 'prefer_linux_gnu' field in base manifest. + case "${tool}" in + cargo-nextest | nextest) + # TODO: don't hardcode required glibc version + required_glibc_version=2.27 + higher_glibc_version=$(sort <<<"${required_glibc_version}"$'\n'"${host_glibc_version}" -Vu | tail -1) + if [[ "${higher_glibc_version}" == "${host_glibc_version}" ]]; then + # musl build of nextest is slow, so use glibc build if host_env is gnu. + # https://github.com/taiki-e/install-action/issues/13 + host_platform="${host_arch}_linux_gnu" + download_info=$(jq <<<"${manifest}" -r ".${host_platform}") fi ;; - *) bail "unsupported OS type '${host_os}' for ${tool}" ;; - esac + esac + fi + ;; + macos | windows) + # Binaries compiled for x86_64 macOS will usually also work on AArch64 macOS. + # Binaries compiled for x86_64 Windows will usually also work on AArch64 Windows 11+. + host_platform="${host_arch}_${host_os}" + download_info=$(call_jq <<<"${manifest}" -r ".${host_platform}") + if [[ "${download_info}" == "null" ]] && [[ "${host_arch}" != "x86_64" ]]; then + host_platform="x86_64_${host_os}" + download_info=$(call_jq <<<"${manifest}" -r ".${host_platform}") + fi + ;; + *) bail "unsupported OS type '${host_os}' for ${tool}" ;; + esac } read_download_info() { - local tool="$1" - local version="$2" - if [[ "${download_info}" == "null" ]]; then - bail "${tool}@${version} for '${host_os}' is not supported" - fi - checksum=$(call_jq <<<"${download_info}" -r '.checksum') - url=$(call_jq <<<"${download_info}" -r '.url') - local tmp - bin_in_archive=() - if [[ "${url}" == "null" ]]; then - local template - template=$(call_jq -r ".template.${host_platform}" "${manifest_dir}/${tool}.json") - url=$(call_jq <<<"${template}" -r '.url') - url="${url//\$\{version\}/${exact_version}}" - tmp=$(call_jq <<<"${template}" -r '.bin' | sed -E "s/\\$\\{version\\}/${exact_version}/g") - if [[ "${tmp}" == *"["* ]]; then - # shellcheck disable=SC2207 - bin_in_archive=($(call_jq <<<"${template}" -r '.bin[]' | sed -E "s/\\$\\{version\\}/${exact_version}/g")) - fi - else - tmp=$(call_jq <<<"${download_info}" -r '.bin') - if [[ "${tmp}" == *"["* ]]; then - # shellcheck disable=SC2207 - bin_in_archive=($(call_jq <<<"${download_info}" -r '.bin[]')) - fi + local tool="$1" + local version="$2" + if [[ "${download_info}" == "null" ]]; then + bail "${tool}@${version} for '${host_os}' is not supported" + fi + checksum=$(call_jq <<<"${download_info}" -r '.checksum') + url=$(call_jq <<<"${download_info}" -r '.url') + local tmp + bin_in_archive=() + if [[ "${url}" == "null" ]]; then + local template + template=$(call_jq -r ".template.${host_platform}" "${manifest_dir}/${tool}.json") + url=$(call_jq <<<"${template}" -r '.url') + url="${url//\$\{version\}/${exact_version}}" + tmp=$(call_jq <<<"${template}" -r '.bin' | sed -E "s/\\$\\{version\\}/${exact_version}/g") + if [[ "${tmp}" == *"["* ]]; then + # shellcheck disable=SC2207 + bin_in_archive=($(call_jq <<<"${template}" -r '.bin[]' | sed -E "s/\\$\\{version\\}/${exact_version}/g")) fi - if [[ ${#bin_in_archive[@]} -eq 0 ]]; then - if [[ "${tmp}" == "null" ]]; then - bin_in_archive=("${tool}${exe}") - else - bin_in_archive=("${tmp}") - fi + else + tmp=$(call_jq <<<"${download_info}" -r '.bin') + if [[ "${tmp}" == *"["* ]]; then + # shellcheck disable=SC2207 + bin_in_archive=($(call_jq <<<"${download_info}" -r '.bin[]')) fi - if [[ "${rust_crate}" == "null" ]]; then - # Moving files to /usr/local/bin requires sudo in some environments, so do not use it: https://github.com/taiki-e/install-action/issues/543 - bin_dir="${install_action_dir}/bin" + fi + if [[ ${#bin_in_archive[@]} -eq 0 ]]; then + if [[ "${tmp}" == "null" ]]; then + bin_in_archive=("${tool}${exe}") else - bin_dir="${cargo_bin}" + bin_in_archive=("${tmp}") fi + fi + if [[ "${rust_crate}" == "null" ]]; then + # Moving files to /usr/local/bin requires sudo in some environments, so do not use it: https://github.com/taiki-e/install-action/issues/543 + bin_dir="${install_action_dir}/bin" + else + bin_dir="${cargo_bin}" + fi } download_from_manifest() { - read_manifest "$@" - download_from_download_info "$@" + read_manifest "$@" + download_from_download_info "$@" } download_from_download_info() { - read_download_info "$@" - download_and_extract "${url}" "${checksum}" "${bin_dir}" "${bin_in_archive[@]}" + read_download_info "$@" + download_and_extract "${url}" "${checksum}" "${bin_dir}" "${bin_in_archive[@]}" } install_cargo_binstall() { - local binstall_version - binstall_version=$(call_jq -r '.latest.version' "${manifest_dir}/cargo-binstall.json") - local install_binstall='1' - _binstall_version=$("cargo-binstall${exe}" binstall -V 2>/dev/null || echo "") - if [[ -n "${_binstall_version}" ]]; then - if [[ "${_binstall_version}" == "${binstall_version}" ]]; then - info "cargo-binstall already installed at ${cargo_bin}/cargo-binstall${exe}" - install_binstall='' - else - info "cargo-binstall already installed at ${cargo_bin}/cargo-binstall${exe}, but is not compatible version with install-action, upgrading" - rm "${cargo_bin}/cargo-binstall${exe}" - fi + local binstall_version + binstall_version=$(call_jq -r '.latest.version' "${manifest_dir}/cargo-binstall.json") + local install_binstall='1' + _binstall_version=$("cargo-binstall${exe}" binstall -V 2>/dev/null || echo "") + if [[ -n "${_binstall_version}" ]]; then + if [[ "${_binstall_version}" == "${binstall_version}" ]]; then + info "cargo-binstall already installed at ${cargo_bin}/cargo-binstall${exe}" + install_binstall='' + else + info "cargo-binstall already installed at ${cargo_bin}/cargo-binstall${exe}, but is not compatible version with install-action, upgrading" + rm "${cargo_bin}/cargo-binstall${exe}" fi + fi - if [[ -n "${install_binstall}" ]]; then - info "installing cargo-binstall@latest (${binstall_version})" - download_from_manifest "cargo-binstall" "latest" - installed_at=$(type -P "cargo-binstall${exe}" || echo "") - if [[ -n "${installed_at}" ]]; then - info "cargo-binstall installed at ${installed_at}" - else - warn "cargo-binstall should be installed at ${bin_dir:-}/cargo-binstall${exe}; but cargo-binstall${exe} not found in path" - fi - rx "cargo-binstall${exe}" binstall -V + if [[ -n "${install_binstall}" ]]; then + info "installing cargo-binstall@latest (${binstall_version})" + download_from_manifest "cargo-binstall" "latest" + installed_at=$(type -P "cargo-binstall${exe}" || echo "") + if [[ -n "${installed_at}" ]]; then + info "cargo-binstall installed at ${installed_at}" + else + warn "cargo-binstall should be installed at ${bin_dir:-}/cargo-binstall${exe}; but cargo-binstall${exe} not found in path" fi + rx "cargo-binstall${exe}" binstall -V + fi } apt_update() { - retry _sudo apt-get -o Acquire::Retries=10 -qq update - apt_updated=1 + retry _sudo apt-get -o Acquire::Retries=10 -qq update + apt_updated=1 } apt_install() { - if [[ -z "${apt_updated:-}" ]]; then - apt_update - fi - retry _sudo apt-get -o Acquire::Retries=10 -o Dpkg::Use-Pty=0 install -y --no-install-recommends "$@" + if [[ -z "${apt_updated:-}" ]]; then + apt_update + fi + retry _sudo apt-get -o Acquire::Retries=10 -o Dpkg::Use-Pty=0 install -y --no-install-recommends "$@" } apt_remove() { - _sudo apt-get -qq -o Dpkg::Use-Pty=0 remove -y "$@" + _sudo apt-get -qq -o Dpkg::Use-Pty=0 remove -y "$@" } snap_install() { - retry _sudo snap install "$@" + retry _sudo snap install "$@" } dnf_install() { - retry _sudo "${dnf}" install -y "$@" + retry _sudo "${dnf}" install -y "$@" } zypper_install() { - retry _sudo zypper install -y "$@" + retry _sudo zypper install -y "$@" } pacman_install() { - retry _sudo pacman -Sy --noconfirm "$@" + retry _sudo pacman -Sy --noconfirm "$@" } apk_install() { - if type -P sudo &>/dev/null; then - retry sudo apk --no-cache add "$@" - elif type -P doas &>/dev/null; then - retry doas apk --no-cache add "$@" - else - retry apk --no-cache add "$@" - fi + if type -P sudo &>/dev/null; then + retry sudo apk --no-cache add "$@" + elif type -P doas &>/dev/null; then + retry doas apk --no-cache add "$@" + else + retry apk --no-cache add "$@" + fi } sys_install() { - case "${base_distro}" in - debian) apt_install "$@" ;; - fedora) dnf_install "$@" ;; - suse) zypper_install "$@" ;; - arch) pacman_install "$@" ;; - alpine) apk_install "$@" ;; - esac + case "${base_distro}" in + debian) apt_install "$@" ;; + fedora) dnf_install "$@" ;; + suse) zypper_install "$@" ;; + arch) pacman_install "$@" ;; + alpine) apk_install "$@" ;; + esac } init_install_action_bin_dir() { - if [[ -z "${init_install_action_bin:-}" ]]; then - init_install_action_bin=1 - mkdir -p "${bin_dir}" - export PATH="${PATH}:${bin_dir}" - local _bin_dir - _bin_dir=$(canonicalize_windows_path "${bin_dir}") - # TODO: avoid this when already added - info "adding '${_bin_dir}' to PATH" - echo "${_bin_dir}" >>"${GITHUB_PATH}" - fi + if [[ -z "${init_install_action_bin:-}" ]]; then + init_install_action_bin=1 + mkdir -p "${bin_dir}" + export PATH="${PATH}:${bin_dir}" + local _bin_dir + _bin_dir=$(canonicalize_windows_path "${bin_dir}") + # TODO: avoid this when already added + info "adding '${_bin_dir}' to PATH" + echo "${_bin_dir}" >>"${GITHUB_PATH}" + fi } canonicalize_windows_path() { - case "${host_os}" in - windows) sed <<<"$1" 's/^\/cygdrive\//\//; s/^\/c\//C:\\/; s/\//\\/g' ;; - *) echo "$1" ;; - esac + case "${host_os}" in + windows) sed <<<"$1" 's/^\/cygdrive\//\//; s/^\/c\//C:\\/; s/\//\\/g' ;; + *) echo "$1" ;; + esac } # cargo-binstall may call `cargo install` on their fallback: https://github.com/taiki-e/install-action/pull/54#issuecomment-1383140833 @@ -411,7 +411,7 @@ export CARGO_NET_RETRY=10 export RUSTUP_MAX_RETRIES=10 if [[ $# -gt 0 ]]; then - bail "invalid argument '$1'" + bail "invalid argument '$1'" fi export DEBIAN_FRONTEND=noninteractive @@ -421,117 +421,117 @@ manifest_dir="$(dirname "$0")/manifests" tool="${INPUT_TOOL:-}" tools=() if [[ -n "${tool}" ]]; then - while read -rd,; do - t="${REPLY# *}" - tools+=("${t%* }") - done <<<"${tool}," + while read -rd,; do + t="${REPLY# *}" + tools+=("${t%* }") + done <<<"${tool}," fi if [[ ${#tools[@]} -eq 0 ]]; then - warn "no tool specified; this could be caused by a dependabot bug where @ tags on this action are replaced by @ tags" - # Exit with 0 for backward compatibility, we want to reject it in the next major release. - exit 0 + warn "no tool specified; this could be caused by a dependabot bug where @ tags on this action are replaced by @ tags" + # Exit with 0 for backward compatibility, we want to reject it in the next major release. + exit 0 fi enable_checksum="${INPUT_CHECKSUM:-}" case "${enable_checksum}" in - true) ;; - false) enable_checksum='' ;; - *) bail "'checksum' input option must be 'true' or 'false': '${enable_checksum}'" ;; + true) ;; + false) enable_checksum='' ;; + *) bail "'checksum' input option must be 'true' or 'false': '${enable_checksum}'" ;; esac fallback="${INPUT_FALLBACK:-}" case "${fallback}" in - none | cargo-binstall) ;; - *) bail "'fallback' input option must be 'none' or 'cargo-binstall': '${fallback}'" ;; + none | cargo-binstall) ;; + *) bail "'fallback' input option must be 'none' or 'cargo-binstall': '${fallback}'" ;; esac # Refs: https://github.com/rust-lang/rustup/blob/HEAD/rustup-init.sh base_distro="" exe="" case "$(uname -s)" in - Linux) - host_os=linux - ldd_version=$(ldd --version 2>&1 || true) - if grep <<<"${ldd_version}" -q 'musl'; then - host_env="musl" - else - host_env="gnu" - host_glibc_version=$(grep <<<"${ldd_version}" -E "GLIBC|GNU libc" | sed "s/.* //g") - fi - if [[ -e /etc/os-release ]]; then - if grep -Eq '^ID_LIKE=' /etc/os-release; then - base_distro=$(grep -E '^ID_LIKE=' /etc/os-release | cut -d= -f2) - case "${base_distro}" in - *debian*) base_distro=debian ;; - *fedora*) base_distro=fedora ;; - *suse*) base_distro=suse ;; - *arch*) base_distro=arch ;; - *alpine*) base_distro=alpine ;; - esac - else - base_distro=$(grep -E '^ID=' /etc/os-release | cut -d= -f2) - fi - elif [[ -e /etc/redhat-release ]]; then - base_distro=fedora - fi + Linux) + host_os=linux + ldd_version=$(ldd --version 2>&1 || true) + if grep <<<"${ldd_version}" -q 'musl'; then + host_env="musl" + else + host_env="gnu" + host_glibc_version=$(grep <<<"${ldd_version}" -E "GLIBC|GNU libc" | sed "s/.* //g") + fi + if [[ -e /etc/os-release ]]; then + if grep -Eq '^ID_LIKE=' /etc/os-release; then + base_distro=$(grep -E '^ID_LIKE=' /etc/os-release | cut -d= -f2) case "${base_distro}" in - fedora) - dnf=dnf - if ! type -P dnf &>/dev/null; then - if type -P microdnf &>/dev/null; then - # fedora-based distributions have "minimal" images that - # use microdnf instead of dnf. - dnf=microdnf - else - # If neither dnf nor microdnf is available, it is - # probably an RHEL7-based distribution that does not - # have dnf installed by default. - dnf=yum - fi - fi - ;; + *debian*) base_distro=debian ;; + *fedora*) base_distro=fedora ;; + *suse*) base_distro=suse ;; + *arch*) base_distro=arch ;; + *alpine*) base_distro=alpine ;; esac + else + base_distro=$(grep -E '^ID=' /etc/os-release | cut -d= -f2) + fi + elif [[ -e /etc/redhat-release ]]; then + base_distro=fedora + fi + case "${base_distro}" in + fedora) + dnf=dnf + if ! type -P dnf &>/dev/null; then + if type -P microdnf &>/dev/null; then + # fedora-based distributions have "minimal" images that + # use microdnf instead of dnf. + dnf=microdnf + else + # If neither dnf nor microdnf is available, it is + # probably an RHEL7-based distribution that does not + # have dnf installed by default. + dnf=yum + fi + fi ;; - Darwin) host_os=macos ;; - MINGW* | MSYS* | CYGWIN* | Windows_NT) - host_os=windows - exe=".exe" - ;; - *) bail "unrecognized OS type '$(uname -s)'" ;; + esac + ;; + Darwin) host_os=macos ;; + MINGW* | MSYS* | CYGWIN* | Windows_NT) + host_os=windows + exe=".exe" + ;; + *) bail "unrecognized OS type '$(uname -s)'" ;; esac case "$(uname -m)" in - aarch64 | arm64) host_arch="aarch64" ;; - xscale | arm | armv*l) - # Ignore Arm for now, as we need to consider the version and whether hard-float is supported. - # https://github.com/rust-lang/rustup/pull/593 - # https://github.com/cross-rs/cross/pull/1018 - # Does it seem only armv7l+ is supported? - # https://github.com/actions/runner/blob/v2.321.0/src/Misc/externals.sh#L178 - # https://github.com/actions/runner/issues/688 - bail "32-bit Arm runner is not supported yet by this action; if you need support for this platform, please submit an issue at " - ;; - # GitHub Actions Runner supports Linux (x86_64, AArch64, Arm), Windows (x86_64, AArch64), - # and macOS (x86_64, AArch64). - # https://github.com/actions/runner/blob/v2.321.0/.github/workflows/build.yml#L21 - # https://docs.github.com/en/actions/hosting-your-own-runners/about-self-hosted-runners#supported-architectures-and-operating-systems-for-self-hosted-runners - # So we can assume x86_64 unless it is AArch64 or Arm. - *) host_arch="x86_64" ;; + aarch64 | arm64) host_arch="aarch64" ;; + xscale | arm | armv*l) + # Ignore Arm for now, as we need to consider the version and whether hard-float is supported. + # https://github.com/rust-lang/rustup/pull/593 + # https://github.com/cross-rs/cross/pull/1018 + # Does it seem only armv7l+ is supported? + # https://github.com/actions/runner/blob/v2.321.0/src/Misc/externals.sh#L178 + # https://github.com/actions/runner/issues/688 + bail "32-bit Arm runner is not supported yet by this action; if you need support for this platform, please submit an issue at " + ;; + # GitHub Actions Runner supports Linux (x86_64, AArch64, Arm), Windows (x86_64, AArch64), + # and macOS (x86_64, AArch64). + # https://github.com/actions/runner/blob/v2.321.0/.github/workflows/build.yml#L21 + # https://docs.github.com/en/actions/hosting-your-own-runners/about-self-hosted-runners#supported-architectures-and-operating-systems-for-self-hosted-runners + # So we can assume x86_64 unless it is AArch64 or Arm. + *) host_arch="x86_64" ;; esac info "host platform: ${host_arch}_${host_os}" home="${HOME}" if [[ "${host_os}" == "windows" ]]; then - if [[ "${home}" == "/home/"* ]]; then - if [[ -d "${home/\/home\//\/c\/Users\/}" ]]; then - # MSYS2 https://github.com/taiki-e/install-action/pull/518#issuecomment-2160736760 - home="${home/\/home\//\/c\/Users\/}" - elif [[ -d "${home/\/home\//\/cygdrive\/c\/Users\/}" ]]; then - # Cygwin https://github.com/taiki-e/install-action/issues/224#issuecomment-1720196288 - home="${home/\/home\//\/cygdrive\/c\/Users\/}" - else - warn "\$HOME starting /home/ (${home}) on Windows bash is usually fake path, this may cause installation issue" - fi + if [[ "${home}" == "/home/"* ]]; then + if [[ -d "${home/\/home\//\/c\/Users\/}" ]]; then + # MSYS2 https://github.com/taiki-e/install-action/pull/518#issuecomment-2160736760 + home="${home/\/home\//\/c\/Users\/}" + elif [[ -d "${home/\/home\//\/cygdrive\/c\/Users\/}" ]]; then + # Cygwin https://github.com/taiki-e/install-action/issues/224#issuecomment-1720196288 + home="${home/\/home\//\/cygdrive\/c\/Users\/}" + else + warn "\$HOME starting /home/ (${home}) on Windows bash is usually fake path, this may cause installation issue" fi + fi fi install_action_dir="${home}/.install-action" tmp_dir="${install_action_dir}/tmp" @@ -540,306 +540,306 @@ cargo_bin="${CARGO_HOME:-"${home}/.cargo"}/bin" # is used ($CARGO_HOME/bin is most likely not included in the PATH), fallback to # $install_action_dir/bin. if [[ "${host_os}" == "windows" ]]; then - if type -P cargo &>/dev/null; then - info "cargo is located at $(type -P cargo)" - cargo_bin=$(dirname "$(type -P cargo)") - else - cargo_bin="${install_action_dir}/bin" - fi -elif [[ ! -e "${cargo_bin}" ]] || [[ "$(type -P cargo || true)" != "${cargo_bin}/cargo"* ]]; then - if type -P cargo &>/dev/null; then - info "cargo is located at $(type -P cargo)" - fi - # Moving files to /usr/local/bin requires sudo in some environments, so do not use it: https://github.com/taiki-e/install-action/issues/543 + if type -P cargo &>/dev/null; then + info "cargo is located at $(type -P cargo)" + cargo_bin=$(dirname "$(type -P cargo)") + else cargo_bin="${install_action_dir}/bin" + fi +elif [[ ! -e "${cargo_bin}" ]] || [[ "$(type -P cargo || true)" != "${cargo_bin}/cargo"* ]]; then + if type -P cargo &>/dev/null; then + info "cargo is located at $(type -P cargo)" + fi + # Moving files to /usr/local/bin requires sudo in some environments, so do not use it: https://github.com/taiki-e/install-action/issues/543 + cargo_bin="${install_action_dir}/bin" fi jq_use_b='' case "${host_os}" in - linux) - if ! type -P jq &>/dev/null || ! type -P curl &>/dev/null || ! type -P tar &>/dev/null; then - case "${base_distro}" in - debian | fedora | suse | arch | alpine) - echo "::group::Install packages required for installation (jq, curl, and/or tar)" - sys_packages=() - if ! type -P curl &>/dev/null; then - sys_packages+=(ca-certificates curl) - fi - if ! type -P tar &>/dev/null; then - sys_packages+=(tar) - fi - if [[ "${dnf:-}" == "yum" ]]; then - # On RHEL7-based distribution jq requires EPEL - if ! type -P jq &>/dev/null; then - sys_packages+=(epel-release) - sys_install "${sys_packages[@]}" - sys_install jq --enablerepo=epel - else - sys_install "${sys_packages[@]}" - fi - else - if ! type -P jq &>/dev/null; then - # https://github.com/taiki-e/install-action/issues/521 - if [[ "${base_distro}" == "arch" ]]; then - sys_packages+=(glibc) - fi - sys_packages+=(jq) - fi - sys_install "${sys_packages[@]}" - fi - echo "::endgroup::" - ;; - *) warn "install-action requires at least jq and curl on non-Debian/Fedora/SUSE/Arch/Alpine-based Linux" ;; - esac - fi - ;; - macos) - if ! type -P jq &>/dev/null || ! type -P curl &>/dev/null; then - warn "install-action requires at least jq and curl on macOS" - fi - ;; - windows) - if ! type -P curl &>/dev/null; then - warn "install-action requires at least curl on Windows" - fi - # https://github.com/jqlang/jq/issues/1854 - jq_use_b=1 - jq="${install_action_dir}/jq/bin/jq.exe" - if [[ ! -f "${jq}" ]]; then - jq_version=$(jq --version || echo "") - case "${jq_version}" in - jq-1.[7-9]* | jq-1.[1-9][0-9]*) jq='' ;; - *) - _tmp=$(jq <<<"{}" -r .a || echo "") - if [[ "${_tmp}" == "null" ]]; then - jq='' - jq_use_b='' - else - info "old jq (${jq_version}) has bug on Windows; downloading jq 1.7 (will not be added to PATH)" - mkdir -p "${install_action_dir}/jq/bin" - url='https://github.com/jqlang/jq/releases/download/jq-1.7.1/jq-windows-amd64.exe' - checksum='7451fbbf37feffb9bf262bd97c54f0da558c63f0748e64152dd87b0a07b6d6ab' - ( - cd "${install_action_dir}/jq/bin" - download_and_checksum "${url}" "${checksum}" - mv tmp jq.exe - ) - echo - fi - ;; - esac - fi - ;; - *) bail "unsupported host OS '${host_os}'" ;; -esac -call_jq() { + linux) + if ! type -P jq &>/dev/null || ! type -P curl &>/dev/null || ! type -P tar &>/dev/null; then + case "${base_distro}" in + debian | fedora | suse | arch | alpine) + echo "::group::Install packages required for installation (jq, curl, and/or tar)" + sys_packages=() + if ! type -P curl &>/dev/null; then + sys_packages+=(ca-certificates curl) + fi + if ! type -P tar &>/dev/null; then + sys_packages+=(tar) + fi + if [[ "${dnf:-}" == "yum" ]]; then + # On RHEL7-based distribution jq requires EPEL + if ! type -P jq &>/dev/null; then + sys_packages+=(epel-release) + sys_install "${sys_packages[@]}" + sys_install jq --enablerepo=epel + else + sys_install "${sys_packages[@]}" + fi + else + if ! type -P jq &>/dev/null; then + # https://github.com/taiki-e/install-action/issues/521 + if [[ "${base_distro}" == "arch" ]]; then + sys_packages+=(glibc) + fi + sys_packages+=(jq) + fi + sys_install "${sys_packages[@]}" + fi + echo "::endgroup::" + ;; + *) warn "install-action requires at least jq and curl on non-Debian/Fedora/SUSE/Arch/Alpine-based Linux" ;; + esac + fi + ;; + macos) + if ! type -P jq &>/dev/null || ! type -P curl &>/dev/null; then + warn "install-action requires at least jq and curl on macOS" + fi + ;; + windows) + if ! type -P curl &>/dev/null; then + warn "install-action requires at least curl on Windows" + fi # https://github.com/jqlang/jq/issues/1854 - if [[ -n "${jq_use_b}" ]]; then - "${jq:-jq}" -b "$@" - else - "${jq:-jq}" "$@" + jq_use_b=1 + jq="${install_action_dir}/jq/bin/jq.exe" + if [[ ! -f "${jq}" ]]; then + jq_version=$(jq --version || echo "") + case "${jq_version}" in + jq-1.[7-9]* | jq-1.[1-9][0-9]*) jq='' ;; + *) + _tmp=$(jq <<<"{}" -r .a || echo "") + if [[ "${_tmp}" == "null" ]]; then + jq='' + jq_use_b='' + else + info "old jq (${jq_version}) has bug on Windows; downloading jq 1.7 (will not be added to PATH)" + mkdir -p "${install_action_dir}/jq/bin" + url='https://github.com/jqlang/jq/releases/download/jq-1.7.1/jq-windows-amd64.exe' + checksum='7451fbbf37feffb9bf262bd97c54f0da558c63f0748e64152dd87b0a07b6d6ab' + ( + cd "${install_action_dir}/jq/bin" + download_and_checksum "${url}" "${checksum}" + mv tmp jq.exe + ) + echo + fi + ;; + esac fi + ;; + *) bail "unsupported host OS '${host_os}'" ;; +esac +call_jq() { + # https://github.com/jqlang/jq/issues/1854 + if [[ -n "${jq_use_b}" ]]; then + "${jq:-jq}" -b "$@" + else + "${jq:-jq}" "$@" + fi } unsupported_tools=() for tool in "${tools[@]}"; do - if [[ "${tool}" == *"@"* ]]; then - version="${tool#*@}" - tool="${tool%@*}" - if [[ ! "${version}" =~ ^([1-9][0-9]*(\.[0-9]+(\.[0-9]+)?)?|0\.[1-9][0-9]*(\.[0-9]+)?|^0\.0\.[0-9]+)$|^latest$ ]]; then - if [[ ! "${version}" =~ ^([1-9][0-9]*(\.[0-9]+(\.[0-9]+)?)?|0\.[1-9][0-9]*(\.[0-9]+)?|^0\.0\.[0-9]+)(-[0-9A-Za-z\.-]+)?(\+[0-9A-Za-z\.-]+)?$|^latest$ ]]; then - bail "install-action does not support semver operators: '${version}'" - fi - bail "install-action v2 does not support semver pre-release and build-metadata: '${version}'; if you need these supports again, please submit an issue at " - fi - else - version="latest" + if [[ "${tool}" == *"@"* ]]; then + version="${tool#*@}" + tool="${tool%@*}" + if [[ ! "${version}" =~ ^([1-9][0-9]*(\.[0-9]+(\.[0-9]+)?)?|0\.[1-9][0-9]*(\.[0-9]+)?|^0\.0\.[0-9]+)$|^latest$ ]]; then + if [[ ! "${version}" =~ ^([1-9][0-9]*(\.[0-9]+(\.[0-9]+)?)?|0\.[1-9][0-9]*(\.[0-9]+)?|^0\.0\.[0-9]+)(-[0-9A-Za-z\.-]+)?(\+[0-9A-Za-z\.-]+)?$|^latest$ ]]; then + bail "install-action does not support semver operators: '${version}'" + fi + bail "install-action v2 does not support semver pre-release and build-metadata: '${version}'; if you need these supports again, please submit an issue at " fi - installed_bin=() - case "${tool}" in - protoc) - info "installing ${tool}@${version}" - read_manifest "protoc" "${version}" - read_download_info "protoc" "${version}" - # Copying files to /usr/local/include requires sudo, so do not use it. - bin_dir="${install_action_dir}/bin" - include_dir="${install_action_dir}/include" - init_install_action_bin_dir - if [[ ! -e "${include_dir}" ]]; then - mkdir -p "${include_dir}" - fi - if ! type -P unzip &>/dev/null; then - case "${base_distro}" in - debian | fedora | suse | arch | alpine) - echo "::group::Install packages required for installation (unzip)" - sys_install unzip - echo "::endgroup::" - ;; - esac - fi - mkdir -p "${tmp_dir}" - ( - cd "${tmp_dir}" - download_and_checksum "${url}" "${checksum}" - unzip -q tmp - mv "bin/protoc${exe}" "${bin_dir}/" - mkdir -p "${include_dir}/" - cp -r include/. "${include_dir}/" - if [[ -z "${PROTOC:-}" ]]; then - _bin_dir=$(canonicalize_windows_path "${bin_dir}") - info "setting PROTOC environment variable to '${_bin_dir}/protoc${exe}'" - echo "PROTOC=${_bin_dir}/protoc${exe}" >>"${GITHUB_ENV}" - fi - ) - rm -rf "${tmp_dir}" - installed_bin=("${tool}${exe}") - ;; - valgrind) - info "installing ${tool}@${version}" - case "${version}" in - latest) ;; - *) warn "specifying the version of ${tool} is not supported yet by this action" ;; - esac - case "${host_os}" in - linux) ;; - macos | windows) bail "${tool} for non-linux is not supported yet by this action" ;; - *) bail "unsupported host OS '${host_os}' for ${tool}" ;; - esac - # libc6-dbg is needed to run Valgrind - apt_install libc6-dbg - # Use snap to install the latest Valgrind - # https://snapcraft.io/install/valgrind/ubuntu - snap_install valgrind --classic - installed_bin=("${tool}${exe}") - ;; - cargo-binstall) - case "${version}" in - latest) ;; - *) warn "specifying the version of ${tool} is not supported by this action" ;; - esac - install_cargo_binstall - echo - continue + else + version="latest" + fi + installed_bin=() + case "${tool}" in + protoc) + info "installing ${tool}@${version}" + read_manifest "protoc" "${version}" + read_download_info "protoc" "${version}" + # Copying files to /usr/local/include requires sudo, so do not use it. + bin_dir="${install_action_dir}/bin" + include_dir="${install_action_dir}/include" + init_install_action_bin_dir + if [[ ! -e "${include_dir}" ]]; then + mkdir -p "${include_dir}" + fi + if ! type -P unzip &>/dev/null; then + case "${base_distro}" in + debian | fedora | suse | arch | alpine) + echo "::group::Install packages required for installation (unzip)" + sys_install unzip + echo "::endgroup::" ;; - *) - # Handle aliases - case "${tool}" in - cargo-nextest | nextest) tool="cargo-nextest" ;; - esac + esac + fi + mkdir -p "${tmp_dir}" + ( + cd "${tmp_dir}" + download_and_checksum "${url}" "${checksum}" + unzip -q tmp + mv "bin/protoc${exe}" "${bin_dir}/" + mkdir -p "${include_dir}/" + cp -r include/. "${include_dir}/" + if [[ -z "${PROTOC:-}" ]]; then + _bin_dir=$(canonicalize_windows_path "${bin_dir}") + info "setting PROTOC environment variable to '${_bin_dir}/protoc${exe}'" + echo "PROTOC=${_bin_dir}/protoc${exe}" >>"${GITHUB_ENV}" + fi + ) + rm -rf "${tmp_dir}" + installed_bin=("${tool}${exe}") + ;; + valgrind) + info "installing ${tool}@${version}" + case "${version}" in + latest) ;; + *) warn "specifying the version of ${tool} is not supported yet by this action" ;; + esac + case "${host_os}" in + linux) ;; + macos | windows) bail "${tool} for non-linux is not supported yet by this action" ;; + *) bail "unsupported host OS '${host_os}' for ${tool}" ;; + esac + # libc6-dbg is needed to run Valgrind + apt_install libc6-dbg + # Use snap to install the latest Valgrind + # https://snapcraft.io/install/valgrind/ubuntu + snap_install valgrind --classic + installed_bin=("${tool}${exe}") + ;; + cargo-binstall) + case "${version}" in + latest) ;; + *) warn "specifying the version of ${tool} is not supported by this action" ;; + esac + install_cargo_binstall + echo + continue + ;; + *) + # Handle aliases + case "${tool}" in + cargo-nextest | nextest) tool="cargo-nextest" ;; + esac - # Use cargo-binstall fallback if tool is not available. - if [[ ! -f "${manifest_dir}/${tool}.json" ]]; then - case "${version}" in - latest) unsupported_tools+=("${tool}") ;; - *) unsupported_tools+=("${tool}@${version}") ;; - esac - continue - fi + # Use cargo-binstall fallback if tool is not available. + if [[ ! -f "${manifest_dir}/${tool}.json" ]]; then + case "${version}" in + latest) unsupported_tools+=("${tool}") ;; + *) unsupported_tools+=("${tool}@${version}") ;; + esac + continue + fi - # Use cargo-binstall fallback if tool is available but the specified version not available. - read_manifest "${tool}" "${version}" - if [[ "${download_info}" == "null" ]]; then - if [[ "${rust_crate}" == "null" ]]; then - bail "${tool}@${version} for '${host_arch}_${host_os}' is not supported" - fi - warn "${tool}@${version} for '${host_arch}_${host_os}' is not supported; fallback to cargo-binstall" - case "${version}" in - latest) unsupported_tools+=("${rust_crate}") ;; - *) unsupported_tools+=("${rust_crate}@${version}") ;; - esac - continue - fi + # Use cargo-binstall fallback if tool is available but the specified version not available. + read_manifest "${tool}" "${version}" + if [[ "${download_info}" == "null" ]]; then + if [[ "${rust_crate}" == "null" ]]; then + bail "${tool}@${version} for '${host_arch}_${host_os}' is not supported" + fi + warn "${tool}@${version} for '${host_arch}_${host_os}' is not supported; fallback to cargo-binstall" + case "${version}" in + latest) unsupported_tools+=("${rust_crate}") ;; + *) unsupported_tools+=("${rust_crate}@${version}") ;; + esac + continue + fi - info "installing ${tool}@${version}" + info "installing ${tool}@${version}" - # Pre-install - case "${tool}" in - shellcheck) - case "${host_os}" in - linux) - if type -P shellcheck &>/dev/null; then - apt_remove -y shellcheck - fi - ;; - esac - ;; - esac + # Pre-install + case "${tool}" in + shellcheck) + case "${host_os}" in + linux) + if type -P shellcheck &>/dev/null; then + apt_remove -y shellcheck + fi + ;; + esac + ;; + esac - download_from_download_info "${tool}" "${version}" - ;; - esac + download_from_download_info "${tool}" "${version}" + ;; + esac - tool_bin_stems=() - for tool_bin in "${installed_bin[@]}"; do - tool_bin=$(basename "${tool_bin}") - tool_bin_stem="${tool_bin%.exe}" - installed_at=$(type -P "${tool_bin}" || echo "") - if [[ -z "${installed_at}" ]]; then - tool_bin="${tool_bin_stem}" - installed_at=$(type -P "${tool_bin}" || echo "") - fi - if [[ -n "${installed_at}" ]]; then - info "${tool_bin_stem} installed at ${installed_at}" - else - warn "${tool_bin_stem} should be installed at ${bin_dir:+"${bin_dir}/"}${tool_bin}${exe}; but ${tool_bin}${exe} not found in path" - fi - tool_bin_stems+=("${tool_bin_stem}") - done - for tool_bin_stem in "${tool_bin_stems[@]}"; do - # cargo-udeps 0.1.30 and wasm-pack 0.12.0 do not support --version flag. + tool_bin_stems=() + for tool_bin in "${installed_bin[@]}"; do + tool_bin=$(basename "${tool_bin}") + tool_bin_stem="${tool_bin%.exe}" + installed_at=$(type -P "${tool_bin}" || echo "") + if [[ -z "${installed_at}" ]]; then + tool_bin="${tool_bin_stem}" + installed_at=$(type -P "${tool_bin}" || echo "") + fi + if [[ -n "${installed_at}" ]]; then + info "${tool_bin_stem} installed at ${installed_at}" + else + warn "${tool_bin_stem} should be installed at ${bin_dir:+"${bin_dir}/"}${tool_bin}${exe}; but ${tool_bin}${exe} not found in path" + fi + tool_bin_stems+=("${tool_bin_stem}") + done + for tool_bin_stem in "${tool_bin_stems[@]}"; do + # cargo-udeps 0.1.30 and wasm-pack 0.12.0 do not support --version flag. + case "${tool_bin_stem}" in + # biome up to 1.2.2 exits with 1 on both --version and --help flags. + # cargo-machete up to 0.6.0 does not support --version flag. + # wait-for-them up to 0.4.0 does not support --version flag. + biome | cargo-machete | wait-for-them) rx "${tool_bin_stem}" --version || true ;; + # these packages support neither --version nor --help flag. + cargo-auditable | cargo-careful | wasm-bindgen-test-runner) ;; + # wasm2es6js does not support --version flag and --help flag doesn't contains version info. + wasm2es6js) ;; + # cargo-zigbuild has no --version flag on `cargo zigbuild` subcommand. + cargo-zigbuild) rx "${tool_bin_stem}" --version ;; + # deepsource has version command instead of --version flag. + deepsource) rx "${tool_bin_stem}" version ;; + cargo-*) case "${tool_bin_stem}" in - # biome up to 1.2.2 exits with 1 on both --version and --help flags. - # cargo-machete up to 0.6.0 does not support --version flag. - # wait-for-them up to 0.4.0 does not support --version flag. - biome | cargo-machete | wait-for-them) rx "${tool_bin_stem}" --version || true ;; - # these packages support neither --version nor --help flag. - cargo-auditable | cargo-careful | wasm-bindgen-test-runner) ;; - # wasm2es6js does not support --version flag and --help flag doesn't contains version info. - wasm2es6js) ;; - # cargo-zigbuild has no --version flag on `cargo zigbuild` subcommand. - cargo-zigbuild) rx "${tool_bin_stem}" --version ;; - # deepsource has version command instead of --version flag. - deepsource) rx "${tool_bin_stem}" version ;; - cargo-*) - case "${tool_bin_stem}" in - # cargo-valgrind 2.1.0's --version flag just calls cargo's --version flag - cargo-valgrind) rx "${tool_bin_stem}" "${tool_bin_stem#cargo-}" --help ;; - *) - if ! rx "${tool_bin_stem}" "${tool_bin_stem#cargo-}" --version; then - rx "${tool_bin_stem}" "${tool_bin_stem#cargo-}" --help - fi - ;; - esac - ;; - *) - if ! rx "${tool_bin_stem}" --version; then - rx "${tool_bin_stem}" --help - fi - ;; + # cargo-valgrind 2.1.0's --version flag just calls cargo's --version flag + cargo-valgrind) rx "${tool_bin_stem}" "${tool_bin_stem#cargo-}" --help ;; + *) + if ! rx "${tool_bin_stem}" "${tool_bin_stem#cargo-}" --version; then + rx "${tool_bin_stem}" "${tool_bin_stem#cargo-}" --help + fi + ;; esac - done - echo + ;; + *) + if ! rx "${tool_bin_stem}" --version; then + rx "${tool_bin_stem}" --help + fi + ;; + esac + done + echo done if [[ ${#unsupported_tools[@]} -gt 0 ]]; then - IFS=',' - case "${fallback}" in - none) bail "install-action does not support ${unsupported_tools[*]} (fallback is disabled by 'fallback: none' input option)" ;; - esac - info "install-action does not support ${unsupported_tools[*]}; fallback to cargo-binstall" - IFS=$'\n\t' - install_cargo_binstall - if [[ -z "${GITHUB_TOKEN:-}" ]] && [[ -n "${DEFAULT_GITHUB_TOKEN:-}" ]]; then - export GITHUB_TOKEN="${DEFAULT_GITHUB_TOKEN}" - fi - # By default, cargo-binstall enforce downloads over secure transports only. - # As a result, http will be disabled, and it will also set - # min tls version to be 1.2 - cargo-binstall binstall --force --no-confirm --locked "${unsupported_tools[@]}" - if ! type -P cargo >/dev/null; then - _bin_dir=$(canonicalize_windows_path "${home}/.cargo/bin") - # TODO: avoid this when already added - info "adding '${_bin_dir}' to PATH" - echo "${_bin_dir}" >>"${GITHUB_PATH}" - fi + IFS=',' + case "${fallback}" in + none) bail "install-action does not support ${unsupported_tools[*]} (fallback is disabled by 'fallback: none' input option)" ;; + esac + info "install-action does not support ${unsupported_tools[*]}; fallback to cargo-binstall" + IFS=$'\n\t' + install_cargo_binstall + if [[ -z "${GITHUB_TOKEN:-}" ]] && [[ -n "${DEFAULT_GITHUB_TOKEN:-}" ]]; then + export GITHUB_TOKEN="${DEFAULT_GITHUB_TOKEN}" + fi + # By default, cargo-binstall enforce downloads over secure transports only. + # As a result, http will be disabled, and it will also set + # min tls version to be 1.2 + cargo-binstall binstall --force --no-confirm --locked "${unsupported_tools[@]}" + if ! type -P cargo >/dev/null; then + _bin_dir=$(canonicalize_windows_path "${home}/.cargo/bin") + # TODO: avoid this when already added + info "adding '${_bin_dir}' to PATH" + echo "${_bin_dir}" >>"${GITHUB_PATH}" + fi fi diff --git a/tools/ci/manifest.sh b/tools/ci/manifest.sh index cbbe7f609..56bc8e935 100755 --- a/tools/ci/manifest.sh +++ b/tools/ci/manifest.sh @@ -6,12 +6,12 @@ trap -- 's=$?; printf >&2 "%s\n" "${0##*/}:${LINENO}: \`${BASH_COMMAND}\` exit w cd -- "$(dirname -- "$0")"/../.. bail() { - printf >&2 'error: %s\n' "$*" - exit 1 + printf >&2 'error: %s\n' "$*" + exit 1 } if [[ -z "${CI:-}" ]]; then - bail "this script is intended to call from release workflow on CI" + bail "this script is intended to call from release workflow on CI" fi git config user.name 'Taiki Endo' @@ -21,28 +21,28 @@ set -x has_update='' for manifest in manifests/*.json; do - git add -N "${manifest}" - if ! git diff --exit-code -- "${manifest}"; then - name="${manifest##*/}" - name="${name%.*}" - git stash - old_version=$(jq -r '.latest.version' "${manifest}") - git stash pop - new_version=$(jq -r '.latest.version' "${manifest}") - if [[ "${old_version}" != "${new_version}" ]]; then - # TODO: If there is a line about updating the same tool in the "Unreleased" section, replace it. - msg="Update \`${name}@latest\` to ${new_version}" - sed -Ei "s/^## \\[Unreleased\\]/## [Unreleased]\\n\\n- ${msg}./" CHANGELOG.md - git add "${manifest}" CHANGELOG.md - else - msg="Update ${name} manifest" - git add "${manifest}" - fi - git commit -m "${msg}" - has_update=1 + git add -N "${manifest}" + if ! git diff --exit-code -- "${manifest}"; then + name="${manifest##*/}" + name="${name%.*}" + git stash + old_version=$(jq -r '.latest.version' "${manifest}") + git stash pop + new_version=$(jq -r '.latest.version' "${manifest}") + if [[ "${old_version}" != "${new_version}" ]]; then + # TODO: If there is a line about updating the same tool in the "Unreleased" section, replace it. + msg="Update \`${name}@latest\` to ${new_version}" + sed -Ei "s/^## \\[Unreleased\\]/## [Unreleased]\\n\\n- ${msg}./" CHANGELOG.md + git add "${manifest}" CHANGELOG.md + else + msg="Update ${name} manifest" + git add "${manifest}" fi + git commit -m "${msg}" + has_update=1 + fi done if [[ -n "${has_update}" ]] && [[ -n "${GITHUB_OUTPUT:-}" ]]; then - printf 'success=false\n' >>"${GITHUB_OUTPUT}" + printf 'success=false\n' >>"${GITHUB_OUTPUT}" fi diff --git a/tools/ci/tool-list.sh b/tools/ci/tool-list.sh index d65b08d8f..b56d5d13e 100755 --- a/tools/ci/tool-list.sh +++ b/tools/ci/tool-list.sh @@ -8,181 +8,181 @@ cd -- "$(dirname -- "$0")"/../.. # They don't provide prebuilt binaries for musl or old glibc host. # version `GLIBC_2.34' not found glibc_pre_2_34_incompat=( - cargo-cyclonedx - cargo-spellcheck - wait-for-them - xbuild + cargo-cyclonedx + cargo-spellcheck + wait-for-them + xbuild ) # version `GLIBC_2.31' not found glibc_pre_2_31_incompat=( - "${glibc_pre_2_34_incompat[@]}" - cargo-sort - espup - zola + "${glibc_pre_2_34_incompat[@]}" + cargo-sort + espup + zola ) # version `GLIBC_2.28' not found glibc_pre_2_28_incompat=( - "${glibc_pre_2_31_incompat[@]}" - wasmtime + "${glibc_pre_2_31_incompat[@]}" + wasmtime ) # version `GLIBC_2.27' not found glibc_pre_2_27_incompat=( - "${glibc_pre_2_28_incompat[@]}" - cargo-watch - mdbook-linkcheck - protoc - valgrind + "${glibc_pre_2_28_incompat[@]}" + cargo-watch + mdbook-linkcheck + protoc + valgrind ) # version `GLIBC_2.17' not found glibc_pre_2_17_incompat=( - "${glibc_pre_2_27_incompat[@]}" - deepsource + "${glibc_pre_2_27_incompat[@]}" + deepsource ) musl_incompat=( - "${glibc_pre_2_17_incompat[@]}" + "${glibc_pre_2_17_incompat[@]}" ) win2019_gnu_incompat=( - cargo-spellcheck + cargo-spellcheck ) incompat_tools=() case "${1:-}" in - '') version=latest ;; - major.minor.patch | major.minor | major) - version="$1" - # Specifying the version of valgrind and cargo-binstall is not supported. - incompat_tools+=(valgrind cargo-binstall) - ;; - *) - printf 'tool=%s\n', "$1" - exit 1 - ;; + '') version=latest ;; + major.minor.patch | major.minor | major) + version="$1" + # Specifying the version of valgrind and cargo-binstall is not supported. + incompat_tools+=(valgrind cargo-binstall) + ;; + *) + printf 'tool=%s\n', "$1" + exit 1 + ;; esac runner="${2:-}" bash="${3:-}" case "$(uname -s)" in - Linux) - host_os=linux - ldd_version=$(ldd --version 2>&1 || true) - if grep -Fq musl <<<"${ldd_version}"; then - incompat_tools+=("${musl_incompat[@]}") - else - host_glibc_version=$(grep -E "GLIBC|GNU libc" <<<"${ldd_version}" | sed "s/.* //g") - higher_glibc_version=$(sort -Vu <<<"2.34"$'\n'"${host_glibc_version}" | tail -1) + Linux) + host_os=linux + ldd_version=$(ldd --version 2>&1 || true) + if grep -Fq musl <<<"${ldd_version}"; then + incompat_tools+=("${musl_incompat[@]}") + else + host_glibc_version=$(grep -E "GLIBC|GNU libc" <<<"${ldd_version}" | sed "s/.* //g") + higher_glibc_version=$(sort -Vu <<<"2.34"$'\n'"${host_glibc_version}" | tail -1) + if [[ "${higher_glibc_version}" != "${host_glibc_version}" ]]; then + higher_glibc_version=$(sort -Vu <<<"2.31"$'\n'"${host_glibc_version}" | tail -1) + if [[ "${higher_glibc_version}" != "${host_glibc_version}" ]]; then + higher_glibc_version=$(sort -Vu <<<"2.28"$'\n'"${host_glibc_version}" | tail -1) + if [[ "${higher_glibc_version}" != "${host_glibc_version}" ]]; then + higher_glibc_version=$(sort -Vu <<<"2.27"$'\n'"${host_glibc_version}" | tail -1) if [[ "${higher_glibc_version}" != "${host_glibc_version}" ]]; then - higher_glibc_version=$(sort -Vu <<<"2.31"$'\n'"${host_glibc_version}" | tail -1) - if [[ "${higher_glibc_version}" != "${host_glibc_version}" ]]; then - higher_glibc_version=$(sort -Vu <<<"2.28"$'\n'"${host_glibc_version}" | tail -1) - if [[ "${higher_glibc_version}" != "${host_glibc_version}" ]]; then - higher_glibc_version=$(sort -Vu <<<"2.27"$'\n'"${host_glibc_version}" | tail -1) - if [[ "${higher_glibc_version}" != "${host_glibc_version}" ]]; then - higher_glibc_version=$(sort -Vu <<<"2.17"$'\n'"${host_glibc_version}" | tail -1) - if [[ "${higher_glibc_version}" != "${host_glibc_version}" ]]; then - incompat_tools+=("${glibc_pre_2_17_incompat[@]}") - else - incompat_tools+=("${glibc_pre_2_27_incompat[@]}") - fi - else - incompat_tools+=("${glibc_pre_2_28_incompat[@]}") - fi - else - incompat_tools+=("${glibc_pre_2_31_incompat[@]}") - fi - else - incompat_tools+=("${glibc_pre_2_34_incompat[@]}") - fi + higher_glibc_version=$(sort -Vu <<<"2.17"$'\n'"${host_glibc_version}" | tail -1) + if [[ "${higher_glibc_version}" != "${host_glibc_version}" ]]; then + incompat_tools+=("${glibc_pre_2_17_incompat[@]}") + else + incompat_tools+=("${glibc_pre_2_27_incompat[@]}") + fi + else + incompat_tools+=("${glibc_pre_2_28_incompat[@]}") fi + else + incompat_tools+=("${glibc_pre_2_31_incompat[@]}") + fi + else + incompat_tools+=("${glibc_pre_2_34_incompat[@]}") fi - if ! type -P snap >/dev/null; then - incompat_tools+=(valgrind) + fi + fi + if ! type -P snap >/dev/null; then + incompat_tools+=(valgrind) + fi + ;; + Darwin) host_os=macos ;; + MINGW* | MSYS* | CYGWIN* | Windows_NT) + host_os=windows + case "${bash}" in + msys64 | cygwin) + if [[ "${runner}" == "windows-2019" ]]; then + incompat_tools+=("${win2019_gnu_incompat[@]}") fi ;; - Darwin) host_os=macos ;; - MINGW* | MSYS* | CYGWIN* | Windows_NT) - host_os=windows - case "${bash}" in - msys64 | cygwin) - if [[ "${runner}" == "windows-2019" ]]; then - incompat_tools+=("${win2019_gnu_incompat[@]}") - fi - ;; - esac - ;; - *) bail "unrecognized OS type '$(uname -s)'" ;; + esac + ;; + *) bail "unrecognized OS type '$(uname -s)'" ;; esac # See main.sh case "$(uname -m)" in - aarch64 | arm64) host_arch=aarch64 ;; - xscale | arm | armv*l) bail "32-bit Arm runner is not supported yet by this action; if you need support for this platform, please submit an issue at " ;; - *) host_arch=x86_64 ;; + aarch64 | arm64) host_arch=aarch64 ;; + xscale | arm | armv*l) bail "32-bit Arm runner is not supported yet by this action; if you need support for this platform, please submit an issue at " ;; + *) host_arch=x86_64 ;; esac tools=() for manifest in tools/codegen/base/*.json; do - tool_name="${manifest##*/}" - tool_name="${tool_name%.*}" - # cross -V requires rustc - if [[ "${tool_name}" == "cross" ]] && ! type -P rustc >/dev/null; then + tool_name="${manifest##*/}" + tool_name="${tool_name%.*}" + # cross -V requires rustc + if [[ "${tool_name}" == "cross" ]] && ! type -P rustc >/dev/null; then + continue + fi + case "${host_os}" in + linux*) + if [[ "${host_arch}" != "x86_64" ]] && [[ "$(jq -r ".platform.${host_arch}_${host_os}_gnu" "${manifest}")" == "null" ]] && [[ "$(jq -r ".platform.${host_arch}_${host_os}_musl" "${manifest}")" == "null" ]]; then + continue + fi + ;; + *) + if [[ "$(jq -r ".platform.x86_64_${host_os}" "${manifest}")" == "null" ]] && [[ "$(jq -r ".platform.${host_arch}_${host_os}" "${manifest}")" == "null" ]]; then continue + fi + ;; + esac + for incompat in ${incompat_tools[@]+"${incompat_tools[@]}"}; do + if [[ "${incompat}" == "${tool_name}" ]]; then + tool_name='' + break fi - case "${host_os}" in - linux*) - if [[ "${host_arch}" != "x86_64" ]] && [[ "$(jq -r ".platform.${host_arch}_${host_os}_gnu" "${manifest}")" == "null" ]] && [[ "$(jq -r ".platform.${host_arch}_${host_os}_musl" "${manifest}")" == "null" ]]; then - continue - fi - ;; - *) - if [[ "$(jq -r ".platform.x86_64_${host_os}" "${manifest}")" == "null" ]] && [[ "$(jq -r ".platform.${host_arch}_${host_os}" "${manifest}")" == "null" ]]; then - continue - fi - ;; - esac - for incompat in ${incompat_tools[@]+"${incompat_tools[@]}"}; do - if [[ "${incompat}" == "${tool_name}" ]]; then - tool_name='' - break - fi - done - if [[ -n "${tool_name}" ]]; then - if [[ "${version}" != "latest" ]]; then - latest_version=$(jq -r '.latest.version' "manifests/${tool_name}.json") - case "${version}" in - major.minor.patch) tool_name+="@${latest_version}" ;; - major.minor) tool_name+="@${latest_version%.*}" ;; - major) tool_name+="@${latest_version%%.*}" ;; - *) exit 1 ;; - esac - fi - if [[ "${tool_name}" != *"@0" ]] && [[ "${tool_name}" != *"@0.0" ]]; then - tools+=("${tool_name}") - fi + done + if [[ -n "${tool_name}" ]]; then + if [[ "${version}" != "latest" ]]; then + latest_version=$(jq -r '.latest.version' "manifests/${tool_name}.json") + case "${version}" in + major.minor.patch) tool_name+="@${latest_version}" ;; + major.minor) tool_name+="@${latest_version%.*}" ;; + major) tool_name+="@${latest_version%%.*}" ;; + *) exit 1 ;; + esac fi + if [[ "${tool_name}" != *"@0" ]] && [[ "${tool_name}" != *"@0.0" ]]; then + tools+=("${tool_name}") + fi + fi done if [[ "${version}" != "latest" ]]; then - tools_tmp=() - for tool in "${tools[@]}"; do - tools_tmp+=("${tool}") - done - tools=("${tools_tmp[@]}") + tools_tmp=() + for tool in "${tools[@]}"; do + tools_tmp+=("${tool}") + done + tools=("${tools_tmp[@]}") fi # Not manifest-based case "${host_os}" in - linux*) - # Installing snap to container is difficult... - # Specifying the version of valgrind is not supported. - if type -P snap >/dev/null && [[ "${version}" == "latest" ]]; then - tools+=(valgrind) - fi - ;; + linux*) + # Installing snap to container is difficult... + # Specifying the version of valgrind is not supported. + if type -P snap >/dev/null && [[ "${version}" == "latest" ]]; then + tools+=(valgrind) + fi + ;; esac # cargo-watch/watchexec-cli is supported by cargo-binstall (through quickinstall) case "${version}" in - latest) tools+=(cargo-watch watchexec-cli) ;; - major.minor.patch) tools+=(cargo-watch@8.5.2 watchexec-cli@2.1.2) ;; - major.minor) tools+=(cargo-watch@8.5 watchexec-cli@2.1) ;; - major) tools+=(cargo-watch@8 watchexec-cli@2) ;; - *) exit 1 ;; + latest) tools+=(cargo-watch watchexec-cli) ;; + major.minor.patch) tools+=(cargo-watch@8.5.2 watchexec-cli@2.1.2) ;; + major.minor) tools+=(cargo-watch@8.5 watchexec-cli@2.1) ;; + major) tools+=(cargo-watch@8 watchexec-cli@2) ;; + *) exit 1 ;; esac # sort and dedup diff --git a/tools/manifest.sh b/tools/manifest.sh index 8975aac0e..cd83e1814 100755 --- a/tools/manifest.sh +++ b/tools/manifest.sh @@ -11,12 +11,12 @@ cd -- "$(dirname -- "$0")"/.. # ./tools/manifest.sh [PACKAGE [VERSION_REQ]] if [[ $# -gt 0 ]]; then - cargo run --manifest-path tools/codegen/Cargo.toml --release -- "$@" - exit 0 + cargo run --manifest-path tools/codegen/Cargo.toml --release -- "$@" + exit 0 fi for manifest in tools/codegen/base/*.json; do - package="${manifest##*/}" - package="${package%.*}" - cargo run --manifest-path tools/codegen/Cargo.toml --release -- "${package}" latest + package="${manifest##*/}" + package="${package%.*}" + cargo run --manifest-path tools/codegen/Cargo.toml --release -- "${package}" latest done diff --git a/tools/publish.sh b/tools/publish.sh index 2da0b64bc..7e92e6c13 100755 --- a/tools/publish.sh +++ b/tools/publish.sh @@ -14,18 +14,18 @@ cd -- "$(dirname -- "$0")"/.. # - parse-changelog retry() { - for i in {1..10}; do - if "$@"; then - return 0 - else - sleep "${i}" - fi - done - "$@" + for i in {1..10}; do + if "$@"; then + return 0 + else + sleep "${i}" + fi + done + "$@" } bail() { - printf >&2 'error: %s\n' "$*" - exit 1 + printf >&2 'error: %s\n' "$*" + exit 1 } version="${1:?}" @@ -34,15 +34,15 @@ tag_prefix="v" tag="${tag_prefix}${version}" changelog="CHANGELOG.md" if [[ ! "${version}" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z\.-]+)?(\+[0-9A-Za-z\.-]+)?$ ]]; then - bail "invalid version format '${version}'" + bail "invalid version format '${version}'" fi if [[ $# -gt 1 ]]; then - bail "invalid argument '$2'" + bail "invalid argument '$2'" fi if { sed --help 2>&1 || true; } | grep -Eq -e '-i extension'; then - in_place=(-i '') + in_place=(-i '') else - in_place=(-i) + in_place=(-i) fi # Make sure there is no uncommitted change. @@ -51,67 +51,67 @@ git diff --exit-code --staged # Make sure the same release has not been created in the past. if gh release view "${tag}" &>/dev/null; then - bail "tag '${tag}' has already been created and pushed" + bail "tag '${tag}' has already been created and pushed" fi # Make sure that the release was created from an allowed branch. if ! git branch | grep -Eq '\* main$'; then - bail "current branch is not 'main'" + bail "current branch is not 'main'" fi if ! git remote -v | grep -F origin | grep -Eq 'github\.com[:/]taiki-e/'; then - bail "cannot publish a new release from fork repository" + bail "cannot publish a new release from fork repository" fi release_date=$(date -u '+%Y-%m-%d') tags=$(git --no-pager tag | { grep -E "^${tag_prefix}[0-9]+" || true; }) if [[ -n "${tags}" ]]; then - # Make sure the same release does not exist in changelog. - if grep -Eq "^## \\[${version//./\\.}\\]" "${changelog}"; then - bail "release ${version} already exist in ${changelog}" - fi - if grep -Eq "^\\[${version//./\\.}\\]: " "${changelog}"; then - bail "link to ${version} already exist in ${changelog}" - fi - # Update changelog. - remote_url=$(grep -E '^\[Unreleased\]: https://' "${changelog}" | sed -E 's/^\[Unreleased\]: //; s/\.\.\.HEAD$//') - prev_tag="${remote_url#*/compare/}" - remote_url="${remote_url%/compare/*}" - sed -E "${in_place[@]}" \ - -e "s/^## \\[Unreleased\\]/## [Unreleased]\\n\\n## [${version}] - ${release_date}/" \ - -e "s#^\[Unreleased\]: https://.*#[Unreleased]: ${remote_url}/compare/${tag}...HEAD\\n[${version}]: ${remote_url}/compare/${prev_tag}...${tag}#" "${changelog}" - if ! grep -Eq "^## \\[${version//./\\.}\\] - ${release_date}$" "${changelog}"; then - bail "failed to update ${changelog}" - fi - if ! grep -Eq "^\\[${version//./\\.}\\]: " "${changelog}"; then - bail "failed to update ${changelog}" - fi + # Make sure the same release does not exist in changelog. + if grep -Eq "^## \\[${version//./\\.}\\]" "${changelog}"; then + bail "release ${version} already exist in ${changelog}" + fi + if grep -Eq "^\\[${version//./\\.}\\]: " "${changelog}"; then + bail "link to ${version} already exist in ${changelog}" + fi + # Update changelog. + remote_url=$(grep -E '^\[Unreleased\]: https://' "${changelog}" | sed -E 's/^\[Unreleased\]: //; s/\.\.\.HEAD$//') + prev_tag="${remote_url#*/compare/}" + remote_url="${remote_url%/compare/*}" + sed -E "${in_place[@]}" \ + -e "s/^## \\[Unreleased\\]/## [Unreleased]\\n\\n## [${version}] - ${release_date}/" \ + -e "s#^\[Unreleased\]: https://.*#[Unreleased]: ${remote_url}/compare/${tag}...HEAD\\n[${version}]: ${remote_url}/compare/${prev_tag}...${tag}#" "${changelog}" + if ! grep -Eq "^## \\[${version//./\\.}\\] - ${release_date}$" "${changelog}"; then + bail "failed to update ${changelog}" + fi + if ! grep -Eq "^\\[${version//./\\.}\\]: " "${changelog}"; then + bail "failed to update ${changelog}" + fi else - # Make sure the release exists in changelog. - if ! grep -Eq "^## \\[${version//./\\.}\\] - ${release_date}$" "${changelog}"; then - bail "release ${version} does not exist in ${changelog} or has wrong release date" - fi - if ! grep -Eq "^\\[${version//./\\.}\\]: " "${changelog}"; then - bail "link to ${version} does not exist in ${changelog}" - fi + # Make sure the release exists in changelog. + if ! grep -Eq "^## \\[${version//./\\.}\\] - ${release_date}$" "${changelog}"; then + bail "release ${version} does not exist in ${changelog} or has wrong release date" + fi + if ! grep -Eq "^\\[${version//./\\.}\\]: " "${changelog}"; then + bail "link to ${version} does not exist in ${changelog}" + fi fi # Make sure that a valid release note for this version exists. # https://github.com/taiki-e/parse-changelog changes=$(parse-changelog "${changelog}" "${version}") if [[ -z "${changes}" ]]; then - bail "changelog for ${version} has no body" + bail "changelog for ${version} has no body" fi printf '============== CHANGELOG ==============\n' printf '%s\n' "${changes}" printf '=======================================\n' if [[ -n "${tags}" ]]; then - # Create a release commit. - ( - set -x - git add "${changelog}" - git commit -m "Release ${version}" - ) + # Create a release commit. + ( + set -x + git add "${changelog}" + git commit -m "Release ${version}" + ) fi set -x @@ -124,8 +124,8 @@ major_version_tag="v${version%%.*}" git checkout -b "${major_version_tag}" retry git push origin refs/heads/"${major_version_tag}" if git --no-pager tag | grep -Eq "^${major_version_tag}$"; then - git tag -d "${major_version_tag}" - retry git push --delete origin refs/tags/"${major_version_tag}" + git tag -d "${major_version_tag}" + retry git push --delete origin refs/tags/"${major_version_tag}" fi git tag "${major_version_tag}" retry git push origin --tags @@ -134,8 +134,8 @@ git branch -d "${major_version_tag}" tools=() for tool in tools/codegen/base/*.json; do - tool="${tool##*/}" - tools+=("${tool%.*}") + tool="${tool##*/}" + tools+=("${tool%.*}") done # Alias tools+=(nextest) @@ -143,18 +143,18 @@ tools+=(nextest) tools+=(valgrind) for tool in "${tools[@]}"; do - git checkout -b "${tool}" - sed -E "${in_place[@]}" "s/required: true/required: false/g" action.yml - sed -E "${in_place[@]}" "s/# default: #publish:tool/default: ${tool}/g" action.yml - git add action.yml - git commit -m "${tool}" - retry git push origin -f refs/heads/"${tool}" - if git --no-pager tag | grep -Eq "^${tool}$"; then - git tag -d "${tool}" - retry git push --delete origin refs/tags/"${tool}" - fi - git tag "${tool}" - retry git push origin --tags - git checkout main - git branch -D "${tool}" + git checkout -b "${tool}" + sed -E "${in_place[@]}" "s/required: true/required: false/g" action.yml + sed -E "${in_place[@]}" "s/# default: #publish:tool/default: ${tool}/g" action.yml + git add action.yml + git commit -m "${tool}" + retry git push origin -f refs/heads/"${tool}" + if git --no-pager tag | grep -Eq "^${tool}$"; then + git tag -d "${tool}" + retry git push --delete origin refs/tags/"${tool}" + fi + git tag "${tool}" + retry git push origin --tags + git checkout main + git branch -D "${tool}" done diff --git a/tools/tidy.sh b/tools/tidy.sh index a5485ec5e..344c7fa8b 100755 --- a/tools/tidy.sh +++ b/tools/tidy.sh @@ -25,464 +25,464 @@ trap 's=$?; echo >&2 "$0: error on line "${LINENO}": ${BASH_COMMAND}"; exit ${s} # skipped if the corresponding files do not exist. check_diff() { - if [[ -n "${CI:-}" ]]; then - if ! git --no-pager diff --exit-code "$@"; then - should_fail=1 - fi - else - if ! git --no-pager diff --exit-code "$@" &>/dev/null; then - should_fail=1 - fi + if [[ -n "${CI:-}" ]]; then + if ! git --no-pager diff --exit-code "$@"; then + should_fail=1 fi + else + if ! git --no-pager diff --exit-code "$@" &>/dev/null; then + should_fail=1 + fi + fi } check_config() { - if [[ ! -e "$1" ]]; then - error "could not found $1 in the repository root" - fi + if [[ ! -e "$1" ]]; then + error "could not found $1 in the repository root" + fi } check_install() { - for tool in "$@"; do - if ! type -P "${tool}" &>/dev/null; then - if [[ "${tool}" == "python3" ]]; then - if type -P python &>/dev/null; then - continue - fi - fi - error "'${tool}' is required to run this check" - return 1 + for tool in "$@"; do + if ! type -P "${tool}" &>/dev/null; then + if [[ "${tool}" == "python3" ]]; then + if type -P python &>/dev/null; then + continue fi - done + fi + error "'${tool}' is required to run this check" + return 1 + fi + done } info() { - echo >&2 "info: $*" + echo >&2 "info: $*" } error() { - if [[ -n "${GITHUB_ACTIONS:-}" ]]; then - echo "::error::$*" - else - echo >&2 "error: $*" - fi - should_fail=1 + if [[ -n "${GITHUB_ACTIONS:-}" ]]; then + echo "::error::$*" + else + echo >&2 "error: $*" + fi + should_fail=1 } venv() { - local bin="$1" - shift - "${venv_bin}/${bin}${exe}" "$@" + local bin="$1" + shift + "${venv_bin}/${bin}${exe}" "$@" } venv_install_yq() { - local py_suffix='' - if type -P python3 &>/dev/null; then - py_suffix='3' - fi - exe='' - venv_bin='.venv/bin' - case "$(uname -s)" in - MINGW* | MSYS* | CYGWIN* | Windows_NT) - exe='.exe' - venv_bin='.venv/Scripts' - ;; - esac - if [[ ! -d .venv ]]; then - "python${py_suffix}" -m venv .venv - fi - if [[ ! -e "${venv_bin}/yq${exe}" ]]; then - info "installing yq to ./.venv using pip" - venv "pip${py_suffix}" install yq - fi + local py_suffix='' + if type -P python3 &>/dev/null; then + py_suffix='3' + fi + exe='' + venv_bin='.venv/bin' + case "$(uname -s)" in + MINGW* | MSYS* | CYGWIN* | Windows_NT) + exe='.exe' + venv_bin='.venv/Scripts' + ;; + esac + if [[ ! -d .venv ]]; then + "python${py_suffix}" -m venv .venv + fi + if [[ ! -e "${venv_bin}/yq${exe}" ]]; then + info "installing yq to ./.venv using pip" + venv "pip${py_suffix}" install yq + fi } if [[ $# -gt 0 ]]; then - cat </dev/null - info "running \`rustfmt \$(git ls-files '*.rs')\`" - rustfmt $(git ls-files '*.rs') - else - rustup component add rustfmt --toolchain nightly &>/dev/null - info "running \`rustfmt +nightly \$(git ls-files '*.rs')\`" - rustfmt +nightly $(git ls-files '*.rs') - fi - check_diff $(git ls-files '*.rs') + info "checking Rust code style" + check_install cargo jq python3 + check_config .rustfmt.toml + if check_install rustup; then + # `cargo fmt` cannot recognize files not included in the current workspace and modules + # defined inside macros, so run rustfmt directly. + # We need to use nightly rustfmt because we use the unstable formatting options of rustfmt. + rustc_version=$(rustc -vV | grep '^release:' | cut -d' ' -f2) + if [[ "${rustc_version}" == *"nightly"* ]] || [[ "${rustc_version}" == *"dev"* ]]; then + rustup component add rustfmt &>/dev/null + info "running \`rustfmt \$(git ls-files '*.rs')\`" + rustfmt $(git ls-files '*.rs') + else + rustup component add rustfmt --toolchain nightly &>/dev/null + info "running \`rustfmt +nightly \$(git ls-files '*.rs')\`" + rustfmt +nightly $(git ls-files '*.rs') fi - cast_without_turbofish=$(grep -n -E '\.cast\(\)' $(git ls-files '*.rs') || true) - if [[ -n "${cast_without_turbofish}" ]]; then - error "please replace \`.cast()\` with \`.cast::()\`:" - echo "${cast_without_turbofish}" + check_diff $(git ls-files '*.rs') + fi + cast_without_turbofish=$(grep -n -E '\.cast\(\)' $(git ls-files '*.rs') || true) + if [[ -n "${cast_without_turbofish}" ]]; then + error "please replace \`.cast()\` with \`.cast::()\`:" + echo "${cast_without_turbofish}" + fi + # Sync readme and crate-level doc. + first='1' + for readme in $(git ls-files '*README.md'); do + if ! grep -q '^' "${readme}"; then + continue fi - # Sync readme and crate-level doc. - first='1' - for readme in $(git ls-files '*README.md'); do - if ! grep -q '^' "${readme}"; then - continue - fi - lib="$(dirname "${readme}")/src/lib.rs" - if [[ -n "${first}" ]]; then - first='' - info "checking readme and crate-level doc are synchronized" - fi - if ! grep -q '^' "${readme}"; then - bail "missing '' comment in ${readme}" - fi - if ! grep -q '^' "${lib}"; then - bail "missing '' comment in ${lib}" - fi - if ! grep -q '^' "${lib}"; then - bail "missing '' comment in ${lib}" - fi - new=$(tr <"${readme}" '\n' '\a' | grep -o '.*' | sed 's/\&/\\\&/g; s/\\/\\\\/g') - new=$(tr <"${lib}" '\n' '\a' | awk -v new="${new}" 'gsub(".*",new)' | tr '\a' '\n') - echo "${new}" >"${lib}" - check_diff "${lib}" - done - # Make sure that public Rust crates don't contain executables and binaries. - executables='' - binaries='' - metadata=$(cargo metadata --format-version=1 --no-deps) - has_public_crate='' - has_root_crate='' - venv_install_yq - for id in $(jq <<<"${metadata}" '.workspace_members[]'); do - pkg=$(jq <<<"${metadata}" ".packages[] | select(.id == ${id})") - publish=$(jq <<<"${pkg}" -r '.publish') - manifest_path=$(jq <<<"${pkg}" -r '.manifest_path') - if [[ "$(venv tomlq -c '.lints' "${manifest_path}")" == "null" ]]; then - error "no [lints] table in ${manifest_path} please add '[lints]' with 'workspace = true'" - fi + lib="$(dirname "${readme}")/src/lib.rs" + if [[ -n "${first}" ]]; then + first='' + info "checking readme and crate-level doc are synchronized" + fi + if ! grep -q '^' "${readme}"; then + bail "missing '' comment in ${readme}" + fi + if ! grep -q '^' "${lib}"; then + bail "missing '' comment in ${lib}" + fi + if ! grep -q '^' "${lib}"; then + bail "missing '' comment in ${lib}" + fi + new=$(tr <"${readme}" '\n' '\a' | grep -o '.*' | sed 's/\&/\\\&/g; s/\\/\\\\/g') + new=$(tr <"${lib}" '\n' '\a' | awk -v new="${new}" 'gsub(".*",new)' | tr '\a' '\n') + echo "${new}" >"${lib}" + check_diff "${lib}" + done + # Make sure that public Rust crates don't contain executables and binaries. + executables='' + binaries='' + metadata=$(cargo metadata --format-version=1 --no-deps) + has_public_crate='' + has_root_crate='' + venv_install_yq + for id in $(jq <<<"${metadata}" '.workspace_members[]'); do + pkg=$(jq <<<"${metadata}" ".packages[] | select(.id == ${id})") + publish=$(jq <<<"${pkg}" -r '.publish') + manifest_path=$(jq <<<"${pkg}" -r '.manifest_path') + if [[ "$(venv tomlq -c '.lints' "${manifest_path}")" == "null" ]]; then + error "no [lints] table in ${manifest_path} please add '[lints]' with 'workspace = true'" + fi + # Publishing is unrestricted if null, and forbidden if an empty array. + if [[ "${publish}" == "[]" ]]; then + continue + fi + has_public_crate='1' + done + if [[ -n "${has_public_crate}" ]]; then + info "checking public crates don't contain executables and binaries" + if [[ -f Cargo.toml ]]; then + root_manifest=$(cargo locate-project --message-format=plain --manifest-path Cargo.toml) + root_pkg=$(jq <<<"${metadata}" ".packages[] | select(.manifest_path == \"${root_manifest}\")") + if [[ -n "${root_pkg}" ]]; then + publish=$(jq <<<"${root_pkg}" -r '.publish') # Publishing is unrestricted if null, and forbidden if an empty array. - if [[ "${publish}" == "[]" ]]; then - continue + if [[ "${publish}" != "[]" ]]; then + has_root_crate=1 + exclude=$(venv tomlq -r '.package.exclude[]' Cargo.toml) + if ! grep <<<"${exclude}" -Eq '^/\.\*$'; then + error "top-level Cargo.toml of non-virtual workspace should have 'exclude' field with \"/.*\"" + fi + if [[ -e tools ]] && ! grep <<<"${exclude}" -Eq '^/tools$'; then + error "top-level Cargo.toml of non-virtual workspace should have 'exclude' field with \"/tools\" if it exists" + fi + if [[ -e target-specs ]] && ! grep <<<"${exclude}" -Eq '^/target-specs$'; then + error "top-level Cargo.toml of non-virtual workspace should have 'exclude' field with \"/target-specs\" if it exists" + fi fi - has_public_crate='1' + fi + fi + for p in $(git ls-files); do + # Skip directories. + if [[ -d "${p}" ]]; then + continue + fi + # Top-level hidden files/directories and tools/* are excluded from crates.io (ensured by the above check). + # TODO: fully respect exclude field in Cargo.toml. + case "${p}" in + .* | tools/* | target-specs/*) continue ;; + */*) ;; + *) + # If there is no crate at root, executables at the repository root directory if always okay. + if [[ -z "${has_root_crate}" ]]; then + continue + fi + ;; + esac + if [[ -x "${p}" ]]; then + executables+="${p}"$'\n' + fi + # Use diff instead of file because file treats an empty file as a binary + # https://unix.stackexchange.com/questions/275516/is-there-a-convenient-way-to-classify-files-as-binary-or-text#answer-402870 + if (diff .gitattributes "${p}" || true) | grep -q '^Binary file'; then + binaries+="${p}"$'\n' + fi done - if [[ -n "${has_public_crate}" ]]; then - info "checking public crates don't contain executables and binaries" - if [[ -f Cargo.toml ]]; then - root_manifest=$(cargo locate-project --message-format=plain --manifest-path Cargo.toml) - root_pkg=$(jq <<<"${metadata}" ".packages[] | select(.manifest_path == \"${root_manifest}\")") - if [[ -n "${root_pkg}" ]]; then - publish=$(jq <<<"${root_pkg}" -r '.publish') - # Publishing is unrestricted if null, and forbidden if an empty array. - if [[ "${publish}" != "[]" ]]; then - has_root_crate=1 - exclude=$(venv tomlq -r '.package.exclude[]' Cargo.toml) - if ! grep <<<"${exclude}" -Eq '^/\.\*$'; then - error "top-level Cargo.toml of non-virtual workspace should have 'exclude' field with \"/.*\"" - fi - if [[ -e tools ]] && ! grep <<<"${exclude}" -Eq '^/tools$'; then - error "top-level Cargo.toml of non-virtual workspace should have 'exclude' field with \"/tools\" if it exists" - fi - if [[ -e target-specs ]] && ! grep <<<"${exclude}" -Eq '^/target-specs$'; then - error "top-level Cargo.toml of non-virtual workspace should have 'exclude' field with \"/target-specs\" if it exists" - fi - fi - fi - fi - for p in $(git ls-files); do - # Skip directories. - if [[ -d "${p}" ]]; then - continue - fi - # Top-level hidden files/directories and tools/* are excluded from crates.io (ensured by the above check). - # TODO: fully respect exclude field in Cargo.toml. - case "${p}" in - .* | tools/* | target-specs/*) continue ;; - */*) ;; - *) - # If there is no crate at root, executables at the repository root directory if always okay. - if [[ -z "${has_root_crate}" ]]; then - continue - fi - ;; - esac - if [[ -x "${p}" ]]; then - executables+="${p}"$'\n' - fi - # Use diff instead of file because file treats an empty file as a binary - # https://unix.stackexchange.com/questions/275516/is-there-a-convenient-way-to-classify-files-as-binary-or-text#answer-402870 - if (diff .gitattributes "${p}" || true) | grep -q '^Binary file'; then - binaries+="${p}"$'\n' - fi - done - if [[ -n "${executables}" ]]; then - error "file-permissions-check failed: executables are only allowed to be present in directories that are excluded from crates.io" - echo "=======================================" - echo -n "${executables}" - echo "=======================================" - fi - if [[ -n "${binaries}" ]]; then - error "file-permissions-check failed: binaries are only allowed to be present in directories that are excluded from crates.io" - echo "=======================================" - echo -n "${binaries}" - echo "=======================================" - fi + if [[ -n "${executables}" ]]; then + error "file-permissions-check failed: executables are only allowed to be present in directories that are excluded from crates.io" + echo "=======================================" + echo -n "${executables}" + echo "=======================================" + fi + if [[ -n "${binaries}" ]]; then + error "file-permissions-check failed: binaries are only allowed to be present in directories that are excluded from crates.io" + echo "=======================================" + echo -n "${binaries}" + echo "=======================================" fi + fi elif [[ -e .rustfmt.toml ]]; then - error ".rustfmt.toml is unused" + error ".rustfmt.toml is unused" fi # C/C++ (if exists) if [[ -n "$(git ls-files '*.c' '*.h' '*.cpp' '*.hpp')" ]]; then - info "checking C/C++ code style" - check_config .clang-format - if check_install clang-format; then - info "running \`clang-format -i \$(git ls-files '*.c' '*.h' '*.cpp' '*.hpp')\`" - clang-format -i $(git ls-files '*.c' '*.h' '*.cpp' '*.hpp') - check_diff $(git ls-files '*.c' '*.h' '*.cpp' '*.hpp') - fi + info "checking C/C++ code style" + check_config .clang-format + if check_install clang-format; then + info "running \`clang-format -i \$(git ls-files '*.c' '*.h' '*.cpp' '*.hpp')\`" + clang-format -i $(git ls-files '*.c' '*.h' '*.cpp' '*.hpp') + check_diff $(git ls-files '*.c' '*.h' '*.cpp' '*.hpp') + fi elif [[ -e .clang-format ]]; then - error ".clang-format is unused" + error ".clang-format is unused" fi # YAML/JavaScript/JSON (if exists) if [[ -n "$(git ls-files '*.yml' '*.yaml' '*.js' '*.json')" ]]; then - info "checking YAML/JavaScript/JSON code style" - check_config .editorconfig - if check_install npm; then - info "running \`npx -y prettier -l -w \$(git ls-files '*.yml' '*.yaml' '*.js' '*.json')\`" - npx -y prettier -l -w $(git ls-files '*.yml' '*.yaml' '*.js' '*.json') - check_diff $(git ls-files '*.yml' '*.yaml' '*.js' '*.json') - fi - # Check GitHub workflows. - if [[ -d .github/workflows ]]; then - info "checking GitHub workflows" - if check_install jq python3; then - venv_install_yq - for workflow in .github/workflows/*.yml; do - # The top-level permissions must be weak as they are referenced by all jobs. - permissions=$(venv yq -c '.permissions' "${workflow}") - case "${permissions}" in - '{"contents":"read"}' | '{"contents":"none"}') ;; - null) error "${workflow}: top level permissions not found; it must be 'contents: read' or weaker permissions" ;; - *) error "${workflow}: only 'contents: read' and weaker permissions are allowed at top level; if you want to use stronger permissions, please set job-level permissions" ;; - esac - # Make sure the 'needs' section is not out of date. - if grep -q '# tidy:needs' "${workflow}" && ! grep -Eq '# *needs: \[' "${workflow}"; then - # shellcheck disable=SC2207 - jobs_actual=($(venv yq '.jobs' "${workflow}" | jq -r 'keys_unsorted[]')) - unset 'jobs_actual[${#jobs_actual[@]}-1]' - # shellcheck disable=SC2207 - jobs_expected=($(venv yq -r '.jobs."ci-success".needs[]' "${workflow}")) - if [[ "${jobs_actual[*]}" != "${jobs_expected[*]+"${jobs_expected[*]}"}" ]]; then - printf -v jobs '%s, ' "${jobs_actual[@]}" - sed -i "s/needs: \[.*\] # tidy:needs/needs: [${jobs%, }] # tidy:needs/" "${workflow}" - check_diff "${workflow}" - error "${workflow}: please update 'needs' section in 'ci-success' job" - fi - fi - done + info "checking YAML/JavaScript/JSON code style" + check_config .editorconfig + if check_install npm; then + info "running \`npx -y prettier -l -w \$(git ls-files '*.yml' '*.yaml' '*.js' '*.json')\`" + npx -y prettier -l -w $(git ls-files '*.yml' '*.yaml' '*.js' '*.json') + check_diff $(git ls-files '*.yml' '*.yaml' '*.js' '*.json') + fi + # Check GitHub workflows. + if [[ -d .github/workflows ]]; then + info "checking GitHub workflows" + if check_install jq python3; then + venv_install_yq + for workflow in .github/workflows/*.yml; do + # The top-level permissions must be weak as they are referenced by all jobs. + permissions=$(venv yq -c '.permissions' "${workflow}") + case "${permissions}" in + '{"contents":"read"}' | '{"contents":"none"}') ;; + null) error "${workflow}: top level permissions not found; it must be 'contents: read' or weaker permissions" ;; + *) error "${workflow}: only 'contents: read' and weaker permissions are allowed at top level; if you want to use stronger permissions, please set job-level permissions" ;; + esac + # Make sure the 'needs' section is not out of date. + if grep -q '# tidy:needs' "${workflow}" && ! grep -Eq '# *needs: \[' "${workflow}"; then + # shellcheck disable=SC2207 + jobs_actual=($(venv yq '.jobs' "${workflow}" | jq -r 'keys_unsorted[]')) + unset 'jobs_actual[${#jobs_actual[@]}-1]' + # shellcheck disable=SC2207 + jobs_expected=($(venv yq -r '.jobs."ci-success".needs[]' "${workflow}")) + if [[ "${jobs_actual[*]}" != "${jobs_expected[*]+"${jobs_expected[*]}"}" ]]; then + printf -v jobs '%s, ' "${jobs_actual[@]}" + sed -i "s/needs: \[.*\] # tidy:needs/needs: [${jobs%, }] # tidy:needs/" "${workflow}" + check_diff "${workflow}" + error "${workflow}: please update 'needs' section in 'ci-success' job" + fi fi + done fi + fi fi if [[ -n "$(git ls-files '*.yaml' | (grep -v .markdownlint-cli2.yaml || true))" ]]; then - error "please use '.yml' instead of '.yaml' for consistency" - git ls-files '*.yaml' | (grep -v .markdownlint-cli2.yaml || true) + error "please use '.yml' instead of '.yaml' for consistency" + git ls-files '*.yaml' | (grep -v .markdownlint-cli2.yaml || true) fi # TOML (if exists) if [[ -n "$(git ls-files '*.toml' | (grep -v .taplo.toml || true))" ]]; then - info "checking TOML style" - check_config .taplo.toml - if check_install npm; then - info "running \`npx -y @taplo/cli fmt \$(git ls-files '*.toml')\`" - RUST_LOG=warn npx -y @taplo/cli fmt $(git ls-files '*.toml') - check_diff $(git ls-files '*.toml') - fi + info "checking TOML style" + check_config .taplo.toml + if check_install npm; then + info "running \`npx -y @taplo/cli fmt \$(git ls-files '*.toml')\`" + RUST_LOG=warn npx -y @taplo/cli fmt $(git ls-files '*.toml') + check_diff $(git ls-files '*.toml') + fi elif [[ -e .taplo.toml ]]; then - error ".taplo.toml is unused" + error ".taplo.toml is unused" fi # Markdown (if exists) if [[ -n "$(git ls-files '*.md')" ]]; then - info "checking Markdown style" - check_config .markdownlint-cli2.yaml - if check_install npm; then - info "running \`npx -y markdownlint-cli2 \$(git ls-files '*.md')\`" - npx -y markdownlint-cli2 $(git ls-files '*.md') - fi + info "checking Markdown style" + check_config .markdownlint-cli2.yaml + if check_install npm; then + info "running \`npx -y markdownlint-cli2 \$(git ls-files '*.md')\`" + npx -y markdownlint-cli2 $(git ls-files '*.md') + fi elif [[ -e .markdownlint-cli2.yaml ]]; then - error ".markdownlint-cli2.yaml is unused" + error ".markdownlint-cli2.yaml is unused" fi if [[ -n "$(git ls-files '*.markdown')" ]]; then - error "please use '.md' instead of '.markdown' for consistency" - git ls-files '*.markdown' + error "please use '.md' instead of '.markdown' for consistency" + git ls-files '*.markdown' fi # Shell scripts info "checking Shell scripts" if check_install shfmt; then - check_config .editorconfig - info "running \`shfmt -l -w \$(git ls-files '*.sh')\`" - shfmt -l -w $(git ls-files '*.sh') - check_diff $(git ls-files '*.sh') + check_config .editorconfig + info "running \`shfmt -l -w \$(git ls-files '*.sh')\`" + shfmt -l -w $(git ls-files '*.sh') + check_diff $(git ls-files '*.sh') fi if check_install shellcheck; then - check_config .shellcheckrc - info "running \`shellcheck \$(git ls-files '*.sh')\`" - if ! shellcheck $(git ls-files '*.sh'); then - should_fail=1 - fi - if [[ -n "$(git ls-files '*Dockerfile')" ]]; then - # SC2154 doesn't seem to work on dockerfile. - info "running \`shellcheck -e SC2148,SC2154,SC2250 \$(git ls-files '*Dockerfile')\`" - if ! shellcheck -e SC2148,SC2154,SC2250 $(git ls-files '*Dockerfile'); then - should_fail=1 - fi + check_config .shellcheckrc + info "running \`shellcheck \$(git ls-files '*.sh')\`" + if ! shellcheck $(git ls-files '*.sh'); then + should_fail=1 + fi + if [[ -n "$(git ls-files '*Dockerfile')" ]]; then + # SC2154 doesn't seem to work on dockerfile. + info "running \`shellcheck -e SC2148,SC2154,SC2250 \$(git ls-files '*Dockerfile')\`" + if ! shellcheck -e SC2148,SC2154,SC2250 $(git ls-files '*Dockerfile'); then + should_fail=1 fi + fi fi # License check # TODO: This check is still experimental and does not track all files that should be tracked. if [[ -f tools/.tidy-check-license-headers ]]; then - info "checking license headers (experimental)" - failed_files='' - for p in $(eval $(.cspell.json; echo >&2 "$0: trapped SIGINT"; exit 1' SIGINT - echo "${config_new}" >.cspell.json - if [[ -n "${has_rust}" ]]; then - dependencies_words=$(npx <<<"${dependencies}" -y cspell stdin --no-progress --no-summary --words-only --unique || true) - fi - all_words=$(npx -y cspell --no-progress --no-summary --words-only --unique $(git ls-files | (grep -v "${project_dictionary//\./\\.}" || true)) || true) - echo "${config_old}" >.cspell.json - trap - SIGINT - cat >.github/.cspell/rust-dependencies.txt <.cspell.json; echo >&2 "$0: trapped SIGINT"; exit 1' SIGINT + echo "${config_new}" >.cspell.json + if [[ -n "${has_rust}" ]]; then + dependencies_words=$(npx <<<"${dependencies}" -y cspell stdin --no-progress --no-summary --words-only --unique || true) + fi + all_words=$(npx -y cspell --no-progress --no-summary --words-only --unique $(git ls-files | (grep -v "${project_dictionary//\./\\.}" || true)) || true) + echo "${config_old}" >.cspell.json + trap - SIGINT + cat >.github/.cspell/rust-dependencies.txt <>.github/.cspell/rust-dependencies.txt - fi - if [[ -z "${REMOVE_UNUSED_WORDS:-}" ]]; then - check_diff .github/.cspell/rust-dependencies.txt - fi - if ! grep -Fq '.github/.cspell/rust-dependencies.txt linguist-generated' .gitattributes; then - error "you may want to mark .github/.cspell/rust-dependencies.txt linguist-generated" - fi + if [[ -n "${dependencies_words:-}" ]]; then + echo $'\n'"${dependencies_words}" >>.github/.cspell/rust-dependencies.txt + fi + if [[ -z "${REMOVE_UNUSED_WORDS:-}" ]]; then + check_diff .github/.cspell/rust-dependencies.txt + fi + if ! grep -Fq '.github/.cspell/rust-dependencies.txt linguist-generated' .gitattributes; then + error "you may want to mark .github/.cspell/rust-dependencies.txt linguist-generated" + fi - info "running \`npx -y cspell --no-progress --no-summary \$(git ls-files)\`" - if ! npx -y cspell --no-progress --no-summary $(git ls-files); then - error "spellcheck failed: please fix uses of above words or add to ${project_dictionary} if correct" - fi + info "running \`npx -y cspell --no-progress --no-summary \$(git ls-files)\`" + if ! npx -y cspell --no-progress --no-summary $(git ls-files); then + error "spellcheck failed: please fix uses of above words or add to ${project_dictionary} if correct" + fi - # Make sure the project-specific dictionary does not contain duplicated words. - for dictionary in .github/.cspell/*.txt; do - if [[ "${dictionary}" == "${project_dictionary}" ]]; then - continue - fi - dup=$(sed '/^$/d' "${project_dictionary}" "${dictionary}" | LC_ALL=C sort -f | uniq -d -i | (grep -v '//.*' || true)) - if [[ -n "${dup}" ]]; then - error "duplicated words in dictionaries; please remove the following words from ${project_dictionary}" - echo "=======================================" - echo "${dup}" - echo "=======================================" - fi - done + # Make sure the project-specific dictionary does not contain duplicated words. + for dictionary in .github/.cspell/*.txt; do + if [[ "${dictionary}" == "${project_dictionary}" ]]; then + continue + fi + dup=$(sed '/^$/d' "${project_dictionary}" "${dictionary}" | LC_ALL=C sort -f | uniq -d -i | (grep -v '//.*' || true)) + if [[ -n "${dup}" ]]; then + error "duplicated words in dictionaries; please remove the following words from ${project_dictionary}" + echo "=======================================" + echo "${dup}" + echo "=======================================" + fi + done - # Make sure the project-specific dictionary does not contain unused words. - if [[ -n "${REMOVE_UNUSED_WORDS:-}" ]]; then - grep_args=() - for word in $(grep -Ev '^//.*' "${project_dictionary}" || true); do - if ! grep -Eqi "^${word}$" <<<"${all_words}"; then - grep_args+=(-e "^${word}$") - fi - done - if [[ ${#grep_args[@]} -gt 0 ]]; then - info "removing unused words from ${project_dictionary}" - res=$(grep -Ev "${grep_args[@]}" "${project_dictionary}") - printf '%s\n' "${res}" >|"${project_dictionary}" - fi - else - unused='' - for word in $(grep -Ev '^//.*' "${project_dictionary}" || true); do - if ! grep -Eqi "^${word}$" <<<"${all_words}"; then - unused+="${word}"$'\n' - fi - done - if [[ -n "${unused}" ]]; then - error "unused words in dictionaries; please remove the following words from ${project_dictionary} or run ${0##*/} with REMOVE_UNUSED_WORDS=1" - printf '=======================================\n' - printf '%s' "${unused}" - printf '=======================================\n' - fi + # Make sure the project-specific dictionary does not contain unused words. + if [[ -n "${REMOVE_UNUSED_WORDS:-}" ]]; then + grep_args=() + for word in $(grep -Ev '^//.*' "${project_dictionary}" || true); do + if ! grep -Eqi "^${word}$" <<<"${all_words}"; then + grep_args+=(-e "^${word}$") + fi + done + if [[ ${#grep_args[@]} -gt 0 ]]; then + info "removing unused words from ${project_dictionary}" + res=$(grep -Ev "${grep_args[@]}" "${project_dictionary}") + printf '%s\n' "${res}" >|"${project_dictionary}" + fi + else + unused='' + for word in $(grep -Ev '^//.*' "${project_dictionary}" || true); do + if ! grep -Eqi "^${word}$" <<<"${all_words}"; then + unused+="${word}"$'\n' fi + done + if [[ -n "${unused}" ]]; then + error "unused words in dictionaries; please remove the following words from ${project_dictionary} or run ${0##*/} with REMOVE_UNUSED_WORDS=1" + printf '=======================================\n' + printf '%s' "${unused}" + printf '=======================================\n' + fi fi + fi fi if [[ -n "${should_fail:-}" ]]; then - exit 1 + exit 1 fi