aboutsummaryrefslogtreecommitdiffhomepage
path: root/builder/musl.go
blob: a65a920b73f08ec4a5fe87bfbfc62242c6f55ebd (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
package builder

import (
	"bytes"
	"fmt"
	"os"
	"path/filepath"
	"regexp"
	"strconv"
	"strings"

	"github.com/tinygo-org/tinygo/compileopts"
	"github.com/tinygo-org/tinygo/goenv"
	"tinygo.org/x/go-llvm"
)

var Musl = Library{
	name: "musl",
	makeHeaders: func(target, includeDir string) error {
		bits := filepath.Join(includeDir, "bits")
		err := os.Mkdir(bits, 0777)
		if err != nil {
			return err
		}

		arch := compileopts.MuslArchitecture(target)
		muslDir := filepath.Join(goenv.Get("TINYGOROOT"), "lib", "musl")

		// Create the file alltypes.h.
		f, err := os.Create(filepath.Join(bits, "alltypes.h"))
		if err != nil {
			return err
		}
		infiles := []string{
			filepath.Join(muslDir, "arch", arch, "bits", "alltypes.h.in"),
			filepath.Join(muslDir, "include", "alltypes.h.in"),
		}
		for _, infile := range infiles {
			data, err := os.ReadFile(infile)
			if err != nil {
				return err
			}
			lines := strings.Split(string(data), "\n")
			for _, line := range lines {
				if strings.HasPrefix(line, "TYPEDEF ") {
					matches := regexp.MustCompile(`TYPEDEF (.*) ([^ ]*);`).FindStringSubmatch(line)
					value := matches[1]
					name := matches[2]
					line = fmt.Sprintf("#if defined(__NEED_%s) && !defined(__DEFINED_%s)\ntypedef %s %s;\n#define __DEFINED_%s\n#endif\n", name, name, value, name, name)
				}
				if strings.HasPrefix(line, "STRUCT ") {
					matches := regexp.MustCompile(`STRUCT * ([^ ]*) (.*);`).FindStringSubmatch(line)
					name := matches[1]
					value := matches[2]
					line = fmt.Sprintf("#if defined(__NEED_struct_%s) && !defined(__DEFINED_struct_%s)\nstruct %s %s;\n#define __DEFINED_struct_%s\n#endif\n", name, name, name, value, name)
				}
				f.WriteString(line + "\n")
			}
		}
		f.Close()

		// Create the file syscall.h.
		f, err = os.Create(filepath.Join(bits, "syscall.h"))
		if err != nil {
			return err
		}
		data, err := os.ReadFile(filepath.Join(muslDir, "arch", arch, "bits", "syscall.h.in"))
		if err != nil {
			return err
		}
		_, err = f.Write(bytes.ReplaceAll(data, []byte("__NR_"), []byte("SYS_")))
		if err != nil {
			return err
		}
		f.Close()

		return nil
	},
	cflags: func(target, headerPath string) []string {
		arch := compileopts.MuslArchitecture(target)
		muslDir := filepath.Join(goenv.Get("TINYGOROOT"), "lib/musl")
		cflags := []string{
			"-std=c99",            // same as in musl
			"-D_XOPEN_SOURCE=700", // same as in musl
			// Musl triggers some warnings and we don't want to show any
			// warnings while compiling (only errors or silence), so disable
			// specific warnings that are triggered in musl.
			"-Werror",
			"-Wno-logical-op-parentheses",
			"-Wno-bitwise-op-parentheses",
			"-Wno-shift-op-parentheses",
			"-Wno-ignored-attributes",
			"-Wno-string-plus-int",
			"-Wno-ignored-pragmas",
			"-Wno-tautological-constant-out-of-range-compare",
			"-Qunused-arguments",
			// Select include dirs. Don't include standard library includes
			// (that would introduce host dependencies and other complications),
			// but do include all the include directories expected by musl.
			"-nostdlibinc",
			"-I" + muslDir + "/arch/" + arch,
			"-I" + muslDir + "/arch/generic",
			"-I" + muslDir + "/src/include",
			"-I" + muslDir + "/src/internal",
			"-I" + headerPath,
			"-I" + muslDir + "/include",
			"-fno-stack-protector",
		}
		llvmMajor, _ := strconv.Atoi(strings.SplitN(llvm.Version, ".", 2)[0])
		if llvmMajor >= 15 {
			// This flag was added in Clang 15. It is not present in LLVM 14.
			cflags = append(cflags, "-Wno-deprecated-non-prototype")
		}
		return cflags
	},
	sourceDir: func() string { return filepath.Join(goenv.Get("TINYGOROOT"), "lib/musl/src") },
	librarySources: func(target string) ([]string, error) {
		arch := compileopts.MuslArchitecture(target)
		globs := []string{
			"env/*.c",
			"errno/*.c",
			"exit/*.c",
			"internal/defsysinfo.c",
			"internal/libc.c",
			"internal/syscall_ret.c",
			"internal/vdso.c",
			"legacy/*.c",
			"malloc/*.c",
			"malloc/mallocng/*.c",
			"mman/*.c",
			"math/*.c",
			"signal/*.c",
			"stdio/*.c",
			"string/*.c",
			"thread/" + arch + "/*.s",
			"thread/*.c",
			"time/*.c",
			"unistd/*.c",
		}
		if arch == "arm" {
			// These files need to be added to the start for some reason.
			globs = append([]string{"thread/arm/*.c"}, globs...)
		}

		var sources []string
		seenSources := map[string]struct{}{}
		basepath := goenv.Get("TINYGOROOT") + "/lib/musl/src/"
		for _, pattern := range globs {
			matches, err := filepath.Glob(basepath + pattern)
			if err != nil {
				// From the documentation:
				// > Glob ignores file system errors such as I/O errors reading
				// > directories. The only possible returned error is
				// > ErrBadPattern, when pattern is malformed.
				// So the only possible error is when the (statically defined)
				// pattern is wrong. In other words, a programming bug.
				return nil, fmt.Errorf("musl: could not glob source dirs: %w", err)
			}
			if len(matches) == 0 {
				return nil, fmt.Errorf("musl: did not find any files for pattern %#v", pattern)
			}
			for _, match := range matches {
				relpath, err := filepath.Rel(basepath, match)
				if err != nil {
					// Not sure if this is even possible.
					return nil, err
				}
				// Make sure architecture specific files override generic files.
				id := strings.ReplaceAll(relpath, "/"+arch+"/", "/")
				if _, ok := seenSources[id]; ok {
					// Already seen this file, skipping this (generic) file.
					continue
				}
				seenSources[id] = struct{}{}
				sources = append(sources, relpath)
			}
		}
		return sources, nil
	},
	crt1Source: "../crt/crt1.c", // lib/musl/crt/crt1.c
}