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 <[email protected]> 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
