name: Build and Release
on:
push:
branches: [main]
workflow_dispatch:
permissions:
contents: write
env:
CARGO_TERM_COLOR: always
BINARY_NAME: compactrs
jobs:
build:
name: Build and Release
runs-on: windows-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Rust toolchain
uses: dtolnay/rust-toolchain@nightly
with:
components: rust-src
- name: Cache Rust dependencies
uses: Swatinem/rust-cache@v2
with:
cache-on-failure: true
- name: Check latest UPX version
id: upx-version
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
$fallbackVersion = "v5.0.2"
try {
$headers = @{ "Authorization" = "token $env:GITHUB_TOKEN" }
$response = Invoke-RestMethod -Uri "https://api.github.com/repos/upx/upx/releases/latest" -Headers $headers -ErrorAction Stop
$latest = $response.tag_name
if ([string]::IsNullOrWhiteSpace($latest)) { throw "Empty tag name returned" }
} catch {
Write-Warning "GitHub API Request Failed: $_"
Write-Warning "Rate limit might be exceeded or API is down."
Write-Warning "Using fallback version: $fallbackVersion"
$latest = $fallbackVersion
}
$version = $latest -replace '^v', ''
echo "UPX_VERSION=$version" >> $env:GITHUB_ENV
Write-Host "Selected UPX version: $version"
shell: pwsh
- name: Cache UPX
id: cache-upx
uses: actions/cache@v4
with:
path: upx.exe
key: upx-${{ env.UPX_VERSION }}-windows
- name: Download UPX
if: steps.cache-upx.outputs.cache-hit != 'true'
run: |
$upxUrl = "https://github.com/upx/upx/releases/download/v${{ env.UPX_VERSION }}/upx-${{ env.UPX_VERSION }}-win64.zip"
Invoke-WebRequest -Uri $upxUrl -OutFile upx.zip
Expand-Archive -Path upx.zip -DestinationPath .
Move-Item "upx-${{ env.UPX_VERSION }}-win64/upx.exe" .
shell: pwsh
- name: Calculate Version
id: version
run: |
$version = Get-Date -Format "vyyyy.MM.dd-HHmm"
echo "VERSION=$version" >> $env:GITHUB_OUTPUT
Write-Host "Determined Version: $version"
shell: pwsh
- name: Build release binary
run: |
cargo +nightly build --release --target x86_64-pc-windows-msvc
Copy-Item "target/x86_64-pc-windows-msvc/release/${{ env.BINARY_NAME }}.exe" "target/release/${{ env.BINARY_NAME }}.exe"
Copy-Item "target/release/${{ env.BINARY_NAME }}.exe" "target/release/${{ env.BINARY_NAME }}-normal.exe"
shell: pwsh
env:
APP_VERSION: ${{ steps.version.outputs.VERSION }}
- name: Compress binary with UPX
run: |
$originalSize = (Get-Item "target/release/${{ env.BINARY_NAME }}.exe").Length
./upx.exe --ultra-brute "target/release/${{ env.BINARY_NAME }}.exe"
$compressedSize = (Get-Item "target/release/${{ env.BINARY_NAME }}.exe").Length
$ratio = [math]::Round(($compressedSize / $originalSize) * 100, 2)
Write-Host "Original: $([math]::Round($originalSize / 1KB, 2)) KB"
Write-Host "Compressed: $([math]::Round($compressedSize / 1KB, 2)) KB"
Write-Host "Compression ratio: $ratio%"
shell: pwsh
- name: VirusTotal Upload
id: virustotal
run: |
$files = @(
@{ Type="NORMAL"; Path="target/release/${{ env.BINARY_NAME }}-normal.exe"; Description="Normal (Uncompressed)" },
@{ Type="UPX"; Path="target/release/${{ env.BINARY_NAME }}.exe"; Description="UPX Compressed" }
)
$vtApiKey = "${{ secrets.VIRUSTOTAL_API_KEY }}"
foreach ($file in $files) {
$filePath = $file.Path
$type = $file.Type
# SHA256 hash
$fileHash = (Get-FileHash $filePath -Algorithm SHA256).Hash.ToLower()
$vtPermalink = "https://www.virustotal.com/gui/file/$fileHash"
Write-Host "[$($file.Description)] SHA256: $fileHash"
Write-Host "[$($file.Description)] VirusTotal: $vtPermalink"
echo "VT_LINK_${type}=$vtPermalink" >> $env:GITHUB_OUTPUT
echo "VT_HASH_${type}=$fileHash" >> $env:GITHUB_OUTPUT
if ([string]::IsNullOrEmpty($vtApiKey)) {
Write-Host "::notice::VIRUSTOTAL_API_KEY not set. Link is still valid for $type once file is scanned."
continue
}
Write-Host "Uploading $($file.Description) to VirusTotal..."
$result = curl.exe --silent --show-error `
-X POST "https://www.virustotal.com/api/v3/files" `
-H "x-apikey: $vtApiKey" `
-F "file=@$filePath"
Write-Host "Response: $result"
}
shell: pwsh
- name: Generate Changelog
id: info
run: |
$version = "${{ steps.version.outputs.VERSION }}"
echo "VERSION=$version" >> $env:GITHUB_OUTPUT
$shortSha = "${{ github.sha }}".Substring(0, 7)
$commitMsg = git log -1 --pretty=format:"%s"
# Get commit body and preserve newlines
$commitBodyRaw = git log -1 --pretty=format:"%b"
$author = git log -1 --pretty=format:"%an"
# Convert commit body lines to proper markdown list
$commitBodyLines = $commitBodyRaw -split "`n" | Where-Object { $_.Trim() -ne "" }
$commitBody = ""
foreach ($line in $commitBodyLines) {
$trimmedLine = $line.Trim()
if ($trimmedLine -match "^[-*]") {
# Already a list item, ensure proper markdown format
$trimmedLine = $trimmedLine -replace "^[-*]\s*", ""
$commitBody += "- $trimmedLine`n"
} else {
$commitBody += "$trimmedLine`n"
}
}
$hash_upx = "${{ steps.virustotal.outputs.VT_HASH_UPX }}"
$hash_normal = "${{ steps.virustotal.outputs.VT_HASH_NORMAL }}"
$size_upx = [math]::Round((Get-Item "target/release/${{ env.BINARY_NAME }}.exe").Length / 1KB, 2)
$size_normal = [math]::Round((Get-Item "target/release/${{ env.BINARY_NAME }}-normal.exe").Length / 1KB, 2)
$vtLink_upx = "${{ steps.virustotal.outputs.VT_LINK_UPX }}"
$vtLink_normal = "${{ steps.virustotal.outputs.VT_LINK_NORMAL }}"
$sb = [System.Text.StringBuilder]::new()
[void]$sb.AppendLine("## Security Verification")
[void]$sb.AppendLine("")
[void]$sb.AppendLine("| Build Type | Size | SHA256 | VirusTotal |")
[void]$sb.AppendLine("|---|---|---|---|")
[void]$sb.AppendLine("| **Normal** | $size_normal KB | ``$hash_normal`` | [Scan Result]($vtLink_normal) |")
[void]$sb.AppendLine("| **UPX** | $size_upx KB | ``$hash_upx`` | [Scan Result]($vtLink_upx) |")
[void]$sb.AppendLine("")
[void]$sb.AppendLine("> **Note**: UPX-compressed binaries may trigger false positives in some antivirus engines. Compare with the Normal build to verify.")
[void]$sb.AppendLine("")
[void]$sb.AppendLine("---")
[void]$sb.AppendLine("")
[void]$sb.AppendLine("## Changes")
[void]$sb.AppendLine("")
[void]$sb.AppendLine("**$commitMsg**")
[void]$sb.AppendLine("")
[void]$sb.Append($commitBody)
[void]$sb.AppendLine("")
[void]$sb.AppendLine("## Build Information")
[void]$sb.AppendLine("")
[void]$sb.AppendLine("- **Commit:** ``${{ github.sha }}``")
[void]$sb.AppendLine("- **Author:** $author")
[void]$sb.AppendLine("- **Platform:** Windows x64")
[void]$sb.AppendLine("- **Rust Toolchain:** Nightly")
[void]$sb.AppendLine("")
[void]$sb.AppendLine("## Installation")
[void]$sb.AppendLine("")
[void]$sb.AppendLine("1. Download ``compactrs.exe`` from assets below")
[void]$sb.AppendLine("2. Run as Administrator (required for WOF compression)")
[void]$sb.AppendLine("3. Drag and drop files or folders to compress")
[void]$sb.AppendLine("")
$sb.ToString() | Out-File -FilePath changelog.md -Encoding utf8
shell: pwsh
- name: Create Release
uses: softprops/action-gh-release@v2
with:
name: CompactRS ${{ steps.info.outputs.VERSION }}
tag_name: ${{ steps.info.outputs.VERSION }}
body_path: changelog.md
draft: false
prerelease: false
make_latest: true
files: |
target/release/${{ env.BINARY_NAME }}.exe
target/release/${{ env.BINARY_NAME }}-normal.exe
token: ${{ secrets.GITHUB_TOKEN }}
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: ${{ env.BINARY_NAME }}-windows-x64
path: |
target/release/${{ env.BINARY_NAME }}.exe
target/release/${{ env.BINARY_NAME }}-normal.exe
retention-days: 30