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  }