Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 31 additions & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
name: Build and Release TLEscope

on:
workflow_dispatch:
push:
branches: [ "main", "master" ]
tags:
Expand Down Expand Up @@ -73,6 +74,31 @@ jobs:
path: dist/TLEscope-Linux-arm64-Portable/
retention-days: 14

build-macos:
name: Build macOS
runs-on: macos-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v4

- name: Install macOS Build Dependencies
run: |
brew install raylib pkg-config

- name: Compile and Bundle .app
# bundler compiles the icon + ad-hoc signs natively on the runner
run: make app

- name: Zip .app (preserves bundle structure and exec bits)
run: ditto -c -k --sequesterRsrc --keepParent dist/TLEscope.app TLEscope-macOS.zip

- name: Upload macOS Artifact
uses: actions/upload-artifact@v4
with:
name: TLEscope-macOS
path: TLEscope-macOS.zip
retention-days: 14

build-windows:
name: Build Windows
runs-on: windows-latest
Expand Down Expand Up @@ -162,7 +188,7 @@ jobs:

release:
name: Publish Release
needs: [build-linux, build-linux-arm64, build-windows, build-windows-arm64]
needs: [build-linux, build-linux-arm64, build-macos, build-windows, build-windows-arm64]
if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags/v'))
runs-on: ubuntu-latest
permissions:
Expand Down Expand Up @@ -191,6 +217,8 @@ jobs:

# Move installer out of its artifact subdirectory for flat publishing
mv TLEscope-Installer/TLEscope-Installer.exe ../
# macOS artifact is already a ditto-zipped .app, just move it up
mv TLEscope-macOS/TLEscope-macOS.zip ../
cd ..

- name: Clean Existing Release Assets
Expand Down Expand Up @@ -223,6 +251,7 @@ jobs:
files: |
TLEscope-Linux-Portable.zip
TLEscope-Linux-arm64-Portable.zip
TLEscope-macOS.zip
TLEscope-Win-Portable.zip
TLEscope-Win-arm64-Portable.zip
TLEscope-Installer.exe
Expand All @@ -235,6 +264,7 @@ jobs:
files: |
TLEscope-Linux-Portable.zip
TLEscope-Linux-arm64-Portable.zip
TLEscope-macOS.zip
TLEscope-Win-Portable.zip
TLEscope-Win-arm64-Portable.zip
TLEscope-Installer.exe
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,8 @@ dist/
# Compoile Commands CLANGD
/compile_commands.json

# mac finder junk
.DS_Store

# csv exports
*.csv
18 changes: 13 additions & 5 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,16 @@ APP_DIR ?= /usr/share/applications

# macOS (Apple Silicon / Intel)
CC_MACOS = clang
RAYLIB_CFLAGS = $(shell pkg-config --cflags raylib 2>/dev/null)
RAYLIB_LIBS = $(shell pkg-config --libs raylib 2>/dev/null)
empty :=
space := $(empty) $(empty)
MACOS_PC_PATH := $(subst $(space),:,$(strip $(wildcard /opt/homebrew/lib/pkgconfig /usr/local/lib/pkgconfig)))
MACOS_PKG_CONFIG_ENV = PKG_CONFIG_PATH="$(PKG_CONFIG_PATH):$(MACOS_PC_PATH)"
RAYLIB_CFLAGS = $(shell $(MACOS_PKG_CONFIG_ENV) pkg-config --cflags raylib 2>/dev/null)
RAYLIB_LIBS = $(shell $(MACOS_PKG_CONFIG_ENV) pkg-config --libs raylib 2>/dev/null)
LDFLAGS_MACOS = $(RAYLIB_LIBS) -lcurl -framework IOKit -framework Cocoa -framework OpenGL
DIST_MACOS = dist/TLEscope-macOS-Portable

.PHONY: all linux macos windows windows-arm64 win-installer clean build bin install uninstall raylib raylib-crossbuild
.PHONY: all linux macos app windows windows-arm64 win-installer clean build bin install uninstall raylib raylib-crossbuild

