Skip to content

Commit

Permalink
[ZigPlanner] Add support for shell and build (#141)
Browse files Browse the repository at this point in the history
## 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.
```
  • Loading branch information
savil authored Sep 21, 2022
1 parent 320c175 commit bbe3c2b
Show file tree
Hide file tree
Showing 8 changed files with 154 additions and 6 deletions.
65 changes: 63 additions & 2 deletions planner/languages/zig/zig_planner.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@
package zig

import (
"fmt"
"os"
"regexp"

"github.com/pkg/errors"
"go.jetpack.io/devbox/planner/plansdk"
)

Expand All @@ -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

}
8 changes: 4 additions & 4 deletions planner/plansdk/analyzer.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand All @@ -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
Expand All @@ -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
}

Expand All @@ -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)
}
14 changes: 14 additions & 0 deletions testdata/zig/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
To create a project, I followed the instructions at:
https://ziglang.org/learn/getting-started/#installing-zig

```
mkdir <folder>
cd <folder>
zig init-exe # executable gets the folder name
# build the project
zig build install
# run the project
zig build run
```
2 changes: 2 additions & 0 deletions testdata/zig/zig-hello-world/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
zig-cache/
zig-out/
34 changes: 34 additions & 0 deletions testdata/zig/zig-hello-world/build.zig
Original file line number Diff line number Diff line change
@@ -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);
}
6 changes: 6 additions & 0 deletions testdata/zig/zig-hello-world/devbox.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"packages": [],
"shell": {
"init_hook": null
}
}
22 changes: 22 additions & 0 deletions testdata/zig/zig-hello-world/plan.json
Original file line number Diff line number Diff line change
@@ -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
}
9 changes: 9 additions & 0 deletions testdata/zig/zig-hello-world/src/main.zig
Original file line number Diff line number Diff line change
@@ -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);
}

0 comments on commit bbe3c2b

Please sign in to comment.