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
22 changes: 22 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,25 @@ jobs:

- name: Test
run: make test

build:
strategy:
fail-fast: false
matrix:
runner: [macos-14, ubuntu-22.04, ubuntu-22.04-arm]
runs-on: ${{ matrix.runner }}
steps:
- uses: actions/checkout@v4

- name: Install uv
uses: astral-sh/setup-uv@v5
with:
enable-cache: true

- name: Setup project
run: |
uv python install 3.13
make setup

- name: Build and smoke test binary
run: make smoke
95 changes: 75 additions & 20 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,26 @@ on:

permissions:
contents: write
pull-requests: write

jobs:
build-and-bump-tap:
runs-on: macos-14 # Apple Silicon runner
env:
HOMEBREW_GITHUB_API_TOKEN: ${{ secrets.HOMEBREW_GITHUB_API_TOKEN }}
build:
strategy:
fail-fast: false
matrix:
include:
- runner: macos-14 # Apple Silicon
os: macos
arch: arm64
# Built on 22.04, not 24.04, on purpose: PyInstaller binaries are only
# forward-compatible w.r.t. glibc. The 22.04 output is verified to run
# on glibc 2.28+ (Rocky 8, Debian 11, Ubuntu 20.04+).
- runner: ubuntu-22.04
os: linux
arch: x86_64
- runner: ubuntu-22.04-arm
os: linux
arch: arm64
runs-on: ${{ matrix.runner }}
steps:
- uses: actions/checkout@v4

Expand All @@ -35,28 +48,70 @@ jobs:
- name: Package tarball
run: |
VERSION="${GITHUB_REF_NAME#v}"
TAR="fithit-cli-${VERSION}-macos.tar.gz"
tar -czf "$TAR" -C dist fithit
echo "VERSION=$VERSION" >> "$GITHUB_ENV"
TAR="fithit-cli-${VERSION}-${{ matrix.os }}-${{ matrix.arch }}.tar.gz"
# COPYFILE_DISABLE keeps macOS tar from embedding AppleDouble xattrs.
COPYFILE_DISABLE=1 tar -czf "$TAR" -C dist fithit
echo "TAR=$TAR" >> "$GITHUB_ENV"

- name: Compute sha256
run: |
SHA256=$(shasum -a 256 "$TAR" | awk '{print $1}')
echo "SHA256=$SHA256" >> "$GITHUB_ENV"
if command -v sha256sum >/dev/null 2>&1; then
SHA256=$(sha256sum "$TAR" | awk '{print $1}')
else
SHA256=$(shasum -a 256 "$TAR" | awk '{print $1}')
fi
echo "$SHA256" > "sha256-${{ matrix.os }}-${{ matrix.arch }}.txt"

- name: Upload release asset
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh release upload "$GITHUB_REF_NAME" "$TAR" --clobber
run: gh release upload "$GITHUB_REF_NAME" "$TAR" --clobber

- name: Share checksum with tap job
uses: actions/upload-artifact@v4
with:
name: sha256-${{ matrix.os }}-${{ matrix.arch }}
path: sha256-${{ matrix.os }}-${{ matrix.arch }}.txt

bump-tap:
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Download checksums
uses: actions/download-artifact@v4
with:
pattern: sha256-*
merge-multiple: true
path: checksums

- name: Bump tap formula
- name: Render formula and open tap PR
env:
GH_TOKEN: ${{ secrets.HOMEBREW_GITHUB_API_TOKEN }}
run: |
URL="https://github.com/voydz/fithit/releases/download/${GITHUB_REF_NAME}/${TAR}"
git config --global user.name "github-actions[bot]"
git config --global user.email "github-actions[bot]@users.noreply.github.com"
brew tap voydz/homebrew-tap
brew bump-formula-pr voydz/tap/fithit \
--url "$URL" \
--sha256 "$SHA256"
set -euo pipefail
VERSION="${GITHUB_REF_NAME#v}"
BRANCH="bump-fithit-${VERSION}"

git clone --depth 1 \
"https://x-access-token:${GH_TOKEN}@github.com/voydz/homebrew-tap.git" tap
git -C tap config user.name "github-actions[bot]"
git -C tap config user.email "github-actions[bot]@users.noreply.github.com"
git -C tap checkout -b "$BRANCH"

