diff --git a/pkgs/by-name/bl/bloop/package.nix b/pkgs/by-name/bl/bloop/package.nix index 0fb6d58c68c8..1690f454f309 100644 --- a/pkgs/by-name/bl/bloop/package.nix +++ b/pkgs/by-name/bl/bloop/package.nix @@ -7,49 +7,40 @@ jre, lib, zlib, + callPackage, + runCommand, + zsh, + fish, }: -stdenv.mkDerivation rec { +let pname = "bloop"; - version = "2.1.1"; + sources = lib.importJSON ./sources.json; + inherit (sources) + repo + version + assets + completions + ; - platform = - if stdenv.hostPlatform.isLinux && stdenv.hostPlatform.isx86_64 then - "x86_64-pc-linux" - else if stdenv.hostPlatform.isDarwin && stdenv.hostPlatform.isx86_64 then - "x86_64-apple-darwin" - else if stdenv.hostPlatform.isDarwin && stdenv.hostPlatform.isAarch64 then - "aarch64-apple-darwin" - else - throw "unsupported platform"; + platforms = builtins.attrNames assets; - bloop-bash = fetchurl { - url = "https://github.com/scalacenter/bloop/releases/download/v${version}/bash-completions"; - sha256 = "sha256-2mt+zUEJvQ/5ixxFLZ3Z0m7uDSj/YE9sg/uNMjamvdE="; - }; + fetchAsset = + { asset, hash }: + fetchurl { + url = "https://github.com/${repo}/releases/download/v${version}/${asset}"; + inherit hash; + }; - bloop-fish = fetchurl { - url = "https://github.com/scalacenter/bloop/releases/download/v${version}/fish-completions"; - sha256 = "sha256-eFESR6iPHRDViGv+Fk3sCvPgVAhk2L1gCG4LnfXO/v4="; - }; - - bloop-zsh = fetchurl { - url = "https://github.com/scalacenter/bloop/releases/download/v${version}/zsh-completions"; - sha256 = "sha256-WNMsPwBfd5EjeRbRtc06lCEVI2FVoLfrqL82OR0G7/c="; - }; - - bloop-binary = fetchurl { - url = "https://github.com/scalacenter/bloop/releases/download/v${version}/bloop-${platform}"; - sha256 = - if stdenv.hostPlatform.isLinux && stdenv.hostPlatform.isx86_64 then - "sha256-F5wRihAwf/TNBSYortTCoK9qKqTI+1N5InJ+rqLFp8A=" - else if stdenv.hostPlatform.isDarwin && stdenv.hostPlatform.isx86_64 then - "sha256-wQXAldzU6Typ6pZB8k3dfX7g+aaVF7jXvd0pnuk5gZU=" - else if stdenv.hostPlatform.isDarwin && stdenv.hostPlatform.isAarch64 then - "sha256-OrONKbC2l0jjfmguDmoiyEaJWdTrKBiP0ZEa5rhizDM=" - else - throw "unsupported platform"; - }; + bloop-binary = fetchAsset ( + assets.${stdenv.hostPlatform.system} or (throw "Unsupported platform ${stdenv.hostPlatform.system}") + ); + bloop-bash = fetchAsset completions.bash; + bloop-fish = fetchAsset completions.fish; + bloop-zsh = fetchAsset completions.zsh; +in +stdenv.mkDerivation (finalAttrs: { + inherit pname version; dontUnpack = true; nativeBuildInputs = [ @@ -61,37 +52,82 @@ stdenv.mkDerivation rec { (lib.getLib stdenv.cc.cc) zlib ]; - propagatedBuildInputs = [ jre ]; - installPhase = '' - runHook preInstall + # upstream documents JDK 17 or higher for the CLI, which is what this packages + installPhase = + assert lib.assertMsg (lib.versionAtLeast jre.version "17.0.0") '' + bloop requires Java 17 or newer, but ${jre.name} is ${jre.version} + ''; + '' + runHook preInstall - install -D -m 0755 ${bloop-binary} $out/.bloop-wrapped + install -D -m 0755 ${bloop-binary} $out/.bloop-wrapped - makeWrapper $out/.bloop-wrapped $out/bin/bloop + # The client starts a jvm build server, so it needs a jre on PATH and in + # JAVA_HOME; propagating the jre put it in the closure but on nobody's PATH. + makeWrapper $out/.bloop-wrapped $out/bin/bloop \ + --prefix PATH : ${lib.makeBinPath [ jre ]} \ + --set JAVA_HOME ${jre.home} - #Install completions - installShellCompletion --name bloop --bash ${bloop-bash} - installShellCompletion --name _bloop --zsh ${bloop-zsh} - installShellCompletion --name bloop.fish --fish ${bloop-fish} + #Install completions + installShellCompletion --name bloop --bash ${bloop-bash} + installShellCompletion --name _bloop --zsh ${bloop-zsh} + installShellCompletion --name bloop.fish --fish ${bloop-fish} - runHook postInstall - ''; + runHook postInstall + ''; + + passthru = { + updateScript = { + command = lib.getExe (callPackage ./update.nix { }); + supportedFeatures = [ "commit" ]; + }; + + tests.help = runCommand "${pname}-help" { nativeBuildInputs = [ finalAttrs.finalPackage ]; } '' + export HOME="$TMPDIR" + + # Anything that reaches the build server, `bloop --version` and + # `bloop about` included, downloads it from Maven Central first and so + # cannot run in the sandbox. --help is answered by the native client + # alone, and still exercises the patched binary and its wrapper. + bloop --help > help.txt + grep -q 'Interact with Bloop' help.txt + grep -q -- '--java-home' help.txt + + touch $out + ''; + + tests.completions = + runCommand "${pname}-completions" + { + nativeBuildInputs = [ + zsh + fish + ]; + } + '' + share=${finalAttrs.finalPackage}/share + + bash -n "$share/bash-completion/completions/bloop" + zsh -n "$share/zsh/site-functions/_bloop" + fish -n "$share/fish/vendor_completions.d/bloop.fish" + + touch $out + ''; + }; meta = { homepage = "https://scalacenter.github.io/bloop/"; + changelog = "https://github.com/${repo}/releases/tag/v${version}"; sourceProvenance = with lib.sourceTypes; [ binaryNativeCode ]; license = lib.licenses.asl20; description = "Scala build server and command-line tool to make the compile and test developer workflows fast and productive in a build-tool-agnostic way"; mainProgram = "bloop"; - platforms = [ - "x86_64-linux" - "aarch64-darwin" - ]; + inherit platforms; maintainers = with lib.maintainers; [ agilesteel kubukoz tomahna ]; }; -} +}) diff --git a/pkgs/by-name/bl/bloop/sources.json b/pkgs/by-name/bl/bloop/sources.json new file mode 100644 index 000000000000..b7184133bcdd --- /dev/null +++ b/pkgs/by-name/bl/bloop/sources.json @@ -0,0 +1,28 @@ +{ + "repo": "scalacenter/bloop", + "version": "2.1.1", + "completions": { + "bash": { + "asset": "bash-completions", + "hash": "sha256-2mt+zUEJvQ/5ixxFLZ3Z0m7uDSj/YE9sg/uNMjamvdE=" + }, + "fish": { + "asset": "fish-completions", + "hash": "sha256-RF6nZxbw4sLwCP4irKJLYCZnlkaRdEkmpYkOHBoSF/8=" + }, + "zsh": { + "asset": "zsh-completions", + "hash": "sha256-WNMsPwBfd5EjeRbRtc06lCEVI2FVoLfrqL82OR0G7/c=" + } + }, + "assets": { + "aarch64-darwin": { + "asset": "bloop-aarch64-apple-darwin", + "hash": "sha256-OrONKbC2l0jjfmguDmoiyEaJWdTrKBiP0ZEa5rhizDM=" + }, + "x86_64-linux": { + "asset": "bloop-x86_64-pc-linux", + "hash": "sha256-F5wRihAwf/TNBSYortTCoK9qKqTI+1N5InJ+rqLFp8A=" + } + } +} diff --git a/pkgs/by-name/bl/bloop/update.nix b/pkgs/by-name/bl/bloop/update.nix new file mode 100644 index 000000000000..d8464fcbb8f3 --- /dev/null +++ b/pkgs/by-name/bl/bloop/update.nix @@ -0,0 +1,156 @@ +{ + writeShellApplication, + coreutils, + curl, + git, + jq, + nix, +}: + +writeShellApplication { + name = "update-bloop"; + + runtimeInputs = [ + coreutils + curl + git + jq + nix + ]; + + text = '' + # stdout is reserved for the JSON expected by the `commit` updateScript + # feature, everything else has to go to stderr. + attr_path="''${UPDATE_NIX_ATTR_PATH:-bloop}" + + nixpkgs=$(git rev-parse --show-toplevel) + position=$(nix-instantiate --eval --json --attr "$attr_path.meta.position" "$nixpkgs" \ + | jq --raw-output .) + + # Everything else is read out of the file that is about to be rewritten. + sources_json="$(dirname "''${position%:*}")/sources.json" + repo=$(jq --raw-output .repo "$sources_json") + old_version=$(jq --raw-output .version "$sources_json") + + auth=() + if [[ -n "''${GITHUB_TOKEN:-}" ]]; then + auth=(--header "Authorization: Bearer $GITHUB_TOKEN") + fi + + release=$(curl --silent --show-error --fail "''${auth[@]}" \ + "https://api.github.com/repos/$repo/releases/latest") + new_version=$(jq --raw-output '.tag_name // "" | ltrimstr("v")' <<< "$release") + + # both versions end up inside Nix expressions below + version_pattern='^[0-9][0-9A-Za-z.+-]*$' + + if [[ ! "$new_version" =~ $version_pattern ]]; then + echo "$repo published an implausible version: '$new_version'" >&2 + exit 1 + fi + + if [[ ! "$old_version" =~ $version_pattern ]]; then + echo "$sources_json holds an implausible version: '$old_version'" >&2 + exit 1 + fi + + order=$(nix-instantiate --eval \ + --expr "builtins.compareVersions \"$new_version\" \"$old_version\"") + + case "$order" in + 0) + echo "$attr_path is already at the latest version $new_version." >&2 + echo '[]' + exit 0 + ;; + -1) + echo "refusing to downgrade $attr_path from $old_version to $new_version." >&2 + exit 1 + ;; + esac + + # An asset that disappeared would otherwise surface as a bare 404 from + # nix-prefetch-url further down. + known=$(jq '[ to_entries[] | .value | objects | .[].asset ]' "$sources_json") + missing=$(jq --raw-output --argjson published "$(jq '[ .assets[].name ]' <<< "$release")" \ + '[ .[] | select(IN($published[]) | not) ] | join(", ")' <<< "$known") + + if [[ -n "$missing" ]]; then + echo "v$new_version does not ship $missing, listed in $sources_json" >&2 + exit 1 + fi + + prefetch() { + local hash + hash=$(nix-prefetch-url --type sha256 \ + "https://github.com/$repo/releases/download/v$new_version/$1") + + nix-hash --to-sri --type sha256 "$hash" + } + + #
-> { "": { asset, hash }, ... } for each key in that section + collect() { + local section="$1" + local objects=() + local key asset hash + + while read -r key; do + asset=$(jq --raw-output --arg section "$section" --arg key "$key" \ + '.[$section][$key].asset' "$sources_json") + hash=$(prefetch "$asset") + + objects+=("$(jq --null-input --compact-output \ + --arg key "$key" \ + --arg asset "$asset" \ + --arg hash "$hash" \ + '{ ($key): { asset: $asset, hash: $hash } }')") + done < <(jq --raw-output --arg section "$section" '.[$section] | keys[]' "$sources_json") + + if [[ ''${#objects[@]} -eq 0 ]]; then + echo '{}' + return + fi + + printf '%s\n' "''${objects[@]}" | jq --slurp add + } + + # Every object-valued key is a section of { : { asset, hash } }, repo + # and version being strings. Which sections exist is therefore data too. + sections='{}' + + while read -r section; do + entries=$(collect "$section") + sections=$(jq --arg section "$section" --argjson entries "$entries" \ + '. + { ($section): $entries }' <<< "$sections") + done < <(jq --raw-output 'to_entries[] | select(.value | type == "object") | .key' "$sources_json") + + # Write beside the target and move into place, so that a failure cannot + # leave a truncated sources.json and an unbuildable package behind. + tmp=$(mktemp "$sources_json.XXXXXX") + trap 'rm -f "$tmp"' EXIT + + jq --null-input \ + --arg repo "$repo" \ + --arg version "$new_version" \ + --argjson sections "$sections" \ + '{ repo: $repo, version: $version } + $sections' \ + > "$tmp" + + chmod --reference="$sources_json" "$tmp" + mv "$tmp" "$sources_json" + + jq --null-input --compact-output \ + --arg attrPath "$attr_path" \ + --arg oldVersion "$old_version" \ + --arg newVersion "$new_version" \ + --arg file "$sources_json" \ + --arg repo "$repo" \ + '[ { + attrPath: $attrPath, + oldVersion: $oldVersion, + newVersion: $newVersion, + files: [ $file ], + commitBody: "https://github.com/\($repo)/releases/tag/v\($newVersion)" + } ]' + ''; +}