#!/usr/bin/env fish # larry: Gentoo overlay management tool. # # Usage: # larry bump Copy latest ebuild to new version, then prepare # larry bump --revision Create next revision of latest ebuild, then prepare # larry prepare [...] Download distfiles, generate dep caches, create Manifest # larry prepare --all Prepare every ebuild in the overlay # larry import [--from ] Import ebuild from another overlay # larry test [options] Test-build an ebuild locally # larry upstream [--fresh] [--verbose] Check all packages for upstream version updates (skips prereleases) # # Examples: # larry bump dev-util/fish-lsp 1.2.0 # larry bump --revision dev-util/fish-lsp # larry bump --prereleases dev-util/my-tool 1.0.0-alpha1 # larry prepare dev-util/fish-lsp/fish-lsp-1.1.3.ebuild # larry test dev-util/fish-lsp # larry test --compile dev-util/javy-8.1.1 # larry upstream --fresh set -g OVERLAY_DIR (realpath (dirname (status --current-filename))/..) set -g LARRY_CACHE (test -n "$XDG_CACHE_HOME" && echo "$XDG_CACHE_HOME/larry" || echo "$HOME/.cache/larry") set -g DISTDIR $LARRY_CACHE/distfiles set -g GH_REPO (git -C $OVERLAY_DIR remote get-url origin | string replace -r '.*github\.com[:/]' '' | string replace -r '\.git$' '') set -g CACHE_DIR "$LARRY_CACHE/overlay-version-check-cache" set -g CACHE_MAX_AGE_HOURS 6 set -g CHECKED_PACKAGES set -g COLOR_RESET \e\[0m set -g COLOR_GREEN \e\[32m set -g COLOR_RED \e\[31m set -g COLOR_YELLOW \e\[33m set -g COLOR_BOLD \e\[1m function msg printf "$COLOR_BOLD$COLOR_GREEN>>>$COLOR_RESET %s\n" "$argv" end function warn printf "$COLOR_BOLD$COLOR_YELLOW***$COLOR_RESET %s\n" "$argv" >&2 end function err printf "$COLOR_BOLD$COLOR_RED!!!$COLOR_RESET %s\n" "$argv" >&2 end function print_usage echo "Usage:" echo " larry bump Version bump, then prepare" echo " larry bump --revision Revision bump, then prepare" echo " larry prepare [...] Fetch distfiles, gen caches, manifest" echo " larry prepare --all Prepare every ebuild in the overlay" echo " larry import Import ebuild from another overlay (::guru, ::gentoo)" echo " larry test [options] Test-build locally (default: fetch+unpack)" echo " larry test --emerge Real emerge in a clean container (GPU passthrough)" echo " larry upstream [--fresh] [--verbose] Check for upstream version updates (skips prereleases)" echo "" echo "Examples:" echo " larry bump dev-util/fish-lsp 1.2.0" echo " larry bump --revision dev-util/fish-lsp" echo " larry bump --prereleases dev-util/my-tool 1.0.0-alpha1" echo " larry prepare dev-util/vtsls/vtsls-0.3.0.ebuild" echo " larry test --compile dev-util/javy-8.1.1" echo " larry test --emerge dev-util/fish-lsp" echo " larry upstream --fresh" end # ── Shared helpers ────────────────────────────────────────────────────────────── # Expand ${PN}, ${PV}, ${P}, and custom ebuild variables in a string. # Handles nested variable references (e.g., MY_WEBUI_P="${MY_WEBUI_NPM_NAME}-0.75.3") # by iterating until no more substitutions occur. function expand_ebuild_vars -a text ebuild_path pn pv p set -l changed true set -l iterations 0 while $changed set changed false set iterations (math $iterations + 1) if test $iterations -gt 10 break end for line in (grep -E '^[A-Z_]+="[^"]*"' $ebuild_path) set -l key (string replace -r '=.*' '' $line) set -l val (string match -r '"([^"]*)"' $line)[2] if string match -q "*\${$key}*" $text set text (string replace -a "\${$key}" $val $text) set changed true end end if string match -q '*${PN}*' $text set text (string replace -a '${PN}' $pn $text) set changed true end if string match -q '*${PV}*' $text set text (string replace -a '${PV}' $pv $text) set changed true end if string match -q '*${P}*' $text set text (string replace -a '${P}' $p $text) set changed true end end echo $text end # Parse PN, PV, P from an ebuild file path. function parse_ebuild -a ebuild_path set -g _pn (basename (dirname $ebuild_path)) set -l ebuild_basename (basename $ebuild_path .ebuild) set -g _pvr (string replace "$_pn-" "" $ebuild_basename) set -g _pv (string replace -r -- '-r[0-9]+$' '' $_pvr) set -g _p "$_pn-$_pv" set -g _pr "$_pn-$_pvr" end function detect_type -a ebuild_path if grep -q 'mirror://npm/' $ebuild_path echo npm else if grep -q '^CRATES="' $ebuild_path echo cargo else if grep -q 'inherit.*go-module' $ebuild_path echo go else echo generic end end # Download all npm distfiles for every ebuild in a package so pkgdev manifest can find them. # pkgdev cannot resolve mirror://npm/ (it's a fake mirror prefix), so we pre-populate DISTDIR. function npm_fetch_all_distfiles -a atom set -l pkg_dir "$OVERLAY_DIR/$atom" if not test -d $pkg_dir return end for ebuild in (find $pkg_dir -name '*.ebuild' | sort) if not grep -q 'mirror://npm/' $ebuild continue end set -l pn (basename (dirname $ebuild)) set -l ebuild_bn (basename $ebuild .ebuild) set -l pvr (string replace "$pn-" "" $ebuild_bn) set -l pv (string replace -r -- '-r[0-9]+$' '' $pvr) set -l p "$pn-$pv" set -l npm_lines (grep 'mirror://npm/' $ebuild | sed -E 's/^\s*[A-Za-z0-9_+-]+\? ?\( //;s/\) //;s/^[[:space:]]+//;s/[[:space:]]+$//') for line in $npm_lines set -l expanded (expand_ebuild_vars "$line" $ebuild $pn $pv $p) set -l df_name set -l url_part if string match -q '* -> *' $expanded set df_name (string split ' -> ' $expanded)[2] set url_part (string split ' -> ' $expanded)[1] else set df_name (basename $expanded) set url_part $expanded end set -l url (string replace 'mirror://npm/' 'https://registry.npmjs.org/' $url_part) if not test -f "$DISTDIR/$df_name" msg " npm: $df_name" curl -sfLo "$DISTDIR/$df_name" "$url" or begin err " Failed: $url" return 1 end end end end end function generate_manifest -a ebuild_path msg "Generating Manifest..." mkdir -p $DISTDIR cd $OVERLAY_DIR set -l cat_pkg (basename (dirname (dirname $ebuild_path)))/(basename (dirname $ebuild_path)) # Pre-fetch any missing npm distfiles for all ebuilds in this package # (pkgdev cannot resolve mirror://npm/ on its own) npm_fetch_all_distfiles $cat_pkg rm -f $OVERLAY_DIR/$cat_pkg/Manifest pkgdev manifest -d $DISTDIR $cat_pkg 2>&1 or begin err "manifest generation failed" return 1 end msg "Done: $_p" echo "" end # ── prepare: npm ──────────────────────────────────────────────────────────────── function prepare_npm -a ebuild_path parse_ebuild $ebuild_path # Handle all mirror://npm/ lines separately (ebuilds may have optional deps like # webui? that also match grep; each line must be expanded independently) # Strip SRC_URI condition prefixes like "webui? (" and trailing ")" before processing set -l npm_lines (grep 'mirror://npm/' $ebuild_path | sed -E 's/^\s*[A-Za-z0-9_+-]+\? ?\( //;s/\) //;s/^[[:space:]]+//;s/[[:space:]]+$//') if test -z "$npm_lines" err "No mirror://npm/ line found in $ebuild_path" return 1 end set -l main_npm_line $npm_lines[1] set -l main_expanded (expand_ebuild_vars "$main_npm_line" $ebuild_path $_pn $_pv $_p) set -l distfile_name if string match -q '* -> *' $main_expanded set distfile_name (string split ' -> ' $main_expanded)[2] set main_expanded (string split ' -> ' $main_expanded)[1] else set distfile_name (basename $main_expanded) end set -l npm_url (string replace 'mirror://npm/' 'https://registry.npmjs.org/' $main_expanded) set -l npm_pkg (string replace -r '/-/.*' '' (string replace 'https://registry.npmjs.org/' '' $npm_url)) msg "Package: $_p" msg "npm: $npm_pkg" msg "Distfile: $distfile_name" mkdir -p $DISTDIR # Download ALL npm tarballs (main + any optional ones) so pkgdev manifest finds them for line in $npm_lines set -l expanded (expand_ebuild_vars "$line" $ebuild_path $_pn $_pv $_p) set -l df_name if string match -q '* -> *' $expanded set df_name (string split ' -> ' $expanded)[2] set expanded (string split ' -> ' $expanded)[1] else set df_name (basename $expanded) end set -l url (string replace 'mirror://npm/' 'https://registry.npmjs.org/' $expanded) if not test -f "$DISTDIR/$df_name" msg "Downloading $df_name..." curl -sfLo "$DISTDIR/$df_name" "$url" or begin err "Failed to download $url" return 1 end msg " -> $df_name ("(du -h "$DISTDIR/$df_name" | cut -f1)")" end end # Verify ebuild deps URL uses package name (PN) as release tag, not P if grep -q 'releases/download/\${P}/' $ebuild_path err "SRC_URI uses \${P} as release tag but deps upload to \${PN} tag" err "Change releases/download/\${P}/ to releases/download/\${PN}/" return 1 end msg "Generating deps tarball..." set -l work (mktemp -d) cd "$work" npm --cache ./npm-cache install "$DISTDIR/$distfile_name" 2>&1 or begin err "npm install failed" rm -rf "$work" return 1 end tar -caf "$DISTDIR/$_p-deps.tar.xz" npm-cache or begin err "tar failed" rm -rf "$work" return 1 end rm -rf "$work" msg "Deps: $DISTDIR/$_p-deps.tar.xz ("(du -h "$DISTDIR/$_p-deps.tar.xz" | cut -f1)")" msg "Uploading to GitHub release..." if gh release view $_pn --repo $GH_REPO >/dev/null 2>&1 gh release upload $_pn "$DISTDIR/$_p-deps.tar.xz" --repo $GH_REPO --clobber 2>&1 else gh release create $_pn --repo $GH_REPO \ --title "$_pn deps" \ --notes "npm dependency cache for $_pn" \ "$DISTDIR/$_p-deps.tar.xz" 2>&1 end or begin err "GitHub release upload failed" return 1 end generate_manifest $ebuild_path end # ── prepare: cargo ────────────────────────────────────────────────────────────── function update_ebuild_crates -a cargo_lock_file ebuild_path if not test -f "$cargo_lock_file" -a -f "$ebuild_path" return 1 end # Parse Cargo.lock for crates matching source = "registry+..." set -l crates (awk ' BEGIN { name=""; version=""; source="" } /^\[\[package\]\]/ { if (name && version && source ~ /^registry\+/) { print name "@" version } name=""; version=""; source="" } /^name = / { gsub(/^name = "|"$|"[ \t]*#.*/, ""); name=$0 } /^version = / { gsub(/^version = "|"$|"[ \t]*#.*/, ""); version=$0 } /^source = / { gsub(/^source = "|"$|"[ \t]*#.*/, ""); source=$0 } END { if (name && version && source ~ /^registry\+/) { print name "@" version } } ' "$cargo_lock_file" | sort -u) set -l count (count $crates) if test $count -eq 0 return 1 end # Format into Gentoo CRATES="..." block set -l crates_formatted (string join \n (string replace -r '^' '\t' $crates)) set -l block "CRATES=\"\n$crates_formatted\n\"" # Replace existing CRATES="..." block in ebuild or append if missing if grep -q 'CRATES="' "$ebuild_path" awk -v block="$block" ' BEGIN { in_crates = 0 } /CRATES="/ { print block in_crates = 1 next } in_crates { if ($0 ~ /"$/) { in_crates = 0 } next } { print } ' "$ebuild_path" >"$ebuild_path.tmp" && mv "$ebuild_path.tmp" "$ebuild_path" else echo -e "\n$block" >>"$ebuild_path" end echo $count end function prepare_cargo -a ebuild_path parse_ebuild $ebuild_path msg "Package: $_p (cargo)" set -l forge "" set -l repo "" set -l tag "" # Expand variables in ebuild content for URL matching set -l ebuild_content (cat $ebuild_path | string replace -a '${PN}' $_pn | string replace -a '${PV}' $_pv | string replace -a '${P}' $_p) set -l gh_url (echo $ebuild_content | grep -oE 'https://github\.com/[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+' | head -1) set -l srht_url (echo $ebuild_content | grep -oE 'https://git\.sr\.ht/~[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+' | head -1) # Also check HOMEPAGE for sr.ht project pages (sr.ht vs git.sr.ht) if test -z "$srht_url" set -l srht_project (echo $ebuild_content | grep -oE 'https://sr\.ht/~[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+' | head -1) if test -n "$srht_project" set srht_url (string replace 'https://sr.ht/' 'https://git.sr.ht/' $srht_project) end end if test -n "$gh_url" set forge github set repo (echo $gh_url | string replace -r '.*github\.com/' '') set tag "v$_pv" else if test -n "$srht_url" set forge sourcehut set repo $srht_url # Extract tag from archive URL pattern, expand ebuild variables set -l archive_tag (grep -oP 'archive/\K[^.]+(?=\.tar)' $ebuild_path | head -1) if test -n "$archive_tag" set tag (expand_ebuild_vars "$archive_tag" $ebuild_path $_pn $_pv $_p) end if test -z "$tag" set tag "$_pn-$_pv" end else err "Could not find GitHub or SourceHut URL in ebuild" return 1 end set tag (string replace -r -- '-r[0-9]+$' '' $tag) msg "Forge: $forge" msg "Repo: $repo" msg "Tag: $tag" set -l cargo_lock_file (mktemp) msg "Downloading Cargo.lock..." set -l _found false switch $forge case github for _subdir in '' bindings/python/ if curl -sfo "$cargo_lock_file" "https://raw.githubusercontent.com/$repo/$tag/$_subdir"Cargo.lock set _found true break end end case sourcehut for _subdir in '' bindings/python/ if curl -sfo "$cargo_lock_file" "$repo/blob/$tag/$_subdir"Cargo.lock set _found true break end end end if test "$_found" = false err "Failed to download Cargo.lock" rm -f "$cargo_lock_file" return 1 end msg "Parsing crates and updating ebuild..." set -l crate_count (update_ebuild_crates $cargo_lock_file $ebuild_path) rm -f "$cargo_lock_file" if test -z "$crate_count" err "No crates found in Cargo.lock" return 1 end msg "Updated CRATES ($crate_count registry crates)" generate_manifest $ebuild_path end # ── prepare: go ───────────────────────────────────────────────────────────────── function prepare_go -a ebuild_path parse_ebuild $ebuild_path msg "Package: $_p (go)" set -l gh_url (grep -oE 'https://github\.com/[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+' $ebuild_path | head -1) if test -z "$gh_url" err "Could not find GitHub URL in ebuild" return 1 end set -l tag "v$_pv" set tag (string replace -r -- '-r[0-9]+$' '' $tag) msg "Tag: $tag" set -l work (mktemp -d) msg "Downloading source..." curl -sL "$gh_url/archive/refs/tags/$tag.tar.gz" | tar xz -C "$work" or begin err "Failed to download source" rm -rf "$work" return 1 end set -l src_dir (find "$work" -maxdepth 1 -mindepth 1 -type d | head -1) msg "Vendoring Go dependencies..." cd "$src_dir" go mod vendor 2>&1 or begin err "go mod vendor failed" rm -rf "$work" cd $OVERLAY_DIR return 1 end cd $OVERLAY_DIR set -l pkg_dir (dirname $ebuild_path) set -l deps_file "$pkg_dir/$_p-deps.tar.gz" tar -czf "$deps_file" -C "$src_dir" vendor or begin err "tar failed" rm -rf "$work" return 1 end rm -rf "$work" msg "Deps: $deps_file ("(du -h "$deps_file" | cut -f1)")" mkdir -p $DISTDIR cp "$deps_file" "$DISTDIR/$_p-deps.tar.gz" generate_manifest $ebuild_path end # ── prepare: generic ──────────────────────────────────────────────────────────── function prepare_rpi_kernel -a ebuild_path parse_ebuild $ebuild_path msg "Package: $_p (rpi-kernel)" # Extract firmware date from PV (e.g. 6.18.32_p20260521 -> 20260521) set -l fw_date (string match -r '_p(\d{8})' $_pv)[2] if test -z "$fw_date" err "Cannot extract firmware date from version: $_pv" return 1 end set -l fw_tag "1.$fw_date" # Look up kernel commit from firmware release set -l commit (curl -sfL \ "https://raw.githubusercontent.com/raspberrypi/firmware/$fw_tag/extra/git_hash" \ 2>/dev/null | string trim) if test -z "$commit" err "Cannot fetch kernel commit for firmware $fw_tag" return 1 end if not string match -qr '^[0-9a-f]{40}$' "$commit" err "Invalid commit hash from firmware $fw_tag: $commit" return 1 end msg "Firmware $fw_tag -> commit $commit" # Update COMMIT= line in ebuild (rollback on manifest failure) if not grep -q '^COMMIT=' $ebuild_path err "No COMMIT= line found in ebuild" return 1 end set -l old_commit (grep '^COMMIT=' $ebuild_path) sed -i "s|^COMMIT=.*|COMMIT=\"$commit\"|" $ebuild_path msg "Updated COMMIT in ebuild" generate_manifest $ebuild_path or begin # Restore original COMMIT on manifest failure sed -i "s|^COMMIT=.*|$old_commit|" $ebuild_path err "Manifest failed; restored original COMMIT" return 1 end end function prepare_generic -a ebuild_path parse_ebuild $ebuild_path msg "Package: $_p (generic)" generate_manifest $ebuild_path end # ── prepare dispatch ──────────────────────────────────────────────────────────── function do_prepare_one -a ebuild_path forced_type if not test -f $ebuild_path err "Not a file: $ebuild_path" return 1 end set -l type $forced_type if test -z "$type" set type (detect_type $ebuild_path) end switch $type case npm prepare_npm $ebuild_path case cargo prepare_cargo $ebuild_path case go prepare_go $ebuild_path case rpi-kernel prepare_rpi_kernel $ebuild_path case generic prepare_generic $ebuild_path case '*' err "Unknown type: $type" return 1 end end # ── atom parsing (shared by bump and test) ────────────────────────────────────── # Parse a category/package[-version] atom, resolve the package directory. # Sets: _atom_category, _atom_pn, _atom_version (empty if no version), _atom_pkg_dir function parse_atom -a atom if not string match -qr '^[a-zA-Z0-9_-]+/[a-zA-Z0-9_.+-]+$' $atom err "Invalid atom: $atom (expected category/package[-version])" return 1 end set -g _atom_category (string split / $atom)[1] set -l remainder (string split / $atom)[2] set -l cat_dir "$OVERLAY_DIR/$_atom_category" if not test -d "$cat_dir" err "Category not found: $_atom_category" return 1 end # Try exact directory match (no version in atom) if test -d "$cat_dir/$remainder" set -g _atom_pn $remainder set -g _atom_version "" set -g _atom_pkg_dir "$cat_dir/$remainder" return 0 end # Walk backwards through hyphens to find the longest package name matching a directory set -l parts (string split - $remainder) for i in (seq (count $parts) -1 1) set -l candidate (string join - $parts[1..$i]) if test -d "$cat_dir/$candidate" set -g _atom_pn $candidate set -g _atom_pkg_dir "$cat_dir/$candidate" if test $i -lt (count $parts) set -g _atom_version (string join - $parts[(math $i + 1)..(count $parts)]) else set -g _atom_version "" end return 0 end end err "Package not found: $atom" return 1 end # Find the latest non-9999 ebuild for the parsed atom. # Sets: _latest_ebuild, _latest_pv function find_latest_ebuild set -l ebuilds (find $_atom_pkg_dir -name "$_atom_pn-*.ebuild" ! -name "*-9999.ebuild" 2>/dev/null | sort -V) if test -z "$ebuilds" err "No ebuilds found in $_atom_pkg_dir" return 1 end set -g _latest_ebuild $ebuilds[-1] set -l bn (basename $_latest_ebuild .ebuild) set -g _latest_pv (string replace "$_atom_pn-" "" $bn) end # Find a specific or latest ebuild for the parsed atom. # Returns the ebuild path on stdout. function find_ebuild_for_atom if test -n "$_atom_version" set -l ebuild "$_atom_pkg_dir/$_atom_pn-$_atom_version.ebuild" if test -f "$ebuild" echo $ebuild return 0 end err "Ebuild not found: $_atom_pn-$_atom_version" return 1 end # No version: find latest (including 9999 fallback) set -l ebuilds (find $_atom_pkg_dir -name "$_atom_pn-*.ebuild" ! -name "*-9999.ebuild" 2>/dev/null | sort -V) if test -z "$ebuilds" set ebuilds (find $_atom_pkg_dir -name "$_atom_pn-9999.ebuild" 2>/dev/null) end if test -z "$ebuilds" err "No ebuilds found in $_atom_pkg_dir" return 1 end echo $ebuilds[-1] end function next_revision -a pv if string match -qr -- '-r([0-9]+)$' $pv set -l rev (string match -r -- '-r([0-9]+)$' $pv)[2] set -l next_rev (math $rev + 1) string replace -r -- '-r[0-9]+$' "-r$next_rev" $pv else echo "$pv-r1" end end function do_bump -a atom parse_atom $atom; or return 1 find_latest_ebuild; or return 1 set -l new_pv if test (count $argv) -ge 2 set new_pv $argv[2] else set -l ver (get_package_latest_version $atom false) if test -z "$ver" err "Cannot determine latest version for $atom" return 1 end set new_pv $ver end set -l new_ebuild "$_atom_pkg_dir/$_atom_pn-$new_pv.ebuild" if test -f "$new_ebuild" err "Already exists: $new_ebuild" return 1 end cp "$_latest_ebuild" "$new_ebuild"; or begin err "Copy failed" return 1 end set -l rel_src (string replace "$OVERLAY_DIR/" "" $_latest_ebuild) set -l rel_dst (string replace "$OVERLAY_DIR/" "" $new_ebuild) msg "Copied: $rel_src" msg " to: $rel_dst" do_prepare_one $rel_dst end # ── test ─────────────────────────────────────────────────────────────────────── set -g LARRY_IMAGE larry-test function do_test set -g _test_phases set -l pretend false set -l use_container false set -l atom "" # Valid ebuild phases for treating -- as a phase set -l valid_phases clean fetch unpack prepare configure compile install test package preinst postinst prerm postrm nofetch for arg in $argv switch $arg case --compile -c set _test_phases clean fetch unpack compile case --merge -m set _test_phases clean fetch unpack compile install case --pretend -p set pretend true case --emerge -e set use_container true case --help -h echo "Usage: larry test [options] " echo "" echo "Options:" echo " --compile, -c Run through compile phase (default: fetch+unpack)" echo " --merge, -m Full build + install to image (NOT live system)" echo " --pretend, -p Show what would be run" echo " --emerge, -e Real emerge in a clean container (GPU passthrough)" echo " -- Run a specific ebuild phase (e.g., --prepare, --clean)" return 0 case '--*' set -l phase (string replace -- '--' '' $arg) if contains $phase $valid_phases set -a _test_phases $phase else err "Unknown option or phase: $arg" return 1 end case '-*' err "Unknown option: $arg" return 1 case '*' if test -n "$atom" err "Multiple atoms not supported" return 1 end set atom $arg end end if test -z "$atom" err "No package atom specified" return 1 end if test (count $_test_phases) -eq 0 set _test_phases clean fetch unpack end if $use_container do_test_container $atom $pretend else do_test_local $atom $pretend end end function do_test_local -a atom pretend parse_atom $atom; or return 1 set -l ebuild (find_ebuild_for_atom); or return 1 set -l ver (string replace "$_atom_pn-" "" (basename $ebuild .ebuild)) msg "Package: $_atom_category/$_atom_pn-$ver" msg "Ebuild: $ebuild" msg "Phases: $_test_phases" echo "" if test "$pretend" = true warn "Pretend mode: would run:" echo " ebuild $ebuild $_test_phases" return 0 end mkdir -p $DISTDIR set -l tmpdir /tmp/portage-test mkdir -p $tmpdir set -x FEATURES "-sandbox -usersandbox -network-sandbox -ipc-sandbox -pid-sandbox" set -x PORTAGE_TMPDIR $tmpdir set -x DISTDIR $DISTDIR set -x GENTOO_MIRRORS "" ebuild $ebuild $_test_phases set -l status_code $status echo "" if test $status_code -eq 0 msg "Success: $_atom_category/$_atom_pn-$ver ($_test_phases)" else err "Failed: $_atom_category/$_atom_pn-$ver ($_test_phases)" err "Exit code: $status_code" end return $status_code end function ensure_test_image if podman image exists $LARRY_IMAGE 2>/dev/null return 0 end msg "Building test container image (first time only)..." podman build -t $LARRY_IMAGE $OVERLAY_DIR or begin err "Failed to build container image" err "Run: podman build -t $LARRY_IMAGE $OVERLAY_DIR" return 1 end end function do_test_container -a atom pretend mkdir -p $LARRY_CACHE/distfiles $LARRY_CACHE/binpkgs set -l emerge_args -1 --verbose --autounmask=y --autounmask-write=y if test "$pretend" = true set -a emerge_args --pretend end msg "Container emerge: $atom" if test "$pretend" != true ensure_test_image; or return 1 end set -l podman_args \ --rm \ --device /dev/kfd \ --device /dev/dri \ --group-add video \ -v $OVERLAY_DIR:/var/db/repos/bennypowers:ro \ -v $LARRY_CACHE/distfiles:/var/cache/distfiles \ -v $LARRY_CACHE/binpkgs:/var/cache/binpkgs if test "$pretend" = true warn "Pretend mode: would run:" echo " podman run $podman_args $LARRY_IMAGE emerge $emerge_args $atom" return 0 end podman run $podman_args $LARRY_IMAGE emerge $emerge_args $atom set -l status_code $status if test $status_code -eq 0 msg "Container emerge succeeded: $atom" else err "Container emerge failed: $atom (exit $status_code)" end return $status_code end # ── upstream ──────────────────────────────────────────────────────────────────── function setup_upstream_cache if test -d $CACHE_DIR set -l cache_age_min (math (math (date +%s) - (stat -c %Y $CACHE_DIR)) / 60) if test $cache_age_min -gt (math $CACHE_MAX_AGE_HOURS \* 60) rm -rf $CACHE_DIR end end mkdir -p $CACHE_DIR end function get_version_from_filename -a ebuild_file set -l bn (basename $ebuild_file .ebuild) set -l package_name (basename (dirname $ebuild_file)) set -l ver (string replace -r "^$package_name-" "" $bn) string replace --regex -- '-r[0-9]+$' "" $ver end function get_package_info -a ebuild_path set -l package_name (basename (dirname $ebuild_path)) set -l category (basename (dirname (dirname $ebuild_path))) echo "$category/$package_name" end function find_all_ebuilds find $OVERLAY_DIR -name "*.ebuild" -type f | grep -v metadata | sort end function find_latest_versioned_ebuild -a category package set -l package_dir "$OVERLAY_DIR/$category/$package" set -l ebuilds (find $package_dir -name "*.ebuild" ! -name "*-9999.ebuild" 2>/dev/null | sort -V | tail -1) if test -n "$ebuilds" echo $ebuilds return 0 end # Fall back to live ebuild if that's all that exists set -l live (find $package_dir -name "*-9999.ebuild" 2>/dev/null | head -1) if test -n "$live" echo $live return 0 end return 1 end function has_live_ebuild set -l category $argv[1] set -l package $argv[2] test -f "$OVERLAY_DIR/$category/$package/$package-9999.ebuild" end function get_ebuild_var -a ebuild_file var_name grep -E "^$var_name=" $ebuild_file | head -1 | sed -E "s/^$var_name=\"?([^\"]*)\"?.*/\1/" | string trim end function is_pypi_package -a ebuild_file grep -q "inherit.*pypi" $ebuild_file end function is_npm_package -a ebuild_file grep -q 'mirror://npm/' $ebuild_file end function get_npm_name -a ebuild_file set -l package_name (basename (dirname $ebuild_file)) set -l pv (get_version_from_filename $ebuild_file) set -l p "$package_name-$pv" # Only take the first mirror://npm/ line (ebuilds may have optional deps like webui? # that also match grep, and multi-line processing breaks variable expansion) set -l npm_line (grep 'mirror://npm/' $ebuild_file | head -1 | string trim) if test -z "$npm_line" echo $package_name return end for line in (grep -E '^[A-Z_]+="[^"]*"' $ebuild_file) set -l key (string replace -r '=.*' '' $line) set -l val (string match -r '"([^"]*)"' $line)[2] set npm_line (string replace -a "\${$key}" $val $npm_line) end set npm_line (string replace -a '${PN}' $package_name $npm_line) set npm_line (string replace -a '${PV}' $pv $npm_line) set npm_line (string replace -a '${P}' $p $npm_line) set -l npm_path (string replace 'mirror://npm/' '' $npm_line) string replace -r '/-/.*' '' $npm_path end function fetch_npm_version set -l npm_name $argv[1] set -l cache_file "$CACHE_DIR/npm-"(string replace -a / - $npm_name) if test -f $cache_file cat $cache_file return 0 end set -l ver (curl -s "https://registry.npmjs.org/$npm_name/latest" | jq -r '.version // empty' 2>/dev/null) if test -n "$ver" echo $ver | tee $cache_file return 0 end return 1 end function extract_github_repo -a ebuild_file set -l github_urls (grep -oE 'https?://github\.com/[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+' $ebuild_file) for url in $github_urls set -l repo (echo $url | sed -E 's|.*github\.com/||' | sed -E 's|\.git$||' | cut -d/ -f1,2) if string match -qr '\$\{' "$repo" continue end if string match -qr '^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$' "$repo" echo $repo return 0 end end return 1 end function extract_gitlab_repo -a ebuild_file set -l gitlab_urls (grep -oE 'https?://gitlab\.[A-Za-z0-9.-]+/[A-Za-z0-9_.-]+(/[A-Za-z0-9_.-]+)+' $ebuild_file | head -1) if test -z "$gitlab_urls" return 1 end set -l host (echo $gitlab_urls | sed -E 's|^(https?://)||' | cut -d/ -f1) set -l full_path (echo $gitlab_urls | sed -E 's|.*gitlab\.[A-Za-z0-9.-]+/||' | sed -E 's|\.git$||') if string match -qr '\$\{' "$full_path" return 1 end if test -n "$full_path" echo "$host:$full_path" return 0 end return 1 end function extract_gitea_repo -a ebuild_file # Matches codeberg.org and other Gitea instances set -l codeberg_urls (grep -oE 'https?://codeberg\.org/[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+' $ebuild_file | head -1) if test -z "$codeberg_urls" return 1 end set -l repo (echo $codeberg_urls | sed -E 's|.*codeberg\.org/||' | sed -E 's|\.git$||' | cut -d/ -f1,2) if string match -qr '^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$' "$repo" echo $repo return 0 end return 1 end function expand_pn -a value pn switch $value case '${PN//-/_}' '$PN//-/_' string replace -a - _ $pn case '${PN/-/_}' '$PN/-/_' string replace - _ $pn case '${PN}' '$PN' echo $pn case '*' echo $value end end function get_pypi_name -a ebuild_file set -l package_name (basename (dirname $ebuild_file)) set -l pypi_pn (get_ebuild_var $ebuild_file PYPI_PN) if test -n "$pypi_pn" echo (expand_pn $pypi_pn $package_name) return 0 end set -l my_pn (get_ebuild_var $ebuild_file MY_PN) if test -n "$my_pn" echo (expand_pn $my_pn $package_name) return 0 end echo $package_name end function fetch_pypi_version set -l package_name $argv[1] set -l cache_file "$CACHE_DIR/pypi-$package_name" if test -f $cache_file cat $cache_file return 0 end set -l ver (curl -s "https://pypi.org/pypi/$package_name/json" | jq -r '.info.version' 2>/dev/null) if test -n "$ver" -a "$ver" != null echo $ver | tee $cache_file return 0 end return 1 end function extract_date_from_string set -l date_match (string match -r '(20\d{6})' $argv[1]) if test (count $date_match) -ge 2 echo $date_match[2] return 0 end return 1 end function fetch_github_latest_tag_by_date -a repo set -l cache_file "$CACHE_DIR/github-tags-"(string replace -a / - $repo) if test -f $cache_file cat $cache_file return 0 end set -l owner (string split / $repo)[1] set -l name (string split / $repo)[2] set -l query ' query($owner: String!, $name: String!) { repository(owner: $owner, name: $name) { refs(refPrefix: "refs/tags/", orderBy: {field: TAG_COMMIT_DATE, direction: DESC}, first: 10) { nodes { name target { ... on Commit { committedDate } ... on Tag { target { ... on Commit { committedDate } } tagger { date } } } } } } }' set -l tag_data (gh api graphql \ -f query="$query" -f owner="$owner" -f name="$name" \ --jq '.data.repository.refs.nodes[] | .name + "|" + (.target.committedDate // .target.tagger.date // .target.target.committedDate // "")' \ 2>/dev/null) if test -z "$tag_data" return 1 end for line in $tag_data set -l tag_name (string split '|' $line)[1] set -l tag_raw_date (string split '|' $line)[2] if string match -qri 'alpha|beta|rc|dev|nightly|canary' "$tag_name" continue end set -l tag_date (extract_date_from_string "$tag_name") if test -z "$tag_date" -a -n "$tag_raw_date" set tag_date (string replace -r -a '-' '' (string sub -l 10 $tag_raw_date)) end if test -n "$tag_date" echo "$tag_name|$tag_date" | tee $cache_file return 0 end end return 1 end function strip_tag_prefix -a tag set tag (string replace --regex -- '^(bun|rocm|release)-' '' $tag) set tag (string replace --regex -- '\^\{.*\}' '' $tag) set tag (string replace --regex -- '^[vV]\.' '' $tag) set tag (string replace --regex -- '^[vV](?=\d)' '' $tag) echo $tag end function is_version_like string match -qr '^\d+[\.\d]*' $argv[1] end function fetch_github_version -a repo prereleases set -l cache_file "$CACHE_DIR/github-"(string replace -a / - $repo) if test -f $cache_file cat $cache_file return 0 end set -l release_ver "" set -l tag_ver "" # 1. Fetch official latest release (handles draft filtering automatically) # prereleases=false means we reject prereleases (default) set -l raw_release (gh api repos/$repo/releases/latest --jq '.tag_name' 2>/dev/null) if test -n "$raw_release" -a "$raw_release" != null if not string match -q -r '^\{' "$raw_release" if test "$prereleases" = false set -l is_prerelease (gh api repos/$repo/releases/latest --jq '.prerelease' 2>/dev/null) if test "$is_prerelease" = true set raw_release "" end end end end if test -n "$raw_release" -a "$raw_release" != null set release_ver (strip_tag_prefix $raw_release) end # 2. Fetch latest release tag (not just any tag) sorted by semver/date fallback # Using release list so we can filter prereleases if test "$prereleases" = false set -l raw_tag (gh release list -R $repo --limit 100 \ --exclude-pre-releases --json tagName -q '.[0].tagName' 2>/dev/null) else set -l raw_tag (gh release list -R $repo --limit 100 \ --json tagName -q '.[0].tagName' 2>/dev/null) end if test -n "$raw_tag" -a "$raw_tag" != null if not string match -q -r '^\{' "$raw_tag" set tag_ver (strip_tag_prefix $raw_tag) end end set -l ver "" if test -n "$release_ver" -o -n "$tag_ver" if is_version_like $release_ver; and is_version_like $tag_ver set -l newer (compare_versions $release_ver $tag_ver) if test "$newer" = outdated set ver $tag_ver else set ver $release_ver end else if is_version_like $tag_ver set ver $tag_ver else if is_version_like $release_ver set ver $release_ver end else if test -n "$release_ver" set ver $release_ver else if test -n "$tag_ver" set ver $tag_ver end if test -n "$ver"; and is_version_like $ver echo $ver | tee $cache_file return 0 end # Fallback: use git ls-remote to find latest version-like tag set -l remote_tags (git ls-remote --tags "https://github.com/$repo.git" 2>/dev/null \ | awk '{print $2}' | sed 's|^refs/tags/||' \ | grep -E '^[vV]?[0-9]' | sort -V | tail -1) if test -n "$remote_tags" set ver (strip_tag_prefix $remote_tags) end if test -n "$ver"; and is_version_like $ver echo $ver | tee $cache_file return 0 end set -l tag_info (fetch_github_latest_tag_by_date $repo) if test -n "$tag_info" set -g GITHUB_TAG_INFO $tag_info return 2 end return 1 end function fetch_gitlab_version -a repo prereleases set -l cache_file "$CACHE_DIR/gitlab-"(string replace -a / - $repo | string replace -a : -) if test -f $cache_file cat $cache_file return 0 end # repo is host:path (e.g., gitlab.gnome.org:GNOME/geary) set -l host (string split : $repo)[1] set -l path (string split : $repo)[2] set -l encoded_path (string replace -a / '%2F' $path) set -l tags (curl -sf "https://$host/api/v4/projects/$encoded_path/repository/tags" \ | jq -r '.[0].name' 2>/dev/null) if test -z "$tags" -o "$tags" = null return 1 end set -l tag_ver (strip_tag_prefix $tags) if is_version_like $tag_ver echo $tag_ver | tee $cache_file return 0 end return 1 end function fetch_gitea_version -a repo prereleases set -l cache_file "$CACHE_DIR/codeberg-"(string replace -a / - $repo) if test -f $cache_file cat $cache_file return 0 end # Codeberg uses Gitea API at https://codeberg.org/api/v1/repos/{owner}/{repo} # Tags: /api/v1/repos/{owner}/{repo}/tags # Releases: /api/v1/repos/{owner}/{repo}/releases set -l releases (curl -sf "https://codeberg.org/api/v1/repos/$repo/releases" 2>/dev/null | jq -r '.[0].tag_name' 2>/dev/null) if test -z "$releases" -o "$releases" = null return 1 end set -l tag_ver (strip_tag_prefix $releases) if is_version_like $tag_ver echo $tag_ver | tee $cache_file return 0 end return 1 end function compare_versions -a current latest if test -z "$current" -o -z "$latest" echo unknown return 1 end if test "$current" = "$latest" echo current return 0 end # Normalize Gentoo suffixes into sort -V compatible semver order: # alpha (.a) < beta (.b) < pre (.c) < rc (.d) < release (.e) < patch (.p) function _norm_ver -a v # Append .e to plain releases so 1.0 sorts AFTER 1.0_rc1 (.1.0.d1) but BEFORE 1.0_p1 (.1.0.p1) set -l norm (string replace -r '(_alpha|_beta|_pre|_rc|_p|\d+)$' '$1' "$v") # If no suffix exists before a patch or at end, tag main release with .e set norm (string replace -r '([0-9]+)(_p|\z)' '$1.e$2' "$v") string replace -a _alpha '.a' "$norm" \ | string replace -a _beta '.b' \ | string replace -a _pre '.c' \ | string replace -a _rc '.d' \ | string replace -a _p '.p' end set -l norm_cur (_norm_ver "$current") set -l norm_lat (_norm_ver "$latest") if test "$norm_cur" = "$norm_lat" echo current return 0 end # Sort normalized strings using version sort set -l sorted (printf "%s\n%s\n" "$norm_cur" "$norm_lat" | sort -V) if test "$sorted[1]" = "$norm_cur" echo outdated else echo newer end end function parse_upstream_dependencies -a ebuild_file grep -E "^\s*(R|B)?DEPEND=" $ebuild_file | sed 's/.*="\?\(.*\)"\?/\1/' | grep -oE '[a-z0-9-]+/[a-z0-9_-]+' | sort -u end function get_latest_ebuild -a atom set -l parts (string split / $atom) echo (find_latest_versioned_ebuild $parts[1] $parts[2]) end function is_in_overlay -a atom set -l _splits (string split / $atom) set -l category $_splits[1] set -l package $_splits[2] test -d "$OVERLAY_DIR/$category/$package" end function _get_package_type -a atom set -l ebuild_file (get_latest_ebuild $atom) if test -z "$ebuild_file" err "!! No ebuild file for $atom" return 1 end if is_pypi_package $ebuild_file echo PyPI else if is_npm_package $ebuild_file echo npm else if set -l r (extract_github_repo $ebuild_file) echo GitHub else if set -l r (extract_gitlab_repo $ebuild_file) echo GitLab else if set -l r (extract_gitea_repo $ebuild_file) echo Codeberg else echo unknown end end function should_skip_package -a category package # Skip acct-group and acct-user packages (no upstream versions to check) if string match -qr '^acct-(group|user)/' $category/$package return 0 end return 1 end function get_package_latest_version -a atom prereleases set -l ebuild_file (get_latest_ebuild $atom) set -l type (_get_package_type $atom) switch $type case PyPI set -l pypi_name (get_pypi_name $ebuild_file) echo (fetch_pypi_version $pypi_name) case npm set -l npm_name (get_npm_name $ebuild_file) echo (fetch_npm_version $npm_name) case GitHub set -l repo (extract_github_repo $ebuild_file) echo (fetch_github_version $repo $prereleases) case GitLab set -l repo (extract_gitlab_repo $ebuild_file) echo (fetch_gitlab_version $repo $prereleases) case Codeberg set -l repo (extract_gitea_repo $ebuild_file) echo (fetch_gitea_version $repo $prereleases) case '*' echo unknown end return end function check_package -a atom depth prereleases verbose set -l _splits (string split / $atom) set -l category $_splits[1] set -l package $_splits[2] set -l type (_get_package_type $atom) if contains $atom $CHECKED_PACKAGES return 0 end set -g CHECKED_PACKAGES $CHECKED_PACKAGES $atom # Skip acct-group and acct-user (nothing to bump) if should_skip_package $category $package return 0 end set -l ebuild_file (get_latest_ebuild $atom) set -l package_dir "$OVERLAY_DIR/$category/$package" set -l indent (string repeat -n (math $depth \* 2) " ") # If the only ebuild is live (9999), elide unless verbose if string match -q '*-9999.ebuild' -- $ebuild_file # Check if there are any versioned ebuilds alongside it set -l versioned (find $package_dir -name '*.ebuild' ! -name '*-9999.ebuild' 2>/dev/null | head -1) if test -z "$versioned" if test "$verbose" = "true" set -l latest_ver (get_package_latest_version $atom $prereleases) set -l status_label LIVE if test -n "$latest_ver" set status_label HAS-RELEASE end printf '%s%s\t%s\t%s\t%s\t%s\n' "$indent" "$atom" "9999 (live)" "$latest_ver" "$status_label" "$type" end return 0 end # Has both versioned and live; get_latest_ebuild already returned the versioned one end set -l current_ver (get_version_from_filename $ebuild_file) set -l latest_ver unknown set -l src unknown set -l status_label UNKNOWN set -g GITHUB_TAG_INFO "" set -l fetch_result 1 switch $type case PyPI set -l pypi_name (get_pypi_name $ebuild_file) set src "PyPI:$pypi_name" set latest_ver (fetch_pypi_version $pypi_name) and set fetch_result 0 case npm set -l npm_name (get_npm_name $ebuild_file) set src "npm:$npm_name" set latest_ver (fetch_npm_version $npm_name) and set fetch_result 0 case GitHub set -l repo (extract_github_repo $ebuild_file) set src "GitHub:$repo" set latest_ver (fetch_github_version $repo $prereleases) set fetch_result $status case GitLab set -l repo (extract_gitlab_repo $ebuild_file) set src "GitLab:$repo" set latest_ver (fetch_gitlab_version $repo $prereleases) set fetch_result $status case Codeberg set -l repo (extract_gitea_repo $ebuild_file) set src "Codeberg:$repo" set latest_ver (fetch_gitea_version $repo $prereleases) set fetch_result $status end if test $fetch_result -eq 0 -a -n "$latest_ver" -a "$latest_ver" != unknown set -l comparison (compare_versions $current_ver $latest_ver) switch $comparison case outdated set status_label OUTDATED case current set status_label UP-TO-DATE case newer set status_label AHEAD end else if test $fetch_result -eq 2 -a -n "$GITHUB_TAG_INFO" set -l raw_tag_name (string split '|' $GITHUB_TAG_INFO)[1] set -l tag_name (string replace -r '^[vV](?=\d)' '' $raw_tag_name) set -l tag_date (string split '|' $GITHUB_TAG_INFO)[2] set -l current_date (extract_date_from_string $current_ver) set latest_ver "$tag_name" if is_version_like "$current_ver" -a is_version_like "$tag_name" if test -n "$current_date" -a -n "$tag_date" if test "$tag_date" -gt "$current_date" set status_label NEWER-TAG else if test "$tag_date" = "$current_date" set status_label UP-TO-DATE else set status_label AHEAD end else set -l comparison (compare_versions $current_ver $tag_name) switch $comparison case outdated set status_label OUTDATED case current set status_label UP-TO-DATE case newer set status_label AHEAD end end else set status_label CHECK-TAG end else set latest_ver "?" end printf '%s%s\t%s\t%s\t%s\t%s\n' "$indent" "$atom" "$current_ver" "$latest_ver" "$status_label" "$src" if test $depth -lt 3 set -l deps (parse_upstream_dependencies $ebuild_file) for d in $deps if is_in_overlay $d check_package $d (math $depth + 1) $prereleases $verbose end end end end function colorize_upstream sed -E \ -e '1{p;s/./─/g;}' \ -e 's/OUTDATED/\x1b[31mOUTDATED\x1b[0m/' \ -e 's/UP-TO-DATE/\x1b[32mUP-TO-DATE\x1b[0m/' \ -e 's/AHEAD/\x1b[34mAHEAD\x1b[0m/' \ -e 's/NEWER-TAG/\x1b[33mNEWER-TAG\x1b[0m/' \ -e 's/CHECK-TAG/\x1b[33mCHECK-TAG\x1b[0m/' \ -e 's/HAS-RELEASE/\x1b[33mHAS-RELEASE\x1b[0m/' \ -e 's/LIVE/\x1b[90mLIVE\x1b[0m/' \ -e 's/UNKNOWN/\x1b[90mUNKNOWN\x1b[0m/' \ -e 's|GitHub:([^ ]+)|\x1b[34m\x1b]8;;https://github.com/\1\x07GitHub:\1\x1b]8;;\x07\x1b[0m|' \ -e 's|PyPI:([^ ]+)|\x1b[34m\x1b]8;;https://pypi.org/project/\1/\x07PyPI:\1\x1b]8;;\x07\x1b[0m|' \ -e 's|npm:([^ ]+)|\x1b[34m\x1b]8;;https://www.npmjs.com/package/\1\x07npm:\1\x1b]8;;\x07\x1b[0m|' end function do_upstream set -l fresh false set -l verbose false set -l prereleases false set -l target_packages for arg in $argv switch $arg case --fresh -f set fresh true case --verbose -v set verbose true case --prereleases set prereleases true case --help -h echo "Usage: larry upstream [--fresh] [--verbose] [--prereleases] [cat/pkg ...]" echo " --fresh, -f Clear cache and fetch all versions fresh" echo " --verbose, -v Show UP-TO-DATE packages (hidden by default)" echo " --prereleases Include GitHub prereleases in checks (default: skip)" echo " cat/pkg Only check specified package(s)" return 0 case '*' set -a target_packages $arg end end if $fresh rm -rf $CACHE_DIR end echo "Checking packages in overlay: $OVERLAY_DIR" echo "" setup_upstream_cache # Determine which packages to check set -l packages if test (count $target_packages) -gt 0 set packages $target_packages else for ebuild in (find_all_ebuilds) set -l cat_pkg (get_package_info $ebuild) if not contains $cat_pkg $packages set packages $packages $cat_pkg end end end begin printf 'Package\tCurrent\tLatest\tStatus\tSource\n' for cat_pkg in $packages check_package $cat_pkg 0 $prereleases $verbose end end | if not $verbose grep -v UP-TO-DATE else cat end | column -t -s \t | colorize_upstream echo "" echo "Check complete. Cache stored in: $CACHE_DIR (expires after "$CACHE_MAX_AGE_HOURS"h)" echo "Use --fresh to force a refresh." end # ── import ───────────────────────────────────────────────────────────────────── function find_package_in_repos -a cat_pkg # Search all repos for a package, return the repo path set -l category (string split / $cat_pkg)[1] set -l package (string split / $cat_pkg)[2] for repo_dir in /var/db/repos/*/ set -l repo_name (basename $repo_dir) # Skip our own overlay if test "$repo_dir" = /var/db/repos/bennypowers/ continue end if test -d "$repo_dir$category/$package" echo "$repo_dir$category/$package" return 0 end end return 1 end function do_import set -l atom "" set -l from_repo "" for arg in $argv switch $arg case --help -h echo "Usage: larry import [--from ] " echo "" echo "Import an ebuild from another overlay (e.g., ::guru, ::gentoo)." echo "Copies the latest ebuild, metadata.xml, and files/ directory." echo "Rewrites maintainer to overlay owner and runs prepare." echo "" echo "If --from is omitted, searches all repos in /var/db/repos/." return 0 case --from set from_repo __next__ case '--from=*' set from_repo (string replace '--from=' '' $arg) case '-*' err "Unknown option: $arg" return 1 case '*' if test "$from_repo" = __next__ set from_repo $arg else if test -z "$atom" set atom $arg else err "Too many arguments" return 1 end end end if test -z "$atom" err "No package atom specified" return 1 end set -l category (string split / $atom)[1] set -l package (string split / $atom)[2] # Find the source set -l src_dir if test -n "$from_repo" -a "$from_repo" != __next__ set src_dir "$from_repo" if not test -d "$src_dir" # Try as a repo name set src_dir "/var/db/repos/$from_repo/$category/$package" end if not test -d "$src_dir" err "Source not found: $from_repo" return 1 end else set src_dir (find_package_in_repos $atom) if test -z "$src_dir" err "Package $atom not found in any overlay" return 1 end end # Determine which repo it came from set -l repo_name (basename (dirname (dirname $src_dir))) msg "Importing $atom from ::$repo_name" msg "Source: $src_dir" # Find the latest ebuild in the source set -l src_ebuilds (find $src_dir -name "$package-*.ebuild" ! -name "*-9999.ebuild" 2>/dev/null | sort -V) if test -z "$src_ebuilds" err "No ebuilds found in $src_dir" return 1 end set -l src_ebuild $src_ebuilds[-1] set -l ebuild_name (basename $src_ebuild) # Create destination set -l dst_dir "$OVERLAY_DIR/$category/$package" mkdir -p "$dst_dir" # Copy ebuild cp "$src_ebuild" "$dst_dir/$ebuild_name" msg "Copied: $ebuild_name" # Copy files/ if it exists if test -d "$src_dir/files" cp -r "$src_dir/files" "$dst_dir/" msg "Copied: files/" end # Create or copy metadata.xml with our maintainer set -l upstream_id "" if test -f "$src_dir/metadata.xml" # Extract upstream remote-id if present set upstream_id (grep -oP '[^<]+' "$src_dir/metadata.xml" | head -1) end set -l meta " web@bennypowers.com Benny Powers " if test -n "$upstream_id" set meta "$meta $upstream_id " end set meta "$meta " printf "%s\n" $meta >"$dst_dir/metadata.xml" msg "Created: metadata.xml" # Prepare the imported ebuild set -l rel_path "$category/$package/$ebuild_name" msg "" do_prepare_one $rel_path end # ── main ──────────────────────────────────────────────────────────────────────── if test (count $argv) -eq 0 print_usage exit 1 end set -l subcommand $argv[1] set -l rest $argv[2..] switch $subcommand case help --help -h print_usage exit 0 case bump set -l mode version set -l prereleases false set -l atom "" set -l new_version "" for arg in $rest switch $arg case --help -h echo "Usage: larry bump [--revision] [--prereleases] []" exit 0 case --revision -r set mode revision case --prereleases set prereleases true case '-*' err "Unknown option: $arg" exit 1 case '*' if test -z "$atom" set atom $arg else if test -z "$new_version" set new_version $arg else err "Too many arguments" exit 1 end end end if test -z "$atom" err "No package atom specified" exit 1 end switch $mode case revision set -l pv_for_rev "" parse_atom $atom; or exit 1 find_latest_ebuild; or exit 1 set pv_for_rev (next_revision $_latest_pv) do_bump $atom $pv_for_rev case version if test -z "$new_version" set new_version (get_package_latest_version $atom $prereleases) end do_bump $atom $new_version end case prepare set -l forced_type "" set -l ebuilds set -l all false for arg in $rest switch $arg case --help -h echo "Usage: larry prepare [--type npm|cargo|generic] [--all] [...]" exit 0 case --all -a set all true case --type set forced_type __next__ case '--type=*' set forced_type (string replace '--type=' '' $arg) case '*' if test "$forced_type" = __next__ set forced_type $arg else set -a ebuilds $arg end end end if $all # Use pkgdev manifest directly for bulk updates -- it's smarter # about skipping unmodified packages msg "Updating all manifests..." mkdir -p $DISTDIR cd $OVERLAY_DIR pkgdev manifest -d $DISTDIR exit $status end if test (count $ebuilds) -eq 0 echo "Usage: larry prepare [--all] [...]" exit 1 end set -l failures 0 for ebuild in $ebuilds do_prepare_one $ebuild $forced_type or set failures (math $failures + 1) end if test $failures -gt 0 err "$failures package(s) failed" exit 1 end case import do_import $rest case test do_test $rest case upstream do_upstream $rest case '*' err "Unknown command: $subcommand" print_usage exit 1 end