2021-09-09 20:25:25 +08:00
//go:build ignore
2017-02-18 05:54:32 +08:00
// Cross compile rclone - in go because I hate bash ;-)
package main
import (
"flag"
2017-12-08 05:49:32 +08:00
"fmt"
2017-02-18 05:54:32 +08:00
"log"
"os"
"os/exec"
2018-02-26 19:24:56 +08:00
"path"
2017-02-18 05:54:32 +08:00
"path/filepath"
2017-05-13 16:55:29 +08:00
"regexp"
2017-02-18 05:54:32 +08:00
"runtime"
2018-07-02 17:09:18 +08:00
"sort"
2017-02-18 05:54:32 +08:00
"strings"
"sync"
2018-02-26 19:24:56 +08:00
"text/template"
2017-02-18 05:54:32 +08:00
"time"
)
var (
// Flags
2022-09-28 20:37:38 +08:00
debug = flag . Bool ( "d" , false , "Print commands instead of running them" )
parallel = flag . Int ( "parallel" , runtime . NumCPU ( ) , "Number of commands to run in parallel" )
2021-02-05 18:20:19 +08:00
copyAs = flag . String ( "release" , "" , "Make copies of the releases with this name" )
gitLog = flag . String ( "git-log" , "" , "git log to include as well" )
include = flag . String ( "include" , "^.*$" , "os/arch regexp to include" )
exclude = flag . String ( "exclude" , "^$" , "os/arch regexp to exclude" )
cgo = flag . Bool ( "cgo" , false , "Use cgo for the build" )
2022-09-28 20:37:38 +08:00
noClean = flag . Bool ( "no-clean" , false , "Don't clean the build directory before running" )
2021-02-05 18:20:19 +08:00
tags = flag . String ( "tags" , "" , "Space separated list of build tags" )
buildmode = flag . String ( "buildmode" , "" , "Passed to go build -buildmode flag" )
2022-09-28 20:37:38 +08:00
compileOnly = flag . Bool ( "compile-only" , false , "Just build the binary, not the zip" )
2021-02-05 18:20:19 +08:00
extraEnv = flag . String ( "env" , "" , "comma separated list of VAR=VALUE env vars to set" )
macOSSDK = flag . String ( "macos-sdk" , "" , "macOS SDK to use" )
macOSArch = flag . String ( "macos-arch" , "" , "macOS arch to use" )
extraCgoCFlags = flag . String ( "cgo-cflags" , "" , "extra CGO_CFLAGS" )
extraCgoLdFlags = flag . String ( "cgo-ldflags" , "" , "extra CGO_LDFLAGS" )
2017-02-18 05:54:32 +08:00
)
// GOOS/GOARCH pairs we build for
2020-06-02 18:27:16 +08:00
//
// If the GOARCH contains a - it is a synthetic arch with more parameters
2017-02-18 05:54:32 +08:00
var osarches = [ ] string {
"windows/386" ,
"windows/amd64" ,
2021-11-20 01:36:43 +08:00
"windows/arm64" ,
2017-02-18 05:54:32 +08:00
"darwin/amd64" ,
2021-02-05 18:20:19 +08:00
"darwin/arm64" ,
2017-02-18 05:54:32 +08:00
"linux/386" ,
"linux/amd64" ,
"linux/arm" ,
2022-11-04 06:36:07 +08:00
"linux/arm-v6" ,
2020-06-02 18:27:16 +08:00
"linux/arm-v7" ,
2017-02-18 05:54:32 +08:00
"linux/arm64" ,
"linux/mips" ,
"linux/mipsle" ,
"freebsd/386" ,
"freebsd/amd64" ,
"freebsd/arm" ,
2022-11-04 06:36:07 +08:00
"freebsd/arm-v6" ,
2020-06-02 18:27:16 +08:00
"freebsd/arm-v7" ,
2017-02-18 05:54:32 +08:00
"netbsd/386" ,
"netbsd/amd64" ,
"netbsd/arm" ,
2022-11-04 06:36:07 +08:00
"netbsd/arm-v6" ,
2020-06-02 18:27:16 +08:00
"netbsd/arm-v7" ,
2017-02-18 05:54:32 +08:00
"openbsd/386" ,
"openbsd/amd64" ,
"plan9/386" ,
"plan9/amd64" ,
"solaris/amd64" ,
2024-08-04 19:17:34 +08:00
// "js/wasm", // Rclone is too big for js/wasm until https://github.com/golang/go/issues/64856 is fixed
2017-02-18 05:54:32 +08:00
}
2017-05-09 18:58:29 +08:00
// Special environment flags for a given arch
var archFlags = map [ string ] [ ] string {
2021-02-05 00:37:23 +08:00
"386" : { "GO386=softfloat" } ,
2018-11-05 13:37:41 +08:00
"mips" : { "GOMIPS=softfloat" } ,
"mipsle" : { "GOMIPS=softfloat" } ,
2022-11-04 06:36:07 +08:00
"arm" : { "GOARM=5" } ,
"arm-v6" : { "GOARM=6" } ,
2020-06-02 18:27:16 +08:00
"arm-v7" : { "GOARM=7" } ,
2017-05-09 18:58:29 +08:00
}
2022-02-02 19:48:29 +08:00
// Map Go architectures to NFPM architectures
// Any missing are passed straight through
var goarchToNfpm = map [ string ] string {
2022-11-04 06:36:07 +08:00
"arm" : "arm5" ,
"arm-v6" : "arm6" ,
2022-02-02 19:48:29 +08:00
"arm-v7" : "arm7" ,
}
2017-02-18 05:54:32 +08:00
// runEnv - run a shell command with env
2018-07-02 17:09:18 +08:00
func runEnv ( args , env [ ] string ) error {
2017-02-18 05:54:32 +08:00
if * debug {
args = append ( [ ] string { "echo" } , args ... )
}
cmd := exec . Command ( args [ 0 ] , args [ 1 : ] ... )
if env != nil {
cmd . Env = append ( os . Environ ( ) , env ... )
}
2017-05-09 18:58:29 +08:00
if * debug {
log . Printf ( "args = %v, env = %v\n" , args , cmd . Env )
}
2018-07-02 17:09:18 +08:00
out , err := cmd . CombinedOutput ( )
2017-02-18 05:54:32 +08:00
if err != nil {
2018-07-02 17:09:18 +08:00
log . Print ( "----------------------------" )
log . Printf ( "Failed to run %v: %v" , args , err )
log . Printf ( "Command output was:\n%s" , out )
log . Print ( "----------------------------" )
2017-02-18 05:54:32 +08:00
}
2018-07-02 17:09:18 +08:00
return err
2017-02-18 05:54:32 +08:00
}
// run a shell command
func run ( args ... string ) {
2018-07-02 17:09:18 +08:00
err := runEnv ( args , nil )
if err != nil {
log . Fatalf ( "Exiting after error: %v" , err )
}
2017-02-18 05:54:32 +08:00
}
2018-02-26 19:24:56 +08:00
// chdir or die
func chdir ( dir string ) {
err := os . Chdir ( dir )
if err != nil {
log . Fatalf ( "Couldn't cd into %q: %v" , dir , err )
}
}
// substitute data from go template file in to file out
func substitute ( inFile , outFile string , data interface { } ) {
t , err := template . ParseFiles ( inFile )
if err != nil {
2022-09-28 20:37:38 +08:00
log . Fatalf ( "Failed to read template file %q: %v" , inFile , err )
2018-02-26 19:24:56 +08:00
}
out , err := os . Create ( outFile )
if err != nil {
2022-09-28 20:37:38 +08:00
log . Fatalf ( "Failed to create output file %q: %v" , outFile , err )
2018-02-26 19:24:56 +08:00
}
defer func ( ) {
err := out . Close ( )
if err != nil {
2022-09-28 20:37:38 +08:00
log . Fatalf ( "Failed to close output file %q: %v" , outFile , err )
2018-02-26 19:24:56 +08:00
}
} ( )
err = t . Execute ( out , data )
if err != nil {
2022-09-28 20:37:38 +08:00
log . Fatalf ( "Failed to substitute template file %q: %v" , inFile , err )
2018-02-26 19:24:56 +08:00
}
}
// build the zip package return its name
func buildZip ( dir string ) string {
// Now build the zip
run ( "cp" , "-a" , "../MANUAL.txt" , filepath . Join ( dir , "README.txt" ) )
run ( "cp" , "-a" , "../MANUAL.html" , filepath . Join ( dir , "README.html" ) )
run ( "cp" , "-a" , "../rclone.1" , dir )
if * gitLog != "" {
run ( "cp" , "-a" , * gitLog , dir )
}
zip := dir + ".zip"
run ( "zip" , "-r9" , zip , dir )
return zip
}
// Build .deb and .rpm packages
//
// It returns a list of artifacts it has made
func buildDebAndRpm ( dir , version , goarch string ) [ ] string {
// Make internal version number acceptable to .deb and .rpm
pkgVersion := version [ 1 : ]
2022-05-17 00:11:45 +08:00
pkgVersion = strings . ReplaceAll ( pkgVersion , "β" , "-beta" )
pkgVersion = strings . ReplaceAll ( pkgVersion , "-" , "." )
2022-02-02 19:48:29 +08:00
nfpmArch , ok := goarchToNfpm [ goarch ]
if ! ok {
nfpmArch = goarch
}
2018-02-26 19:24:56 +08:00
// Make nfpm.yaml from the template
substitute ( "../bin/nfpm.yaml" , path . Join ( dir , "nfpm.yaml" ) , map [ string ] string {
"Version" : pkgVersion ,
2022-02-02 19:48:29 +08:00
"Arch" : nfpmArch ,
2018-02-26 19:24:56 +08:00
} )
// build them
var artifacts [ ] string
for _ , pkg := range [ ] string { ".deb" , ".rpm" } {
artifact := dir + pkg
run ( "bash" , "-c" , "cd " + dir + " && nfpm -f nfpm.yaml pkg -t ../" + artifact )
artifacts = append ( artifacts , artifact )
}
return artifacts
}
2020-06-02 18:27:16 +08:00
// Trip a version suffix off the arch if present
func stripVersion ( goarch string ) string {
i := strings . Index ( goarch , "-" )
if i < 0 {
return goarch
}
return goarch [ : i ]
}
2021-02-05 18:20:19 +08:00
// run the command returning trimmed output
func runOut ( command ... string ) string {
out , err := exec . Command ( command [ 0 ] , command [ 1 : ] ... ) . Output ( )
if err != nil {
log . Fatalf ( "Failed to run %q: %v" , command , err )
}
return strings . TrimSpace ( string ( out ) )
}
2022-09-28 20:37:38 +08:00
// Generate Windows resource system object file (.syso), which can be picked
// up by the following go build for embedding version information and icon
// resources into the executable.
func generateResourceWindows ( version , arch string ) func ( ) {
sysoPath := fmt . Sprintf ( "../resource_windows_%s.syso" , arch ) // Use explicit destination filename, even though it should be same as default, so that we are sure we have the correct reference to it
if err := os . Remove ( sysoPath ) ; ! os . IsNotExist ( err ) {
// Note: This one we choose to treat as fatal, to avoid any risk of picking up an old .syso file without noticing.
log . Fatalf ( "Failed to remove existing Windows %s resource system object file %s: %v" , arch , sysoPath , err )
}
args := [ ] string { "go" , "run" , "../bin/resource_windows.go" , "-arch" , arch , "-version" , version , "-syso" , sysoPath }
if err := runEnv ( args , nil ) ; err != nil {
log . Printf ( "Warning: Couldn't generate Windows %s resource system object file, binaries will not have version information or icon embedded" , arch )
return nil
}
if _ , err := os . Stat ( sysoPath ) ; err != nil {
log . Printf ( "Warning: Couldn't find generated Windows %s resource system object file, binaries will not have version information or icon embedded" , arch )
return nil
}
return func ( ) {
if err := os . Remove ( sysoPath ) ; err != nil && ! os . IsNotExist ( err ) {
log . Printf ( "Warning: Couldn't remove generated Windows %s resource system object file %s: %v. Please remove it manually." , arch , sysoPath , err )
}
}
}
2018-07-02 17:09:18 +08:00
// build the binary in dir returning success or failure
func compileArch ( version , goos , goarch , dir string ) bool {
2020-09-04 20:41:48 +08:00
log . Printf ( "Compiling %s/%s into %s" , goos , goarch , dir )
2022-09-28 20:37:38 +08:00
goarchBase := stripVersion ( goarch )
2017-02-18 05:54:32 +08:00
output := filepath . Join ( dir , "rclone" )
2017-02-21 00:36:54 +08:00
if goos == "windows" {
output += ".exe"
2022-09-28 20:37:38 +08:00
if cleanupFn := generateResourceWindows ( version , goarchBase ) ; cleanupFn != nil {
defer cleanupFn ( )
2020-06-11 16:26:14 +08:00
}
2017-02-21 00:36:54 +08:00
}
2017-02-18 05:54:32 +08:00
err := os . MkdirAll ( dir , 0777 )
if err != nil {
log . Fatalf ( "Failed to mkdir: %v" , err )
}
args := [ ] string {
"go" , "build" ,
2019-07-29 01:47:38 +08:00
"--ldflags" , "-s -X github.com/rclone/rclone/fs.Version=" + version ,
2020-04-30 19:43:40 +08:00
"-trimpath" ,
2017-02-18 05:54:32 +08:00
"-o" , output ,
2017-05-19 22:46:13 +08:00
"-tags" , * tags ,
2017-02-18 05:54:32 +08:00
}
2021-01-07 21:02:57 +08:00
if * buildmode != "" {
args = append ( args ,
"-buildmode" , * buildmode ,
)
}
args = append ( args ,
".." ,
)
2017-02-18 05:54:32 +08:00
env := [ ] string {
"GOOS=" + goos ,
2022-09-28 20:37:38 +08:00
"GOARCH=" + goarchBase ,
2017-05-13 16:55:29 +08:00
}
2021-02-05 18:20:19 +08:00
if * extraEnv != "" {
env = append ( env , strings . Split ( * extraEnv , "," ) ... )
}
var (
cgoCFlags [ ] string
cgoLdFlags [ ] string
)
if * macOSSDK != "" {
flag := "-isysroot " + runOut ( "xcrun" , "--sdk" , * macOSSDK , "--show-sdk-path" )
cgoCFlags = append ( cgoCFlags , flag )
cgoLdFlags = append ( cgoLdFlags , flag )
}
if * macOSArch != "" {
flag := "-arch " + * macOSArch
cgoCFlags = append ( cgoCFlags , flag )
cgoLdFlags = append ( cgoLdFlags , flag )
}
if * extraCgoCFlags != "" {
cgoCFlags = append ( cgoCFlags , * extraCgoCFlags )
}
if * extraCgoLdFlags != "" {
cgoLdFlags = append ( cgoLdFlags , * extraCgoLdFlags )
}
if len ( cgoCFlags ) > 0 {
env = append ( env , "CGO_CFLAGS=" + strings . Join ( cgoCFlags , " " ) )
}
if len ( cgoLdFlags ) > 0 {
env = append ( env , "CGO_LDFLAGS=" + strings . Join ( cgoLdFlags , " " ) )
}
2017-05-13 16:55:29 +08:00
if ! * cgo {
env = append ( env , "CGO_ENABLED=0" )
} else {
env = append ( env , "CGO_ENABLED=1" )
2017-02-18 05:54:32 +08:00
}
2017-05-09 18:58:29 +08:00
if flags , ok := archFlags [ goarch ] ; ok {
env = append ( env , flags ... )
}
2018-07-02 17:09:18 +08:00
err = runEnv ( args , env )
if err != nil {
log . Printf ( "Error compiling %s/%s: %v" , goos , goarch , err )
return false
}
2017-08-05 06:20:26 +08:00
if ! * compileOnly {
2020-08-01 02:57:48 +08:00
if goos != "js" {
artifacts := [ ] string { buildZip ( dir ) }
// build a .deb and .rpm if appropriate
if goos == "linux" {
2022-02-02 19:48:29 +08:00
artifacts = append ( artifacts , buildDebAndRpm ( dir , version , goarch ) ... )
2020-08-01 02:57:48 +08:00
}
if * copyAs != "" {
for _ , artifact := range artifacts {
run ( "ln" , artifact , strings . Replace ( artifact , "-" + version , "-" + * copyAs , 1 ) )
}
2018-02-26 19:24:56 +08:00
}
2017-08-05 06:20:26 +08:00
}
2018-02-26 19:24:56 +08:00
// tidy up
2017-08-05 06:20:26 +08:00
run ( "rm" , "-rf" , dir )
2017-02-21 01:08:07 +08:00
}
2017-02-18 05:54:32 +08:00
log . Printf ( "Done compiling %s/%s" , goos , goarch )
2018-07-02 17:09:18 +08:00
return true
2017-02-18 05:54:32 +08:00
}
func compile ( version string ) {
start := time . Now ( )
wg := new ( sync . WaitGroup )
run := make ( chan func ( ) , * parallel )
for i := 0 ; i < * parallel ; i ++ {
wg . Add ( 1 )
go func ( ) {
defer wg . Done ( )
for f := range run {
f ( )
}
} ( )
}
2017-05-13 16:55:29 +08:00
includeRe , err := regexp . Compile ( * include )
if err != nil {
log . Fatalf ( "Bad -include regexp: %v" , err )
}
excludeRe , err := regexp . Compile ( * exclude )
if err != nil {
log . Fatalf ( "Bad -exclude regexp: %v" , err )
}
compiled := 0
2018-07-02 17:09:18 +08:00
var failuresMu sync . Mutex
var failures [ ] string
2017-02-18 05:54:32 +08:00
for _ , osarch := range osarches {
2017-05-13 16:55:29 +08:00
if excludeRe . MatchString ( osarch ) || ! includeRe . MatchString ( osarch ) {
continue
}
2017-02-18 05:54:32 +08:00
parts := strings . Split ( osarch , "/" )
if len ( parts ) != 2 {
log . Fatalf ( "Bad osarch %q" , osarch )
}
goos , goarch := parts [ 0 ] , parts [ 1 ]
userGoos := goos
if goos == "darwin" {
userGoos = "osx"
}
dir := filepath . Join ( "rclone-" + version + "-" + userGoos + "-" + goarch )
run <- func ( ) {
2018-07-02 17:09:18 +08:00
if ! compileArch ( version , goos , goarch , dir ) {
failuresMu . Lock ( )
failures = append ( failures , goos + "/" + goarch )
failuresMu . Unlock ( )
}
2017-02-18 05:54:32 +08:00
}
2017-05-13 16:55:29 +08:00
compiled ++
2017-02-18 05:54:32 +08:00
}
close ( run )
wg . Wait ( )
2017-05-13 16:55:29 +08:00
log . Printf ( "Compiled %d arches in %v" , compiled , time . Since ( start ) )
2018-07-02 17:09:18 +08:00
if len ( failures ) > 0 {
sort . Strings ( failures )
log . Printf ( "%d compile failures:\n %s\n" , len ( failures ) , strings . Join ( failures , "\n " ) )
os . Exit ( 1 )
}
2017-02-18 05:54:32 +08:00
}
func main ( ) {
flag . Parse ( )
args := flag . Args ( )
if len ( args ) != 1 {
log . Fatalf ( "Syntax: %s <version>" , os . Args [ 0 ] )
}
version := args [ 0 ]
2017-05-15 16:36:55 +08:00
if ! * noClean {
run ( "rm" , "-rf" , "build" )
run ( "mkdir" , "build" )
}
2018-02-26 19:24:56 +08:00
chdir ( "build" )
2022-08-20 22:38:02 +08:00
err := os . WriteFile ( "version.txt" , [ ] byte ( fmt . Sprintf ( "rclone %s\n" , version ) ) , 0666 )
2017-12-08 05:49:32 +08:00
if err != nil {
log . Fatalf ( "Couldn't write version.txt: %v" , err )
}
2017-02-18 05:54:32 +08:00
compile ( version )
}