diff options
| author | nsfisis <nsfisis@gmail.com> | 2025-09-05 22:03:54 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2025-09-05 22:04:06 +0900 |
| commit | bd2ebe9396475632908b23d8263be70166197dc2 (patch) | |
| tree | 21d2ad6f4e106aaa881d66544c27ccee2a96b970 /worker/swift | |
| parent | 3022b45715b9aabb6f54e5e19429c6b06abc1afd (diff) | |
| download | iosdc-japan-2025-albatross-bd2ebe9396475632908b23d8263be70166197dc2.tar.gz iosdc-japan-2025-albatross-bd2ebe9396475632908b23d8263be70166197dc2.tar.zst iosdc-japan-2025-albatross-bd2ebe9396475632908b23d8263be70166197dc2.zip | |
feat(worker-swift): compile and run in one request
Diffstat (limited to 'worker/swift')
| -rw-r--r-- | worker/swift/exec.go | 175 | ||||
| -rw-r--r-- | worker/swift/handlers.go | 42 | ||||
| -rw-r--r-- | worker/swift/main.go | 4 | ||||
| -rw-r--r-- | worker/swift/models.go | 56 |
4 files changed, 121 insertions, 156 deletions
diff --git a/worker/swift/exec.go b/worker/swift/exec.go index 37f542b..8ad5615 100644 --- a/worker/swift/exec.go +++ b/worker/swift/exec.go @@ -12,35 +12,20 @@ import ( const ( dataRootDir = "/app/data" - // Stores *.swift files. - dataSwiftRootDir = dataRootDir + "/swift" - // Stores *.wasm files. - dataWasmRootDir = dataRootDir + "/wasm" - // Stores *.cwasm files (compiled wasm generated by "wasmtime compile"). - dataCwasmRootDir = dataRootDir + "/cwasm" wasmMaxMemorySize = 10 * 1024 * 1024 // 10 MiB ) func prepareDirectories() error { - if err := os.MkdirAll(dataSwiftRootDir, 0755); err != nil { - return err - } - if err := os.MkdirAll(dataWasmRootDir, 0755); err != nil { - return err - } - if err := os.MkdirAll(dataCwasmRootDir, 0755); err != nil { + if err := os.MkdirAll(dataRootDir, 0755); err != nil { return err } return nil } -func calcFilePath(hash, ext string) string { - return fmt.Sprintf("%s/%s/%s.%s", dataRootDir, ext, hash, ext) -} - func execCommandWithTimeout( ctx context.Context, + workingDir string, maxDuration time.Duration, makeCmd func(context.Context) *exec.Cmd, ) (string, string, error) { @@ -48,6 +33,7 @@ func execCommandWithTimeout( defer cancel() cmd := makeCmd(ctx) + cmd.Dir = workingDir var stdout bytes.Buffer var stderr bytes.Buffer @@ -67,114 +53,171 @@ func execCommandWithTimeout( } } -func convertCommandErrorToResultType(err error, isCompile bool) string { +func convertCommandErrorToResultType(err error, defaultErrorStatus string) string { if err != nil { if err == context.DeadlineExceeded { return resultTimeout } - if isCompile { - return resultCompileError - } - return resultRuntimeError + return defaultErrorStatus } return resultSuccess } -func execSwiftCompile( - ctx context.Context, - code string, - codeHash string, - maxDuration time.Duration, -) swiftCompileResponseData { - inPath := calcFilePath(codeHash, "swift") - outPath := calcFilePath(codeHash, "wasm") - - if err := os.WriteFile(inPath, []byte(code), 0644); err != nil { - return swiftCompileResponseData{ +func prepareWorkingDir(workingDir string) execResponseData { + err := os.MkdirAll(workingDir, 0755) + if err != nil { + return execResponseData{ Status: resultInternalError, Stdout: "", - Stderr: err.Error(), + Stderr: "Failed to create project directory", } } + return execResponseData{ + Status: resultSuccess, + Stdout: "", + Stderr: "", + } +} + +func removeWorkingDir(workingDir string) { + err := os.RemoveAll(workingDir) + _ = err +} +func initSwiftProject( + ctx context.Context, + workingDir string, + maxDuration time.Duration, +) execResponseData { stdout, stderr, err := execCommandWithTimeout( ctx, + workingDir, maxDuration, func(ctx context.Context) *exec.Cmd { return exec.CommandContext( ctx, - "swiftc", - "-target", "wasm32-unknown-wasi", - "-o", outPath, - inPath, + "swift", + "package", + "init", + "--type", "executable", ) }, ) - - return swiftCompileResponseData{ - Status: convertCommandErrorToResultType(err, true), + return execResponseData{ + Status: convertCommandErrorToResultType(err, resultInternalError), Stdout: stdout, Stderr: stderr, } } -func execWasmCompile( +func putSwiftSourceFile( + workingDir string, + code string, +) execResponseData { + err := os.WriteFile(workingDir+"/Sources/main.swift", []byte(code), 0644) + + if err != nil { + return execResponseData{ + Status: convertCommandErrorToResultType(err, resultInternalError), + Stdout: "", + Stderr: "Failed to copy source file", + } + } + return execResponseData{ + Status: resultSuccess, + Stdout: "", + Stderr: "", + } +} + +func buildSwiftProject( ctx context.Context, - codeHash string, + workingDir string, maxDuration time.Duration, -) wasmCompileResponseData { - inPath := calcFilePath(codeHash, "wasm") - outPath := calcFilePath(codeHash, "cwasm") - +) execResponseData { stdout, stderr, err := execCommandWithTimeout( ctx, + workingDir, maxDuration, func(ctx context.Context) *exec.Cmd { return exec.CommandContext( ctx, - "wasmtime", "compile", - "-O", "opt-level=0", - "-C", "cache=n", - "-W", fmt.Sprintf("max-memory-size=%d", wasmMaxMemorySize), - "-o", outPath, - inPath, + "swift", + "build", + "--swift-sdk", "wasm32-unknown-wasi", ) }, ) - - return wasmCompileResponseData{ - Status: convertCommandErrorToResultType(err, true), + return execResponseData{ + Status: convertCommandErrorToResultType(err, resultCompileError), Stdout: stdout, Stderr: stderr, } } -func execTestRun( +func runWasm( ctx context.Context, + workingDir string, codeHash string, stdin string, maxDuration time.Duration, -) testRunResponseData { - inPath := calcFilePath(codeHash, "cwasm") - +) execResponseData { stdout, stderr, err := execCommandWithTimeout( ctx, + workingDir, maxDuration, func(ctx context.Context) *exec.Cmd { cmd := exec.CommandContext( ctx, - "wasmtime", "run", - "--allow-precompiled", - inPath, + "wasmtime", + "-W", fmt.Sprintf("max-memory-size=%d", wasmMaxMemorySize), + ".build/wasm32-unknown-wasi/debug/"+codeHash+".wasm", ) cmd.Stdin = strings.NewReader(stdin) return cmd }, ) - - return testRunResponseData{ - Status: convertCommandErrorToResultType(err, false), + return execResponseData{ + Status: convertCommandErrorToResultType(err, resultRuntimeError), Stdout: stdout, Stderr: stderr, } } + +func doExec( + ctx context.Context, + code string, + codeHash string, + stdin string, + maxDuration time.Duration, +) execResponseData { + workingDir := dataRootDir + "/" + codeHash + + res := prepareWorkingDir(workingDir) + if !res.success() { + return res + } + defer removeWorkingDir(workingDir) + + res = initSwiftProject(ctx, workingDir, maxDuration) + if !res.success() { + return res + } + + res = putSwiftSourceFile(workingDir, code) + if !res.success() { + return res + } + + res = buildSwiftProject(ctx, workingDir, maxDuration) + if !res.success() { + return res + } + + res = runWasm(ctx, workingDir, codeHash, stdin, maxDuration) + if !res.success() { + return res + } + + return res +} diff --git a/worker/swift/handlers.go b/worker/swift/handlers.go index ac9701f..4cb9999 100644 --- a/worker/swift/handlers.go +++ b/worker/swift/handlers.go @@ -11,8 +11,8 @@ func newBadRequestError(err error) *echo.HTTPError { return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid request: %s", err.Error())) } -func handleSwiftCompile(c echo.Context) error { - var req swiftCompileRequestData +func handleExec(c echo.Context) error { + var req execRequestData if err := c.Bind(&req); err != nil { return newBadRequestError(err) } @@ -20,46 +20,10 @@ func handleSwiftCompile(c echo.Context) error { return newBadRequestError(err) } - res := execSwiftCompile( + res := doExec( c.Request().Context(), req.Code, req.CodeHash, - req.maxDuration(), - ) - - return c.JSON(http.StatusOK, res) -} - -func handleWasmCompile(c echo.Context) error { - var req wasmCompileRequestData - if err := c.Bind(&req); err != nil { - return newBadRequestError(err) - } - if err := req.validate(); err != nil { - return newBadRequestError(err) - } - - res := execWasmCompile( - c.Request().Context(), - req.CodeHash, - req.maxDuration(), - ) - - return c.JSON(http.StatusOK, res) -} - -func handleTestRun(c echo.Context) error { - var req testRunRequestData - if err := c.Bind(&req); err != nil { - return newBadRequestError(err) - } - if err := req.validate(); err != nil { - return newBadRequestError(err) - } - - res := execTestRun( - c.Request().Context(), - req.CodeHash, req.Stdin, req.maxDuration(), ) diff --git a/worker/swift/main.go b/worker/swift/main.go index 02a0a6a..21a9464 100644 --- a/worker/swift/main.go +++ b/worker/swift/main.go @@ -18,9 +18,7 @@ func main() { e.Use(middleware.Logger()) e.Use(middleware.Recover()) - e.POST("/api/swiftc", handleSwiftCompile) - e.POST("/api/wasmc", handleWasmCompile) - e.POST("/api/testrun", handleTestRun) + e.POST("/exec", handleExec) if err := e.Start(":80"); err != http.ErrServerClosed { log.Fatal(err) diff --git a/worker/swift/models.go b/worker/swift/models.go index 4a318d0..f00265e 100644 --- a/worker/swift/models.go +++ b/worker/swift/models.go @@ -17,70 +17,30 @@ var ( errInvalidMaxDuration = errors.New("'max_duration_ms' must be positive") ) -type swiftCompileRequestData struct { - MaxDurationMilliseconds int `json:"max_duration_ms"` +type execRequestData struct { Code string `json:"code"` CodeHash string `json:"code_hash"` + Stdin string `json:"stdin"` + MaxDurationMilliseconds int `json:"max_duration_ms"` } -func (req *swiftCompileRequestData) maxDuration() time.Duration { +func (req *execRequestData) maxDuration() time.Duration { return time.Duration(req.MaxDurationMilliseconds) * time.Millisecond } -func (req *swiftCompileRequestData) validate() error { +func (req *execRequestData) validate() error { if req.MaxDurationMilliseconds <= 0 { return errInvalidMaxDuration } return nil } -type swiftCompileResponseData struct { +type execResponseData struct { Status string `json:"status"` Stdout string `json:"stdout"` Stderr string `json:"stderr"` } -type wasmCompileRequestData struct { - MaxDurationMilliseconds int `json:"max_duration_ms"` - CodeHash string `json:"code_hash"` -} - -type wasmCompileResponseData struct { - Status string `json:"status"` - Stdout string `json:"stdout"` - Stderr string `json:"stderr"` -} - -func (req *wasmCompileRequestData) maxDuration() time.Duration { - return time.Duration(req.MaxDurationMilliseconds) * time.Millisecond -} - -func (req *wasmCompileRequestData) validate() error { - if req.MaxDurationMilliseconds <= 0 { - return errInvalidMaxDuration - } - return nil -} - -type testRunRequestData struct { - MaxDurationMilliseconds int `json:"max_duration_ms"` - CodeHash string `json:"code_hash"` - Stdin string `json:"stdin"` -} - -func (req *testRunRequestData) maxDuration() time.Duration { - return time.Duration(req.MaxDurationMilliseconds) * time.Millisecond -} - -func (req *testRunRequestData) validate() error { - if req.MaxDurationMilliseconds <= 0 { - return errInvalidMaxDuration - } - return nil -} - -type testRunResponseData struct { - Status string `json:"status"` - Stdout string `json:"stdout"` - Stderr string `json:"stderr"` +func (res *execResponseData) success() bool { + return res.Status == resultSuccess } |
