feat: extract build/, release/, sdk/ from go-devops
Build system (8 builders, signing, archiving), release pipeline
(7 publishers, versioning, changelog), and SDK generation
(OpenAPI diff, code gen). 18K LOC, all tests pass except Go
builder workspace isolation (pre-existing).
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-09 12:37:36 +00:00
// Package builders provides build implementations for different project types.
package builders
import (
"context"
2026-03-26 17:41:53 +00:00
"dappco.re/go/core"
"dappco.re/go/core/build/internal/ax"
2026-03-22 01:53:16 +00:00
"dappco.re/go/core/build/pkg/build"
"dappco.re/go/core/io"
coreerr "dappco.re/go/core/log"
feat: extract build/, release/, sdk/ from go-devops
Build system (8 builders, signing, archiving), release pipeline
(7 publishers, versioning, changelog), and SDK generation
(OpenAPI diff, code gen). 18K LOC, all tests pass except Go
builder workspace isolation (pre-existing).
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-09 12:37:36 +00:00
)
// LinuxKitBuilder builds LinuxKit images.
2026-03-31 18:33:36 +01:00
//
// b := builders.NewLinuxKitBuilder()
feat: extract build/, release/, sdk/ from go-devops
Build system (8 builders, signing, archiving), release pipeline
(7 publishers, versioning, changelog), and SDK generation
(OpenAPI diff, code gen). 18K LOC, all tests pass except Go
builder workspace isolation (pre-existing).
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-09 12:37:36 +00:00
type LinuxKitBuilder struct { }
// NewLinuxKitBuilder creates a new LinuxKit builder.
2026-03-31 18:33:36 +01:00
//
// b := builders.NewLinuxKitBuilder()
feat: extract build/, release/, sdk/ from go-devops
Build system (8 builders, signing, archiving), release pipeline
(7 publishers, versioning, changelog), and SDK generation
(OpenAPI diff, code gen). 18K LOC, all tests pass except Go
builder workspace isolation (pre-existing).
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-09 12:37:36 +00:00
func NewLinuxKitBuilder ( ) * LinuxKitBuilder {
return & LinuxKitBuilder { }
}
// Name returns the builder's identifier.
2026-03-31 18:33:36 +01:00
//
// name := b.Name() // → "linuxkit"
feat: extract build/, release/, sdk/ from go-devops
Build system (8 builders, signing, archiving), release pipeline
(7 publishers, versioning, changelog), and SDK generation
(OpenAPI diff, code gen). 18K LOC, all tests pass except Go
builder workspace isolation (pre-existing).
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-09 12:37:36 +00:00
func ( b * LinuxKitBuilder ) Name ( ) string {
return "linuxkit"
}
2026-04-01 14:35:36 +00:00
// Detect checks if a linuxkit.yml, linuxkit.yaml, or nested YAML config exists in the directory.
2026-03-31 18:33:36 +01:00
//
// ok, err := b.Detect(io.Local, ".")
feat: extract build/, release/, sdk/ from go-devops
Build system (8 builders, signing, archiving), release pipeline
(7 publishers, versioning, changelog), and SDK generation
(OpenAPI diff, code gen). 18K LOC, all tests pass except Go
builder workspace isolation (pre-existing).
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-09 12:37:36 +00:00
func ( b * LinuxKitBuilder ) Detect ( fs io . Medium , dir string ) ( bool , error ) {
// Check for linuxkit.yml
2026-04-01 14:35:36 +00:00
if fs . IsFile ( ax . Join ( dir , "linuxkit.yml" ) ) || fs . IsFile ( ax . Join ( dir , "linuxkit.yaml" ) ) {
feat: extract build/, release/, sdk/ from go-devops
Build system (8 builders, signing, archiving), release pipeline
(7 publishers, versioning, changelog), and SDK generation
(OpenAPI diff, code gen). 18K LOC, all tests pass except Go
builder workspace isolation (pre-existing).
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-09 12:37:36 +00:00
return true , nil
}
// Check for .core/linuxkit/
2026-03-26 17:41:53 +00:00
lkDir := ax . Join ( dir , ".core" , "linuxkit" )
feat: extract build/, release/, sdk/ from go-devops
Build system (8 builders, signing, archiving), release pipeline
(7 publishers, versioning, changelog), and SDK generation
(OpenAPI diff, code gen). 18K LOC, all tests pass except Go
builder workspace isolation (pre-existing).
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-09 12:37:36 +00:00
if fs . IsDir ( lkDir ) {
entries , err := fs . List ( lkDir )
if err == nil {
for _ , entry := range entries {
2026-04-01 14:35:36 +00:00
if entry . IsDir ( ) {
continue
}
name := entry . Name ( )
if core . HasSuffix ( name , ".yml" ) || core . HasSuffix ( name , ".yaml" ) {
feat: extract build/, release/, sdk/ from go-devops
Build system (8 builders, signing, archiving), release pipeline
(7 publishers, versioning, changelog), and SDK generation
(OpenAPI diff, code gen). 18K LOC, all tests pass except Go
builder workspace isolation (pre-existing).
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-09 12:37:36 +00:00
return true , nil
}
}
}
}
return false , nil
}
// Build builds LinuxKit images for the specified targets.
2026-03-31 18:33:36 +01:00
//
// artifacts, err := b.Build(ctx, cfg, []build.Target{{OS: "linux", Arch: "amd64"}})
feat: extract build/, release/, sdk/ from go-devops
Build system (8 builders, signing, archiving), release pipeline
(7 publishers, versioning, changelog), and SDK generation
(OpenAPI diff, code gen). 18K LOC, all tests pass except Go
builder workspace isolation (pre-existing).
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-09 12:37:36 +00:00
func ( b * LinuxKitBuilder ) Build ( ctx context . Context , cfg * build . Config , targets [ ] build . Target ) ( [ ] build . Artifact , error ) {
2026-03-30 01:13:57 +00:00
linuxkitCommand , err := b . resolveLinuxKitCli ( )
if err != nil {
feat: extract build/, release/, sdk/ from go-devops
Build system (8 builders, signing, archiving), release pipeline
(7 publishers, versioning, changelog), and SDK generation
(OpenAPI diff, code gen). 18K LOC, all tests pass except Go
builder workspace isolation (pre-existing).
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-09 12:37:36 +00:00
return nil , err
}
// Determine config file path
configPath := cfg . LinuxKitConfig
if configPath == "" {
// Auto-detect
2026-03-26 17:41:53 +00:00
if cfg . FS . IsFile ( ax . Join ( cfg . ProjectDir , "linuxkit.yml" ) ) {
configPath = ax . Join ( cfg . ProjectDir , "linuxkit.yml" )
2026-04-01 14:35:36 +00:00
} else if cfg . FS . IsFile ( ax . Join ( cfg . ProjectDir , "linuxkit.yaml" ) ) {
configPath = ax . Join ( cfg . ProjectDir , "linuxkit.yaml" )
feat: extract build/, release/, sdk/ from go-devops
Build system (8 builders, signing, archiving), release pipeline
(7 publishers, versioning, changelog), and SDK generation
(OpenAPI diff, code gen). 18K LOC, all tests pass except Go
builder workspace isolation (pre-existing).
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-09 12:37:36 +00:00
} else {
// Look in .core/linuxkit/
2026-03-26 17:41:53 +00:00
lkDir := ax . Join ( cfg . ProjectDir , ".core" , "linuxkit" )
feat: extract build/, release/, sdk/ from go-devops
Build system (8 builders, signing, archiving), release pipeline
(7 publishers, versioning, changelog), and SDK generation
(OpenAPI diff, code gen). 18K LOC, all tests pass except Go
builder workspace isolation (pre-existing).
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-09 12:37:36 +00:00
if cfg . FS . IsDir ( lkDir ) {
entries , err := cfg . FS . List ( lkDir )
if err == nil {
for _ , entry := range entries {
2026-04-01 14:35:36 +00:00
if entry . IsDir ( ) {
continue
}
name := entry . Name ( )
if core . HasSuffix ( name , ".yml" ) || core . HasSuffix ( name , ".yaml" ) {
2026-03-26 17:41:53 +00:00
configPath = ax . Join ( lkDir , entry . Name ( ) )
feat: extract build/, release/, sdk/ from go-devops
Build system (8 builders, signing, archiving), release pipeline
(7 publishers, versioning, changelog), and SDK generation
(OpenAPI diff, code gen). 18K LOC, all tests pass except Go
builder workspace isolation (pre-existing).
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-09 12:37:36 +00:00
break
}
}
}
}
}
2026-04-02 02:50:47 +00:00
} else if ! ax . IsAbs ( configPath ) {
configPath = ax . Join ( cfg . ProjectDir , configPath )
feat: extract build/, release/, sdk/ from go-devops
Build system (8 builders, signing, archiving), release pipeline
(7 publishers, versioning, changelog), and SDK generation
(OpenAPI diff, code gen). 18K LOC, all tests pass except Go
builder workspace isolation (pre-existing).
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-09 12:37:36 +00:00
}
if configPath == "" {
2026-03-16 21:03:21 +00:00
return nil , coreerr . E ( "LinuxKitBuilder.Build" , "no LinuxKit config file found. Specify with --config or create linuxkit.yml" , nil )
feat: extract build/, release/, sdk/ from go-devops
Build system (8 builders, signing, archiving), release pipeline
(7 publishers, versioning, changelog), and SDK generation
(OpenAPI diff, code gen). 18K LOC, all tests pass except Go
builder workspace isolation (pre-existing).
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-09 12:37:36 +00:00
}
// Validate config file exists
if ! cfg . FS . IsFile ( configPath ) {
2026-03-16 21:03:21 +00:00
return nil , coreerr . E ( "LinuxKitBuilder.Build" , "config file not found: " + configPath , nil )
feat: extract build/, release/, sdk/ from go-devops
Build system (8 builders, signing, archiving), release pipeline
(7 publishers, versioning, changelog), and SDK generation
(OpenAPI diff, code gen). 18K LOC, all tests pass except Go
builder workspace isolation (pre-existing).
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-09 12:37:36 +00:00
}
// Determine output formats
formats := cfg . Formats
if len ( formats ) == 0 {
formats = [ ] string { "qcow2-bios" } // Default to QEMU-compatible format
}
// Create output directory
outputDir := cfg . OutputDir
if outputDir == "" {
2026-03-26 17:41:53 +00:00
outputDir = ax . Join ( cfg . ProjectDir , "dist" )
feat: extract build/, release/, sdk/ from go-devops
Build system (8 builders, signing, archiving), release pipeline
(7 publishers, versioning, changelog), and SDK generation
(OpenAPI diff, code gen). 18K LOC, all tests pass except Go
builder workspace isolation (pre-existing).
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-09 12:37:36 +00:00
}
if err := cfg . FS . EnsureDir ( outputDir ) ; err != nil {
2026-03-16 21:03:21 +00:00
return nil , coreerr . E ( "LinuxKitBuilder.Build" , "failed to create output directory" , err )
feat: extract build/, release/, sdk/ from go-devops
Build system (8 builders, signing, archiving), release pipeline
(7 publishers, versioning, changelog), and SDK generation
(OpenAPI diff, code gen). 18K LOC, all tests pass except Go
builder workspace isolation (pre-existing).
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-09 12:37:36 +00:00
}
// Determine base name from config file or project name
baseName := cfg . Name
if baseName == "" {
2026-03-26 17:41:53 +00:00
baseName = core . TrimSuffix ( ax . Base ( configPath ) , ".yml" )
2026-04-01 14:35:36 +00:00
baseName = core . TrimSuffix ( baseName , ".yaml" )
feat: extract build/, release/, sdk/ from go-devops
Build system (8 builders, signing, archiving), release pipeline
(7 publishers, versioning, changelog), and SDK generation
(OpenAPI diff, code gen). 18K LOC, all tests pass except Go
builder workspace isolation (pre-existing).
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-09 12:37:36 +00:00
}
// If no targets, default to linux/amd64
if len ( targets ) == 0 {
targets = [ ] build . Target { { OS : "linux" , Arch : "amd64" } }
}
var artifacts [ ] build . Artifact
// Build for each target and format
for _ , target := range targets {
// LinuxKit only supports Linux
if target . OS != "linux" {
2026-03-26 17:41:53 +00:00
core . Print ( nil , "Skipping %s/%s (LinuxKit only supports Linux)" , target . OS , target . Arch )
feat: extract build/, release/, sdk/ from go-devops
Build system (8 builders, signing, archiving), release pipeline
(7 publishers, versioning, changelog), and SDK generation
(OpenAPI diff, code gen). 18K LOC, all tests pass except Go
builder workspace isolation (pre-existing).
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-09 12:37:36 +00:00
continue
}
for _ , format := range formats {
2026-03-26 17:41:53 +00:00
outputName := core . Sprintf ( "%s-%s" , baseName , target . Arch )
feat: extract build/, release/, sdk/ from go-devops
Build system (8 builders, signing, archiving), release pipeline
(7 publishers, versioning, changelog), and SDK generation
(OpenAPI diff, code gen). 18K LOC, all tests pass except Go
builder workspace isolation (pre-existing).
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-09 12:37:36 +00:00
args := b . buildLinuxKitArgs ( configPath , format , outputName , outputDir , target . Arch )
2026-03-26 17:41:53 +00:00
core . Print ( nil , "Building LinuxKit image: %s (%s, %s)" , outputName , format , target . Arch )
2026-04-01 16:02:09 +00:00
if err := ax . ExecWithEnv ( ctx , cfg . ProjectDir , cfg . Env , linuxkitCommand , args ... ) ; err != nil {
2026-03-16 21:03:21 +00:00
return nil , coreerr . E ( "LinuxKitBuilder.Build" , "build failed for " + target . Arch + "/" + format , err )
feat: extract build/, release/, sdk/ from go-devops
Build system (8 builders, signing, archiving), release pipeline
(7 publishers, versioning, changelog), and SDK generation
(OpenAPI diff, code gen). 18K LOC, all tests pass except Go
builder workspace isolation (pre-existing).
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-09 12:37:36 +00:00
}
// Determine the actual output file path
artifactPath := b . getArtifactPath ( outputDir , outputName , format )
// Verify the artifact was created
if ! cfg . FS . Exists ( artifactPath ) {
// Try alternate naming conventions
artifactPath = b . findArtifact ( cfg . FS , outputDir , outputName , format )
if artifactPath == "" {
2026-03-16 21:03:21 +00:00
return nil , coreerr . E ( "LinuxKitBuilder.Build" , "artifact not found after build: expected " + b . getArtifactPath ( outputDir , outputName , format ) , nil )
feat: extract build/, release/, sdk/ from go-devops
Build system (8 builders, signing, archiving), release pipeline
(7 publishers, versioning, changelog), and SDK generation
(OpenAPI diff, code gen). 18K LOC, all tests pass except Go
builder workspace isolation (pre-existing).
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-09 12:37:36 +00:00
}
}
artifacts = append ( artifacts , build . Artifact {
Path : artifactPath ,
OS : target . OS ,
Arch : target . Arch ,
} )
}
}
return artifacts , nil
}
// buildLinuxKitArgs builds the arguments for linuxkit build command.
func ( b * LinuxKitBuilder ) buildLinuxKitArgs ( configPath , format , outputName , outputDir , arch string ) [ ] string {
args := [ ] string { "build" }
// Output format
args = append ( args , "--format" , format )
// Output name
args = append ( args , "--name" , outputName )
// Output directory
args = append ( args , "--dir" , outputDir )
// Architecture (if not amd64)
if arch != "amd64" {
args = append ( args , "--arch" , arch )
}
// Config file
args = append ( args , configPath )
return args
}
// getArtifactPath returns the expected path of the built artifact.
func ( b * LinuxKitBuilder ) getArtifactPath ( outputDir , outputName , format string ) string {
ext := b . getFormatExtension ( format )
2026-03-26 17:41:53 +00:00
return ax . Join ( outputDir , outputName + ext )
feat: extract build/, release/, sdk/ from go-devops
Build system (8 builders, signing, archiving), release pipeline
(7 publishers, versioning, changelog), and SDK generation
(OpenAPI diff, code gen). 18K LOC, all tests pass except Go
builder workspace isolation (pre-existing).
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-09 12:37:36 +00:00
}
// findArtifact searches for the built artifact with various naming conventions.
func ( b * LinuxKitBuilder ) findArtifact ( fs io . Medium , outputDir , outputName , format string ) string {
// LinuxKit can create files with different suffixes
extensions := [ ] string {
b . getFormatExtension ( format ) ,
"-bios" + b . getFormatExtension ( format ) ,
"-efi" + b . getFormatExtension ( format ) ,
}
for _ , ext := range extensions {
2026-03-26 17:41:53 +00:00
path := ax . Join ( outputDir , outputName + ext )
feat: extract build/, release/, sdk/ from go-devops
Build system (8 builders, signing, archiving), release pipeline
(7 publishers, versioning, changelog), and SDK generation
(OpenAPI diff, code gen). 18K LOC, all tests pass except Go
builder workspace isolation (pre-existing).
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-09 12:37:36 +00:00
if fs . Exists ( path ) {
return path
}
}
// Try to find any file matching the output name
entries , err := fs . List ( outputDir )
if err == nil {
for _ , entry := range entries {
2026-03-26 17:41:53 +00:00
if core . HasPrefix ( entry . Name ( ) , outputName ) {
match := ax . Join ( outputDir , entry . Name ( ) )
feat: extract build/, release/, sdk/ from go-devops
Build system (8 builders, signing, archiving), release pipeline
(7 publishers, versioning, changelog), and SDK generation
(OpenAPI diff, code gen). 18K LOC, all tests pass except Go
builder workspace isolation (pre-existing).
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-09 12:37:36 +00:00
// Return first match that looks like an image
2026-04-01 18:17:52 +00:00
if isLinuxKitArtifact ( match ) {
feat: extract build/, release/, sdk/ from go-devops
Build system (8 builders, signing, archiving), release pipeline
(7 publishers, versioning, changelog), and SDK generation
(OpenAPI diff, code gen). 18K LOC, all tests pass except Go
builder workspace isolation (pre-existing).
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-09 12:37:36 +00:00
return match
}
}
}
}
return ""
}
// getFormatExtension returns the file extension for a LinuxKit output format.
func ( b * LinuxKitBuilder ) getFormatExtension ( format string ) string {
switch format {
case "iso" , "iso-bios" , "iso-efi" :
return ".iso"
case "raw" , "raw-bios" , "raw-efi" :
return ".raw"
case "qcow2" , "qcow2-bios" , "qcow2-efi" :
return ".qcow2"
case "vmdk" :
return ".vmdk"
case "vhd" :
return ".vhd"
case "gcp" :
return ".img.tar.gz"
case "aws" :
return ".raw"
2026-04-01 19:21:32 +00:00
case "docker" :
return ".docker.tar"
case "tar" :
return ".tar"
case "kernel+initrd" :
return "-initrd.img"
feat: extract build/, release/, sdk/ from go-devops
Build system (8 builders, signing, archiving), release pipeline
(7 publishers, versioning, changelog), and SDK generation
(OpenAPI diff, code gen). 18K LOC, all tests pass except Go
builder workspace isolation (pre-existing).
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-09 12:37:36 +00:00
default :
2026-03-26 17:41:53 +00:00
return "." + core . TrimSuffix ( format , "-bios" )
feat: extract build/, release/, sdk/ from go-devops
Build system (8 builders, signing, archiving), release pipeline
(7 publishers, versioning, changelog), and SDK generation
(OpenAPI diff, code gen). 18K LOC, all tests pass except Go
builder workspace isolation (pre-existing).
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-09 12:37:36 +00:00
}
}
2026-04-01 18:17:52 +00:00
// isLinuxKitArtifact reports whether a file path looks like a LinuxKit build output.
func isLinuxKitArtifact ( path string ) bool {
switch {
case core . HasSuffix ( path , ".img.tar.gz" ) :
return true
2026-04-01 19:21:32 +00:00
case core . HasSuffix ( path , ".docker.tar" ) :
return true
case core . HasSuffix ( path , "-initrd.img" ) :
return true
case core . HasSuffix ( path , ".tar" ) :
return true
2026-04-01 18:17:52 +00:00
case core . HasSuffix ( path , ".iso" ) :
return true
case core . HasSuffix ( path , ".qcow2" ) :
return true
case core . HasSuffix ( path , ".raw" ) :
return true
case core . HasSuffix ( path , ".vmdk" ) :
return true
case core . HasSuffix ( path , ".vhd" ) :
return true
default :
return false
}
}
2026-03-30 01:13:57 +00:00
// resolveLinuxKitCli returns the executable path for the linuxkit CLI.
func ( b * LinuxKitBuilder ) resolveLinuxKitCli ( paths ... string ) ( string , error ) {
if len ( paths ) == 0 {
paths = [ ] string {
"/usr/local/bin/linuxkit" ,
"/opt/homebrew/bin/linuxkit" ,
}
feat: extract build/, release/, sdk/ from go-devops
Build system (8 builders, signing, archiving), release pipeline
(7 publishers, versioning, changelog), and SDK generation
(OpenAPI diff, code gen). 18K LOC, all tests pass except Go
builder workspace isolation (pre-existing).
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-09 12:37:36 +00:00
}
2026-03-30 01:13:57 +00:00
command , err := ax . ResolveCommand ( "linuxkit" , paths ... )
if err != nil {
return "" , coreerr . E ( "LinuxKitBuilder.resolveLinuxKitCli" , "linuxkit CLI not found. Install with: brew install linuxkit (macOS) or see https://github.com/linuxkit/linuxkit" , err )
feat: extract build/, release/, sdk/ from go-devops
Build system (8 builders, signing, archiving), release pipeline
(7 publishers, versioning, changelog), and SDK generation
(OpenAPI diff, code gen). 18K LOC, all tests pass except Go
builder workspace isolation (pre-existing).
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-09 12:37:36 +00:00
}
2026-03-30 01:13:57 +00:00
return command , nil
feat: extract build/, release/, sdk/ from go-devops
Build system (8 builders, signing, archiving), release pipeline
(7 publishers, versioning, changelog), and SDK generation
(OpenAPI diff, code gen). 18K LOC, all tests pass except Go
builder workspace isolation (pre-existing).
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-09 12:37:36 +00:00
}