github.com/icexin/eggos@v0.4.2-0.20220216025428-78b167e4f349/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 Clean() { 162 rmGlob("*.o") 163 rmGlob("kernel.elf") 164 rmGlob("multiboot.elf") 165 rmGlob("qemu.log") 166 rmGlob("qemu.pcap") 167 rmGlob("eggos.iso") 168 rmGlob("egg") 169 rmGlob("boot64.elf") 170 rmGlob("bochs.log") 171 } 172 173 func detectToolPrefix() string { 174 prefix := os.Getenv("TOOLPREFIX") 175 if prefix != "" { 176 return prefix 177 } 178 179 if hasOutput("elf32-i386", "x86_64-elf-objdump", "-i") { 180 return "x86_64-elf-" 181 } 182 183 if hasOutput("elf32-i386", "i386-elf-objdump", "-i") { 184 return "i386-elf-" 185 } 186 187 if hasOutput("elf32-i386", "objdump", "-i") { 188 return "" 189 } 190 panic(` 191 *** Error: Couldn't find an i386-*-elf or x86_64-*-elf version of GCC/binutils 192 *** Is the directory with i386-elf-gcc or x86_64-elf-gcc in your PATH? 193 *** If your i386/x86_64-*-elf toolchain is installed with a command 194 *** prefix other than 'i386/x86_64-elf-', set your TOOLPREFIX 195 *** environment variable to that prefix and run 'make' again. 196 `) 197 } 198 199 var goVersionRegexp = regexp.MustCompile(`go(\d+)\.(\d+)\.?(\d?)`) 200 201 func goVersion() (string, int, int, error) { 202 versionBytes, err := cmdOutput(gobin(), "version") 203 if err != nil { 204 return "", 0, 0, err 205 } 206 version := strings.TrimSpace(string(versionBytes)) 207 result := goVersionRegexp.FindStringSubmatch(version) 208 if len(result) < 3 { 209 return "", 0, 0, fmt.Errorf("use of unreleased go version `%s`, may not work", version) 210 } 211 major, _ := strconv.Atoi(result[1]) 212 minor, _ := strconv.Atoi(result[2]) 213 return version, major, minor, nil 214 } 215 216 func detectGoVersion() { 217 version, major, minor, err := goVersion() 218 if err != nil { 219 fmt.Printf("warning: %s\n", err) 220 return 221 } 222 if !(major == goMajorVersionSupported && minor <= maxGoMinorVersionSupported) { 223 fmt.Printf("warning: max supported go version go%d.%d.x, found go version `%s`, may not work\n", 224 goMajorVersionSupported, maxGoMinorVersionSupported, version, 225 ) 226 return 227 } 228 } 229 230 func gobin() string { 231 goroot := os.Getenv("EGGOS_GOROOT") 232 if goroot != "" { 233 return filepath.Join(goroot, "bin", "go") 234 } 235 return "go" 236 } 237 238 func detectQemu() { 239 if !hasCommand(QEMU64) { 240 panic(QEMU64 + ` command not found`) 241 } 242 } 243 244 func accelArg() []string { 245 switch runtime.GOOS { 246 case "darwin": 247 return []string{"-M", "accel=hvf"} 248 default: 249 // fmt.Printf("accel method not found") 250 return nil 251 } 252 } 253 254 func initCflags() []string { 255 cflags := strings.Fields("-fno-pic -static -fno-builtin -fno-strict-aliasing -O2 -Wall -Werror -fno-omit-frame-pointer -I. -nostdinc") 256 if hasOutput("-fno-stack-protector", CC, "--help") { 257 cflags = append(cflags, "-fno-stack-protector") 258 } 259 if hasOutput("[^f]no-pie", CC, "-dumpspecs") { 260 cflags = append(cflags, "-fno-pie", "-no-pie") 261 } 262 if hasOutput("[^f]nopie", CC, "-dumpspecs") { 263 cflags = append(cflags, "-fno-pie", "-nopie") 264 } 265 return cflags 266 } 267 268 func initLdflags() []string { 269 ldflags := strings.Fields("-N -e _start") 270 return ldflags 271 } 272 273 func initQemuOpt() []string { 274 var opts []string 275 if os.Getenv("QEMU_ACCEL") != "" { 276 opts = append(opts, accelArg()...) 277 } 278 if os.Getenv("QEMU_GRAPHIC") == "" { 279 opts = append(opts, "-nographic") 280 } 281 return opts 282 } 283 284 func initQemuDebugOpt() []string { 285 opts := ` 286 -d int -D qemu.log 287 -object filter-dump,id=f1,netdev=eth0,file=qemu.pcap 288 -s -S 289 ` 290 ret := append([]string{}, initQemuOpt()...) 291 ret = append(ret, strings.Fields(opts)...) 292 return ret 293 } 294 295 func compileCfile(file string, extFlags ...string) { 296 args := append([]string{}, CFLAGS...) 297 args = append(args, extFlags...) 298 args = append(args, "-c", file) 299 err := sh.RunV(CC, args...) 300 if err != nil { 301 panic(err) 302 } 303 } 304 305 func rundir(dir string, envs map[string]string, cmd string, args ...string) error { 306 current, _ := os.Getwd() 307 os.Chdir(dir) 308 defer os.Chdir(current) 309 return sh.RunWithV(envs, cmd, args...) 310 } 311 312 func eggrun(qemuArgs []string, flags ...string) error { 313 qemuOpts := quoteArgs(qemuArgs) 314 var args []string 315 args = append(args, "run") 316 args = append(args, "-p", "8080:80") 317 args = append(args, flags...) 318 envs := map[string]string{ 319 "QEMU_OPTS": qemuOpts, 320 } 321 return sh.RunWithV(envs, eggBin, args...) 322 } 323 324 func cmdOutput(cmd string, args ...string) ([]byte, error) { 325 return exec.Command(cmd, args...).CombinedOutput() 326 } 327 328 // quote string which has spaces with "" 329 func quoteArgs(args []string) string { 330 var ret []string 331 for _, s := range args { 332 if strings.Index(s, " ") != -1 { 333 ret = append(ret, strconv.Quote(s)) 334 } else { 335 ret = append(ret, s) 336 } 337 } 338 return strings.Join(ret, " ") 339 } 340 341 func hasCommand(cmd string) bool { 342 _, err := exec.LookPath(cmd) 343 if err != nil { 344 return false 345 } 346 return true 347 } 348 349 func hasOutput(regstr, cmd string, args ...string) bool { 350 out, err := cmdOutput(cmd, args...) 351 if err != nil { 352 return false 353 } 354 match, err := regexp.Match(regstr, []byte(out)) 355 if err != nil { 356 return false 357 } 358 return match 359 } 360 361 func rmGlob(patten string) error { 362 match, err := filepath.Glob(patten) 363 if err != nil { 364 return err 365 } 366 for _, file := range match { 367 err = os.Remove(file) 368 if err != nil { 369 return err 370 } 371 } 372 return nil 373 }