github.com/kekek/gb@v0.4.5-0.20170222120241-d4ba64b0b297/gc.go (about) 1 package gb 2 3 import ( 4 "bytes" 5 "fmt" 6 "go/build" 7 "io" 8 "io/ioutil" 9 "os" 10 "path/filepath" 11 "runtime" 12 "strings" 13 14 "github.com/pkg/errors" 15 ) 16 17 // gc toolchain 18 19 type gcToolchain struct { 20 gc, cc, ld, as, pack string 21 } 22 23 func GcToolchain() func(c *Context) error { 24 return func(c *Context) error { 25 // TODO(dfc) this should come from the context, not the runtime. 26 goroot := runtime.GOROOT() 27 28 if goversion == 1.4 && (c.gohostos != c.gotargetos || c.gohostarch != c.gotargetarch) { 29 // cross-compliation is not supported yet #31 30 return errors.Errorf("cross compilation from host %s/%s to target %s/%s not supported with Go 1.4", c.gohostos, c.gohostarch, c.gotargetos, c.gotargetarch) 31 } 32 33 tooldir := filepath.Join(goroot, "pkg", "tool", c.gohostos+"_"+c.gohostarch) 34 exe := "" 35 if c.gohostos == "windows" { 36 exe += ".exe" 37 } 38 switch { 39 case goversion == 1.4: 40 archchar, err := build.ArchChar(c.gotargetarch) 41 if err != nil { 42 return err 43 } 44 c.tc = &gcToolchain{ 45 gc: filepath.Join(tooldir, archchar+"g"+exe), 46 ld: filepath.Join(tooldir, archchar+"l"+exe), 47 as: filepath.Join(tooldir, archchar+"a"+exe), 48 cc: filepath.Join(tooldir, archchar+"c"+exe), 49 pack: filepath.Join(tooldir, "pack"+exe), 50 } 51 case goversion > 1.4: 52 c.tc = &gcToolchain{ 53 gc: filepath.Join(tooldir, "compile"+exe), 54 ld: filepath.Join(tooldir, "link"+exe), 55 as: filepath.Join(tooldir, "asm"+exe), 56 pack: filepath.Join(tooldir, "pack"+exe), 57 } 58 default: 59 return errors.Errorf("unsupported Go version: %v", runtime.Version()) 60 } 61 return nil 62 } 63 } 64 65 func (t *gcToolchain) Asm(pkg *Package, ofile, sfile string) error { 66 args := []string{"-o", ofile, "-D", "GOOS_" + pkg.gotargetos, "-D", "GOARCH_" + pkg.gotargetarch} 67 switch { 68 case goversion == 1.4: 69 includedir := filepath.Join(runtime.GOROOT(), "pkg", pkg.gotargetos+"_"+pkg.gotargetarch) 70 args = append(args, "-I", includedir) 71 case goversion > 1.4: 72 odir := filepath.Join(filepath.Dir(ofile)) 73 includedir := filepath.Join(runtime.GOROOT(), "pkg", "include") 74 args = append(args, "-I", odir, "-I", includedir) 75 default: 76 return errors.Errorf("unsupported Go version: %v", runtime.Version()) 77 } 78 args = append(args, sfile) 79 if err := mkdir(filepath.Dir(ofile)); err != nil { 80 return errors.Errorf("gc:asm: %v", err) 81 } 82 var buf bytes.Buffer 83 err := runOut(&buf, pkg.Dir, nil, t.as, args...) 84 if err != nil { 85 fmt.Fprintf(os.Stderr, "# %s\n", pkg.ImportPath) 86 io.Copy(os.Stderr, &buf) 87 } 88 return err 89 } 90 91 func (t *gcToolchain) Ld(pkg *Package) error { 92 // to ensure we don't write a partial binary, link the binary to a temporary file in 93 // in the target directory, then rename. 94 dir := pkg.bindir() 95 if err := mkdir(dir); err != nil { 96 return err 97 } 98 tmp, err := ioutil.TempFile(dir, ".gb-link") 99 if err != nil { 100 return err 101 } 102 tmp.Close() 103 104 args := append(pkg.ldflags, "-o", tmp.Name()) 105 for _, d := range pkg.includePaths() { 106 args = append(args, "-L", d) 107 } 108 args = append(args, "-extld", linkCmd(pkg, "CC", defaultCC)) 109 if goversion > 1.4 { 110 args = append(args, "-buildmode", pkg.buildmode) 111 } 112 args = append(args, pkg.objfile()) 113 114 var buf bytes.Buffer 115 if err = runOut(&buf, ".", nil, t.ld, args...); err != nil { 116 os.Remove(tmp.Name()) // remove partial file 117 fmt.Fprintf(os.Stderr, "# %s\n", pkg.ImportPath) 118 io.Copy(os.Stderr, &buf) 119 return err 120 } 121 return os.Rename(tmp.Name(), pkg.Binfile()) 122 } 123 124 func (t *gcToolchain) Cc(pkg *Package, ofile, cfile string) error { 125 if goversion > 1.4 { 126 return errors.Errorf("gc %f does not support cc", goversion) 127 } 128 args := []string{ 129 "-F", "-V", "-w", 130 "-trimpath", pkg.Context.Workdir(), 131 "-I", pkg.Workdir(), 132 "-I", filepath.Join(runtime.GOROOT(), "pkg", pkg.gohostos+"_"+pkg.gohostarch), // for runtime.h 133 "-o", ofile, 134 "-D", "GOOS_" + pkg.gotargetos, 135 "-D", "GOARCH_" + pkg.gotargetarch, 136 cfile, 137 } 138 var buf bytes.Buffer 139 err := runOut(&buf, pkg.Dir, nil, t.cc, args...) 140 if err != nil { 141 fmt.Fprintf(os.Stderr, "# %s\n", pkg.ImportPath) 142 io.Copy(os.Stderr, &buf) 143 } 144 return err 145 } 146 147 func (t *gcToolchain) Pack(pkg *Package, afiles ...string) error { 148 args := []string{"r"} 149 args = append(args, afiles...) 150 dir := filepath.Dir(afiles[0]) 151 var buf bytes.Buffer 152 err := runOut(&buf, dir, nil, t.pack, args...) 153 if err != nil { 154 fmt.Fprintf(os.Stderr, "# %s\n", pkg.ImportPath) 155 io.Copy(os.Stderr, &buf) 156 } 157 return err 158 } 159 160 func (t *gcToolchain) compiler() string { return t.gc } 161 func (t *gcToolchain) linker() string { return t.ld } 162 163 func (t *gcToolchain) Gc(pkg *Package, files []string) error { 164 outfile := pkg.objfile() 165 args := append(pkg.gcflags, "-p", pkg.ImportPath, "-pack") 166 args = append(args, "-o", outfile) 167 for _, d := range pkg.includePaths() { 168 args = append(args, "-I", d) 169 } 170 if pkg.Goroot && pkg.ImportPath == "runtime" { 171 // runtime compiles with a special gc flag to emit 172 // additional reflect type data. 173 args = append(args, "-+") 174 } 175 176 switch { 177 case pkg.Complete(): 178 args = append(args, "-complete") 179 case goversion > 1.4: 180 asmhdr := filepath.Join(filepath.Dir(outfile), pkg.Name, "go_asm.h") 181 args = append(args, "-asmhdr", asmhdr) 182 } 183 184 // If there are vendored components, create an -importmap to map the import statement 185 // to the vendored import path. The possibilities for abusing this flag are endless. 186 if goversion > 1.5 && pkg.Goroot { 187 for _, path := range pkg.Package.Imports { 188 if i := strings.LastIndex(path, "/vendor/"); i >= 0 { 189 args = append(args, "-importmap", path[i+len("/vendor/"):]+"="+path) 190 } else if strings.HasPrefix(path, "vendor/") { 191 args = append(args, "-importmap", path[len("vendor/"):]+"="+path) 192 } 193 } 194 } 195 196 args = append(args, files...) 197 if err := mkdir(filepath.Join(filepath.Dir(outfile), pkg.Name)); err != nil { 198 return errors.Wrap(err, "mkdir") 199 } 200 var buf bytes.Buffer 201 err := runOut(&buf, pkg.Dir, nil, t.gc, args...) 202 if err != nil { 203 fmt.Fprintf(os.Stderr, "# %s\n", pkg.ImportPath) 204 io.Copy(os.Stderr, &buf) 205 } 206 return err 207 }