├── .gitignore ├── LICENSE ├── README.md ├── go.mod ├── makefat.go └── makefat_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Dependency directories (remove the comment below to include it) 15 | # vendor/ 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # makefat 2 | A tool for making fat OSX binaries (a portable lipo) 3 | 4 | You give it some executables, it makes a fat executable from them. The fat executable will run on any architecture supported by one of the input executables. 5 | 6 | ``` 7 | makefat ... 8 | ``` 9 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/randall77/makefat 2 | 3 | go 1.13 4 | -------------------------------------------------------------------------------- /makefat.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | // makefat ... 4 | 5 | import ( 6 | "debug/macho" 7 | "encoding/binary" 8 | "fmt" 9 | "io/ioutil" 10 | "os" 11 | ) 12 | 13 | const ( 14 | MagicFat64 = macho.MagicFat + 1 // TODO: add to stdlib (...when it works) 15 | 16 | // Alignment wanted for each sub-file. 17 | // amd64 needs 12 bits, arm64 needs 14. We choose the max of all requirements here. 18 | alignBits = 14 19 | align = 1 << alignBits 20 | ) 21 | 22 | func main() { 23 | if len(os.Args) < 3 { 24 | fmt.Fprintf(os.Stderr, "usage: %s ...\n", os.Args[0]) 25 | os.Exit(2) 26 | } 27 | 28 | // Read input files. 29 | type input struct { 30 | data []byte 31 | cpu uint32 32 | subcpu uint32 33 | offset int64 34 | } 35 | var inputs []input 36 | offset := int64(align) 37 | for _, i := range os.Args[2:] { 38 | data, err := ioutil.ReadFile(i) 39 | if err != nil { 40 | panic(err) 41 | } 42 | if len(data) < 12 { 43 | panic(fmt.Sprintf("file %s too small", i)) 44 | } 45 | // All currently supported mac archs (386,amd64,arm,arm64) are little endian. 46 | magic := binary.LittleEndian.Uint32(data[0:4]) 47 | if magic != macho.Magic32 && magic != macho.Magic64 { 48 | panic(fmt.Sprintf("input %s is not a macho file, magic=%x", i, magic)) 49 | } 50 | cpu := binary.LittleEndian.Uint32(data[4:8]) 51 | subcpu := binary.LittleEndian.Uint32(data[8:12]) 52 | inputs = append(inputs, input{data: data, cpu: cpu, subcpu: subcpu, offset: offset}) 53 | offset += int64(len(data)) 54 | offset = (offset + align - 1) / align * align 55 | } 56 | 57 | // Decide on whether we're doing fat32 or fat64. 58 | sixtyfour := false 59 | if inputs[len(inputs)-1].offset >= 1<<32 || len(inputs[len(inputs)-1].data) >= 1<<32 { 60 | sixtyfour = true 61 | // fat64 doesn't seem to work: 62 | // - the resulting binary won't run. 63 | // - the resulting binary is parseable by lipo, but reports that the contained files are "hidden". 64 | // - the native OSX lipo can't make a fat64. 65 | panic("files too large to fit into a fat binary") 66 | } 67 | 68 | // Make output file. 69 | out, err := os.Create(os.Args[1]) 70 | if err != nil { 71 | panic(err) 72 | } 73 | err = out.Chmod(0755) 74 | if err != nil { 75 | panic(err) 76 | } 77 | 78 | // Build a fat_header. 79 | var hdr []uint32 80 | if sixtyfour { 81 | hdr = append(hdr, MagicFat64) 82 | } else { 83 | hdr = append(hdr, macho.MagicFat) 84 | } 85 | hdr = append(hdr, uint32(len(inputs))) 86 | 87 | // Build a fat_arch for each input file. 88 | for _, i := range inputs { 89 | hdr = append(hdr, i.cpu) 90 | hdr = append(hdr, i.subcpu) 91 | if sixtyfour { 92 | hdr = append(hdr, uint32(i.offset>>32)) // big endian 93 | } 94 | hdr = append(hdr, uint32(i.offset)) 95 | if sixtyfour { 96 | hdr = append(hdr, uint32(len(i.data)>>32)) // big endian 97 | } 98 | hdr = append(hdr, uint32(len(i.data))) 99 | hdr = append(hdr, alignBits) 100 | if sixtyfour { 101 | hdr = append(hdr, 0) // reserved 102 | } 103 | } 104 | 105 | // Write header. 106 | // Note that the fat binary header is big-endian, regardless of the 107 | // endianness of the contained files. 108 | err = binary.Write(out, binary.BigEndian, hdr) 109 | if err != nil { 110 | panic(err) 111 | } 112 | offset = int64(4 * len(hdr)) 113 | 114 | // Write each contained file. 115 | for _, i := range inputs { 116 | if offset < i.offset { 117 | _, err = out.Write(make([]byte, i.offset-offset)) 118 | if err != nil { 119 | panic(err) 120 | } 121 | offset = i.offset 122 | } 123 | _, err := out.Write(i.data) 124 | if err != nil { 125 | panic(err) 126 | } 127 | offset += int64(len(i.data)) 128 | } 129 | err = out.Close() 130 | if err != nil { 131 | panic(err) 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /makefat_test.go: -------------------------------------------------------------------------------- 1 | package main_test 2 | 3 | import ( 4 | "io/ioutil" 5 | "os" 6 | "os/exec" 7 | "path/filepath" 8 | "runtime" 9 | "testing" 10 | ) 11 | 12 | func TestMakeFat(t *testing.T) { 13 | if runtime.GOOS != "darwin" { 14 | t.Skip("works on darwin only") 15 | } 16 | 17 | // Make a directory to work in. 18 | dir, err := ioutil.TempDir("", "makefat") 19 | if err != nil { 20 | t.Fatalf("could not create directory: %v", err) 21 | } 22 | defer os.RemoveAll(dir) 23 | 24 | // List files we're working with. 25 | src := filepath.Join(dir, "test.go") 26 | amd64 := filepath.Join(dir, "amd64") 27 | arm64 := filepath.Join(dir, "arm64") 28 | fat := filepath.Join(dir, "fat") 29 | 30 | // Create test source. 31 | f, err := os.Create(src) 32 | if err != nil { 33 | t.Fatalf("could not create source file: %v", err) 34 | } 35 | f.Write([]byte(` 36 | package main 37 | import "fmt" 38 | func main() { 39 | fmt.Println("hello world") 40 | } 41 | `)) 42 | f.Close() 43 | 44 | // Compile test code in both amd64 and arm64. 45 | cmd := exec.Command("go", "build", "-o", amd64, src) 46 | cmd.Env = append(os.Environ(), "GOARCH=amd64") 47 | out, err := cmd.CombinedOutput() 48 | if err != nil { 49 | t.Fatalf("could not build amd64 target: %v\n%s\n", err, string(out)) 50 | } 51 | cmd = exec.Command("go", "build", "-o", arm64, src) 52 | cmd.Env = append(os.Environ(), "GOARCH=arm64") 53 | out, err = cmd.CombinedOutput() 54 | if err != nil { 55 | t.Fatalf("could not build arm64 target: %v\n%s\n", err, string(out)) 56 | } 57 | 58 | // Build fat binary. 59 | cmd = exec.Command("go", "run", "makefat.go", fat, amd64, arm64) 60 | out, err = cmd.CombinedOutput() 61 | if err != nil { 62 | t.Fatalf("could not build fat target: %v\n%s\n", err, string(out)) 63 | } 64 | 65 | // Run fat binary. 66 | cmd = exec.Command(fat) 67 | out, err = cmd.CombinedOutput() 68 | if err != nil { 69 | t.Fatalf("could not run fat target: %v", err) 70 | } 71 | if string(out) != "hello world\n" { 72 | t.Errorf("got=%s, want=hello world\n", string(out)) 73 | } 74 | } 75 | --------------------------------------------------------------------------------