github.com/256dpi/max-go@v0.7.0/cmd/maxgo/main.go (about)

     1  package main
     2  
     3  import (
     4  	"flag"
     5  	"fmt"
     6  	"io/ioutil"
     7  	"os"
     8  	"os/exec"
     9  	"path/filepath"
    10  	"runtime"
    11  
    12  	"github.com/otiai10/copy"
    13  )
    14  
    15  // On macOS, we need to make sure that we always write external binaries to a
    16  // new file. Otherwise, kernel-side code-signing caches become outdated and Max
    17  // crashes with a SIGKILL when loading the modified external.
    18  // https://developer.apple.com/documentation/security/updating_mac_software
    19  
    20  var name = flag.String("name", "", "the name of the external")
    21  var out = flag.String("out", "out", "the output directory")
    22  var cross = flag.Bool("cross", false, "cross compile for Windows on macOS")
    23  var install = flag.String("install", "", "install into specified package")
    24  
    25  func main() {
    26  	// parse flags
    27  	flag.Parse()
    28  
    29  	// log
    30  	fmt.Println("==> checking system...")
    31  
    32  	// check go
    33  	_, err := exec.LookPath("go")
    34  	if err != nil {
    35  		panic("missing go command (you may need to install Go)")
    36  	}
    37  
    38  	// check cross compile
    39  	if *cross {
    40  		// check OS
    41  		if runtime.GOOS != "darwin" {
    42  			panic("cannot cross compile for macOS from Windows")
    43  		}
    44  
    45  		// check zig
    46  		_, err := exec.LookPath("zig")
    47  		if err != nil {
    48  			panic("missing zig command (you may need to install zig)")
    49  		}
    50  	}
    51  
    52  	// log
    53  	fmt.Println("==> preparing build...")
    54  
    55  	// check name
    56  	if *name == "" {
    57  		panic("missing external name")
    58  	}
    59  
    60  	// get out dir
    61  	var outDir = filepath.Join(".", "out")
    62  	if *out != "" {
    63  		outDir, err = filepath.Abs(*out)
    64  		check(err)
    65  	}
    66  
    67  	// print
    68  	fmt.Printf("name: %s\n", *name)
    69  	fmt.Printf("out: %s\n", outDir)
    70  
    71  	// clear directory (see top notes)
    72  	check(os.RemoveAll(outDir))
    73  	check(os.MkdirAll(outDir, os.ModePerm))
    74  
    75  	// build
    76  	switch runtime.GOOS {
    77  	case "darwin":
    78  		buildDarwin(outDir)
    79  		if *cross {
    80  			crossBuildWindows(outDir)
    81  		}
    82  	case "windows":
    83  		buildWindows(outDir)
    84  	}
    85  
    86  	// install
    87  	if *install != "" {
    88  		// log
    89  		fmt.Println("==> installing external...")
    90  
    91  		// get home dir
    92  		user, err := os.UserHomeDir()
    93  		check(err)
    94  
    95  		// prepare path
    96  		dir, err := filepath.Abs(filepath.Join(user, "Documents", "Max 8", "Packages", *install, "externals"))
    97  		check(err)
    98  
    99  		// log
   100  		fmt.Printf("target: %s\n", dir)
   101  
   102  		// create path
   103  		check(os.MkdirAll(dir, os.ModePerm))
   104  
   105  		// copy external (see top notes)
   106  		switch runtime.GOOS {
   107  		case "darwin":
   108  			check(os.RemoveAll(filepath.Join(dir, *name+".mxo")))
   109  			check(copy.Copy(filepath.Join(outDir, *name+".mxo"), filepath.Join(dir, *name+".mxo")))
   110  		case "windows":
   111  			check(os.RemoveAll(filepath.Join(dir, *name+".mxe64")))
   112  			check(copy.Copy(filepath.Join(outDir, *name+".mxe64"), filepath.Join(dir, *name+".mxe64")))
   113  		}
   114  	}
   115  
   116  	// log
   117  	fmt.Println("==> done!")
   118  }
   119  
   120  func buildDarwin(outDir string) {
   121  	// log
   122  	fmt.Println("==> building...")
   123  
   124  	// prepare bin file
   125  	bin := filepath.Join("out", *name)
   126  
   127  	// build arm64 and amd64
   128  	run("go",
   129  		[]string{"build", "-v", "-buildmode=c-shared", "-o", bin + "-arm64"},
   130  		[]string{"CGO_ENABLED=1", "GOARCH=arm64", "CGO_LDFLAGS=-Wl,-no_fixup_chains"},
   131  	)
   132  	run("go",
   133  		[]string{"build", "-v", "-buildmode=c-shared", "-o", bin + "-amd64"},
   134  		[]string{"CGO_ENABLED=1", "GOARCH=amd64", "CGO_LDFLAGS=-Wl,-no_fixup_chains"},
   135  	)
   136  
   137  	// assemble universal binary
   138  	run("lipo",
   139  		[]string{"-create", "-output", bin, bin + "-amd64", bin + "-arm64"},
   140  		nil,
   141  	)
   142  
   143  	// ensure directory
   144  	check(os.MkdirAll(filepath.Join(outDir, *name+".mxo", "Contents", "MacOS"), os.ModePerm))
   145  
   146  	// copy binary
   147  	check(os.Rename(bin, filepath.Join(outDir, *name+".mxo", "Contents", "MacOS", *name)))
   148  
   149  	// write info plist
   150  	check(ioutil.WriteFile(filepath.Join(outDir, *name+".mxo", "Contents", "Info.plist"), []byte(infoPlist(*name)), os.ModePerm))
   151  
   152  	// write package info
   153  	check(ioutil.WriteFile(filepath.Join(outDir, *name+".mxo", "Contents", "PkgInfo"), []byte(pkgInfo), os.ModePerm))
   154  }
   155  
   156  func buildWindows(outDir string) {
   157  	// log
   158  	fmt.Println("==> building...")
   159  
   160  	// build
   161  	run("go",
   162  		[]string{"build", "-v", "-buildmode=c-shared", "-o", filepath.Join(outDir, *name+".mxe64")},
   163  		[]string{"CGO_ENABLED=1"},
   164  	)
   165  }
   166  
   167  func crossBuildWindows(outDir string) {
   168  	// log
   169  	fmt.Println("==> cross building...")
   170  
   171  	// build
   172  	run("go",
   173  		[]string{"build", "-v", "-buildmode=c-shared", "-o", filepath.Join(outDir, *name+".mxe64")},
   174  		[]string{`CC=zig cc -target x86_64-windows-gnu`, "GOOS=windows", "GOARCH=amd64", "CGO_ENABLED=1"},
   175  	)
   176  }
   177  
   178  func run(bin string, args []string, env []string) {
   179  	// construct
   180  	cmd := exec.Command(bin, args...)
   181  	cmd.Stdout = os.Stdout
   182  	cmd.Stderr = os.Stdout
   183  	cmd.Env = append(env, os.Environ()...)
   184  
   185  	// run
   186  	err := cmd.Run()
   187  	if err != nil {
   188  		panic("command failed")
   189  	}
   190  }
   191  
   192  func check(err error) {
   193  	if err != nil {
   194  		panic(err)
   195  	}
   196  }