Skip to content

Windows build

Windows build #107

Workflow file for this run

name: Windows build
on:
workflow_dispatch:
permissions:
contents: read
env:
PYTHON_VERSION: '3.14'
jobs:
build:
permissions:
contents: write
strategy:
matrix:
include:
- arch: x64
runner: windows-latest
python_arch: x64
artifact_suffix: x64
unsigned_exe_name: unsigned-exes-x64
unsigned_msi_name: unsigned-msi-x64
unsigned_dll_name: unsigned-dll-x64
dll_file: YASBTrayHook.dll
- arch: arm64
runner: windows-11-arm
python_arch: arm64
artifact_suffix: aarch64
unsigned_exe_name: unsigned-exes-arm64
unsigned_msi_name: unsigned-msi-arm64
unsigned_dll_name: unsigned-dll-arm64
dll_file: YASBTrayHook_arm64.dll
runs-on: ${{ matrix.runner }}
name: Build (${{ matrix.arch }})
steps:
- name: Checkout Repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: ${{ env.PYTHON_VERSION }}
architecture: ${{ matrix.python_arch }}
- name: Create virtual environment
run: |
python -m venv venv
shell: pwsh
- name: Upload unsigned DLL artifact
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.unsigned_dll_name }}
path: src/core/widgets/services/systray/hook/${{ matrix.dll_file }}
- name: Get unsigned-dll artifact id
id: get_dll_artifact
uses: actions/github-script@v7
env:
ARTIFACT_NAME: ${{ matrix.unsigned_dll_name }}
with:
github-token: ${{ secrets.PAT }}
script: |
const { owner, repo } = context.repo;
const run_id = context.runId;
const expectedName = process.env.ARTIFACT_NAME;
const res = await github.rest.actions.listWorkflowRunArtifacts({ owner, repo, run_id });
const artifact = res.data.artifacts.find(a => a.name === expectedName);
if (!artifact) throw new Error(`${expectedName} artifact not found`);
return artifact.id;
- name: Submit DLL signing request
id: sign_dll
uses: signpath/github-action-submit-signing-request@v1
with:
api-token: '${{ secrets.SIGN_TOKEN }}'
organization-id: '9efb6764-d1fc-46c5-b050-5ef07bb67a8c'
project-slug: 'yasb'
signing-policy-slug: '${{ secrets.SIGN_POLICY_SLUG }}'
artifact-configuration-slug: 'signing_dll'
github-artifact-id: '${{ steps.get_dll_artifact.outputs.result }}'
wait-for-completion: 'true'
output-artifact-directory: 'src/signed'
- name: Replace unsigned DLL with signed
if: steps.sign_dll.outcome == 'success'
run: |
$signedDir = 'src/signed'
if (-not (Test-Path $signedDir)) {
Write-Host "Signed artifacts directory $signedDir not found. Ensure SignPath places signed files there.";
exit 1
}
# Copy signed DLL back to source location fail if none found
$signedDlls = Get-ChildItem -Path $signedDir -File -Filter '*.dll' -Recurse -ErrorAction SilentlyContinue
if ($null -eq $signedDlls -or $signedDlls.Count -eq 0) {
Write-Error "No signed .dll files found in $signedDir after extraction. Failing the job."
exit 1
}
Write-Host "Copying signed DLL from $signedDir to src/core/widgets/services/systray/hook"
foreach ($f in $signedDlls) {
Copy-Item -Path $f.FullName -Destination (Join-Path 'src/core/widgets/services/systray/hook' $f.Name) -Force
}
# Clean up signed directory after successful copy
try {
Get-ChildItem -Path $signedDir -Recurse -Force | Remove-Item -Force -Recurse
Write-Host "Cleaned up $signedDir"
} catch {
Write-Warning "Failed to clean up ${signedDir}: $($_.Exception.Message)"
}
shell: pwsh
- name: Activate virtual environment and install dependencies
run: |
.\venv\Scripts\Activate
python -m pip install --upgrade pip
pip install --force --no-cache .[packaging]
shell: pwsh
- name: Build EXE
run: |
.\venv\Scripts\Activate
cd src
python build.py build
shell: pwsh
- name: Upload unsigned EXE artifacts
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.unsigned_exe_name }}
path: |
src/dist/yasb.exe
src/dist/yasbc.exe
src/dist/yasb_themes.exe
- name: Get unsigned-exes artifact id
id: get_exes_artifact
uses: actions/github-script@v7
env:
ARTIFACT_NAME: ${{ matrix.unsigned_exe_name }}
with:
github-token: ${{ secrets.PAT }}
script: |
const { owner, repo } = context.repo;
const run_id = context.runId;
const expectedName = process.env.ARTIFACT_NAME;
const res = await github.rest.actions.listWorkflowRunArtifacts({ owner, repo, run_id });
const artifact = res.data.artifacts.find(a => a.name === expectedName);
if (!artifact) throw new Error(`${expectedName} artifact not found`);
return artifact.id;
- name: Submit EXE signing request
id: sign_exes
uses: signpath/github-action-submit-signing-request@v1
with:
api-token: '${{ secrets.SIGN_TOKEN }}'
organization-id: '9efb6764-d1fc-46c5-b050-5ef07bb67a8c'
project-slug: 'yasb'
signing-policy-slug: '${{ secrets.SIGN_POLICY_SLUG }}'
artifact-configuration-slug: 'signing_executable'
github-artifact-id: '${{ steps.get_exes_artifact.outputs.result }}'
wait-for-completion: 'true'
output-artifact-directory: 'src/signed'
- name: Replace unsigned EXE with signed
if: steps.sign_exes.outcome == 'success'
run: |
.\venv\Scripts\Activate
$signedDir = 'src/signed'
if (-not (Test-Path $signedDir)) {
Write-Host "Signed artifacts directory $signedDir not found. Ensure SignPath places signed files there.";
exit 1
}
# Copy signed EXEs into the distribution folder; fail if none found
$signedExes = Get-ChildItem -Path $signedDir -File -Filter '*.exe' -Recurse -ErrorAction SilentlyContinue
if ($null -eq $signedExes -or $signedExes.Count -eq 0) {
Write-Error "No signed .exe files found in $signedDir after extraction. Failing the job."
exit 1
}
Write-Host "Copying signed EXEs from $signedDir to src/dist"
foreach ($f in $signedExes) {
Copy-Item -Path $f.FullName -Destination (Join-Path 'src/dist' $f.Name) -Force
}
# Clean up signed directory after successful copy
try {
Get-ChildItem -Path $signedDir -Recurse -Force | Remove-Item -Force -Recurse
Write-Host "Cleaned up $signedDir"
} catch {
Write-Warning "Failed to clean up ${signedDir}: $($_.Exception.Message)"
}
shell: pwsh
- name: Build MSI
run: |
.\venv\Scripts\Activate
cd src
python build.py bdist_msi
shell: pwsh
- name: Upload unsigned MSI
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.unsigned_msi_name }}
path: src/dist/out/*.msi
- name: Get unsigned-msi artifact id
id: get_msi_artifact
uses: actions/github-script@v7
env:
ARTIFACT_NAME: ${{ matrix.unsigned_msi_name }}
with:
github-token: ${{ secrets.PAT }}
script: |
const { owner, repo } = context.repo;
const run_id = context.runId;
const expectedName = process.env.ARTIFACT_NAME;
const res = await github.rest.actions.listWorkflowRunArtifacts({ owner, repo, run_id });
const artifact = res.data.artifacts.find(a => a.name === expectedName);
if (!artifact) throw new Error(`${expectedName} artifact not found`);
return artifact.id;
- name: Submit MSI signing request
id: sign_msi
uses: signpath/github-action-submit-signing-request@v1
with:
api-token: '${{ secrets.SIGN_TOKEN }}'
organization-id: '9efb6764-d1fc-46c5-b050-5ef07bb67a8c'
project-slug: 'yasb'
signing-policy-slug: '${{ secrets.SIGN_POLICY_SLUG }}'
artifact-configuration-slug: 'signing_installer'
github-artifact-id: '${{ steps.get_msi_artifact.outputs.result }}'
wait-for-completion: 'true'
output-artifact-directory: 'src/signed'
- name: Replace unsigned MSI with signed
if: steps.sign_msi.outcome == 'success'
run: |
.\venv\Scripts\Activate
$signedDir = 'src/signed'
if (-not (Test-Path $signedDir)) {
Write-Host "Signed artifacts directory $signedDir not found. Ensure SignPath places signed files there.";
exit 1
}
# Copy signed MSIs into the output folder; fail if none found
$signedMsis = Get-ChildItem -Path $signedDir -File -Filter '*.msi' -Recurse -ErrorAction SilentlyContinue
if ($null -eq $signedMsis -or $signedMsis.Count -eq 0) {
Write-Error "No signed .msi files found in $signedDir after extraction. Failing the job."
exit 1
}
Write-Host "Copying signed MSIs from $signedDir to src/dist/out"
foreach ($f in $signedMsis) {
Copy-Item -Path $f.FullName -Destination (Join-Path 'src/dist/out' $f.Name) -Force
}
# Clean up signed directory after successful copy
try {
Get-ChildItem -Path $signedDir -Recurse -Force | Remove-Item -Force -Recurse
Write-Host "Cleaned up $signedDir"
} catch {
Write-Warning "Failed to clean up ${signedDir}: $($_.Exception.Message)"
}
shell: pwsh
- name: Upload Built MSI
uses: actions/upload-artifact@v4
with:
name: built-msi-${{ matrix.artifact_suffix }}
path: src/dist/out/*.msi
- name: Delete Artifacts
if: github.event_name == 'workflow_dispatch'
uses: geekyeggo/delete-artifact@v5
with:
name: |
unsigned-*
signed-*
release:
needs: build
runs-on: windows-latest
permissions:
contents: write
models: read
steps:
- name: Checkout Repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: Create virtual environment
run: |
python -m venv venv
shell: pwsh
- name: Download all MSI artifacts
uses: actions/download-artifact@v4
with:
pattern: built-msi-*
path: dist-msis
merge-multiple: true
- name: Get App Info
id: get_version
run: |
.\venv\Scripts\Activate
$version = (Get-Content src/settings.py | Select-String -Pattern 'BUILD_VERSION\s*=\s*"([^"]+)"').Matches.Groups[1].Value
echo "VERSION=$version" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
echo "VERSION=$version" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append
shell: pwsh
- name: Create Tag
id: create_tag
uses: actions/github-script@v7
with:
github-token: ${{ secrets.PAT }}
script: |
const version = `v${process.env.VERSION}`;
const { owner, repo } = context.repo;
const sha = context.sha;
try {
const { data: tags } = await github.rest.repos.listTags({
owner,
repo,
});
const tagExists = tags.some(tag => tag.name === version);
if (!tagExists) {
console.log(`Creating tag ${version}`);
await github.rest.git.createRef({
owner,
repo,
ref: `refs/tags/${version}`,
sha,
});
} else {
console.log(`Tag ${version} already exists, skipping tag creation`);
}
} catch (error) {
console.error(`Error fetching tags: ${error.message}`);
throw error;
}
- name: Install dependencies for schema generation
run: |
.\venv\Scripts\Activate
pip install --force --no-cache .
shell: pwsh
- name: Generate schema.json
run: |
.\venv\Scripts\Activate
python src/core/validation/export_schema.py
shell: pwsh
- name: Commit schema.json
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add schema.json
git diff --cached --quiet || git commit -m "chore(schema): update schema.json for v${{ steps.get_version.outputs.VERSION }}"
git push
shell: pwsh
- name: Fetch all tags
run: git fetch --tags
shell: pwsh
- name: Create Changelog
id: changelog
uses: loopwerk/tag-changelog@v1.3.0
with:
token: ${{ secrets.PAT }}
config_file: .github/changelog/changelog.js
- name: Compute Commit Range
id: commit_range
shell: pwsh
run: |
$currentTag = "v${{ steps.get_version.outputs.VERSION }}"
$prevTag = (git tag --sort=-version:refname | Where-Object { $_ -ne $currentTag } | Select-Object -First 1)
if ([string]::IsNullOrWhiteSpace($prevTag)) {
$prevTag = (git describe --tags --abbrev=0 2>$null)
}
if ([string]::IsNullOrWhiteSpace($prevTag)) {
$range = "HEAD"
$commits = git log -n 100 --pretty=format:"- %s (%h)%n%b%n----"
} else {
$range = "$prevTag..$currentTag"
$commits = git log $range -n 100 --pretty=format:"- %s (%h)%n%b%n----"
}
"range=$range" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append
"commits<<EOF" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append
$commits | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append
"EOF" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append
- name: Summarize Changelog
id: ai_summary
uses: actions/github-script@v8
env:
AI_SYSTEM_PROMPT: ${{ secrets.AI_SYSTEM_PROMPT }}
AI_USER_PROMPT_TEMPLATE: ${{ secrets.AI_USER_PROMPT }}
AI_MODEL: ${{ secrets.AI_MODEL }}
AI_MAX_TOKENS: ${{ secrets.AI_MAX_TOKENS }}
COMMIT_RANGE: ${{ steps.commit_range.outputs.range }}
COMMITS: ${{ steps.commit_range.outputs.commits }}
CHANGELOG: ${{ steps.changelog.outputs.changes }}
with:
github-token: ${{ secrets.AI_TOKEN }}
script: |
const systemPrompt = process.env.AI_SYSTEM_PROMPT;
const promptTemplate = process.env.AI_USER_PROMPT_TEMPLATE;
const model = process.env.AI_MODEL;
const maxTokens = parseInt(process.env.AI_MAX_TOKENS, 300);
if (!systemPrompt || !promptTemplate) {
console.log('Prompts not configured. Skipping summary.');
core.setOutput('summary', '');
return;
}
const userPrompt = promptTemplate
.replace('{commit_range}', process.env.COMMIT_RANGE || '')
.replace('{commits}', process.env.COMMITS || '')
.replace('{changelog}', process.env.CHANGELOG || '');
try {
const response = await fetch('https://models.github.ai/inference/chat/completions', {
method: 'POST',
headers: {
'Authorization': `Bearer ${{ secrets.AI_TOKEN }}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
model: model,
messages: [
{ role: 'system', content: systemPrompt },
{ role: 'user', content: userPrompt }
],
max_tokens: maxTokens
})
});
if (!response.ok) {
console.log(`Request failed: ${response.status}`);
core.setOutput('summary', '');
return;
}
const data = await response.json();
const summary = data.choices?.[0]?.message?.content?.trim() || '';
core.setOutput('summary', summary);
} catch (error) {
console.log(`Error: ${error.message}`);
core.setOutput('summary', '');
}
- name: Build Release Body
id: release_body
uses: actions/github-script@v8
env:
AI_SUMMARY: ${{ steps.ai_summary.outputs.summary }}
CHANGELOG: ${{ steps.changelog.outputs.changes }}
with:
github-token: ${{ secrets.PAT }}
script: |
const summary = (process.env.AI_SUMMARY || '').trim();
const changelog = (process.env.CHANGELOG || '').trim();
let body = '';
if (summary && summary.length > 50) {
body = `${summary}\n\n\n\n${changelog}`;
} else {
body = changelog;
}
core.setOutput('body', body);
- name: Generate Checksum
run: |
.\venv\Scripts\Activate
$checksums = Get-FileHash dist-msis/*.msi -Algorithm SHA256
$output = @()
foreach ($checksum in $checksums) {
$filename = [System.IO.Path]::GetFileName($checksum.Path)
$output += "$($checksum.Hash) $filename"
}
$output -join "`n" > dist-msis/checksums.txt
shell: pwsh
- name: Create and Upload Release
id: create_release
uses: softprops/action-gh-release@v2
with:
tag_name: v${{ steps.get_version.outputs.VERSION }}
name: v${{ steps.get_version.outputs.VERSION }}
body: |
${{ steps.release_body.outputs.body }}
append_body: true
files: |
dist-msis/*.msi
dist-msis/checksums.txt
prerelease: false
generate_release_notes: true
draft: true
env:
GITHUB_TOKEN: ${{ secrets.PAT }}