Skip to content

Commit ee3ffc2

Browse files
committed
Create & release standalone binaries
Fixes #701
1 parent 6ab4ec1 commit ee3ffc2

3 files changed

Lines changed: 160 additions & 0 deletions

File tree

.github/workflows/build.yml

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
name: Build
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
pull_request:
8+
types: [opened, synchronize, reopened]
9+
10+
permissions:
11+
contents: read
12+
13+
jobs:
14+
15+
build:
16+
strategy:
17+
matrix:
18+
platform: [ubuntu-latest] #, macos-latest, windows-latest]
19+
runs-on: ${{ matrix.platform }}
20+
permissions:
21+
contents: read
22+
security-events: write
23+
24+
steps:
25+
- name: Harden the runner (Audit all outbound calls)
26+
uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1
27+
with:
28+
egress-policy: audit
29+
30+
- uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493 # v5.0.0
31+
32+
- name: Setup Python
33+
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
34+
with:
35+
python-version: '3.14'
36+
37+
- name: Install patchelf (patchelf)
38+
if: matrix.platform == 'ubuntu-latest'
39+
run: |
40+
sudo apt-get update
41+
sudo apt-get install -y patchelf=0.12-1 ccache=4.2-1
42+
43+
# - name: Install Subversion (SVN)
44+
# if: matrix.platform == 'macos-latest'
45+
# run: |
46+
# brew install svn
47+
# svn --version # Verify installation
48+
49+
# - name: Install Subversion (SVN)
50+
# if: matrix.platform == 'windows-latest'
51+
# run: |
52+
# choco install svn -y
53+
# $env:PATH = "C:\Program Files (x86)\Subversion\bin;$env:PATH"
54+
# echo "C:\Program Files (x86)\Subversion\bin" >> $env:GITHUB_PATH
55+
# svn --version # Verify installation
56+
57+
- name: Create binary
58+
run: |
59+
pip install .[build]
60+
python script/build.py
61+
62+
- name: Store the distribution packages
63+
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
64+
with:
65+
name: binary-distribution
66+
path: build/dfetch
67+
68+
- run: build/dfetch environment
69+
- run: build/dfetch validate
70+
- run: build/dfetch check
71+
- run: build/dfetch update
72+
- run: build/dfetch update
73+
- run: build/dfetch report -t sbom
74+
75+
- name: Run example
76+
working-directory: ./example
77+
env:
78+
CI: 'false'
79+
run: |
80+
build/dfetch update
81+
build/dfetch update
82+
build/dfetch report

pyproject.toml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,3 +170,20 @@ standard = ["dfetch", "features"]
170170
reportMissingImports = false
171171
reportMissingModuleSource = false
172172
pythonVersion = "3.9"
173+
174+
[tool.nuitka]
175+
mode = "standalone" # Switch this between standalone and onefile as needed
176+
jobs = "4"
177+
assume-yes-for-downloads = true
178+
179+
# Include data files (local path = target path in standalone)
180+
include-data-dir = [{ source = "dfetch/resources", destination = "resources" }]
181+
182+
output-dir = "build"
183+
output-filename-win = "dfetch.exe"
184+
output-filename-linux = "dfetch"
185+
output-filename-macos = "dfetch"
186+
187+
# windows-icon-from-ico = "static/favicon.ico"
188+
# windows-company-name = "dfetch-org"
189+
show-progress = true

script/build.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
#!/usr/bin/env python3
2+
"""This script builds the dfetch executable using Nuitka."""
3+
import os
4+
import subprocess
5+
import sys
6+
import tomllib as toml
7+
from typing import Union
8+
9+
10+
def parse_option(
11+
option_name: str, option_value: Union[bool, str, list, dict]
12+
) -> list[str]:
13+
"""
14+
Convert a config value to CLI args for Nuitka.
15+
Handles booleans, strings, lists, and nested dicts.
16+
"""
17+
args = []
18+
cli_key = f"--{option_name.replace('_','-')}"
19+
20+
if isinstance(option_value, bool):
21+
if option_value:
22+
args.append(cli_key)
23+
elif isinstance(option_value, str):
24+
args.append(f"{cli_key}={option_value}")
25+
elif isinstance(option_value, list):
26+
for v in option_value:
27+
if isinstance(v, dict):
28+
parts = [f"{v[k]}" for k in v]
29+
args.append(f"{cli_key}={'='.join(parts)}")
30+
else:
31+
args.append(f"{cli_key}={v}")
32+
else:
33+
args.append(f"{cli_key}={option_value}")
34+
35+
return args
36+
37+
38+
# Load pyproject.toml
39+
with open("pyproject.toml", "r", encoding="UTF-8") as pyproject_file:
40+
pyproject = toml.loads(pyproject_file.read())
41+
nuitka_opts = pyproject.get("tool", {}).get("nuitka", {})
42+
43+
44+
if sys.platform.startswith("win"):
45+
nuitka_opts["output-filename"] = nuitka_opts["output-filename-win"]
46+
elif sys.platform.startswith("linux"):
47+
nuitka_opts["output-filename"] = nuitka_opts["output-filename-linux"]
48+
elif sys.platform.startswith("darwin"):
49+
nuitka_opts["output-filename"] = nuitka_opts["output-filename-macos"]
50+
51+
for key in ["output-filename-win", "output-filename-linux", "output-filename-macos"]:
52+
nuitka_opts.pop(key, None)
53+
54+
command = [sys.executable, "-m", "nuitka"]
55+
for key, value in nuitka_opts.items():
56+
command.extend(parse_option(key, value))
57+
58+
command.append(os.path.join("dfetch", "__main__.py"))
59+
60+
print(command)
61+
subprocess.check_call(command)

0 commit comments

Comments
 (0)