From 9f7c9f2f31acd7bc544dd988af1c8c2fe63ec504 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 5 Aug 2026 23:14:29 -0500 Subject: Initial commit of skiff scaffolding --- internal/release/release.go | 52 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 internal/release/release.go (limited to 'internal/release/release.go') diff --git a/internal/release/release.go b/internal/release/release.go new file mode 100644 index 0000000..fe48aa4 --- /dev/null +++ b/internal/release/release.go @@ -0,0 +1,52 @@ +// Package release runs an app's own release-phase Makefile target, if it +// declares one - mirroring Heroku's Procfile `release:` line, but as a +// `release` target in the same Makefile that builds the app. Skiff never +// interprets what the target does (run migrations, warm a cache, +// whatever); it only checks whether one is declared and gates the deploy +// on its exit code. +package release + +import ( + "context" + "fmt" + "io" + "strings" + + "skiff/internal/sh" +) + +// notDeclared is bmake's exact wording (verified against FreeBSD 15's +// /usr/bin/make) when asked to build a target with no matching rule - +// explicit, implicit, or file-on-disk. Skiff is FreeBSD-only, so this +// doesn't need to also cover GNU make's differently-worded message. +const notDeclared = "don't know how to make release" + +// Declared reports whether dir's Makefile defines a `release` target, by +// asking bmake itself via a dry run rather than parsing Makefile syntax - +// robust regardless of how the target is actually defined (directly, via +// an include, a conditional, a pattern rule). dir is the release's +// host-side checkout; this runs on the host, before the jail is involved. +func Declared(ctx context.Context, dir string) (bool, error) { + out, err := sh.CombinedOutput(ctx, dir, "make", "-n", "release") + if err == nil { + return true, nil + } + if strings.Contains(out, notDeclared) { + return false, nil + } + return false, fmt.Errorf("make -n release: %w\n%s", err, out) +} + +// Run executes the `release` Makefile target as a one-off inside app's +// already-running jail (the jail holds the process's filesystem and any +// state the target needs to reach - e.g. the app's own data.db). +// releaseDirInJail must be the new release's own checkout, jail-relative - +// not `current`, which still points at the previous release until cutover +// happens after this returns successfully. -B forces the recipe to run +// even if a stray file/directory named `release` already exists in the +// checkout. A non-nil error means the deploy must abort before +// restart/cutover. +func Run(ctx context.Context, out io.Writer, app, releaseDirInJail string) error { + fmt.Fprintf(out, "==> release: make release (in %s)\n", releaseDirInJail) + return sh.Run(ctx, out, "", "jexec", app, "make", "-B", "-C", releaseDirInJail, "release") +} -- cgit v1.3