Skip to content

Commit

Permalink
Adding in workflows for curating binaries for mac,linux,and windows. …
Browse files Browse the repository at this point in the history
…Adding code toe check shell and run on windows systems
  • Loading branch information
BitlyTwiser committed Sep 15, 2024
1 parent 9d4dedb commit a2b07cf
Show file tree
Hide file tree
Showing 4 changed files with 307 additions and 3 deletions.
59 changes: 59 additions & 0 deletions .github/workflows/changelog.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# Note - This is not my original work.
# Credit where credit is due, checkout the original works here: https://github.com/portapack-mayhem/mayhem-firmware/blob/next/.github/workflows/changelog.py
import os
import sys

import requests
from datetime import datetime, timedelta, timezone

# Set up your personal access token and the repository details
token = os.environ.get('GH_TOKEN')
repo_owner = "bitlytwiser"
repo_name = "apprunner"

def print_stable_changelog():
url = f"compare/{changelog_from_last_commit}...next"
commits = handle_get_request(url)
for commit in commits["commits"]:
# Print the commit details
print(format_output(commit))

def changelog_from_last_commit():
headers = {} if token == None else {"Authorization": f"Bearer {token}"}
url_base = f"https://api.github.com/repos/{repo_owner}/{repo_name}/commits/master"
response = requests.get(url_base, headers=headers)

if response.status_code == 200:
data = response.json()
return data["sha"]
else:
print(f"Request failed with status code: {response.status_code}")
return None

def curate_changelog_from_dates():
# Calculate the date and time 24 hours ago
since_time = (datetime.now(timezone.utc) - timedelta(hours=24)).isoformat() # Check that this is UTC
url = "commits"
commits = handle_get_request(url, since_time)
for commit in commits:
print(format_output(commit))

def handle_get_request(path, offset=None):
headers = {} if token == None else {"Authorization": f"Bearer {token}"}
params = {"since": offset}
url_base = f"https://api.github.com/repos/{repo_owner}/{repo_name}/"
response = requests.get(url_base + path, headers=headers, params=params)

if response.status_code == 200:
return response.json()
else:
print(f"Request failed with status code: {response.status_code}")
return None


def format_output(commit):
message_lines = commit["commit"]["message"].split("\n")
author = commit["author"]["login"] if commit["author"] and "login" in commit["author"] else commit["commit"]["author"]["name"]
return '- ' + commit["sha"][:8] + ' - @' + author + ': ' + message_lines[0]

print_stable_changelog
123 changes: 123 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
name: Binary Release

on:
push:
branches:
- main
pull_request:
branches:
- main
workflow_dispatch:

jobs:
deploy:
runs-on: ubuntu-latest
steps:
# Single checkout, with submodules
- name: Checkout
uses: actions/checkout@master
with:
fetch-depth: 0
submodules: true

- name: Print latest commit
run: echo ${{ github.sha }}

- name: "Install Zig"
run: "sudo snap install zig --classic --beta"

# Date for versioning
- name: Get current date
id: date
run: echo "date=$(date +'%Y-%m-%d')" >> $GITHUB_OUTPUT

- name: Get version date
id: version_date
run: echo "date=n_$(date +'%y%m%d')" >> $GITHUB_OUTPUT

# Linux binary creation
- name: Package and create binary for Linux
run: |
mkdir -p apprunner_linux
zig build -p apprunner_linux/ --release=fast -Dtarget=x86_64-linux
zip -r apprunner_linux.zip apprunner_linux
# Windows binary creation
- name: Package and create binary for Windows
run: |
mkdir -p apprunner_windows
zig build -p apprunner_windows/--release=fast -Dtarget=x86_64-windows
zip -r apprunner_windows.zip apprunner_windows
# MacOS Binary Creation
- name: Package and create binary for MacOS
run: |
mkdir -p apprunner_windows
zig build -p apprunner_windows/--release=fast -Dtarget=x86_64-macos
zip -r apprunner_macos.zip apprunner_macos
# Changelog generation
- name: Create changelog
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
{
echo 'content<<EOF'
python3 .github/workflows/changelog.py
echo EOF
} >> "$GITHUB_OUTPUT"
id: changelog

# Release creation
- name: Create Release
id: create_release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: binary-tag-${{ steps.date.outputs.date }}-${{ github.sha }}
release_name: Binary Release - ${{ steps.date.outputs.date }}
body: |
**Binary release - ${{ steps.date.outputs.date }}**
This build is the latest code changes for apprunner.
## Release notes
### Revision (${{ steps.version_date.outputs.date }}):
${{ steps.changelog.outputs.content }}
draft: false
prerelease: true

# Upload binaries (Windows)
- name: Upload Apprunner Binary Windows
id: upload-apprunner-binary-windows
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./apprunner.zip
asset_name: apprunner${{ steps.version_date.outputs.date }}_binary_windows.zip
asset_content_type: application/zip

# Upload binaries (Linux)
- name: Upload Apprunner Binary Linux
id: upload-apprunner-binary-linux
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./apprunner.zip
asset_name: apprunner${{ steps.version_date.outputs.date }}_binary_linux.zip
asset_content_type: application/zip

# Upload binaries (MacOS)
- name: Upload Apprunner Binary MacOS
id: upload-apprunner-binary-mac
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./apprunner.zip
asset_name: apprunner${{ steps.version_date.outputs.date }}_binary_macos.zip
asset_content_type: application/zip
51 changes: 50 additions & 1 deletion src/main.zig
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
const std = @import("std");
const builtin = @import("builtin");
const os = std.os;
const assert = std.debug.assert;
const config = @import("./config.zig");
const runner = @import("./runner.zig");
const shell_err = runner.ShellError;

