From fe779c41b1c7b6251e2b74e2700d6c1d496d6e0b Mon Sep 17 00:00:00 2001 From: Javier Sagredo Date: Mon, 4 May 2026 02:26:18 +0200 Subject: [PATCH] Add classgraph-plugin-flag.sh for unintrusive plugin usage --- classgraph-plugin-flag.sh | 98 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100755 classgraph-plugin-flag.sh diff --git a/classgraph-plugin-flag.sh b/classgraph-plugin-flag.sh new file mode 100755 index 0000000..bf63968 --- /dev/null +++ b/classgraph-plugin-flag.sh @@ -0,0 +1,98 @@ +#!/usr/bin/env bash +# +# Print a -fplugin-library= line that lets a downstream package use the +# classgraph plugin WITHOUT adding `classgraph` to its build-depends. This +# avoids the plugin's own dependency bounds (aeson, ghc, file-embed, …) +# leaking into the consumer's build plan — useful for projects with tight +# bounds, like ouroboros-consensus or cardano-ledger. +# +# Usage: +# +# # Print the flag (default plugin output dir = .classgraph): +# ./classgraph-plugin-flag.sh +# +# # With a custom output dir: +# ./classgraph-plugin-flag.sh --dir my-output +# +# # As a snippet ready to drop into cabal.project.local: +# ./classgraph-plugin-flag.sh --cabal >> cabal.project.local +# +# # Per-package form, for one package only: +# ./classgraph-plugin-flag.sh --cabal --package ouroboros-consensus +# +# After pasting into the consumer's cabal.project.local, run +# +# cabal build +# +# in the consumer; it will load classgraph from the prebuilt .so and emit +# JSON dumps under the given directory. Re-run this script (and re-paste, +# or just re-source the line) whenever you rebuild classgraph itself — +# the inplace-suffix in the package id is regenerated on each build. + +set -euo pipefail + +cd "$(dirname "$(readlink -f "$0")")" + +DIR=.classgraph +EMIT=raw # raw | cabal +PACKAGE= +while [ $# -gt 0 ]; do + case "$1" in + --dir) DIR=$2; shift 2 ;; + --cabal) EMIT=cabal; shift ;; + --package) PACKAGE=$2; shift 2 ;; + -h|--help) + sed -n '/^#$/q;/^# /{s/^# \?//;p;}' "$0" + exit 0 ;; + *) echo "unknown argument: $1" >&2; exit 2 ;; + esac +done + +# Make sure the .so exists; build silently if not. +if [ -z "$(find dist-newstyle -type f -name 'libHSclassgraph-*.so' 2>/dev/null | head -1)" ]; then + cabal build --silent lib:classgraph +fi + +SO=$(find dist-newstyle -type f -name 'libHSclassgraph-*.so' | head -1) +if [ -z "$SO" ]; then + echo "Could not find a libHSclassgraph-*.so under dist-newstyle/." >&2 + echo "Run \`cabal build classgraph\` first." >&2 + exit 1 +fi +SO=$(readlink -f "$SO") + +# Derive the package-id from the .so file name: +# libHS-ghc.so +basename=${SO##*/libHS} +basename=${basename%.so} +PKG_ID=${basename%-ghc*} + +# GHC's parser (parseExternalPluginSpec) reads the args field with +# `readMaybe :: String -> Maybe [String]`, so it must be a Haskell list +# literal — `["dir=.classgraph"]`, not `dir=.classgraph` or +# `[dir=.classgraph]`. +# +# In the cabal-config form we wrap the args portion in cabal's `"..."` +# token grouping and escape the inner Haskell quotes with `\"`. The +# bytes that end up in cabal.project.local look like: +# ;"[\"dir=DIR\"]" +# which cabal hands to GHC as `["dir=DIR"]`. +# +# In the raw form we leave the `"`s as plain quotes, since users +# typically wrap the value in single quotes when pasting into a shell. +RAW_FLAG='-fplugin-library='"${SO};${PKG_ID};Classgraph.Plugin;[\"dir=${DIR}\"]" +CABAL_FLAG=$(printf '%s;%s;Classgraph.Plugin;"[\\"dir=%s\\"]"' \ + "-fplugin-library=$SO" "$PKG_ID" "$DIR") + +case "$EMIT" in + raw) + echo "$RAW_FLAG" + ;; + cabal) + if [ -n "$PACKAGE" ]; then + printf 'package %s\n ghc-options:\n %s\n' "$PACKAGE" "$CABAL_FLAG" + else + printf 'program-options\n ghc-options:\n %s\n' "$CABAL_FLAG" + fi + ;; +esac -- 2.54.0