all: linux

Expand All @@ -68,7 +72,11 @@ macos: bin/TLEscope-macos
cp logo*.png $(DIST_MACOS)/ 2>/dev/null || true
@echo "macOS build bundled in $(DIST_MACOS)/"
@echo "Run it with: cd $(DIST_MACOS)/ && ./TLEscope"
@echo "To make a MacOS bundle: ./macos_bundle.sh"
@echo "To make a proper .app bundle: make app"

# proper .app bundle: compiles the icon, generates Info.plist, codesigns
app: bin/TLEscope-macos
./scripts/macos-bundle.sh

windows: bin/TLEscope.exe
@mkdir -p $(DIST_WIN)
Expand Down Expand Up @@ -103,7 +111,7 @@ bin/TLEscope: $(OBJ) | bin
$(CC_LINUX) $(CFLAGS) -o $@ $^ $(LDFLAGS_LIN)

bin/TLEscope-macos: $(SRC) | bin
@if ! pkg-config --exists raylib 2>/dev/null; then echo "Error: raylib not found. Install with: brew install raylib"; exit 1; fi
@if ! $(MACOS_PKG_CONFIG_ENV) pkg-config --exists raylib 2>/dev/null; then echo "Error: raylib not found. Install with: brew install raylib"; exit 1; fi
$(CC_MACOS) $(CFLAGS) $(RAYLIB_CFLAGS) -o $@ $^ $(LDFLAGS_MACOS)

bin/TLEscope.exe: $(SRC) | bin
Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,11 @@ brew install raylib pkg-config
git clone https://github.com/aweeri/TLEscope
cd TLEscope
make macos # Results in dist/TLEscope-macOS-Portable/
make app # Results in dist/TLEscope.app
```

`make app` additionally bundles everything into a proper `TLEscope.app`: it compiles `AppIcon.icns` from `logo.png`, generates an `Info.plist` with the git version baked in, and ad-hoc codesigns the bundle (required for Apple Silicon to run at all). To sign with a real identity instead, set `CODE_SIGN_IDENTITY="Developer ID Application: ..."` before bundling. The app must be built on macOS itself - the icon and signing steps use native tooling.

**Windows systems**
Install [MSYS2](https://www.msys2.org/), then run the following in a UCRT64 or MINGW64 terminal:
```
Expand Down
4 changes: 0 additions & 4 deletions bundle/Contents/MacOS/TLEscope

This file was deleted.

Binary file removed bundle/Contents/Resources/AppIcon.icns
Binary file not shown.
5 changes: 0 additions & 5 deletions macos_bundle.sh

This file was deleted.

93 changes: 93 additions & 0 deletions scripts/macos-bundle.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
#!/usr/bin/env bash
# bundles a freshly built macOS binary into a proper TLEscope.app
#
# does everything natively on the build machine:
# - compiles AppIcon.icns from logo.png (sips + iconutil, no precompiled icon)
# - generates Info.plist with the real git version baked in
# - ad-hoc codesigns by default (arm64 binaries refuse to run unsigned)
#
# usage: scripts/macos-bundle.sh [binary] default binary: bin/TLEscope-macos
#
# env vars:
# CODE_SIGN_IDENTITY set to e.g. "Developer ID Application: Your Name (TEAMID)"
# for real signing. default is "-" (ad-hoc).
set -euo pipefail

APP_NAME="TLEscope"
BUNDLE_ID="com.aweeri.tlescope"
BIN="${1:-bin/${APP_NAME}-macos}"
APP="dist/${APP_NAME}.app"

GIT_VERSION=$(git describe --tags --always --dirty 2>/dev/null || echo "vUnknown")

for tool in sips iconutil codesign; do
command -v "$tool" >/dev/null || { echo "error: '$tool' not found - this script must run on macOS"; exit 1; }
done
[ -x "$BIN" ] || { echo "error: $BIN not found - run 'make macos' first"; exit 1; }