// Functions from std
const print = std.debug.print;
Expand All @@ -23,8 +27,53 @@ pub fn main() !void {
defer allocator.free(results.apps);

// Spawns all threads and waits
var run = try runner.Runner.init(allocator);
var run = runner.Runner.init(allocator) catch |err| {
switch (err) {
runner.ShellError.ShellNotFound => {
print("error finding appropriate shell to run tmux commands. Please change shells and try again", .{});
},
}
return;
};
try run.spawner(results.apps);

// Listen for the exit events on ctrl+c to gracefully exit
try setAbortSignalHandler(handleAbortSignal);
}

// Gracefully exit on signal termination events
fn setAbortSignalHandler(comptime handler: *const fn () void) !void {
if (builtin.os.tag == .windows) {
const handler_routine = struct {
fn handler_routine(dwCtrlType: os.windows.DWORD) callconv(os.windows.WINAPI) os.windows.BOOL {
if (dwCtrlType == os.windows.CTRL_C_EVENT) {
handler();
return os.windows.TRUE;
} else {
return os.windows.FALSE;
}
}
}.handler_routine;
try os.windows.SetConsoleCtrlHandler(handler_routine, true);
} else {
const internal_handler = struct {
fn internal_handler(sig: c_int) callconv(.C) void {
assert(sig == os.linux.SIG.INT);
handler();
}
}.internal_handler;
const act = os.linux.Sigaction{
.handler = .{ .handler = internal_handler },
.mask = os.linux.empty_sigset,
.flags = 0,
};
_ = os.linux.sigaction(os.linux.SIG.INT, &act, null);
}
}

fn handleAbortSignal() void {
print("Goodbye! Thanks for using apprunner", .{});
std.process.exit(0);
}

test "simple test" {}
77 changes: 75 additions & 2 deletions src/runner.zig
Original file line number Diff line number Diff line change
@@ -1,29 +1,54 @@
const std = @import("std");
const builtin = @import("builtin");
const config = @import("./config.zig");
const print = std.debug.print;

const app_name = "apprunner";
const default_shell_nix = "bash";
const default_shell_win = "powershell";
const cmdline_path = "/proc/$$/cmdline";

pub const ShellError = error{ShellNotFound};

// Default shells
const shellType = enum {
zsh,
sh,
bash,
powershell,
};

/// Runner is responsible for running all commands parsed from the yaml to TMUX
pub const Runner = struct {
const Self = @This();

allocator: std.mem.Allocator,
shell_command: []const u8,
shell_sub_command: []const u8,

pub fn init(allocator: std.mem.Allocator) ShellError!Self {
// Get shell information here so we can exit gracefully
const command_base = commandBase(allocator) catch return ShellError.ShellNotFound;
const sub_command = subCommand() catch return ShellError.ShellNotFound;

pub fn init(allocator: std.mem.Allocator) !Self {
return Self{
.allocator = allocator,
.shell_command = command_base,
.shell_sub_command = sub_command,
};
}

pub fn spawner(self: *Self, apps: []config.App) !void {
var thread_pool = try self.allocator.alloc(std.Thread, apps.len);
for (apps, 0..) |app, i| {
thread_pool[i] = try std.Thread.spawn(.{ .allocator = self.allocator }, spawnProcess, .{ self, app.name, app.stand, app.command, app.location, i });
// Its too fast lol - Try sleeping for a moment to avoid missing shells
std.time.sleep(5000 * 3);
}

// Wait for all threads to stop program from exiting
// We could also detach and run forever with a loop
// Ctrl+C event is handled from main
for (thread_pool) |thread| {
thread.join();
}
Expand All @@ -40,7 +65,7 @@ pub const Runner = struct {
) !void {
// Base command to start tmux session
const exec_command = try tmuxConfig(self, name, standalone, command, location, index);
var child = std.process.Child.init(&[_][]const u8{ "sh", "-c", exec_command }, self.allocator);
var child = std.process.Child.init(&[_][]const u8{ self.shell_command, self.shell_sub_command, exec_command }, self.allocator);
try child.spawn();
_ = try child.wait();
}
Expand Down Expand Up @@ -71,3 +96,51 @@ pub const Runner = struct {
return r_command;
}
};

/// determines which base command to run depending on execution environment. I.e. windows/linux/macOS
fn commandBase(allocator: std.mem.Allocator) ![]const u8 {
return try captureShell(allocator);
}

// Process sub-command for running command when spawning shell
fn subCommand() ![]const u8 {
if (builtin.os.tag == .windows) return "-Command";

return "-c";
}

// In nix systems, parse cmdline_path above to fine current shell.
// Windows its assumed powershell lol
fn captureShell(allocator: std.mem.Allocator) ![]const u8 {
if (builtin.os.tag == .windows) return "powershell";

const env_map = try std.process.getEnvMap(allocator);
const shell = env_map.get("SHELL");

if (shell) |sh| {
var split_shell = std.mem.splitBackwards(u8, sh, "/");

// Shell type is always last
return @as([]const u8, split_shell.first());
} else {
// Posix systems/macos do not have /proc, so the commands below fail to check the shell
if (builtin.os.tag == .macos) return "bash";

const file = try std.fs.openFileAbsolute(cmdline_path, .{ .mode = .read_only });
defer file.close();

// Rather small, but this should only be a single line
var buf: [512]u8 = undefined;
_ = try file.reader().read(&buf);

return &buf;
}
}

test "get env variables for shell" {
const env_map = try std.process.getEnvMap(std.heap.page_allocator);
const shell = env_map.get("SHELL") orelse "";
var split_shell = std.mem.splitBackwards(u8, shell, "/");

print("{s}", .{split_shell.first()});
}

0 comments on commit a2b07cf

Please sign in to comment.