github.com/jspc/eggos@v0.5.1-0.20221028160421-556c75c878a5/magefile.go (about) 1 //go:build mage 2 // +build mage 3 4 package main 5 6 import ( 7 "fmt" 8 "os" 9 "os/exec" 10 "path/filepath" 11 "regexp" 12 "runtime" 13 "strconv" 14 "strings" 15 16 "github.com/magefile/mage/mg" 17 "github.com/magefile/mage/sh" 18 ) 19 20 var ( 21 TOOLPREFIX = detectToolPrefix() 22 CC = TOOLPREFIX + "gcc" 23 LD = TOOLPREFIX + "ld" 24 25 CFLAGS = initCflags() 26 LDFLAGS = initLdflags() 27 ) 28 29 var ( 30 GOTAGS = "nes phy prometheus" 31 GOGCFLAGS = "" 32 ) 33 34 var ( 35 QEMU64 = "qemu-system-x86_64" 36 QEMU32 = "qemu-system-i386" 37 38 QEMU_OPT = initQemuOpt() 39 QEMU_DEBUG_OPT = initQemuDebugOpt() 40 ) 41 42 var ( 43 eggBin string 44 ) 45 46 const ( 47 goMajorVersionSupported = 1 48 maxGoMinorVersionSupported = 16 49 ) 50 51 // Kernel target build the elf kernel for eggos, generate kernel.elf 52 func Kernel() error { 53 mg.Deps(Egg) 54 55 detectGoVersion() 56 return rundir("app", nil, eggBin, "build", "-o", "../kernel.elf", 57 "-gcflags", GOGCFLAGS, 58 "-tags", GOTAGS, 59 "./kmain") 60 } 61 62 func Boot64() error { 63 compileCfile("boot/boot64.S", "-m64") 64 compileCfile("boot/boot64main.c", "-m64") 65 ldflags := "-Ttext 0x3200000 -m elf_x86_64 -o boot64.elf boot64.o boot64main.o" 66 ldArgs := append([]string{}, LDFLAGS...) 67 ldArgs = append(ldArgs, strings.Fields(ldflags)...) 68 return sh.RunV(LD, ldArgs...) 69 } 70 71 // Multiboot target build Multiboot specification compatible elf format, generate multiboot.elf 72 func Multiboot() error { 73 mg.Deps(Boot64) 74 compileCfile("boot/multiboot.c", "-m32") 75 compileCfile("boot/multiboot_header.S", "-m32") 76 ldflags := "-Ttext 0x3300000 -m elf_i386 -o multiboot.elf multiboot.o multiboot_header.o -b binary boot64.elf" 77 ldArgs := append([]string{}, LDFLAGS...) 78 ldArgs = append(ldArgs, strings.Fields(ldflags)...) 79 err := sh.RunV(LD, ldArgs...) 80 if err != nil { 81 return err 82 } 83 return sh.Copy( 84 filepath.Join("cmd", "egg", "assets", "boot", "multiboot.elf"), 85 "multiboot.elf", 86 ) 87 } 88 89 func Test() error { 90 mg.Deps(Egg) 91 92 envs := map[string]string{ 93 "QEMU_OPTS": quoteArgs(QEMU_OPT), 94 } 95 return rundir("tests", envs, eggBin, "test") 96 } 97 98 func TestDebug() error { 99 mg.Deps(Egg) 100 101 envs := map[string]string{ 102 "QEMU_OPTS": quoteArgs(QEMU_DEBUG_OPT), 103 } 104 return rundir("tests", envs, eggBin, "test") 105 } 106 107 // Qemu run multiboot.elf on qemu. 108 // If env QEMU_ACCEL is set,QEMU acceleration will be enabled. 109 // If env QEMU_GRAPHIC is set QEMU will run in graphic mode. 110 // Use Crtl+a c to switch console, and type `quit` 111 func Qemu() error { 112 mg.Deps(Kernel) 113 114 detectQemu() 115 return eggrun(QEMU_OPT, "kernel.elf") 116 } 117 118 // QemuDebug run multiboot.elf in debug mode. 119 // Monitor GDB connection on port 1234 120 func QemuDebug() error { 121 GOGCFLAGS += " -N -l" 122 mg.Deps(Kernel) 123 124 detectQemu() 125 return eggrun(QEMU_DEBUG_OPT, "kernel.elf") 126 } 127 128 // Iso generate eggos.iso, which can be used with qemu -cdrom option. 129 func Iso() error { 130 mg.Deps(Kernel) 131 return sh.RunV(eggBin, "pack", "-o", "eggos.iso", "-k", "kernel.elf") 132 } 133 134 // Graphic run eggos.iso on qemu, which vbe is enabled. 135 func Graphic() error { 136 detectQemu() 137 138 mg.Deps(Iso) 139 return eggrun(QEMU_OPT, "eggos.iso") 140 } 141 142 // GraphicDebug run eggos.iso on qemu in debug mode. 143 func GraphicDebug() error { 144 detectQemu() 145 146 GOGCFLAGS += " -N -l" 147 mg.Deps(Iso) 148 return eggrun(QEMU_DEBUG_OPT, "eggos.iso") 149 } 150 151 func Egg() error { 152 err := rundir("cmd", nil, "go", "build", "-o", "../egg", "./egg") 153 if err != nil { 154 return err 155 } 156 current, _ := os.Getwd() 157 eggBin = filepath.Join(current, "egg") 158 return nil 159 } 160 161 func Includes() error { 162 mg.Deps(Egg) 163 return sh.RunV(eggBin, "generate") 164 } 165 166 func Clean() { 167 rmGlob("*.o") 168 rmGlob("kernel.elf") 169 rmGlob("multiboot.elf") 170 rmGlob("qemu.log") 171 rmGlob("qemu.pcap") 172 rmGlob("eggos.iso") 173 rmGlob("egg") 174 rmGlob("boot64.elf") 175 rmGlob("bochs.log") 176 } 177 178 func detectToolPrefix() string { 179 prefix := os.Getenv("TOOLPREFIX") 180 if prefix != "" { 181 return prefix 182 } 183 184 if hasOutput("elf32-i386", "x86_64-elf-objdump", "-i") { 185 return "x86_64-elf-" 186 } 187 188 if hasOutput("elf32-i386", "i386-elf-objdump", "-i") { 189 return "i386-elf-" 190 } 191 192 if hasOutput("elf32-i386", "objdump", "-i") { 193 return "" 194 } 195 panic(` 196 *** Error: Couldn't find an i386-*-elf or x86_64-*-elf version of GCC/binutils 197 *** Is the directory with i386-elf-gcc or x86_64-elf-gcc in your PATH? 198 *** If your i386/x86_64-*-elf toolchain is installed with a command 199 *** prefix other than 'i386/x86_64-elf-', set your TOOLPREFIX 200 *** environment variable to that prefix and run 'make' again. 201 `) 202 } 203 204 var goVersionRegexp = regexp.MustCompile(`go(\d+)\.(\d+)\.?(\d?)`) 205 206 func goVersion() (string, int, int, error) { 207 versionBytes, err := cmdOutput(gobin(), "version") 208 if err != nil { 209 return "", 0, 0, err 210 } 211 version := strings.TrimSpace(string(versionBytes)) 212 result := goVersionRegexp.FindStringSubmatch(version) 213 if len(result) < 3 { 214 return "", 0, 0, fmt.Errorf("use of unreleased go version `%s`, may not work", version) 215 } 216 major, _ := strconv.Atoi(result[1]) 217 minor, _ := strconv.Atoi(result[2]) 218 return version, major, minor, nil 219 } 220 221 func detectGoVersion() { 222 version, major, minor, err := goVersion() 223 if err != nil { 224 fmt.Printf("warning: %s\n", err) 225 return 226 } 227 if !(major == goMajorVersionSupported && minor <= maxGoMinorVersionSupported) { 228 fmt.Printf("warning: max supported go version go%d.%d.x, found go version `%s`, may not work\n", 229 goMajorVersionSupported, maxGoMinorVersionSupported, version, 230 ) 231 return 232 } 233 } 234 235 func gobin() string { 236 goroot := os.Getenv("EGGOS_GOROOT") 237 if goroot != "" { 238 return filepath.Join(goroot, "bin", "go") 239 } 240 return "go" 241 } 242 243 func detectQemu() { 244 if !hasCommand(QEMU64) { 245 panic(QEMU64 + ` command not found`) 246 } 247 } 248 249 func accelArg() []string { 250 switch runtime.GOOS { 251 case "darwin": 252 return []string{"-M", "accel=hvf"} 253 default: 254 // fmt.Printf("accel method not found") 255 return nil 256 } 257 } 258 259 func initCflags() []string { 260 cflags := strings.Fields("-fno-pic -static -fno-builtin -fno-strict-aliasing -O2 -Wall -Werror -fno-omit-frame-pointer -I. -nostdinc") 261 if hasOutput("-fno-stack-protector", CC, "--help") { 262 cflags = append(cflags, "-fno-stack-protector") 263 } 264 if hasOutput("[^f]no-pie", CC, "-dumpspecs") { 265 cflags = append(cflags, "-fno-pie", "-no-pie") 266 } 267 if hasOutput("[^f]nopie", CC, "-dumpspecs") { 268 cflags = append(cflags, "-fno-pie", "-nopie") 269 } 270 return cflags 271 } 272 273 func initLdflags() []string { 274 ldflags := strings.Fields("-N -e _start") 275 return ldflags 276 } 277 278 func initQemuOpt() []string { 279 var opts []string 280 if os.Getenv("QEMU_ACCEL") != "" { 281 opts = append(opts, accelArg()...) 282 } 283 if os.Getenv("QEMU_GRAPHIC") == "" { 284 opts = append(opts, "-nographic") 285 } 286 return opts 287 } 288 289 func initQemuDebugOpt() []string { 290 opts := ` 291 -d int -D qemu.log 292 -object filter-dump,id=f1,netdev=eth0,file=qemu.pcap 293 -s -S 294 ` 295 ret := append([]string{}, initQemuOpt()...) 296 ret = append(ret, strings.Fields(opts)...) 297 return ret 298 } 299 300 func compileCfile(file string, extFlags ...string) { 301 args := append([]string{}, CFLAGS...) 302 args = append(args, extFlags...) 303 args = append(args, "-c", file) 304 err := sh.RunV(CC, args...) 305 if err != nil { 306 panic(err) 307 } 308 } 309 310 func rundir(dir string, envs map[string]string, cmd string, args ...string) error { 311 current, _ := os.Getwd() 312 os.Chdir(dir) 313 defer os.Chdir(current) 314 return sh.RunWithV(envs, cmd, args...) 315 } 316 317 func eggrun(qemuArgs []string, flags ...string) error { 318 qemuOpts := quoteArgs(qemuArgs) 319 var args []string 320 args = append(args, "run") 321 args = append(args, "-p", "8080:80") 322 args = append(args, flags...) 323 envs := map[string]string{ 324 "QEMU_OPTS": qemuOpts, 325 } 326 return sh.RunWithV(envs, eggBin, args...) 327 } 328 329 func cmdOutput(cmd string, args ...string) ([]byte, error) { 330 return exec.Command(cmd, args...).CombinedOutput() 331 } 332 333 // quote string which has spaces with "" 334 func quoteArgs(args []string) string { 335 var ret []string 336 for _, s := range args { 337 if strings.Index(s, " ") != -1 { 338 ret = append(ret, strconv.Quote(s)) 339 } else { 340 ret = append(ret, s) 341 } 342 } 343 return strings.Join(ret, " ") 344 } 345 346 func hasCommand(cmd string) bool { 347 _, err := exec.LookPath(cmd) 348 if err != nil { 349 return false 350 } 351 return true 352 } 353 354 func hasOutput(regstr, cmd string, args ...string) bool { 355 out, err := cmdOutput(cmd, args...) 356 if err != nil { 357 return false 358 } 359 match, err := regexp.Match(regstr, []byte(out)) 360 if err != nil { 361 return false 362 } 363 return match 364 } 365 366 func rmGlob(patten string) error { 367 match, err := filepath.Glob(patten) 368 if err != nil { 369 return err 370 } 371 for _, file := range match { 372 err = os.Remove(file) 373 if err != nil { 374 return err 375 } 376 } 377 return nil 378 }