Skip to content

Commit 05283e1

Browse files
committed
add scoop package for windows and repo auth
1 parent 5161c2e commit 05283e1

File tree

9 files changed

+200
-20
lines changed

9 files changed

+200
-20
lines changed

.github/workflows/ci.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,13 @@ jobs:
2121
- name: Install Poetry
2222
run: pip install poetry
2323

24+
# Configure Poetry to use NI Artifacts credentials for the private repository
25+
# These secrets must be set in the GitHub repository settings
26+
- name: Configure Poetry for NI Artifacts
27+
run: |
28+
# Poetry will use these credentials for the 'rnd-pypi-ci' source defined in pyproject.toml
29+
poetry config http-basic.rnd-pypi-ci "${{ secrets.NIARTIFACTS_USER }}" "${{ secrets.NIARTIFACTS_TOKEN }}"
30+
2431
- name: Install dependencies
2532
run: poetry install
2633

.github/workflows/scoop.yml

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
name: Scoop Windows Build
2+
3+
on:
4+
workflow_dispatch:
5+
push:
6+
branches: [ main ]
7+
pull_request:
8+
branches: [ main ]
9+
10+
jobs:
11+
build-scoop:
12+
runs-on: windows-latest
13+
steps:
14+
- name: Checkout code
15+
uses: actions/checkout@v4
16+
17+
- name: Set up Python
18+
uses: actions/setup-python@v5
19+
with:
20+
python-version: '3.11'
21+
22+
- name: Install Poetry
23+
run: pip install poetry
24+
25+
- name: Install dependencies
26+
run: poetry install
27+
28+
- name: Build PyInstaller binary (Windows)
29+
run: poetry run build-pyinstaller
30+
31+
- name: Build Scoop manifest
32+
run: poetry run python scripts/build_scoop.py
33+
34+
- name: Upload Scoop manifest artifact
35+
uses: actions/upload-artifact@v4
36+
with:
37+
name: scoop-slcli-manifest
38+
path: dist/scoop-slcli.json
39+
40+
- name: Upload slcli.exe artifact
41+
uses: actions/upload-artifact@v4
42+
with:
43+
name: slcli-exe
44+
path: dist/slcli.exe

