Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(build): optimize cross-compilation targets in build.zig #24

Merged
merged 11 commits into from
Apr 3, 2024
58 changes: 9 additions & 49 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,61 +7,23 @@ on:

jobs:
build:
runs-on: ${{ matrix.os }}
strategy:
matrix:
include:
- os: ubuntu-latest
arch: x86_64-linux
- os: macos-latest
arch: x86_64-macos
# ...and so on for each supported architecture
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2

- name: Update submodules
run: git submodule update --init --recursive


# Conditional step for installing dependencies on Linux
- name: Install dependencies on Linux
if: startsWith(matrix.os, 'ubuntu')
run: sudo apt-get install libarchive-dev

# Conditional step for installing dependencies on MacOS
- name: Install dependencies on MacOS
if: startsWith(matrix.os, 'macos')
run: |
brew install automake autoconf libarchive

# Setup Zig step remains the same
- name: Setup Zig
uses: goto-bus-stop/setup-zig@v2
with:
version: 0.11.0

# Install dependencies and build for x86_64 and MacOS
- name: Build for x86_64 and MacOS
run: |
if [ "${{ matrix.os }}" = "ubuntu-latest" ]; then
sudo apt-get update
sudo apt-get install -y libarchive-dev
elif [ "${{ matrix.os }}" = "macos-latest" ]; then
brew install automake autoconf
make libarchive
fi
zig build -Dtarget=${{ matrix.arch }} -p "zig-out/${{ matrix.arch }}"
version: master

# Create a tarball of the artifacts
- name: Tarball artifact
run: tar -czvf "zvm-${{ matrix.arch }}.tar.gz" -C "zig-out/${{ matrix.arch }}/bin" zvm
- name: Build release
run:
zig build release

