1//===-- llvm-go.go - go tool wrapper for LLVM -----------------------------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8//
9// This tool lets us build LLVM components within the tree by setting up a
10// $GOPATH that resembles a tree fetched in the normal way with "go get".
11//
12//===----------------------------------------------------------------------===//
13
14package main
15
16import (
17	"fmt"
18	"io/ioutil"
19	"os"
20	"os/exec"
21	"path/filepath"
22	"runtime"
23	"strings"
24)
25
26const (
27	linkmodeComponentLibs = "component-libs"
28	linkmodeDylib         = "dylib"
29)
30
31type pkg struct {
32	llvmpath, pkgpath string
33}
34
35var packages = []pkg{
36	{"bindings/go/llvm", "llvm.org/llvm/bindings/go/llvm"},
37}
38
39type compilerFlags struct {
40	cpp, cxx, ld string
41}
42
43var components = []string{
44	"all-targets",
45	"analysis",
46	"asmparser",
47	"asmprinter",
48	"bitreader",
49	"bitwriter",
50	"codegen",
51	"core",
52	"coroutines",
53	"debuginfodwarf",
54	"executionengine",
55	"instrumentation",
56	"interpreter",
57	"ipo",
58	"irreader",
59	"linker",
60	"mc",
61	"mcjit",
62	"objcarcopts",
63	"option",
64	"profiledata",
65	"scalaropts",
66	"support",
67	"target",
68}
69
70func llvmConfig(args ...string) string {
71	configpath := os.Getenv("LLVM_CONFIG")
72	if configpath == "" {
73		bin, _ := filepath.Split(os.Args[0])
74		configpath = filepath.Join(bin, "llvm-config")
75	}
76
77	cmd := exec.Command(configpath, args...)
78	cmd.Stderr = os.Stderr
79	out, err := cmd.Output()
80	if err != nil {
81		panic(err.Error())
82	}
83
84	outstr := string(out)
85	outstr = strings.TrimSuffix(outstr, "\n")
86	outstr = strings.Replace(outstr, "\n", " ", -1)
87	return outstr
88}
89
90func llvmFlags() compilerFlags {
91	args := append([]string{"--ldflags", "--libs", "--system-libs"}, components...)
92	ldflags := llvmConfig(args...)
93	stdLibOption := ""
94	if strings.Contains(llvmConfig("--cxxflags"), "-stdlib=libc++") {
95		// If libc++ is used to build LLVM libraries, -stdlib=libc++ is
96		// needed to resolve dependent symbols
97		stdLibOption = "-stdlib=libc++"
98	}
99	if runtime.GOOS != "darwin" {
100		// OS X doesn't like -rpath with cgo. See:
101		// https://github.com/golang/go/issues/7293
102		ldflags = "-Wl,-rpath," + llvmConfig("--libdir") + " " + ldflags
103	}
104	return compilerFlags{
105		cpp: llvmConfig("--cppflags"),
106		cxx: "-std=c++14" + " " + stdLibOption,
107		ld:  ldflags,
108	}
109}
110
111func addTag(args []string, tag string) []string {
112	args = append([]string{}, args...)
113	addedTag := false
114	for i, a := range args {
115		if strings.HasPrefix(a, "-tags=") {
116			args[i] = a + " " + tag
117			addedTag = true
118		} else if a == "-tags" && i+1 < len(args) {
119			args[i+1] = args[i+1] + " " + tag
120			addedTag = true
121		}
122	}
123	if !addedTag {
124		args = append([]string{args[0], "-tags", tag}, args[1:]...)
125	}
126	return args
127}
128
129func printComponents() {
130	fmt.Println(strings.Join(components, " "))
131}
132
133func printConfig() {
134	flags := llvmFlags()
135
136	fmt.Printf(`// +build !byollvm
137
138// This file is generated by llvm-go, do not edit.
139
140package llvm
141
142/*
143#cgo CPPFLAGS: %s
144#cgo CXXFLAGS: %s
145#cgo LDFLAGS: %s
146*/
147import "C"
148
149type (run_build_sh int)
150`, flags.cpp, flags.cxx, flags.ld)
151}
152
153func runGoWithLLVMEnv(args []string, cc, cxx, gocmd, llgo, cppflags, cxxflags, ldflags string, packages []pkg) {
154	args = addTag(args, "byollvm")
155
156	srcdir := llvmConfig("--src-root")
157
158	tmpgopath, err := ioutil.TempDir("", "gopath")
159	if err != nil {
160		panic(err.Error())
161	}
162
163	for _, p := range packages {
164		path := filepath.Join(tmpgopath, "src", p.pkgpath)
165		err := os.MkdirAll(filepath.Dir(path), os.ModePerm)
166		if err != nil {
167			panic(err.Error())
168		}
169
170		abspath := p.llvmpath
171		if !filepath.IsAbs(abspath) {
172			abspath = filepath.Join(srcdir, abspath)
173		}
174
175		err = os.Symlink(abspath, path)
176		if err != nil {
177			panic(err.Error())
178		}
179	}
180
181	newpath := os.Getenv("PATH")
182
183	newgopathlist := []string{tmpgopath}
184	newgopathlist = append(newgopathlist, filepath.SplitList(os.Getenv("GOPATH"))...)
185	newgopath := strings.Join(newgopathlist, string(filepath.ListSeparator))
186
187	flags := llvmFlags()
188
189	newenv := []string{
190		"CC=" + cc,
191		"CXX=" + cxx,
192		"CGO_CPPFLAGS=" + flags.cpp + " " + cppflags,
193		"CGO_CXXFLAGS=" + flags.cxx + " " + cxxflags,
194		"CGO_LDFLAGS=" + flags.ld + " " + ldflags,
195		"GOPATH=" + newgopath,
196		"PATH=" + newpath,
197	}
198	if llgo != "" {
199		newenv = append(newenv, "GCCGO="+llgo)
200	}
201
202	for _, v := range os.Environ() {
203		if !strings.HasPrefix(v, "CC=") &&
204			!strings.HasPrefix(v, "CXX=") &&
205			!strings.HasPrefix(v, "CGO_CPPFLAGS=") &&
206			!strings.HasPrefix(v, "CGO_CXXFLAGS=") &&
207			!strings.HasPrefix(v, "CGO_LDFLAGS=") &&
208			!strings.HasPrefix(v, "GCCGO=") &&
209			!strings.HasPrefix(v, "GOPATH=") &&
210			!strings.HasPrefix(v, "PATH=") {
211			newenv = append(newenv, v)
212		}
213	}
214
215	gocmdpath, err := exec.LookPath(gocmd)
216	if err != nil {
217		panic(err.Error())
218	}
219
220	proc, err := os.StartProcess(gocmdpath, append([]string{gocmd}, args...),
221		&os.ProcAttr{
222			Env:   newenv,
223			Files: []*os.File{os.Stdin, os.Stdout, os.Stderr},
224		})
225	if err != nil {
226		panic(err.Error())
227	}
228	ps, err := proc.Wait()
229	if err != nil {
230		panic(err.Error())
231	}
232
233	os.RemoveAll(tmpgopath)
234
235	if !ps.Success() {
236		os.Exit(1)
237	}
238}
239
240func usage() {
241	fmt.Println(`Usage: llvm-go subcommand [flags]
242
243Available subcommands: build get install run test print-components print-config`)
244	os.Exit(0)
245}
246
247func main() {
248	cc := os.Getenv("CC")
249	cxx := os.Getenv("CXX")
250	cppflags := os.Getenv("CGO_CPPFLAGS")
251	cxxflags := os.Getenv("CGO_CXXFLAGS")
252	ldflags := os.Getenv("CGO_LDFLAGS")
253	gocmd := "go"
254	llgo := ""
255	packagesString := ""
256
257	flags := []struct {
258		name string
259		dest *string
260	}{
261		{"cc", &cc},
262		{"cxx", &cxx},
263		{"go", &gocmd},
264		{"llgo", &llgo},
265		{"cppflags", &cppflags},
266		{"ldflags", &ldflags},
267		{"packages", &packagesString},
268	}
269
270	args := os.Args[1:]
271LOOP:
272	for {
273		if len(args) == 0 {
274			usage()
275		}
276		for _, flag := range flags {
277			if strings.HasPrefix(args[0], flag.name+"=") {
278				*flag.dest = args[0][len(flag.name)+1:]
279				args = args[1:]
280				continue LOOP
281			}
282		}
283		break
284	}
285
286	packages := packages
287	if packagesString != "" {
288		for _, field := range strings.Fields(packagesString) {
289			pos := strings.IndexRune(field, '=')
290			if pos == -1 {
291				fmt.Fprintf(os.Stderr, "invalid packages value %q, expected 'pkgpath=llvmpath [pkgpath=llvmpath ...]'\n", packagesString)
292				os.Exit(1)
293			}
294			packages = append(packages, pkg{
295				pkgpath:  field[:pos],
296				llvmpath: field[pos+1:],
297			})
298		}
299	}
300
301	switch args[0] {
302	case "build", "get", "install", "run", "test":
303		runGoWithLLVMEnv(args, cc, cxx, gocmd, llgo, cppflags, cxxflags, ldflags, packages)
304	case "print-components":
305		printComponents()
306	case "print-config":
307		printConfig()
308	default:
309		usage()
310	}
311}
312