In this post I share a way to compile and run a Zig file using Python, which in turn can be used to render the output of the Zig program in Quarto.
Python Script
The Python script I developed is similar to the one I made for Rust, except that it does two phases. While Zig has a very powerful and flexible build system, I will only attempt to compile a single project file.
"""Build and run a Zig project, returning the program's standard output.
The project is built with ``zig build`` and the executable produced under
``zig-out/bin`` is run so its output can be captured. This is the standalone
version of the code that used to live in the "Build and Run a Zig Project from
Quarto Using Python" post.
"""
import subprocess
import os
import stat
def log_permissions(path):
st = os.stat(path)
permissions = stat.filemode(st.st_mode)
print(f"Permissions for {path}: {permissions}")
def find_executable(bin_dir):
"""Find the executable file in the given directory."""
for root, dirs, files in os.walk(bin_dir):
for file in files:
file_path = os.path.join(root, file)
if os.access(file_path, os.X_OK):
return file_path
raise FileNotFoundError("No executable found in the bin directory.")
def build_and_run_zig(project_dir):
project_dir = os.path.abspath(project_dir)
# Build the Zig project
try:
build_process = subprocess.run(
["zig", "build"],
cwd=project_dir,
check=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
)
except subprocess.CalledProcessError as e:
print(f"Build Error: {e.stderr}")
return
# Find the executable in the zig-out/bin/ directory
bin_dir = os.path.join(project_dir, "zig-out", "bin")
if not os.path.exists(bin_dir):
raise FileNotFoundError("The bin directory does not exist after the build.")
exe_path = find_executable(bin_dir)
# Set the executable permissions
try:
os.chmod(
exe_path,
stat.S_IRWXU | stat.S_IRGRP | stat.S_IXGRP | stat.S_IROTH | stat.S_IXOTH,
)
except PermissionError as e:
print(f"Error setting permissions: {e}")
return
# Run the compiled executable and print its output
try:
run_process = subprocess.run(
[exe_path],
check=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
)
# print(run_process.stdout) # Print the output from the Zig executable
# print(run_process.stderr) # Print any error output
if run_process.stdout:
return run_process.stdout
else:
return run_process.stderr
except subprocess.CalledProcessError as e:
print(f"Execution Error: {e.stderr}")
return
# Example usage
if __name__ == "__main__":
here = os.path.dirname(os.path.abspath(__file__))
output = build_and_run_zig(os.path.join(here, "..", "hello"))
print(output)Let’s see it in action.
Single File Example
Here I will define a simple “Hello, World” example. We can do this by creating a project folder hello, and calling zig init within it to create a default project. In the default main.zig I put the following Zig code.
const std = @import("std");
pub fn main() void {
std.debug.print("Hello, World!\n", .{});
}I have used Rust’s syntax highlighting for the above Zig code.
Now we can build and run our Zig project by using the earlier Python script.
"""Build and run the ``hello`` Zig project and print its output."""
import os
from run_zig import build_and_run_zig
here = os.path.dirname(os.path.abspath(__file__))
print(build_and_run_zig(os.path.join(here, "..", "hello")))Hello, World!Conclusion
While Zig doesn’t really have a REPL (that I’m aware of), you can include its standard output in Quarto blogs using Python’s subprocess library.