Skip to content

Commit 214113a

Browse files
Include shell source and build source directories in proc.Spawn (#102)
* dev: refactor initdata I needed to make some changes to initdata for #89, and in the process made it simpler. Now the list of files is in one place, and we always embed them into the bootloader. However, for debug builds we default to embedding just the filepaths instead of their contents, and fetching them at runtime. You still have the option of embedding the file contents by setting the `PROD` environment variable. This supports previous behavior while simplifying the codepaths. * Embed shell source instead of binary and make spawn build source directories Closes #88 Closes #89 I though it was a bit silly casting `s.fsys` everywhere, so I modifed `fs.go` to store the `watchfs` seperately. There was also an issue in `wasm.js:writeSync()` where it would be passed a null value for `buf` after the build tool finished in `proc.go:Spawn()`. I added a catch-all fix, but perhaps its a bug that the null is passed in the first place. There's still some kinks to work out before you can edit the shell seamlessly, namely using better heuristics for when files should be updated/rebuilt. We can use mtime/ctime for those. * kernel: compare `mtime`'s when building source or copying files from `initfs` Closes #66 Enables proper live editing within Wanix! `proc.Spawn` will rebuild commands from source if it's been modified, and `kernel.fs` will copy in `initfs` files if they've been modified externally *(note this will overwrite local files inside Wanix)*.
1 parent 218faa9 commit 214113a

File tree

15 files changed

+271
-150
lines changed

15 files changed

+271
-150
lines changed

dev/bootloader.js

+17-24
Original file line numberDiff line numberDiff line change
@@ -81,31 +81,24 @@ if (!globalThis["ServiceWorkerGlobalScope"]) {
8181
await setupServiceWorker();
8282

8383
globalThis.initfs = {};
84-
const load = async (path) => {
85-
const basename = (path) => path.replace(/\\/g,'/').split('/').pop();
86-
if (globalThis.initdata) {
87-
// use embedded data if present
88-
path = `./~init/${basename(path)}`;
84+
const load = async (name, file) => {
85+
// Determine if file contains a path to fetch or embedded file contents to load.
86+
if(file.type === "text/plain") {
87+
globalThis.initfs[name] = { mtimeMs: file.mtimeMs, blob: await (await fetch(`./sys/dev/${file.data}`)).blob() };
88+
} else {
89+
globalThis.initfs[name] = { mtimeMs: file.mtimeMs, blob: await (await fetch(`./~init/${name}`)).blob() };
8990
}
90-
globalThis.initfs[basename(path)] = await (await fetch(path)).blob();
91+
};
92+
93+
// allow loading concurrently
94+
let loads = [];
95+
for(const property in globalThis.initdata) {
96+
loads.push(load(property, globalThis.initdata[property]));
9197
}
92-
// TODO: define these in one place. duplicated in initdata.go
93-
await Promise.all([
94-
load("./sys/dev/kernel/web/lib/duplex.js"),
95-
load("./sys/dev/kernel/web/lib/worker.js"),
96-
load("./sys/dev/kernel/web/lib/syscall.js"),
97-
load("./sys/dev/kernel/web/lib/task.js"),
98-
load("./sys/dev/kernel/web/lib/wasm.js"),
99-
load("./sys/dev/kernel/web/lib/host.js"),
100-
load("./sys/dev/internal/indexedfs/indexedfs.js"), // maybe load from kernel?
101-
load("./sys/dev/local/bin/kernel"),
102-
load("./sys/dev/local/bin/shell"),
103-
load("./sys/dev/local/bin/build"),
104-
load("./sys/dev/local/bin/micro"),
105-
]);
106-
107-
globalThis.duplex = await import(URL.createObjectURL(initfs["duplex.js"]));
108-
globalThis.task = await import(URL.createObjectURL(initfs["task.js"]));
98+
await Promise.all(loads);
99+
100+
globalThis.duplex = await import(URL.createObjectURL(initfs["duplex.js"].blob));
101+
globalThis.task = await import(URL.createObjectURL(initfs["task.js"].blob));
109102

110103
globalThis.sys = new task.Task(initfs);
111104

@@ -114,7 +107,7 @@ if (!globalThis["ServiceWorkerGlobalScope"]) {
114107
await sys.exec("kernel");
115108

116109
// load host API
117-
await import(URL.createObjectURL(initfs["host.js"]));
110+
await import(URL.createObjectURL(initfs["host.js"].blob));
118111

119112
})();
120113
}

dev/bundle.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ func main() {
1414
fatal(err)
1515
defer f.Close()
1616

17-
PackFilesTo(f)
17+
PackFilesTo(f, PackFileData)
1818

1919
src, err := os.ReadFile("./dev/bootloader.js")
2020
fatal(err)

dev/initdata.go

+69-34
Original file line numberDiff line numberDiff line change
@@ -7,42 +7,75 @@ import (
77
"io"
88
"log"
99
"os"
10-
"path/filepath"
1110
"strings"
1211
"text/template"
1312
)
1413

15-
func PackFilesTo(w io.Writer) {
16-
var files []File
17-
for _, path := range []string{
18-
"./kernel/web/lib/duplex.js",
19-
"./kernel/web/lib/worker.js",
20-
"./kernel/web/lib/syscall.js",
21-
"./kernel/web/lib/task.js",
22-
"./kernel/web/lib/wasm.js",
23-
"./kernel/web/lib/host.js",
24-
"./internal/indexedfs/indexedfs.js",
25-
"./local/bin/kernel",
26-
"./local/bin/shell",
27-
"./local/bin/build",
28-
"./local/bin/micro",
29-
} {
30-
typ := "application/octet-stream"
31-
if strings.HasSuffix(path, ".js") {
32-
typ = "application/javascript"
14+
// Files must be local to the Wanix project root.
15+
var files = []File{
16+
{Name: "duplex.js", Path: "./kernel/web/lib/duplex.js"},
17+
{Name: "worker.js", Path: "./kernel/web/lib/worker.js"},
18+
{Name: "syscall.js", Path: "./kernel/web/lib/syscall.js"},
19+
{Name: "task.js", Path: "./kernel/web/lib/task.js"},
20+
{Name: "wasm.js", Path: "./kernel/web/lib/wasm.js"},
21+
{Name: "host.js", Path: "./kernel/web/lib/host.js"},
22+
{Name: "indexedfs.js", Path: "./internal/indexedfs/indexedfs.js"},
23+
{Name: "kernel", Path: "./local/bin/kernel"},
24+
{Name: "build", Path: "./local/bin/build"},
25+
{Name: "macro", Path: "./local/bin/micro"},
26+
27+
// Shell source files
28+
{Name: "shell/main.go", Path: "shell/main.go"},
29+
{Name: "shell/copy.go", Path: "shell/copy.go"},
30+
{Name: "shell/download.go", Path: "shell/download.go"},
31+
{Name: "shell/main.go", Path: "shell/main.go"},
32+
{Name: "shell/open.go", Path: "shell/open.go"},
33+
{Name: "shell/preprocessor.go", Path: "shell/preprocessor.go"},
34+
{Name: "shell/smallcmds.go", Path: "shell/smallcmds.go"},
35+
{Name: "shell/tree.go", Path: "shell/tree.go"},
36+
{Name: "shell/util.go", Path: "shell/util.go"},
37+
{Name: "shell/watch.go", Path: "shell/watch.go"},
38+
}
39+
40+
type PackMode int
41+
42+
const (
43+
PackFileData PackMode = iota
44+
PackFilePaths
45+
)
46+
47+
func PackFilesTo(w io.Writer, mode PackMode) {
48+
switch mode {
49+
case PackFileData:
50+
for i := range files {
51+
if strings.HasSuffix(files[i].Path, ".js") {
52+
files[i].Type = "application/javascript"
53+
} else {
54+
files[i].Type = "application/octet-stream"
55+
}
56+
57+
fi, err := os.Stat(files[i].Path)
58+
fatal(err)
59+
files[i].Mtime = fi.ModTime().UnixMilli()
60+
61+
data, err := os.ReadFile(files[i].Path)
62+
fatal(err)
63+
var gzipBuffer bytes.Buffer
64+
gzipWriter := gzip.NewWriter(&gzipBuffer)
65+
_, err = gzipWriter.Write(data)
66+
fatal(err)
67+
fatal(gzipWriter.Close())
68+
files[i].Data = base64.StdEncoding.EncodeToString(gzipBuffer.Bytes())
69+
}
70+
71+
case PackFilePaths:
72+
for i := range files {
73+
files[i].Type = "text/plain"
74+
files[i].Data = files[i].Path
75+
fi, err := os.Stat(files[i].Path)
76+
fatal(err)
77+
files[i].Mtime = fi.ModTime().UnixMilli()
3378
}
34-
data, err := os.ReadFile(path)
35-
fatal(err)
36-
var gzipBuffer bytes.Buffer
37-
gzipWriter := gzip.NewWriter(&gzipBuffer)
38-
_, err = gzipWriter.Write(data)
39-
fatal(err)
40-
fatal(gzipWriter.Close())
41-
files = append(files, File{
42-
Name: filepath.Base(path),
43-
Type: typ,
44-
Data: base64.StdEncoding.EncodeToString(gzipBuffer.Bytes()),
45-
})
4679
}
4780

4881
t := template.Must(template.New("initdata.tmpl").ParseFiles("./dev/initdata.tmpl"))
@@ -52,9 +85,11 @@ func PackFilesTo(w io.Writer) {
5285
}
5386

5487
type File struct {
55-
Name string
56-
Type string
57-
Data string
88+
Name string
89+
Path string
90+
Type string
91+
Data string
92+
Mtime int64
5893
}
5994

6095
func fatal(err error) {

dev/initdata.tmpl

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
globalThis.initdata = {
22
{{range .}}
3-
"{{.Name}}": {type: "{{.Type}}", data: "{{.Data}}"},
3+
"{{.Name}}": {type: "{{.Type}}", mtimeMs: {{.Mtime}}, data: "{{.Data}}"},
44
{{end}}
55
}

dev/server.go

+10-6
Original file line numberDiff line numberDiff line change
@@ -38,17 +38,21 @@ func main() {
3838
mux.Handle(fmt.Sprintf("%s/wanix-bootloader.js", basePath), http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
3939
w.Header().Set("content-type", "application/javascript")
4040

41-
if os.Getenv("PROD") != "1" {
42-
http.ServeFile(w, r, "./dev/bootloader.js")
43-
return
41+
var packMode PackMode
42+
if os.Getenv("PROD") == "1" {
43+
log.Printf("Packing self-contained bootloader...\n")
44+
packMode = PackFileData
45+
} else {
46+
packMode = PackFilePaths
4447
}
4548

46-
// emulate a build
47-
PackFilesTo(w)
49+
// TODO: Does this need to pack on every request for the bootloader?
50+
// I don't think you want to be changing PROD at runtime, so we can
51+
// probably cache this.
52+
PackFilesTo(w, packMode)
4853
f, err := os.ReadFile("./dev/bootloader.js")
4954
fatal(err)
5055
w.Write(f)
51-
5256
}))
5357
mux.Handle(fmt.Sprintf("%s/~init/", basePath), http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
5458
http.Error(w, "Not found", http.StatusNotFound)

internal/app/terminal/index.html

+1-1
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@
6161
const enc = new TextEncoder();
6262
const dec = new TextDecoder();
6363

64-
const resp = await sys.call("tty.open", ["shell", [], { TERM: "xterm-256color" }]);
64+
const resp = await sys.call("tty.open", ["sys/cmd/shell", [], { TERM: "xterm-256color" }]);
6565
const pid = resp.value;
6666
const ch = resp.channel;
6767
//parent.wanix.termCh = ch;

internal/indexedfs/indexedfs.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ type FS struct {
5454

5555
func New() (*FS, error) {
5656
if helper.IsUndefined() {
57-
blob := js.Global().Get("initfs").Get("indexedfs.js")
57+
blob := js.Global().Get("initfs").Get("indexedfs.js").Get("blob")
5858
url := js.Global().Get("URL").Call("createObjectURL", blob)
5959
helper = jsutil.Await(js.Global().Call("import", url))
6060
}

0 commit comments

Comments
 (0)