README.md

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,15 +39,46 @@ SystemLink Integrator CLI (slcli) is a robust, cross-platform Python CLI for Sys
3939
poetry run python -m slcli
4040
```
4141

42-
## Build a Standalone Binary
42+
## Build a Standalone Binary (Cross-Platform)
4343

44-
To build a single-file executable using PyInstaller:
44+
### macOS/Linux (Homebrew/PyInstaller)
45+
46+
To build a single-file executable and Homebrew formula:
4547

4648
```bash
47-
poetry run build-pyinstaller
49+
poetry run python scripts/build_homebrew.py
4850
```
4951

50-
The binary will be in the `dist/` directory.
52+
- This will:
53+
- Build the PyInstaller binary in `dist/slcli/`
54+
- Create a tarball `dist/slcli.tar.gz`
55+
- Generate a Homebrew formula `dist/homebrew-slcli.rb` with the correct SHA256
56+
57+
You can then install locally with:
58+
59+
```bash
60+
brew install ./dist/homebrew-slcli.rb
61+
```
62+
63+
### Windows (Scoop/PyInstaller)
64+
65+
To build a Windows executable and Scoop manifest:
66+
67+
```powershell
68+
poetry run python scripts/build_pyinstaller.py
69+
poetry run python scripts/build_scoop.py
70+
```
71+
72+
- This will:
73+
- Build `dist/slcli.exe`
74+
- Generate a Scoop manifest `dist/scoop-slcli.json` with the correct SHA256
75+
76+
You can use the manifest in your own Scoop bucket for easy installation.
77+
78+
### CI/CD Automation
79+
80+
- All builds, tests, and packaging are automated via GitHub Actions for both Homebrew and Scoop.
81+
- Artifacts (`slcli.tar.gz`, `homebrew-slcli.rb`, `slcli.exe`, `scoop-slcli.json`) are uploaded for each build.
5182

5283
## Testing & Linting
5384

poetry.lock

Lines changed: 7 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,6 @@ pytest = ">=7.0"
2929
pytest-cov = ">=3.0"
3030
pyinstaller = "^6.14.2"
3131

32-
[[tool.poetry.source]]
33-
name = "rnd-pypi-ci"
34-
url = "https://niartifacts.jfrog.io/artifactory/api/pypi/rnd-pypi-ci/simple"
35-
priority = "primary"
36-
3732
[tool.pytest.ini_options]
3833
addopts = "--cov slcli --strict-markers --doctest-modules"
3934

scripts/build_homebrew.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,16 @@
44
import sys
55
from pathlib import Path
66

7+
import toml
8+
79
ROOT = Path(__file__).parent.parent.resolve()
810
DIST = ROOT / "dist"
911
SLCLI_DIR = DIST / "slcli"
1012
TARBALL = DIST / "slcli.tar.gz"
1113
FORMULA = ROOT / "homebrew-slcli.rb"
1214
FORMULA_TEMPLATE = ROOT / "scripts" / "homebrew-slcli.rb.j2"
1315
DIST_FORMULA = DIST / "homebrew-slcli.rb"
16+
PYPROJECT = ROOT / "pyproject.toml"
1417

1518

1619
def run(cmd, **kwargs):
@@ -19,6 +22,12 @@ def run(cmd, **kwargs):
1922
subprocess.run(cmd, check=True, **kwargs)
2023

2124

25+
def get_version():
26+
"""Extract the version from pyproject.toml."""
27+
data = toml.load(PYPROJECT)
28+
return data["tool"]["poetry"]["version"]
29+
30+
2231
def build_pyinstaller():
2332
"""Build the PyInstaller binary using Poetry."""
2433
run(["poetry", "run", "python", str(ROOT / "scripts" / "build_pyinstaller.py")])
@@ -43,13 +52,13 @@ def compute_sha256():
4352
return sha256
4453

4554

46-
def render_formula(sha256):
55+
def render_formula(sha256, version):
4756
"""Render the Homebrew formula from the template and write to dist/homebrew-slcli.rb."""
4857
if not FORMULA_TEMPLATE.exists():
4958
print(f"Error: {FORMULA_TEMPLATE} not found.")
5059
sys.exit(1)
5160
template = FORMULA_TEMPLATE.read_text()
52-
rendered = template.replace("{{ sha256 }}", sha256)
61+
rendered = template.replace("{{ sha256 }}", sha256).replace("{{ version }}", version)
5362
DIST_FORMULA.write_text(rendered)
5463
print(f"Wrote Homebrew formula to {DIST_FORMULA}")
5564

@@ -72,10 +81,11 @@ def update_formula(sha256):
7281

7382
def main():
7483
"""Build, package, and update the Homebrew formula for slcli."""
84+
version = get_version()
7585
build_pyinstaller()
7686
create_tarball()
7787
sha256 = compute_sha256()
78-
render_formula(sha256)
88+
render_formula(sha256, version)
7989
update_formula(sha256)
8090
print("Homebrew tarball build complete.")
8191

scripts/build_scoop.py

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
"""Builds a Scoop manifest for the slcli Windows binary and writes it to dist/scoop-slcli.json."""
2+
3+
import subprocess
4+
import sys
5+
from pathlib import Path
6+
7+
import toml
8+
9+
ROOT = Path(__file__).parent.parent.resolve()
10+
DIST = ROOT / "dist"
11+
SLCLI_EXE = DIST / "slcli.exe"
12+
MANIFEST_TEMPLATE = ROOT / "scripts" / "scoop-slcli.json.j2"
13+
DIST_MANIFEST = DIST / "scoop-slcli.json"
14+
PYPROJECT = ROOT / "pyproject.toml"
15+
16+
17+
def run(cmd, **kwargs):
18+
"""Run a shell command and print it."""
19+
print(f"$ {' '.join(cmd) if isinstance(cmd, list) else cmd}")
20+
subprocess.run(cmd, check=True, **kwargs)
21+
22+
23+
def compute_sha256():
24+
"""Compute the SHA256 checksum of the slcli.exe binary."""
25+
result = subprocess.run(
26+
[
27+
"powershell",
28+
"-Command",
29+
f"Get-FileHash -Algorithm SHA256 '{SLCLI_EXE}' | Select-Object -ExpandProperty Hash",
30+
],
31+
capture_output=True,
32+
text=True,
33+
)
34+
sha256 = result.stdout.strip()
35+
print(f"SHA256 for Scoop manifest: {sha256}")
36+
return sha256
37+
38+
39+
def get_version():
40+
"""Extract the version from pyproject.toml."""
41+
data = toml.load(PYPROJECT)
42+
return data["tool"]["poetry"]["version"]
43+
44+
45+
def render_manifest(version, url, sha256):
46+
"""Render the Scoop manifest from the template and write to dist/scoop-slcli.json."""
47+
if not MANIFEST_TEMPLATE.exists():
48+
print(f"Error: {MANIFEST_TEMPLATE} not found.")
49+
sys.exit(1)
50+
template = MANIFEST_TEMPLATE.read_text()
51+
rendered = (
52+
template.replace("{{ version }}", version)
53+
.replace("{{ url }}", url)
54+
.replace("{{ sha256 }}", sha256)
55+
)
56+
DIST_MANIFEST.write_text(rendered)
57+
print(f"Wrote Scoop manifest to {DIST_MANIFEST}")
58+
59+
60+
def main():
61+
"""Build and render the Scoop manifest for slcli."""
62+
version = get_version()
63+
url = f"https://github.com/ni/systemlink-cli/releases/download/v{version}/slcli.exe"
64+
sha256 = compute_sha256()
65+
render_manifest(version, url, sha256)
66+
print("Scoop manifest build complete.")
67+
68+
69+
if __name__ == "__main__":
70+
main()

scripts/homebrew-slcli.rb.j2

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ class HomebrewSlcli < Formula
22
desc "SystemLink Integrator CLI: Manage SystemLink test plan templates and workflows"
33
homepage "https://github.com/ni/systemlink-cli"
44
url "file://#{Dir.pwd}/dist/slcli.tar.gz"
5-
version "1.0.0"
5+
version "{{ version }}"
66
sha256 "{{ sha256 }}"
77
license "MIT"
88

scripts/scoop-slcli.json.j2

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{
2+
"version": "{{ version }}",
3+
"description": "SystemLink Integrator CLI",
4+
"homepage": "https://github.com/ni/systemlink-cli",
5+
"license": "MIT",
6+
"architecture": {
7+
"64bit": {
8+
"url": "{{ url }}",
9+
"hash": "{{ sha256 }}"
10+
}
11+
},
12+
"bin": "slcli.exe",
13+
"checkver": {
14+
"github": "https://github.com/ni/systemlink-cli"
15+
},
16+
"autoupdate": {
17+
"architecture": {
18+
"64bit": {
19+
"url": "https://github.com/ni/systemlink-cli/releases/download/v$version/slcli.exe"
20+
}
21+
}
22+
}
23+
}

0 commit comments

Comments
 (0)