rm -rf "$APP"
mkdir -p "$APP/Contents/MacOS" "$APP/Contents/Resources"

# portable resource layout lives inside Resources (the binary chdirs there)
cp -r themes "$APP/Contents/Resources/themes"
cp logo.png logo_l.png "$APP/Contents/Resources/"
cp settings.json "$APP/Contents/Resources/" 2>/dev/null || true
cp data.tle "$APP/Contents/Resources/" 2>/dev/null || true
cp "$BIN" "$APP/Contents/MacOS/$APP_NAME"

# compile a multi-resolution .icns from logo.png
ICONSET_DIR="$(mktemp -d)"
ICONSET="$ICONSET_DIR/${APP_NAME}.iconset"
mkdir -p "$ICONSET"
for size in 16 32 128 256 512; do
double=$((size * 2))
sips -z "$size" "$size" logo.png --out "$ICONSET/icon_${size}x${size}.png" >/dev/null
sips -z "$double" "$double" logo.png --out "$ICONSET/icon_${size}x${size}@2x.png" >/dev/null
done
iconutil -c icns "$ICONSET" -o "$APP/Contents/Resources/AppIcon.icns"
rm -rf "$ICONSET_DIR"

cat > "$APP/Contents/Info.plist" <<EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleExecutable</key>
<string>${APP_NAME}</string>
<key>CFBundleIconFile</key>
<string>AppIcon</string>
<key>CFBundleIdentifier</key>
<string>${BUNDLE_ID}</string>
<key>CFBundleName</key>
<string>${APP_NAME}</string>
<key>CFBundleDisplayName</key>
<string>${APP_NAME}</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>${GIT_VERSION}</string>
<key>CFBundleVersion</key>
<string>${GIT_VERSION}</string>
<key>NSHighResolutionCapable</key>
<true/>
<key>LSMinimumSystemVersion</key>
<string>11.0</string>
<key>NSHumanReadableCopyright</key>
<string>See LICENSE</string>
</dict>
</plist>
EOF

# signing: ad-hoc by default so the app actually launches on Apple Silicon.
# with CODE_SIGN_IDENTITY set we also opt into hardened runtime (needed for notarization).
# note: settings.json lives inside Resources, so editing it in-place after
# signing invalidates the signature - Gatekeeper only cares on first launch tho
if [ "${CODE_SIGN_IDENTITY:--}" = "-" ]; then
echo "ad-hoc signing $APP (set CODE_SIGN_IDENTITY for real signing)"
codesign --force --deep --sign - "$APP"
else
codesign --force --deep --sign "$CODE_SIGN_IDENTITY" --options runtime "$APP"
fi
codesign --verify --strict "$APP"

echo "built $APP (${GIT_VERSION})"
26 changes: 26 additions & 0 deletions src/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,28 @@
#include <stdlib.h>
#include <string.h>

#if defined(__APPLE__)
#include <unistd.h>
#include <libgen.h>
#include <mach-o/dyld.h>
/* launchd starts .app bundles with cwd=/ but all our assets (themes,
* settings.json, logo.png, ...) load relative to cwd, so when the binary
* lives inside a bundle hop over to Contents/Resources. no wrapper needed */
static void BundleChdir(void)
{
char exe[4096];
uint32_t size = sizeof(exe);
if (_NSGetExecutablePath(exe, &size) != 0)
return;
char *contents = strstr(exe, "/Contents/MacOS/");
if (!contents)
return;
strcpy(contents + strlen("/Contents"), "/Resources");
if (chdir(exe) != 0)
fprintf(stderr, "warning: could not chdir into %s\n", exe);
}
#endif

#include "astro.h"
#include "config.h"

Expand Down Expand Up @@ -530,6 +552,10 @@ static bool GetMouseEarthIntersection(Vector2 mouse, bool is_2d, Camera2D cam2d,

int main(void)
{
#if defined(__APPLE__)
BundleChdir();
#endif

LoadAppConfig("settings.json", &cfg);

/* window setup and msaa */
Expand Down
Loading