Skip to content

Commit bbe3c2b

Browse files
authored
[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 ziglang/zig#3660 (comment) - 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. ```
1 parent 320c175 commit bbe3c2b

File tree

8 files changed

+154
-6
lines changed

8 files changed

+154
-6
lines changed

planner/languages/zig/zig_planner.go

Lines changed: 63 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@
44
package zig
55

66
import (
7+
"fmt"
8+
"os"
9+
"regexp"
10+
11+
"github.com/pkg/errors"
712
"go.jetpack.io/devbox/planner/plansdk"
813
)
914

@@ -17,9 +22,65 @@ func (p *Planner) Name() string {
1722
}
1823

1924
func (p *Planner) IsRelevant(srcDir string) bool {
20-
return false
25+
a, err := plansdk.NewAnalyzer(srcDir)
26+
if err != nil {
27+
// We should log that an error has occurred.
28+
return false
29+
}
30+
return a.HasAnyFile("build.zig")
2131
}
2232

2333
func (p *Planner) GetPlan(srcDir string) *plansdk.Plan {
24-
return &plansdk.Plan{}
34+
35+
var runtimePkgs []string
36+
var startStage *plansdk.Stage
37+
exeName, err := getZigExecutableName(srcDir)
38+
if err != nil {
39+
runtimePkgs = []string{"zig"}
40+
startStage = &plansdk.Stage{
41+
InputFiles: plansdk.AllFiles(),
42+
Command: "zig build run",
43+
}
44+
} else {
45+
runtimePkgs = []string{}
46+
startStage = &plansdk.Stage{
47+
InputFiles: []string{"./zig-out/bin/"},
48+
Command: fmt.Sprintf("./%s", exeName),
49+
}
50+
}
51+
52+
return &plansdk.Plan{
53+
DevPackages: []string{"zig"},
54+
RuntimePackages: runtimePkgs,
55+
BuildStage: &plansdk.Stage{
56+
InputFiles: plansdk.AllFiles(),
57+
Command: "zig build install",
58+
},
59+
StartStage: startStage,
60+
}
61+
}
62+
63+
func getZigExecutableName(srcDir string) (string, error) {
64+
a, err := plansdk.NewAnalyzer(srcDir)
65+
if err != nil {
66+
// We should log that an error has occurred.
67+
return "", err
68+
}
69+
contents, err := os.ReadFile(a.AbsPath("build.zig"))
70+
if err != nil {
71+
return "", errors.WithStack(err)
72+
}
73+
74+
r := regexp.MustCompile("addExecutable\\(\"(.*)\",.+\\)")
75+
matches := r.FindStringSubmatch(string(contents))
76+
if len(matches) != 2 {
77+
errorPrefix := "Unable to resolve executable name"
78+
if len(matches) < 2 {
79+
return "", errors.Errorf("%s: did not find a matching addExecutable statement", errorPrefix)
80+
} else {
81+
return "", errors.Errorf("%s: found more than one addExecutable statement", errorPrefix)
82+
}
83+
}
84+
return matches[1], nil
85+
2586
}

planner/plansdk/analyzer.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ func NewAnalyzer(rootDir string) (*Analyzer, error) {
3232
// AbsPath resolves the given path and turns it into an absolute path relative
3333
// to the root directory of the analyzer. If the given path is already absolute
3434
// it leaves it as is.
35-
func (a *Analyzer) absPath(path string) string {
35+
func (a *Analyzer) AbsPath(path string) string {
3636
if filepath.IsAbs(path) {
3737
return path
3838
}
@@ -47,7 +47,7 @@ func (a *Analyzer) GlobFiles(patterns ...string) []string {
4747
results := []string{}
4848

4949
for _, p := range patterns {
50-
pattern := a.absPath(p)
50+
pattern := a.AbsPath(p)
5151
matches, err := doublestar.FilepathGlob(pattern)
5252
if err != nil {
5353
continue
@@ -58,7 +58,7 @@ func (a *Analyzer) GlobFiles(patterns ...string) []string {
5858
}
5959

6060
func (a *Analyzer) FileExists(relPath string) bool {
61-
_, err := os.Stat(a.absPath(relPath))
61+
_, err := os.Stat(a.AbsPath(relPath))
6262
return err == nil
6363
}
6464

@@ -68,6 +68,6 @@ func (a *Analyzer) HasAnyFile(patterns ...string) bool {
6868
}
6969

7070
func (a *Analyzer) ParseFile(relPath string, ptr any) error {
71-
abs := a.absPath(relPath)
71+
abs := a.AbsPath(relPath)
7272
return cuecfg.ParseFile(abs, ptr)
7373
}

testdata/zig/README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
To create a project, I followed the instructions at:
2+
https://ziglang.org/learn/getting-started/#installing-zig
3+
4+
```
5+
mkdir <folder>
6+
cd <folder>
7+
zig init-exe # executable gets the folder name
8+
9+
# build the project
10+
zig build install
11+
12+
# run the project
13+
zig build run
14+
```
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
zig-cache/
2+
zig-out/
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
const std = @import("std");
2+
3+
pub fn build(b: *std.build.Builder) void {
4+
// Standard target options allows the person running `zig build` to choose
5+
// what target to build for. Here we do not override the defaults, which
6+
// means any target is allowed, and the default is native. Other options
7+
// for restricting supported target set are available.
8+
const target = b.standardTargetOptions(.{});
9+
10+
// Standard release options allow the person running `zig build` to select
11+
// between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall.
12+
const mode = b.standardReleaseOptions();
13+
14+
const exe = b.addExecutable("zig-hello-world", "src/main.zig");
15+
exe.setTarget(target);
16+
exe.setBuildMode(mode);
17+
exe.install();
18+
19+
const run_cmd = exe.run();
20+
run_cmd.step.dependOn(b.getInstallStep());
21+
if (b.args) |args| {
22+
run_cmd.addArgs(args);
23+
}
24+
25+
const run_step = b.step("run", "Run the app");
26+
run_step.dependOn(&run_cmd.step);
27+
28+
const exe_tests = b.addTest("src/main.zig");
29+
exe_tests.setTarget(target);
30+
exe_tests.setBuildMode(mode);
31+
32+
const test_step = b.step("test", "Run unit tests");
33+
test_step.dependOn(&exe_tests.step);
34+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"packages": [],
3+
"shell": {
4+
"init_hook": null
5+
}
6+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"dev_packages": [
3+
"zig"
4+
],
5+
"runtime_packages": [],
6+
"install_stage": {
7+
"command": ""
8+
},
9+
"build_stage": {
10+
"command": "zig build install",
11+
"input_files": [
12+
"."
13+
]
14+
},
15+
"start_stage": {
16+
"command": "./zig-hello-world",
17+
"input_files": [
18+
"./zig-out/bin/"
19+
]
20+
},
21+
"definitions": null
22+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
const std = @import("std");
2+
3+
pub fn main() anyerror!void {
4+
std.log.info("Hello World! You are running zig.", .{});
5+
}
6+
7+
test "basic test" {
8+
try std.testing.expectEqual(10, 3 + 7);
9+
}

0 commit comments

Comments
 (0)