# Archive production artifacts
- name: Archive production artifacts
uses: actions/upload-artifact@v2
- name: Archive executable
uses: actions/upload-artifact@v3
with:
name: "zvm-${{ matrix.arch }}-tar"
path: "zvm-${{ matrix.arch }}.tar.gz"
name: zvm
path: zig-out/bin/*

# This job will need to be modified to handle multiple artifacts
create-release:
Expand All @@ -85,8 +47,6 @@ jobs:
- name: Create and Upload Release
uses: ncipollo/release-action@v1
with:
# You will need to dynamically construct the artifacts string based on the outputs of the build job
artifacts: "*.tar.gz"
artifactErrorsFailBuild: true
generateReleaseNotes: true
tag: ${{ github.ref }}
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/zig.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:
- uses: actions/checkout@v2
- uses: goto-bus-stop/setup-zig@v2
with:
version: 0.11.0
version: master
- run: zig build test
lint:
runs-on: ubuntu-latest
Expand Down
122 changes: 74 additions & 48 deletions build.zig
Original file line number Diff line number Diff line change
@@ -1,72 +1,96 @@
const std = @import("std");
const builtin = @import("builtin");

const version = std.SemanticVersion{ .major = 0, .minor = 1, .patch = 2 };
const CrossTargetInfo = struct {
crossTarget: std.zig.CrossTarget,
name: []const u8,
};
// Semantic version of your application
const version = std.SemanticVersion{ .major = 0, .minor = 3, .patch = 0 };

const min_zig_string = "0.12.0-dev.3522+b88ae8dbd";

const Build = blk: {
const current_zig = builtin.zig_version;
const min_zig = std.SemanticVersion.parse(min_zig_string) catch unreachable;
if (current_zig.order(min_zig) == .lt) {
@compileError(std.fmt.comptimePrint("Your Zig version v{} does not meet the minimum build requirement of v{}", .{ current_zig, min_zig }));
}
break :blk std.Build;
};

// Although this function looks imperative, note that its job is to
// declaratively construct a build graph that will be executed by an external
// runner.
pub fn build(b: *std.Build) 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 optimization options allow the person running `zig build` to select
// between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall. Here we do not
// set a preferred release mode, allowing the user to decide how to optimize.
const optimize = b.standardOptimizeOption(.{});

// Add a global option for versioning
const options = b.addOptions();
options.addOption(std.log.Level, "log_level", b.option(std.log.Level, "log_level", "The Log Level to be used.") orelse .info);
options.addOption(std.SemanticVersion, "zvm_version", version);

const exe = b.addExecutable(.{
.name = "zvm",
// In this case the main source file is merely a path, however, in more
// complicated build scripts, this could be a generated file.
.root_source_file = .{ .path = "src/main.zig" },
.target = target,
.optimize = optimize,
.optimize = .ReleaseFast,
.version = version,
});

// Link dependencies and set include paths
exe.linkLibC();

exe.addIncludePath(.{ .path = "src/deps/libarchive/libarchive" });
exe.addLibraryPath(.{ .path = "src/deps" });
exe.addLibraryPath(.{ .path = "/usr/lib/x86_64-linux-gnu" });
exe.addLibraryPath(.{ .path = "/usr/local/lib" });
exe.linkSystemLibrary("archive"); // libarchive
exe.linkSystemLibrary("lzma"); // liblzma

exe.addOptions("options", options);
// This declares intent for the executable to be installed into the
// standard location when the user invokes the "install" step (the default
// step when running `zig build`).
const exe_options_module = options.createModule();
exe.root_module.addImport("options", exe_options_module);

b.installArtifact(exe);

// This *creates* a Run step in the build graph, to be executed when another
// step is evaluated that depends on it. The next line below will establish
// such a dependency.
const run_cmd = b.addRunArtifact(exe);

// By making the run step depend on the install step, it will be run from the
// installation directory rather than directly from within the cache directory.
// This is not necessary, however, if the application depends on other installed
// files, this ensures they will be present and in the expected location.
run_cmd.step.dependOn(b.getInstallStep());

// This allows the user to pass arguments to the application in the build
// command itself, like this: `zig build run -- arg1 arg2 etc`
if (b.args) |args| {
run_cmd.addArgs(args);
}
const release = b.step("release", "make an upstream binary release");
const release_targets = [_]std.Target.Query{
.{
.cpu_arch = .aarch64,
.os_tag = .linux,
},
.{
.cpu_arch = .x86_64,
.os_tag = .linux,
},
.{
.cpu_arch = .x86,
.os_tag = .linux,
},
.{
.cpu_arch = .riscv64,
.os_tag = .linux,
},
.{
.cpu_arch = .x86_64,
.os_tag = .macos,
},
};

for (release_targets) |target_query| {
const resolved_target = b.resolveTargetQuery(target_query);
const t = resolved_target.result;
const rel_exe = b.addExecutable(.{
.name = "zvm",
.root_source_file = .{ .path = "src/main.zig" },
.target = resolved_target,
.optimize = .ReleaseSafe,
.strip = true,
});

rel_exe.linkLibC();

// This creates a build step. It will be visible in the `zig build --help` menu,
// and can be selected like this: `zig build run`
// This will evaluate the `run` step rather than the default, which is "install".
const run_step = b.step("run", "Run the app");
run_step.dependOn(&run_cmd.step);
const rel_exe_options_module = options.createModule();
rel_exe.root_module.addImport("options", rel_exe_options_module);

const install = b.addInstallArtifact(rel_exe, .{});
install.dest_dir = .prefix;
install.dest_sub_path = b.fmt("{s}-{s}-{s}", .{
@tagName(t.cpu.arch), @tagName(t.os.tag), rel_exe.name,
});

release.dependOn(&install.step);
}

// Creates a step for unit testing. This only builds the test executable
// but does not run it.
Expand All @@ -83,4 +107,6 @@ pub fn build(b: *std.Build) void {
// running the unit tests.
const test_step = b.step("test", "Run unit tests");
test_step.dependOn(&run_unit_tests.step);
// Additional build steps for different configurations or tasks
// Add here as needed (e.g., documentation generation, code linting)
}
4 changes: 2 additions & 2 deletions src/alias.zig
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@ pub fn setZigVersion(version: []const u8) !void {
}

fn getUserHome() []const u8 {
return std.os.getenv("HOME") orelse ".";
return std.posix.getenv("HOME") orelse ".";
}

fn updateSymlink(zigPath: []const u8, symlinkPath: []const u8) !void {
if (doesFileExist(symlinkPath)) try std.fs.cwd().deleteFile(symlinkPath);
try std.os.symlink(zigPath, symlinkPath);
try std.posix.symlink(zigPath, symlinkPath);
}

fn doesFileExist(path: []const u8) bool {
Expand Down
8 changes: 4 additions & 4 deletions src/architecture.zig
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,13 @@ pub fn detect(allocator: std.mem.Allocator, params: DetectParams) !?[]u8 {
const result = try allocator.alloc(u8, len);

if (params.reverse) {
std.mem.copy(u8, result[0..archStr.len], archStr);
@memcpy(result[0..archStr.len], archStr);
result[archStr.len] = '-';
std.mem.copy(u8, result[archStr.len + 1 ..], osStr);
@memcpy(result[archStr.len + 1 ..], osStr);
} else {
std.mem.copy(u8, result[0..osStr.len], osStr);
@memcpy(result[0..osStr.len], osStr);
result[osStr.len] = '-';
std.mem.copy(u8, result[osStr.len + 1 ..], archStr);
@memcpy(result[osStr.len + 1 ..], archStr);
}

return result;
Expand Down
1 change: 0 additions & 1 deletion src/deps/libarchive
Submodule libarchive deleted from 67a20c
59 changes: 33 additions & 26 deletions src/download.zig
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ const architecture = @import("architecture.zig");
const Progress = std.Progress;
const alias = @import("alias.zig");
const hash = @import("hash.zig");
const lib = @import("libarchive/libarchive.zig");
const lib = @import("extract.zig");
const crypto = std.crypto;

const archive_ext = if (builtin.os.tag == .windows) "zip" else "tar.xz";

fn getZvmPathSegment(segment: []const u8) ![]u8 {
const user_home = std.os.getenv("HOME") orelse ".";
const user_home = std.posix.getenv("HOME") orelse ".";
return std.fs.path.join(std.heap.page_allocator, &[_][]const u8{ user_home, ".zm", segment });
}

Expand Down Expand Up @@ -85,9 +85,15 @@ fn downloadAndExtract(allocator: std.mem.Allocator, uri: std.Uri, version_path:
var client = std.http.Client{ .allocator = allocator };
defer client.deinit();

var req = try client.request(.GET, uri, .{ .allocator = allocator }, .{});
const sendOptions = std.http.Client.Request.SendOptions{};

// Read the response body with 256kb buffer allocation
var headerBuffer: [262144]u8 = undefined; // 256 * 1024 = 262kb

var req = try client.open(.GET, uri, .{ .server_header_buffer = &headerBuffer });
defer req.deinit();
try req.start();

try req.send(sendOptions);
try req.wait();

try std.testing.expect(req.response.status == .ok);
Expand All @@ -101,7 +107,9 @@ fn downloadAndExtract(allocator: std.mem.Allocator, uri: std.Uri, version_path:
const file_name = try std.mem.concat(allocator, u8, &[_][]const u8{ "zig-", platform, "-", version, ".", archive_ext });
defer allocator.free(file_name);

const totalSize = req.response.content_length orelse 0;
std.debug.print("Constructed file name: {s}\n", .{file_name});

const totalSize: usize = @intCast(req.response.content_length orelse 0);
var downloadedBytes: usize = 0;

const downloadMessage = try std.fmt.allocPrint(allocator, "Downloading Zig version {s} for platform {s}...", .{ version, platform });
Expand All @@ -112,6 +120,8 @@ fn downloadAndExtract(allocator: std.mem.Allocator, uri: std.Uri, version_path:
const file_stream = try zvm_dir.createFile(file_name, .{});
defer file_stream.close();

std.debug.print("Download complete, file written: {s}\n", .{file_name});

var sha256 = sha2.Sha256.init(.{});

while (true) {
Expand All @@ -129,39 +139,36 @@ fn downloadAndExtract(allocator: std.mem.Allocator, uri: std.Uri, version_path:
try file_stream.writeAll(buffer[0..bytes_read]);
}

const file_path = try zvm_dir.realpathAlloc(allocator, file_name);
defer allocator.free(file_path);
//const file_path = try zvm_dir.realpathAlloc(allocator, file_stream);
//defer allocator.free(file_path);
download_node.end();

// libarchive can't set dest path so it extracts to cwd
// rename here moves the extracted folder to the correct path
// (cwd)/zig-linux-x86_64-0.11.0 -> ~/zvm/versions/0.11.0
var extract_node = root_node.start("Extracting", 1);
extract_node.activate();
progress.refresh();
_ = try lib.extractTarXZ(file_path);
extract_node.end();
// TODO: use std.tar.pipeToFileSystem() in the future, currently very slow
const c_allocator = std.heap.c_allocator;

var move_node = root_node.start("Copy Files", 1);
move_node.activate();
progress.refresh();
const folder_name = try std.fmt.allocPrint(allocator, "zig-{s}-{s}", .{ platform, version });
defer allocator.free(folder_name);
// ~/.zm/versions/zig-macos-x86_64-0.10.0.tar.xz
const zvm_path = try getZvmPathSegment("");
const downloaded_file_path = try std.fs.path.join(allocator, &.{ zvm_path, file_name });
defer allocator.free(downloaded_file_path);

std.debug.print("Downloaded file path: {s}\n", .{downloaded_file_path});

// Construct the full file path
// example: ~/.zm/0.10.0
const folder_path = try std.fs.path.join(allocator, &.{ version_path, version });
defer allocator.free(folder_path);

std.fs.makeDirAbsolute(version_path) catch {};
std.fs.makeDirAbsolute(folder_path) catch {};

//std.debug.print("Renaming '{s}' to '{s}'\n", .{ folder_name, folder_path });
const zvm_dir_version = try std.fs.openDirAbsolute(folder_path, .{});

if (std.fs.cwd().rename(folder_name, folder_path)) |_| {
//std.debug.print("✓ Successfully renamed {s} to {s}.\n", .{ folder_name, folder_path });
} else |err| {
std.debug.print("✗ Error: Failed to rename {s} to {s}. Reason: {any}\n", .{ folder_name, folder_path, err });
}
move_node.end();
const downloaded_file = try zvm_dir.openFile(downloaded_file_path, .{});
defer downloaded_file.close();

_ = try lib.extract_tarxz_to_dir(c_allocator, zvm_dir_version, downloaded_file);
extract_node.end();

var result: [32]u8 = undefined;
sha256.final(&result);
Expand Down
8 changes: 8 additions & 0 deletions src/extract.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
const std = @import("std");

pub fn extract_tarxz_to_dir(allocator: std.mem.Allocator, outDir: std.fs.Dir, file: std.fs.File) !void {
var buffered_reader = std.io.bufferedReader(file.reader());
var decompressed = try std.compress.xz.decompress(allocator, buffered_reader.reader());
defer decompressed.deinit();
try std.tar.pipeToFileSystem(outDir, decompressed.reader(), .{ .mode_mode = .executable_bit_only, .strip_components = 1 });
}
Loading
Loading