Last active
November 7, 2022 00:37
-
-
Save AphonicChaos/b27b1326fada1506f8f457286e8df5db to your computer and use it in GitHub Desktop.
Build and release binaries for a Haskell app on Windows, MacOS X, and Linux (x86_64, sorry Apple Silicon)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
name: CI | |
on: | |
pull_request: | |
push: | |
branches: | |
- main | |
workflow_call: | |
outputs: | |
version: | |
value: ${{ jobs.build_prod.outputs.version }} | |
jobs: | |
cabal_test: | |
strategy: | |
matrix: | |
ghc: | |
- 9.2.4 | |
cabal: | |
- 3.6.2.0 | |
name: 'cabal_test: ghc-${{ matrix.ghc }}' | |
runs-on: ubuntu-latest | |
steps: | |
- uses: actions/checkout@v2 | |
- uses: haskell/actions/setup@v2 | |
with: | |
ghc-version: ${{ matrix.ghc }} | |
cabal-version: ${{ matrix.cabal }} | |
- uses: actions/cache@v3 | |
with: | |
path: ~/.cabal | |
key: ${{ runner.os }}-cabal-cache-${{ matrix.ghc }}-${{ matrix.cabal }}-${{ hashFiles('log-parser.cabal') }} | |
- run: cabal update | |
- run: cabal test | |
os_test: | |
strategy: | |
matrix: | |
os: | |
- windows-latest | |
- ubuntu-latest | |
- macos-latest | |
name: 'os_test: ${{ matrix.os }}' | |
runs-on: ${{ matrix.os }} | |
steps: | |
- uses: actions/checkout@v2 | |
- uses: actions/cache@v3 | |
with: | |
path: ~/.cabal | |
key: ${{ runner.os }}-os_test-${{ hashFiles('log-parser.cabal') }} | |
- run: cabal test --disable-optimization | |
build_prod: | |
defaults: | |
run: | |
shell: bash | |
strategy: | |
matrix: | |
os: | |
- windows-latest | |
- ubuntu-latest | |
- macos-latest | |
name: 'build_prod: ${{ matrix.os }}' | |
runs-on: ${{ matrix.os }} | |
steps: | |
- uses: actions/checkout@v2 | |
- uses: actions/cache@v3 | |
with: | |
path: ~/.cabal | |
key: ${{ runner.os }}-build_prod-${{ hashFiles('log-parser.cabal') }} | |
- name: Build | |
run: cabal install --install-method=copy --overwrite-policy=always --installdir=./bin/ | |
- name: Get build info | |
run: scripts/GetBuildInfo.hs >> "${GITHUB_OUTPUT}" | |
id: build-info | |
- name: Rename binary | |
run: | | |
cp bin/log-parser$ext bin/log-parser-$version-$os-$arch$ext | |
env: | |
version: ${{ steps.build-info.outputs.version }} | |
os: ${{ steps.build-info.outputs.os }} | |
arch: ${{ steps.build-info.outputs.arch }} | |
ext: ${{ steps.build-info.outputs.ext }} | |
- name: Store binary | |
uses: actions/upload-artifact@v3 | |
with: | |
name: log-parser-binary-${{ matrix.os }} | |
path: bin/log-parser-* | |
outputs: | |
version: ${{ steps.build-info.outputs.version }} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env -S runhaskell | |
{-# LANGUAGE CPP #-} | |
import Data.Char (toLower) | |
import Data.List (intercalate) | |
import Distribution.Package (packageVersion) | |
#if MIN_VERSION_Cabal(3, 8, 1) | |
import Distribution.Simple.PackageDescription (readGenericPackageDescription) | |
#else | |
import Distribution.PackageDescription.Parsec (readGenericPackageDescription) | |
#endif | |
import Distribution.System (buildArch, buildOS, buildPlatform) | |
import Distribution.Simple.BuildPaths (exeExtension) | |
import qualified Distribution.Verbosity as Verbosity | |
import Distribution.Version (versionNumbers) | |
main :: IO () | |
main = do | |
packageDesc <- readGenericPackageDescription Verbosity.silent "log-parser.cabal" | |
let version = intercalate "." . map show . versionNumbers . packageVersion $ packageDesc | |
let os = map toLower . show $ buildOS | |
let arch = map toLower . show $ buildArch | |
let ext = exeExtension buildPlatform | |
setOutput "version" version | |
setOutput "os" os | |
setOutput "arch" arch | |
setOutput "ext" $ if null ext then ext else "." <> ext | |
-- | Set output for a GitHub action. | |
-- https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#setting-an-output-parameter | |
setOutput :: String -> String -> IO () | |
setOutput name value = putStrLn $ name <> "=" <> value |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env bash | |
set -euxo pipefail | |
HERE="$(builtin cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" | |
if [[ ! -d "${HERE}/.venv" ]]; then | |
python3 -m venv "${HERE}/.venv" | |
"${HERE}/.venv/bin/pip" install requests | |
fi | |
exec "${HERE}/.venv/bin/python3" "${HERE}/make_release.py" "$@" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# pyright: strict, reportUnknownMemberType=false | |
from __future__ import annotations | |
import itertools | |
import json | |
import logging | |
import os | |
import requests | |
from pathlib import Path | |
from typing import Any | |
logger = logging.getLogger(__name__) | |
logging.basicConfig(level=logging.DEBUG) | |
def main(): | |
gh_token = os.environ["gh_token"] | |
version = os.environ["version"] | |
bindir = os.environ["bindir"] | |
sdistdir = os.environ["sdistdir"] | |
repo = os.environ["GITHUB_REPOSITORY"] | |
sha = os.environ["GITHUB_SHA"] | |
version_name = f"v{version}" | |
# check inputs | |
# ensure release files exist | |
gh_release_files = [ | |
Path(bindir) / f"log-parser-{version}-linux-x86_64", | |
Path(bindir) / f"log-parser-{version}-osx-x86_64", | |
Path(bindir) / f"log-parser-{version}-windows-x86_64" | |
] | |
sdist_archive = Path(sdistdir) / f"log-parser-{version}.tar.gz" | |
all_files = gh_release_files + [sdist_archive] | |
for file in all_files: | |
if not file.exists(): | |
raise Exception(f"File does not exist: {file}") | |
file_paths = [file.as_posix() for file in all_files] | |
logger.info(f"Creating release {version_name} with files: {file_paths}") | |
# check + parse CHANGELOG | |
changelog = Path("CHANGELOG.md").read_text() | |
if not changelog.startswith(f"## log-parser {version}"): | |
raise Exception("CHANGELOG doesn't look updated") | |
version_changes = get_version_changes(changelog) | |
create_github_release( | |
repo=repo, | |
token=gh_token, | |
sha=sha, | |
version_name=version_name, | |
version_changes=version_changes, | |
files=gh_release_files, | |
) | |
logger.info(f"Released log-parser {version_name}!") | |
def get_version_changes(changelog: str) -> str: | |
lines = changelog.split("\n") | |
# skip initial '## log-parser X.Y.Z' line | |
lines = lines[1:] | |
# take lines until the next '## log-parser X.Y.Z' line | |
lines = itertools.takewhile(lambda line: not line.startswith("## log-parser "), lines) | |
return "\n".join(lines) | |
def create_github_release( | |
*, | |
repo: str, | |
token: str, | |
sha: str, | |
version_name: str, | |
version_changes: str, | |
files: list[Path], | |
): | |
session = init_session() | |
session.headers["Accept"] = "application/vnd.github.v3+json" | |
session.headers["Authorization"] = f"token {token}" | |
session.headers["User-Agent"] = repo | |
payload = { | |
"tag_name": version_name, | |
"target_commitish": sha, | |
"name": version_name, | |
"body": version_changes, | |
} | |
logger.debug(f"Creating release with: {json.dumps(payload)}") | |
create_resp = session.post( | |
f"https://api.github.com/repos/{repo}/releases", | |
json=payload, | |
) | |
# upload_url is in the format: "https://...{?name,label}" | |
upload_url = create_resp.json()["upload_url"] | |
upload_url = upload_url.replace("{?name,label}", "") | |
for file in files: | |
logger.debug(f"Uploading asset: {file}") | |
with file.open("rb") as f: | |
session.post( | |
upload_url, | |
headers={"Content-Type": "application/octet-stream"}, | |
params={"name": file.name}, | |
data=f, | |
) | |
def init_session() -> requests.Session: | |
session = requests.Session() | |
def _check_status(r: requests.Response, *args: Any, **kwargs: Any): | |
r.raise_for_status() | |
# https://github.com/python/typeshed/issues/7776 | |
session.hooks["response"].append( # pyright: ignore[reportFunctionMemberAccess] | |
_check_status, | |
) | |
return session | |
if __name__ == "__main__": | |
main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
name: Release | |
on: | |
workflow_run: | |
workflows: [CI] | |
types: | |
- completed | |
jobs: | |
ci: | |
if: ${{ github.event.workflow_run.conclusion == 'success' }} | |
uses: ./.github/workflows/ci.yml | |
release: | |
runs-on: ubuntu-latest | |
needs: | |
- ci | |
steps: | |
- uses: actions/checkout@v2 | |
with: | |
ref: main | |
#- uses: actions/download-artifact@v3 | |
# with: | |
# name: log-parser-binary-ubuntu-latest | |
# path: ./bin/ | |
#- uses: actions/download-artifact@v3 | |
# with: | |
# name: log-parser-binary-macos-latest | |
# path: ./bin/ | |
- uses: actions/download-artifact@v3 | |
with: | |
name: log-parser-binary-windows-latest | |
path: ./bin/ | |
- name: Make release | |
run: scripts/make-release.sh | |
env: | |
gh_token: ${{ secrets.GITHUB_TOKEN }} | |
version: ${{ needs.ci.outputs.version }} | |
bindir: ./bin/ | |
sdistdir: ./sdist/ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment