From bbe3c2b451b29dbf0b886bd1053b7ad5a7e81487 Mon Sep 17 00:00:00 2001 From: savil <676452+savil@users.noreply.github.com> Date: Tue, 20 Sep 2022 19:13:53 -0500 Subject: [PATCH] [ZigPlanner] Add support for shell and build (#141) ## Summary Adds basic support for shell and build. Build Size optimization: - If I include the `zig` nix package in the runtimePackages, then the image size is really large (700MB) - But I believe the zig executable is standalone https://github.com/ziglang/zig/issues/3660#issuecomment-552610783 - So, I get the zig-executable's name by: - parsing the `build.zig` file (so unfortunate) to find the `addExecutable` line - fallback: if I fail to find exactly one `addExecutable`, then I fallback to installing the `zig` nix package and invoking `zig build run` - the image size is 22MB ## How was it tested? shell: in the testcase folder: ``` > devbox shell (devbox)> zig build run # see output info: All your codebase are belong to us. ``` Don't be alarmed! Just a printout. build: ``` > devbox build > docker run devbox info: All your codebase are belong to us. ``` --- planner/languages/zig/zig_planner.go | 65 ++++++++++++++++++++++- planner/plansdk/analyzer.go | 8 +-- testdata/zig/README.md | 14 +++++ testdata/zig/zig-hello-world/.gitignore | 2 + testdata/zig/zig-hello-world/build.zig | 34 ++++++++++++ testdata/zig/zig-hello-world/devbox.json | 6 +++ testdata/zig/zig-hello-world/plan.json | 22 ++++++++ testdata/zig/zig-hello-world/src/main.zig | 9 ++++ 8 files changed, 154 insertions(+), 6 deletions(-) create mode 100644 testdata/zig/README.md create mode 100644 testdata/zig/zig-hello-world/.gitignore create mode 100644 testdata/zig/zig-hello-world/build.zig create mode 100644 testdata/zig/zig-hello-world/devbox.json create mode 100644 testdata/zig/zig-hello-world/plan.json create mode 100644 testdata/zig/zig-hello-world/src/main.zig diff --git a/planner/languages/zig/zig_planner.go b/planner/languages/zig/zig_planner.go index 15043232203..583fca6a254 100644 --- a/planner/languages/zig/zig_planner.go +++ b/planner/languages/zig/zig_planner.go @@ -4,6 +4,11 @@ package zig import ( + "fmt" + "os" + "regexp" + + "github.com/pkg/errors" "go.jetpack.io/devbox/planner/plansdk" ) @@ -17,9 +22,65 @@ func (p *Planner) Name() string { } func (p *Planner) IsRelevant(srcDir string) bool { - return false + a, err := plansdk.NewAnalyzer(srcDir) + if err != nil { + // We should log that an error has occurred. + return false + } + return a.HasAnyFile("build.zig") } func (p *Planner) GetPlan(srcDir string) *plansdk.Plan { - return &plansdk.Plan{} + + var runtimePkgs []string + var startStage *plansdk.Stage + exeName, err := getZigExecutableName(srcDir) + if err != nil { + runtimePkgs = []string{"zig"} + startStage = &plansdk.Stage{ + InputFiles: plansdk.AllFiles(), + Command: "zig build run", + } + } else { + runtimePkgs = []string{} + startStage = &plansdk.Stage{ + InputFiles: []string{"./zig-out/bin/"}, + Command: fmt.Sprintf("./%s", exeName), + } + } + + return &plansdk.Plan{ + DevPackages: []string{"zig"}, + RuntimePackages: runtimePkgs, + BuildStage: &plansdk.Stage{ + InputFiles: plansdk.AllFiles(), + Command: "zig build install", + }, + StartStage: startStage, + } +} + +func getZigExecutableName(srcDir string) (string, error) { + a, err := plansdk.NewAnalyzer(srcDir) + if err != nil { + // We should log that an error has occurred. + return "", err + } + contents, err := os.ReadFile(a.AbsPath("build.zig")) + if err != nil { + return "", errors.WithStack(err) + } + + r := regexp.MustCompile("addExecutable\\(\"(.*)\",.+\\)") + matches := r.FindStringSubmatch(string(contents)) + if len(matches) != 2 { + errorPrefix := "Unable to resolve executable name" + if len(matches) < 2 { + return "", errors.Errorf("%s: did not find a matching addExecutable statement", errorPrefix) + } else { + return "", errors.Errorf("%s: found more than one addExecutable statement", errorPrefix) + } + } + return matches[1], nil + } diff --git a/planner/plansdk/analyzer.go b/planner/plansdk/analyzer.go index 2e350cb4ed4..ca554cc45c6 100644 --- a/planner/plansdk/analyzer.go +++ b/planner/plansdk/analyzer.go @@ -32,7 +32,7 @@ func NewAnalyzer(rootDir string) (*Analyzer, error) { // AbsPath resolves the given path and turns it into an absolute path relative // to the root directory of the analyzer. If the given path is already absolute // it leaves it as is. -func (a *Analyzer) absPath(path string) string { +func (a *Analyzer) AbsPath(path string) string { if filepath.IsAbs(path) { return path } @@ -47,7 +47,7 @@ func (a *Analyzer) GlobFiles(patterns ...string) []string { results := []string{} for _, p := range patterns { - pattern := a.absPath(p) + pattern := a.AbsPath(p) matches, err := doublestar.FilepathGlob(pattern) if err != nil { continue @@ -58,7 +58,7 @@ func (a *Analyzer) GlobFiles(patterns ...string) []string { } func (a *Analyzer) FileExists(relPath string) bool { - _, err := os.Stat(a.absPath(relPath)) + _, err := os.Stat(a.AbsPath(relPath)) return err == nil } @@ -68,6 +68,6 @@ func (a *Analyzer) HasAnyFile(patterns ...string) bool { } func (a *Analyzer) ParseFile(relPath string, ptr any) error { - abs := a.absPath(relPath) + abs := a.AbsPath(relPath) return cuecfg.ParseFile(abs, ptr) } diff --git a/testdata/zig/README.md b/testdata/zig/README.md new file mode 100644 index 00000000000..1c498df4db0 --- /dev/null +++ b/testdata/zig/README.md @@ -0,0 +1,14 @@ +To create a project, I followed the instructions at: +https://ziglang.org/learn/getting-started/#installing-zig + +``` +mkdir +cd +zig init-exe # executable gets the folder name + +# build the project +zig build install + +# run the project +zig build run +``` diff --git a/testdata/zig/zig-hello-world/.gitignore b/testdata/zig/zig-hello-world/.gitignore new file mode 100644 index 00000000000..e73c965f8f8 --- /dev/null +++ b/testdata/zig/zig-hello-world/.gitignore @@ -0,0 +1,2 @@ +zig-cache/ +zig-out/ diff --git a/testdata/zig/zig-hello-world/build.zig b/testdata/zig/zig-hello-world/build.zig new file mode 100644 index 00000000000..9728d6b1130 --- /dev/null +++ b/testdata/zig/zig-hello-world/build.zig @@ -0,0 +1,34 @@ +const std = @import("std"); + +pub fn build(b: *std.build.Builder) void { + // Standard target options allows the person running `zig build` to choose + // what target to build for. Here we do not override the defaults, which + // means any target is allowed, and the default is native. Other options + // for restricting supported target set are available. + const target = b.standardTargetOptions(.{}); + + // Standard release options allow the person running `zig build` to select + // between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall. + const mode = b.standardReleaseOptions(); + + const exe = b.addExecutable("zig-hello-world", "src/main.zig"); + exe.setTarget(target); + exe.setBuildMode(mode); + exe.install(); + + const run_cmd = exe.run(); + run_cmd.step.dependOn(b.getInstallStep()); + if (b.args) |args| { + run_cmd.addArgs(args); + } + + const run_step = b.step("run", "Run the app"); + run_step.dependOn(&run_cmd.step); + + const exe_tests = b.addTest("src/main.zig"); + exe_tests.setTarget(target); + exe_tests.setBuildMode(mode); + + const test_step = b.step("test", "Run unit tests"); + test_step.dependOn(&exe_tests.step); +} diff --git a/testdata/zig/zig-hello-world/devbox.json b/testdata/zig/zig-hello-world/devbox.json new file mode 100644 index 00000000000..3352a1b2467 --- /dev/null +++ b/testdata/zig/zig-hello-world/devbox.json @@ -0,0 +1,6 @@ +{ + "packages": [], + "shell": { + "init_hook": null + } +} \ No newline at end of file diff --git a/testdata/zig/zig-hello-world/plan.json b/testdata/zig/zig-hello-world/plan.json new file mode 100644 index 00000000000..b4a1c557b41 --- /dev/null +++ b/testdata/zig/zig-hello-world/plan.json @@ -0,0 +1,22 @@ +{ + "dev_packages": [ + "zig" + ], + "runtime_packages": [], + "install_stage": { + "command": "" + }, + "build_stage": { + "command": "zig build install", + "input_files": [ + "." + ] + }, + "start_stage": { + "command": "./zig-hello-world", + "input_files": [ + "./zig-out/bin/" + ] + }, + "definitions": null +} diff --git a/testdata/zig/zig-hello-world/src/main.zig b/testdata/zig/zig-hello-world/src/main.zig new file mode 100644 index 00000000000..4ec03dc57dc --- /dev/null +++ b/testdata/zig/zig-hello-world/src/main.zig @@ -0,0 +1,9 @@ +const std = @import("std"); + +pub fn main() anyerror!void { + std.log.info("Hello World! You are running zig.", .{}); +} + +test "basic test" { + try std.testing.expectEqual(10, 3 + 7); +}