Download raw body.
devel/cabal: add port update tools
I've grown tired of toiling, so here's a bit of automation which does
all the easy parts of updating cabal-module(5) based ports.
I have a sequence of commits extracting MODCABAL_MANIFEST into cabal.inc
files which I want to commit as a follow-up. Here's the sequence of
commits, look for "extract cabal.inc" commits:
https://github.com/blackgnezdo/ports/commits/automate-openbsd-cabal-updates/
Not making a secret that this was made with help of Claude Opus. I read
and iterated on the code, so I stand behind this code. If people like
it, I'm happy to get an OK and maintain it in the tree. Otherwise, I can
leave it out and run myself.
Thanks
Greg
From 5abcdd33e5defdbb0091b258d7a3ac1362b90ad7 Mon Sep 17 00:00:00 2001
From: Claude <noreply@anthropic.com>
Date: Mon, 29 Dec 2025 02:32:21 +0000
Subject: [PATCH] devel/cabal: add port update tools
Add update-cabal-port.pl and update-all-cabal-ports.sh to help
maintain cabal ports. The Perl script uses cabal database to fetch
version and generates cabal.inc files with dependency manifests.
The batch script finds all ports using devel/cabal module and can update
them in sequence, with optional git commits per port.
devel/cabal: use make show= and simplify update script
- Use OpenBSD ports `make show=VAR` instead of parsing Makefiles
- Handle MODCABAL_REVISION from cabal-bundler output
- Remove REVISION variable on port updates
---
devel/cabal/tools/update-all-cabal-ports.sh | 74 +++++++++
devel/cabal/tools/update-cabal-port.pl | 169 ++++++++++++++++++++
2 files changed, 243 insertions(+)
create mode 100755 devel/cabal/tools/update-all-cabal-ports.sh
create mode 100755 devel/cabal/tools/update-cabal-port.pl
diff --git a/devel/cabal/tools/update-all-cabal-ports.sh b/devel/cabal/tools/update-all-cabal-ports.sh
new file mode 100755
index 00000000000..1f65810a6ad
--- /dev/null
+++ b/devel/cabal/tools/update-all-cabal-ports.sh
@@ -0,0 +1,74 @@
+#!/bin/ksh
+#
+# Update all cabal ports to latest versions
+#
+# Usage: update-all-cabal-ports.sh [options]
+#
+# Options:
+# --package Run 'make package' for each port
+# --git-commit Create git commit for each successful update
+# --help Show this help message
+#
+
+set -e
+
+usage() {
+ sed -n '2,/^$/s/^# \?//p' < "$0"
+ exit "${1:-0}"
+}
+
+typeset -i RUN_PACKAGE=0 GIT_COMMIT=0
+
+while [[ $# -gt 0 ]]; do
+ case "$1" in
+ --package) RUN_PACKAGE=1 ;;
+ --git-commit) GIT_COMMIT=1 ;;
+ --help|-h) usage 0 ;;
+ *) print -u2 "Unknown option: $1"; usage 1 ;;
+ esac
+ shift
+done
+
+UPDATE_SCRIPT="./devel/cabal/tools/update-cabal-port.pl"
+[[ -x "$UPDATE_SCRIPT" ]] || { print -u2 "Update script not found: $UPDATE_SCRIPT"; exit 1; }
+
+CABAL_PORTS=$(grep -rl '^MODULES.*=.*devel/cabal' */*/Makefile | sed 's|/Makefile$||' | sort)
+typeset -i TOTAL=$(print "$CABAL_PORTS" | wc -l)
+
+print "Found $TOTAL cabal ports"
+
+typeset -i SUCCESS=0 SKIPPED=0 FAILED=0 CURRENT=0
+
+for port in $CABAL_PORTS; do
+ ((++CURRENT))
+
+ print "==> [$CURRENT/$TOTAL] $port"
+
+ UPDATE_CMD="$UPDATE_SCRIPT $port"
+ ((RUN_PACKAGE)) && UPDATE_CMD="$UPDATE_CMD --package"
+
+ if $UPDATE_CMD; then
+ cd "$port"
+ if git diff --quiet Makefile cabal.inc 2>/dev/null; then
+ ((++SKIPPED))
+ else
+ ((++SUCCESS))
+ if ((GIT_COMMIT)); then
+ VERSION=$(make show=MODCABAL_VERSION)
+ git add Makefile cabal.inc distinfo 2>/dev/null
+ git commit -m "$port: update to $VERSION"
+ fi
+ fi
+ cd - >/dev/null
+ else
+ print -u2 "==> FAILED: $port"
+ ((++FAILED))
+ print -n "Continue? [Y/n] "
+ read response
+ [[ "$response" == [nN]* ]] && break
+ fi
+done
+
+print "==> Summary: $SUCCESS updated, $SKIPPED unchanged, $FAILED failed"
+((FAILED)) && exit 1
+exit 0
diff --git a/devel/cabal/tools/update-cabal-port.pl b/devel/cabal/tools/update-cabal-port.pl
new file mode 100755
index 00000000000..8e5eaab182d
--- /dev/null
+++ b/devel/cabal/tools/update-cabal-port.pl
@@ -0,0 +1,169 @@
+#!/usr/bin/perl
+#
+# Update a cabal port to a new version
+#
+# Usage: update-cabal-port.pl <port-directory> [options]
+#
+# Options:
+# --version <ver> Update to specific version (default: latest from Hackage)
+# --package Run 'make package' after update
+# --help Show this help message
+#
+
+use v5.36;
+use Getopt::Long qw(:config no_ignore_case);
+
+sub usage($exit_code = 0) {
+ open my $fh, '<', $0 or die "Cannot read $0: $!\n";
+ while (<$fh>) {
+ last if /^$/;
+ next unless s/^# ?//;
+ print;
+ }
+ exit $exit_code;
+}
+
+my %opt = (version => '', package => 0, help => 0);
+
+GetOptions(
+ 'version=s' => \$opt{version},
+ 'package' => \$opt{package},
+ 'help|h' => \$opt{help},
+) or usage(1);
+
+usage(0) if $opt{help};
+
+my $port_dir = shift @ARGV or do { say STDERR "Error: Port directory required"; usage(1) };
+
+chdir $port_dir or die "Cannot chdir to $port_dir: $!\n";
+
+# Extract configuration via make show=
+my $stem = make_show('MODCABAL_STEM') or die "MODCABAL_STEM not set\n";
+my $current_version = make_show('MODCABAL_VERSION');
+my $executables = make_show('MODCABAL_EXECUTABLES');
+
+# Determine target version
+my $target_version = $opt{version} || get_latest_version($stem);
+die "Error: Could not determine version for $stem\n" unless $target_version;
+
+if ($current_version && $current_version eq $target_version) {
+ say "==> $port_dir: already at $target_version";
+ exit 0;
+}
+
+say "==> $port_dir: $current_version -> $target_version";
+
+# Build cabal-bundler command
+my @bundler_args = ('--openbsd', "$stem-$target_version");
+if ($executables) {
+ my $exec = $executables =~ s/\$\{[^}]+\}//gr;
+ $exec =~ s/^\s+|\s+$//g;
+ if ($exec) {
+ push @bundler_args, '--executable', $_ for split /\s+/, $exec;
+ }
+}
+
+my $bundler_cmd = "cabal-bundler " . join(' ', @bundler_args);
+my $output = `$bundler_cmd 2>&1`;
+die "$bundler_cmd failed:\n$output\n" if $?;
+
+# Parse cabal-bundler output for MODCABAL_MANIFEST and MODCABAL_REVISION
+my (@deps, $revision);
+my $in_manifest = 0;
+for (split /\n/, $output) {
+ if (/^MODCABAL_REVISION\s*=\s*(\d+)/) {
+ $revision = $1;
+ } elsif (/^MODCABAL_MANIFEST\s*=\s*(.*)/) {
+ $in_manifest = 1;
+ push @deps, extract_deps($1);
+ } elsif ($in_manifest && /^\s+(.*)/) {
+ push @deps, extract_deps($1);
+ $in_manifest = 0 unless /\\$/;
+ } else {
+ $in_manifest = 0;
+ }
+}
+
+if (@deps) {
+ open my $fh, '>', 'cabal.inc' or die "Cannot write cabal.inc: $!\n";
+ say $fh "MODCABAL_MANIFEST\t= \\";
+ while (@deps >= 3) {
+ my ($pkg, $ver, $rev) = splice(@deps, 0, 3);
+ if (@deps) {
+ say $fh "\t$pkg\t$ver\t$rev\t\\";
+ } else {
+ say $fh "\t$pkg\t$ver\t$rev";
+ }
+ }
+ close $fh;
+}
+
+# Update Makefile: set MODCABAL_VERSION, MODCABAL_REVISION, remove REVISION
+open my $in, '<', 'Makefile' or die "Cannot read Makefile: $!\n";
+my @lines = <$in>;
+close $in;
+
+my $found_version = 0;
+my $found_revision = 0;
+for (@lines) {
+ if (/^MODCABAL_VERSION\s*=/) {
+ $_ = "MODCABAL_VERSION =\t$target_version\n";
+ $found_version = 1;
+ } elsif (/^MODCABAL_REVISION\s*=/) {
+ if (defined $revision) {
+ $_ = "MODCABAL_REVISION =\t$revision\n";
+ } else {
+ $_ = ''; # Remove if no revision in new version
+ }
+ $found_revision = 1;
+ } elsif (/^REVISION\s*=/) {
+ $_ = ''; # Remove REVISION on update
+ }
+}
+
+# Add MODCABAL_REVISION after MODCABAL_VERSION if needed
+if (defined $revision && !$found_revision) {
+ for my $i (0..$#lines) {
+ if ($lines[$i] =~ /^MODCABAL_VERSION\s*=/) {
+ splice @lines, $i+1, 0, "MODCABAL_REVISION =\t$revision\n";
+ last;
+ }
+ }
+}
+
+open my $out, '>', 'Makefile' or die "Cannot write Makefile: $!\n";
+print $out grep { $_ ne '' } @lines;
+close $out;
+
+# Run make makesum
+system('make', 'makesum') == 0 or die "make makesum failed\n";
+
+# Run make package
+if ($opt{package}) {
+ system('make', 'package') == 0 or warn "make package failed\n";
+}
+
+#
+# Helpers
+#
+
+sub make_show($var) {
+ my $val = `make show=$var`;
+ die "make show=$var failed\n" if $?;
+ chomp $val;
+ return $val eq '' ? undef : $val;
+}
+
+sub get_latest_version($package) {
+ my $output = `cabal list --simple-output '^$package\$' 2>/dev/null`;
+ return unless $? == 0 && $output;
+
+ my @lines = split /\n/, $output;
+ return $1 if @lines && $lines[-1] =~ /^\Q$package\E\s+(\S+)$/i;
+ return;
+}
+
+sub extract_deps($line) {
+ $line =~ s/\s*\\$//;
+ return split /\s+/, $line;
+}
--
2.51.2
devel/cabal: add port update tools