sed \
-e "s/@VERSION@/${VERSION}/g" \
-e "s/@SHA256_MACOS_ARM64@/$(cat checksums/sha256-macos-arm64.txt)/" \
-e "s/@SHA256_LINUX_ARM64@/$(cat checksums/sha256-linux-arm64.txt)/" \
-e "s/@SHA256_LINUX_X86_64@/$(cat checksums/sha256-linux-x86_64.txt)/" \
packaging/fithit.rb.tmpl > tap/Formula/fithit.rb

git -C tap commit -am "fithit ${VERSION}"
git -C tap push origin "$BRANCH"

gh pr create \
--repo voydz/homebrew-tap \
--head "$BRANCH" \
--title "fithit ${VERSION}" \
--body "Automated bump for [fithit ${GITHUB_REF_NAME}](https://github.com/voydz/fithit/releases/tag/${GITHUB_REF_NAME}). Builds: macOS arm64, Linux arm64, Linux x86_64."
8 changes: 8 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,11 @@ Standard: `$XDG_DATA_HOME/fithit/workouts.json` (falls back to the standard XDG
- `fithit parse <dtable>`: extrahiert `content.json` aus der `.dtable` (ZIP) und schreibt `workouts.json` + `summary.json`.
- `fithit search ...`: filtert Workouts 1:1 wie das ursprüngliche Script `filter_workouts.py`.
- `fithit info`: Live-Statistiken aus `workouts.json`.

## Build & Release

- `make build` baut die Binary via `fithit.spec` (PyInstaller, immer für die Host-Architektur — Cross-Compiling gibt es nicht).
- `make package` erzeugt `dist/fithit-cli-<version>-<os>-<arch>.tar.gz` für die Host-Plattform.
- Unterstützte Release-Ziele: macOS `arm64`, Linux `x86_64`, Linux `arm64`. Kein Intel-macOS.
- Linux wird bewusst auf Ubuntu 22.04 gebaut (nicht 24.04): PyInstaller-Binaries sind nur vorwärtskompatibel. Das Ergebnis läuft ab glibc 2.28 (getestet: Rocky 8, Debian 11, Ubuntu 20.04/22.04).
- Die Homebrew-Formula wird generiert. `packaging/fithit.rb.tmpl` ist die Quelle; nicht direkt im Tap editieren.
37 changes: 27 additions & 10 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,29 @@

.DEFAULT_GOAL := check

# Release artifacts are named fithit-cli-<version>-<os>-<arch>.tar.gz.
# The binary is always built for the host platform; cross-compiling is not
# supported by PyInstaller, so each target gets its own CI runner.
UNAME_S := $(shell uname -s)
UNAME_M := $(shell uname -m)

ifeq ($(UNAME_S),Darwin)
TARGET_OS := macos
SHA256 := shasum -a 256
else
TARGET_OS := linux
SHA256 := sha256sum
endif

ifneq ($(filter $(UNAME_M),arm64 aarch64),)
TARGET_ARCH := arm64
else
TARGET_ARCH := x86_64
endif

VERSION ?= $(shell grep '^version' pyproject.toml | head -1 | cut -d'"' -f2)
TARBALL := fithit-cli-$(VERSION)-$(TARGET_OS)-$(TARGET_ARCH).tar.gz

setup:
uv venv
uv sync --extra dev
Expand All @@ -23,20 +46,14 @@ test:
check: lint test

build:
uv run pyinstaller \
--onefile \
--name fithit \
--target-arch arm64 \
--collect-all rich \
src/fithitcli/__main__.py
uv run pyinstaller --clean --noconfirm fithit.spec

package: build
@set -e; \
VERSION=$$(grep '^version' pyproject.toml | head -1 | cut -d'"' -f2); \
echo "Packaging fithit v$$VERSION..."; \
echo "Packaging fithit v$(VERSION) for $(TARGET_OS)/$(TARGET_ARCH)..."; \
cd dist && \
tar -czf "fithit-cli-$$VERSION-macos.tar.gz" fithit && \
shasum -a 256 "fithit-cli-$$VERSION-macos.tar.gz"
COPYFILE_DISABLE=1 tar -czf "$(TARBALL)" fithit && \
$(SHA256) "$(TARBALL)"

smoke: build
@set -e; \
Expand Down
26 changes: 24 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,18 @@ Fast, deterministic, and ideal for scripts, notebooks, or a personal training da
brew install voydz/tap/fithit
```

Works with Homebrew on macOS and with Homebrew on Linux (Linuxbrew). Prebuilt
binaries are published for:

| Platform | Architecture |
| --- | --- |
| macOS | `arm64` (Apple Silicon) |
| Linux | `x86_64`, `arm64` |

Intel Macs are not covered by a prebuilt binary — install from source there.
The Linux binaries are built on Ubuntu 22.04 and verified to run on glibc 2.28
and newer (tested on Rocky Linux 8, Debian 11, Ubuntu 20.04/22.04).

Or install from source with [uv](https://docs.astral.sh/uv/):

```bash
Expand Down Expand Up @@ -82,8 +94,18 @@ make test

## Homebrew (Tap)

The tap repo is `voydz/homebrew-tap`, and the formula lives at `Formula/fithit.rb`.
Before publishing, update `homepage`, `url`, and `sha256`.
The tap repo is `voydz/homebrew-tap`, and the formula lives at `Formula/fithit.rb`.

The formula is generated, not hand-edited: `packaging/fithit.rb.tmpl` in this
repo is the source of truth. On a published release, `.github/workflows/release.yml`
builds one binary per platform, uploads the tarballs as release assets, renders
the template with the three checksums, and opens a PR against the tap.

To package locally for the host platform only:

```bash
make package
```

## Skill Integration

Expand Down
2 changes: 1 addition & 1 deletion fithit.spec
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ exe = EXE(
console=True,
disable_windowed_traceback=False,
argv_emulation=False,
target_arch='arm64',
target_arch=None, # build for the host arch; see Makefile
codesign_identity=None,
entitlements_file=None,
)
32 changes: 32 additions & 0 deletions packaging/fithit.rb.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
class Fithit < Formula
desc "CLI zum Parsen und Durchsuchen von Apple Fitness+ Workouts"
homepage "https://github.com/voydz/fithit"
version "@VERSION@"

on_macos do
# Only Apple Silicon binaries are published.
depends_on arch: :arm64

url "https://github.com/voydz/fithit/releases/download/v@VERSION@/fithit-cli-@VERSION@-macos-arm64.tar.gz"
sha256 "@SHA256_MACOS_ARM64@"
end

on_linux do
on_arm do
url "https://github.com/voydz/fithit/releases/download/v@VERSION@/fithit-cli-@VERSION@-linux-arm64.tar.gz"
sha256 "@SHA256_LINUX_ARM64@"
end
on_intel do
url "https://github.com/voydz/fithit/releases/download/v@VERSION@/fithit-cli-@VERSION@-linux-x86_64.tar.gz"
sha256 "@SHA256_LINUX_X86_64@"
end
end

def install
bin.install "fithit"
end

test do
assert_match "fithit", shell_output("#{bin}/fithit --help")
end
end
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "fithit-cli"
version = "0.1.0"
version = "0.2.0"
description = "CLI zum Parsen und Durchsuchen von Apple Fitness+ Workouts"
readme = "README.md"
requires-python = ">=3.11"
Expand Down
11 changes: 4 additions & 7 deletions src/fithitcli/parse.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,10 +83,7 @@ def _check_link_works(link: str, timeout: int) -> bool:
return False
return True
except urllib.error.HTTPError as exc:
if (
exc.code in RETRYABLE_HTTP_STATUS_CODES
and attempt < LINK_CHECK_RETRIES
):
if exc.code in RETRYABLE_HTTP_STATUS_CODES and attempt < LINK_CHECK_RETRIES:
time.sleep(0.25 * (2**attempt))
continue
return False
Expand Down Expand Up @@ -130,9 +127,9 @@ def _filter_unreachable_link_rows(
timeout: int = LINK_CHECK_TIMEOUT_SECONDS,
checker: Callable[[str, int], bool] = _check_link_works,
) -> tuple[int, int]:
prepared_tables: list[tuple[dict[str, Any], list[tuple[dict[str, Any], str | None]]]] = (
[]
)
prepared_tables: list[
tuple[dict[str, Any], list[tuple[dict[str, Any], str | None]]]
] = []
unique_links: set[str] = set()

for table in content.get("tables", []):
Expand Down
2 changes: 1 addition & 1 deletion uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading