2026-09-08 21:49:25 -04:00
// Copyright (C) 2026 Mark Robillard Jr (MARKMENTAL)
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License v3. See LICENSE.
// SPDX-License-Identifier: GPL-3.0-only
2026-09-08 20:14:46 -04:00
// Command compile is the build driver for isthislegit. It cross-compiles
// ./cmd/isthislegit for the requested platform/arch matrix:
//
// go run ./compile.go --platform=linux --arch=amd64 --static=true
//
// Static builds (the default) are CGO-free so every target compiles
// without a cross C toolchain. Dynamic builds (--static=false) enable
// CGO and link libc dynamically; they require a C compiler for the
// target (gcc for host builds, or CC set to a cross compiler).
package main
import (
"flag"
"fmt"
"os"
"os/exec"
"runtime"
"strings"
)
// platformAliases maps friendly platform names to GOOS values.
// "bsd" fans out to freebsd, openbsd, and netbsd.
var platformAliases = map [ string ] [ ] string {
"linux" : { "linux" } ,
"darwin" : { "darwin" } ,
"mac" : { "darwin" } ,
"macos" : { "darwin" } ,
"osx" : { "darwin" } ,
"windows" : { "windows" } ,
"win" : { "windows" } ,
"bsd" : { "freebsd" , "openbsd" , "netbsd" } ,
"freebsd" : { "freebsd" } ,
"openbsd" : { "openbsd" } ,
"netbsd" : { "netbsd" } ,
}
// archAlias resolves a friendly arch name to GOARCH plus optional GOARM.
type archAlias struct {
goarch string
goarm string
}
var archAliases = map [ string ] archAlias {
"amd64" : { goarch : "amd64" } ,
"x64" : { goarch : "amd64" } ,
"x86-64" : { goarch : "amd64" } ,
"386" : { goarch : "386" } ,
"x86" : { goarch : "386" } ,
"i386" : { goarch : "386" } ,
"32bit" : { goarch : "386" } ,
"arm64" : { goarch : "arm64" } ,
"aarch64" : { goarch : "arm64" } ,
"arm" : { goarch : "arm" , goarm : "7" } ,
"armv7" : { goarch : "arm" , goarm : "7" } ,
"armv6" : { goarch : "arm" , goarm : "6" } ,
"armv5" : { goarch : "arm" , goarm : "5" } ,
}
// target is one GOOS/GOARCH(/GOARM) build.
type target struct {
goos string
goarch string
goarm string
}
func resolvePlatforms ( raw string ) ( [ ] string , error ) {
gooses , ok := platformAliases [ strings . ToLower ( strings . TrimSpace ( raw ) ) ]
if ! ok {
names := make ( [ ] string , 0 , len ( platformAliases ) )
for name := range platformAliases {
names = append ( names , name )
}
return nil , fmt . Errorf ( "unknown platform %q (valid: %s)" , raw , strings . Join ( names , ", " ) )
}
return gooses , nil
}
func resolveArch ( raw string ) ( archAlias , error ) {
name := strings . ToLower ( strings . TrimSpace ( raw ) )
if a , ok := archAliases [ name ] ; ok {
return a , nil
}
return archAlias { } , fmt . Errorf ( "unknown arch %q (valid examples: amd64, x64, arm64, aarch64, 386, x86, arm, armv7)" , raw )
}
// supported pairs "goos/goarch" from the local toolchain.
func supportedPairs ( ) ( map [ string ] bool , error ) {
out , err := exec . Command ( "go" , "tool" , "dist" , "list" ) . Output ( )
if err != nil {
return nil , fmt . Errorf ( "cannot list toolchain targets: %w" , err )
}
pairs := make ( map [ string ] bool )
for _ , line := range strings . Split ( string ( out ) , "\n" ) {
if line = strings . TrimSpace ( line ) ; line != "" {
pairs [ line ] = true
}
}
return pairs , nil
}
func outputName ( t target ) string {
name := fmt . Sprintf ( "isthislegit-%s-%s" , t . goos , t . goarch )
if t . goarm != "" {
name += "v" + t . goarm
}
if t . goos == "windows" {
name += ".exe"
}
return name
}
// isHost reports whether t matches the machine running compile.go.
func ( t target ) isHost ( ) bool {
if t . goos != runtime . GOOS || t . goarch != runtime . GOARCH {
return false
}
if t . goarch == "arm" {
return t . goarm == goarmHost ( )
}
return true
}
// goarmHost reads the host GOARM (empty unless arm, where it defaults to 7).
func goarmHost ( ) string {
if arm := os . Getenv ( "GOARM" ) ; arm != "" {
return arm
}
return "7"
}
func build ( t target , cgo bool ) error {
// Both modes strip symbol tables and DWARF via the compiler; the
// linkage (static vs dynamic) comes from CGO_ENABLED alone.
args := [ ] string { "build" , ` -ldflags=-s -w ` }
cgoEnabled := "0"
if cgo {
cgoEnabled = "1"
}
args = append ( args , "-o" , outputName ( t ) , "./cmd/isthislegit" )
cmd := exec . Command ( "go" , args ... )
cmd . Env = append ( os . Environ ( ) ,
"GOOS=" + t . goos ,
"GOARCH=" + t . goarch ,
"CGO_ENABLED=" + cgoEnabled ,
)
if t . goarm != "" {
cmd . Env = append ( cmd . Env , "GOARM=" + t . goarm )
}
cmd . Stdout = os . Stdout
cmd . Stderr = os . Stderr
fmt . Printf ( "[*] GOOS=%s GOARCH=%s CGO_ENABLED=%s" , t . goos , t . goarch , cgoEnabled )
if t . goarm != "" {
fmt . Printf ( " GOARM=%s" , t . goarm )
}
fmt . Printf ( " -> %s\n" , outputName ( t ) )
if err := cmd . Run ( ) ; err != nil {
return fmt . Errorf ( "build %s/%s failed: %w" , t . goos , t . goarch , err )
}
return nil
}
func run ( ) int {
platformFlag := flag . String ( "platform" , "linux" , "target OS: linux, mac/darwin, windows/win, bsd (freebsd+openbsd+netbsd), or a single BSD" )
archFlag := flag . String ( "arch" , "amd64" , "target arch: amd64/x64, arm64/aarch64, 386/x86, arm/armv7, ..." )
staticFlag := flag . Bool ( "static" , true , "true for static (CGO_ENABLED=0), false for dynamic (CGO_ENABLED=1, host builds); both stripped via -ldflags" )
flag . Parse ( )
gooses , err := resolvePlatforms ( * platformFlag )
if err != nil {
fmt . Fprintf ( os . Stderr , "[!] Error: %v.\n" , err )
return 1
}
arch , err := resolveArch ( * archFlag )
if err != nil {
fmt . Fprintf ( os . Stderr , "[!] Error: %v.\n" , err )
return 1
}
pairs , err := supportedPairs ( )
if err != nil {
fmt . Fprintf ( os . Stderr , "[!] Error: %v.\n" , err )
return 1
}
var targets [ ] target
for _ , goos := range gooses {
if ! pairs [ goos + "/" + arch . goarch ] {
fmt . Fprintf ( os . Stderr , "[!] Error: target %s/%s is not supported by this toolchain.\n" , goos , arch . goarch )
return 1
}
targets = append ( targets , target { goos : goos , goarch : arch . goarch , goarm : arch . goarm } )
}
// Dynamic builds need a C compiler for the target: gcc covers host
// builds, while cross builds require CC set to a cross compiler.
if ! * staticFlag && os . Getenv ( "CC" ) == "" {
for _ , t := range targets {
if ! t . isHost ( ) {
fmt . Fprintf ( os . Stderr , "[!] Error: --static=false needs CGO, which cannot cross-compile to %s/%s without a cross C compiler (set CC or use --static=true).\n" , t . goos , t . goarch )
return 1
}
}
}
built := 0
for _ , t := range targets {
if err := build ( t , ! * staticFlag ) ; err != nil {
fmt . Fprintf ( os . Stderr , "[!] Error: %v.\n" , err )
return 1
}
built ++
}
fmt . Printf ( "[+] Built %d of %d target(s).\n" , built , len ( targets ) )
return 0
}
func main ( ) {
os . Exit ( run ( ) )
}