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  }