Skip to content

v1.3.3

v1.3.3 #21

name: Build Windows Installer (GPU)
on:
release:
types: [published]
workflow_dispatch:
inputs:
manual_version:
description: 'Version to build (e.g., 1.0.0 or v1.0.0). Required for manual runs.'
required: false # Set to false to allow an empty string check, but script will enforce it for manual runs
jobs:
build_windows:
runs-on: Windows-2022
permissions:
contents: write
steps:
- name: Checkout repository
uses: actions/checkout@v6
# No 'path' means checkout to the root
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: '3.12'
- name: Install CUDA Toolkit
id: cuda-toolkit
shell: pwsh
run: |
$version = "12.8.1"
$short = "12.8"
$url = "https://developer.download.nvidia.com/compute/cuda/$version/network_installers/cuda_${version}_windows_network.exe"
Write-Host "Downloading CUDA $version network installer..."
Invoke-WebRequest -Uri $url -OutFile cuda_installer.exe -UseBasicParsing
Write-Host "Installing sub-packages..."
$proc = Start-Process -FilePath .\cuda_installer.exe -ArgumentList @(
"-s",
"nvcc_$short",
"cudart_$short",
"cublas_$short",
"cublas_dev_$short",
"curand_$short",
"curand_dev_$short",
"cuda_profiler_api_$short",
"visual_studio_integration_$short"
) -Wait -NoNewWindow -PassThru
if ($proc.ExitCode -ne 0) {
Write-Error "CUDA installer failed with exit code $($proc.ExitCode)"
exit $proc.ExitCode
}
$cudaPath = "C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v$short"
Write-Host "CUDA installed at: $cudaPath"
Write-Host "Contents of CUDA path:"
Get-ChildItem $cudaPath -ErrorAction SilentlyContinue
Write-Host "Contents of include dir:"
Get-ChildItem "$cudaPath\include" -ErrorAction SilentlyContinue | Select-Object -First 10
if (Test-Path "$cudaPath\include\cuda.h") {
Write-Host "cuda.h found"
} else {
Write-Host "ERROR: cuda.h NOT found"
exit 1
}
# Replicate the outputs that Jimver/cuda-toolkit provides so downstream
# steps using steps.cuda-toolkit.outputs.CUDA_PATH require no changes.
"CUDA_PATH=$cudaPath" | Out-File -Append $env:GITHUB_OUTPUT
"CUDA_PATH=$cudaPath" | Out-File -Append $env:GITHUB_ENV
"CUDA_HOME=$cudaPath" | Out-File -Append $env:GITHUB_ENV
"INCLUDE=$cudaPath\include;$env:INCLUDE" | Out-File -Append $env:GITHUB_ENV
"LIB=$cudaPath\lib\x64;$env:LIB" | Out-File -Append $env:GITHUB_ENV
"$cudaPath\bin" | Out-File -Append $env:GITHUB_PATH
- name: Determine Version
id: determine_version
shell: bash
run: |
VERSION=""
if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then
if [ -n "${{ inputs.manual_version }}" ]; then
VERSION="${{ inputs.manual_version }}"
VERSION=${VERSION#v}
echo "Manual run detected. Using provided version: $VERSION"
else
echo "Error: For manual runs (workflow_dispatch), the 'manual_version' input is required."
exit 1
fi
elif [[ "${{ github.ref }}" == refs/tags/v* ]]; then
VERSION=$(echo "${{ github.ref }}" | sed 's!refs/tags/v!!')
elif [[ "${{ github.ref }}" == refs/tags/* ]]; then
VERSION=$(echo "${{ github.ref }}" | sed 's!refs/tags/!!')
fi
if [ -z "$VERSION" ]; then
echo "Error: Could not determine installer version."
echo " github.event_name: ${{ github.event_name }}"
echo " github.ref: ${{ github.ref }}"
echo " inputs.manual_version: ${{ inputs.manual_version }}"
exit 1
fi
echo "installer_version_out=$VERSION" >> "$GITHUB_OUTPUT"
echo "Installer version set to: $VERSION"
- name: Install Inno Setup
shell: pwsh
run: |
$installerUrl = "https://jrsoftware.org/download.php/is.exe"
$installerPath = "${{ runner.temp }}\isetup.exe"
echo "Downloading Inno Setup from $installerUrl..."
Invoke-WebRequest -Uri $installerUrl -OutFile $installerPath
if (-not $?) { Write-Error "Failed to download Inno Setup installer"; exit 1 }
echo "Installing Inno Setup..."
Start-Process -FilePath $installerPath -ArgumentList "/VERYSILENT /SUPPRESSMSGBOXES /NORESTART /SP-" -Wait
if (-not $?) { Write-Error "Failed to install Inno Setup"; exit 1 }
$innoPath = "C:\Program Files (x86)\Inno Setup 6"
echo "Adding Inno Setup directory to PATH: $innoPath"
Add-Content -Path $env:GITHUB_PATH -Value $innoPath
# Verify ISCC.exe exists
if (-not (Test-Path (Join-Path $innoPath "ISCC.exe"))) {
Write-Error "ISCC.exe not found at $innoPath after installation."
exit 1
}
echo "Inno Setup installed and added to PATH."
- name: Install DigiCert Client tools
id: install_digicert_tools # Renamed for clarity
uses: digicert/ssm-code-signing@v1.2.1
- name: Set DigiCert certificate
shell: bash
run: |
if [ -z "${{ secrets.SM_CLIENT_CERT_FILE_B64 }}" ]; then
echo "Error: SM_CLIENT_CERT_FILE_B64 secret is not set."
exit 1
fi
# Ensure the D drive directory exists or create it if not
# This is usually available on Windows runners, but good to be safe.
# However, direct creation like this might be problematic if D: isn't guaranteed.
# The action creates /d/ which on Windows often means a temp drive.
# Let's stick to a path the DigiCert action might expect or a runner temp path.
# The DigiCert docs for GH Actions specify /d/Certificate_pkcs12.p12. On Windows, this might translate to a specific temp path.
# Let's try to use a known writable path like runner.temp
CERT_PATH="${{ runner.temp }}\\Certificate_pkcs12.p12"
echo "Decoding certificate to $CERT_PATH"
echo "${{ secrets.SM_CLIENT_CERT_FILE_B64 }}" | base64 --decode > "$CERT_PATH"
if [ ! -f "$CERT_PATH" ]; then
echo "Error: Failed to create certificate file at $CERT_PATH"
exit 1
fi
echo "SM_CLIENT_CERT_FILE_PATH=$CERT_PATH" >> $GITHUB_ENV
- name: Set DigiCert environment variables
id: set_digicert_config_vars # Renamed for clarity, id might not be strictly needed anymore unless other steps reference it
shell: bash
run: |
if [ -z "${{ secrets.SM_HOST }}" ] || \
[ -z "${{ secrets.SM_API_KEY }}" ] || \
[ -z "${{ secrets.SM_CLIENT_CERT_PASSWORD }}" ] || \
[ -z "${{ secrets.SM_CODE_SIGNING_CERT_SHA1_HASH }}" ]; then
echo "Error: One or more DigiCert secrets (SM_HOST, SM_API_KEY, SM_CLIENT_CERT_PASSWORD, SM_CODE_SIGNING_CERT_SHA1_HASH) are not set."
exit 1
fi
echo "SM_HOST=${{ secrets.SM_HOST }}" >> "$GITHUB_ENV"
echo "SM_API_KEY=${{ secrets.SM_API_KEY }}" >> "$GITHUB_ENV"
echo "SM_CLIENT_CERT_FILE=${{ env.SM_CLIENT_CERT_FILE_PATH }}" >> "$GITHUB_ENV" # This comes from 'Set up DigiCert certificate' step
echo "SM_CLIENT_CERT_PASSWORD=${{ secrets.SM_CLIENT_CERT_PASSWORD }}" >> "$GITHUB_ENV"
# VERSION logic is now removed from this step
- name: Test DigiCert Signing on Dummy File
shell: cmd
# This step runs even if previous steps failed if we want to test signing independently at some point
# but for now, it depends on secrets and tools setup, so it will fail if those did.
run: |
echo "Starting dummy file signing test..."
set SM_LOG_LEVEL=TRACE
echo "SM_LOG_LEVEL set to TRACE for dummy sign test"
REM Create a dummy file to sign
echo @echo Test script for signing > %TEMP%\dummy_sign_test.cmd
set DUMMY_FILE_TO_SIGN=%TEMP%\dummy_sign_test.cmd
echo "Dummy file created at: %DUMMY_FILE_TO_SIGN%"
set PKCS11_CONFIG_PATH_TEST=%USERPROFILE%\AppData\Local\Temp\smtools-windows-x64\pkcs11properties.cfg
echo "PKCS11 Config Path for test: %PKCS11_CONFIG_PATH_TEST%"
if not exist "%PKCS11_CONFIG_PATH_TEST%" (
echo "Error: PKCS11 config file not found for test signing at '%PKCS11_CONFIG_PATH_TEST%'. Ensure 'Install DigiCert Client tools' ran."
exit /b 1
)
echo "Attempting to sync certificates with local store (for dummy test)..."
smctl windows certsync
set CERTSYNC_EXIT_CODE_TEST=%ERRORLEVEL%
set SMCTL_LOG_FILE_PATH_TEST=%USERPROFILE%\.signingmanager\logs\smctl.log
echo "--- smctl.log content after certsync (dummy test from %SMCTL_LOG_FILE_PATH_TEST%) ---"
if exist "%SMCTL_LOG_FILE_PATH_TEST%" (
type "%SMCTL_LOG_FILE_PATH_TEST%"
) else (
echo "smctl.log not found at %SMCTL_LOG_FILE_PATH_TEST% after certsync attempt (dummy test)."
)
echo "--- end of smctl.log content after certsync (dummy test) ---"
if %CERTSYNC_EXIT_CODE_TEST% neq 0 (
echo "Error: (Dummy Test) smctl windows certsync command failed with exit code %CERTSYNC_EXIT_CODE_TEST%."
exit /b 1
)
echo "(Dummy Test) smctl windows certsync completed successfully (exit code %CERTSYNC_EXIT_CODE_TEST%)."
echo "Attempting to sign dummy file: %DUMMY_FILE_TO_SIGN%"
set SMCTL_CLI_OUTPUT_TEMP_FILE_TEST=%TEMP%\smctl_cli_output_test.txt
smctl sign --fingerprint ${{ secrets.SM_CODE_SIGNING_CERT_SHA1_HASH }} --input "%DUMMY_FILE_TO_SIGN%" --config-file "%PKCS11_CONFIG_PATH_TEST%" > "%SMCTL_CLI_OUTPUT_TEMP_FILE_TEST%" 2>&1
set SMCTL_EXIT_CODE_TEST=%ERRORLEVEL%
echo "--- smctl CLI output (dummy test) ---"
type "%SMCTL_CLI_OUTPUT_TEMP_FILE_TEST%"
echo "--- end of smctl CLI output (dummy test) ---"
echo "smctl CLI exit code (dummy test): %SMCTL_EXIT_CODE_TEST%"
set TEMP_CLI_OUTPUT_FOR_FINDSTR_TEST=%SMCTL_CLI_OUTPUT_TEMP_FILE_TEST%
echo "--- smctl.log content after dummy sign attempt (from %SMCTL_LOG_FILE_PATH_TEST%) ---"
if exist "%SMCTL_LOG_FILE_PATH_TEST%" (
type "%SMCTL_LOG_FILE_PATH_TEST%"
) else (
echo "smctl.log not found at %SMCTL_LOG_FILE_PATH_TEST% after dummy sign attempt."
)
echo "--- end of smctl.log content after dummy sign attempt ---"
findstr /I /C:"FAILED" "%TEMP_CLI_OUTPUT_FOR_FINDSTR_TEST%"
set FINDSTR_EXIT_CODE_TEST=%ERRORLEVEL%
del "%TEMP_CLI_OUTPUT_FOR_FINDSTR_TEST%"
if %SMCTL_EXIT_CODE_TEST% neq 0 (
echo "Error: (Dummy Test) smctl sign command failed with exit code %SMCTL_EXIT_CODE_TEST%."
exit /b 1
)
if %FINDSTR_EXIT_CODE_TEST% equ 0 (
echo "Error: (Dummy Test) smctl CLI output indicates failure (found 'FAILED' in output)."
exit /b 1
)
echo "(Dummy Test) Dummy file signed successfully. DigiCert signing seems configured correctly."
REM Do not delete the dummy file itself, it's in %TEMP% and will be cleaned up by runner eventually.
- name: Build local inference wheels
shell: bash # Ensures 'make' and 'rm -rf' work as expected
run: make create_wheels
- name: Install inference_gpu and dependencies from local wheels
shell: bash # Using bash for wildcard expansion in pip install
run: |
pip install -U --index-url https://download.pytorch.org/whl/cu128 torch torchvision
WHEEL_FILE=$(ls dist/inference_gpu-*.whl)
echo "Found GPU wheel: $WHEEL_FILE"
pip install --find-links=./dist/ "$WHEEL_FILE[sam,transformers,clip,http,yolo-world,grounding-dino]"
echo "--- Installed inference_gpu details ---"
pip show inference-gpu
- name: Install PyInstaller and other build dependencies
working-directory: ./app_bundles/windows # Adjusted path
run: |
pip install --upgrade pip
pip install --upgrade pyinstaller pyinstaller-hooks-contrib
pip install -r requirements.txt # This now only installs pyinstaller
echo "DEBUG: PyInstaller version:"
pip show pyinstaller
echo "DEBUG: pyinstaller-hooks-contrib version:"
pip show pyinstaller-hooks-contrib
- name: Build Windows installer
working-directory: ./app_bundles/windows
env:
BUILD_VERSION: ${{ steps.determine_version.outputs.installer_version_out }} # Use output from new Determine Version step
run: python build.py
- name: Sign Windows Installer with DigiCert
shell: cmd
env:
INSTALLER_VERSION_FROM_STEP: ${{ steps.determine_version.outputs.installer_version_out }}
run: |
echo "Starting installer signing process..."
REM Set smctl log level to TRACE for detailed logging
set SM_LOG_LEVEL=TRACE
echo "SM_LOG_LEVEL set to TRACE"
set PKCS11_CONFIG_PATH=%USERPROFILE%\AppData\Local\Temp\smtools-windows-x64\pkcs11properties.cfg
echo "PKCS11 Config Path: %PKCS11_CONFIG_PATH%"
REM Sync KeyLocker certificates to the local Windows certificate store as per DigiCert docs
echo "Attempting to sync certificates with local store using 'smctl windows certsync'..."
smctl windows certsync
set CERTSYNC_EXIT_CODE=%ERRORLEVEL%
REM Always display smctl.log after certsync, as it might contain TRACE details for this operation
set SMCTL_LOG_FILE_PATH_AFTER_CERTSYNC=%USERPROFILE%\.signingmanager\logs\smctl.log
if %CERTSYNC_EXIT_CODE% neq 0 (
echo "Error: smctl windows certsync command failed with exit code %CERTSYNC_EXIT_CODE%."
exit /b 1
)
echo "smctl windows certsync completed successfully (exit code %CERTSYNC_EXIT_CODE%)."
set INSTALLER_BASE_PATH=app_bundles\windows\installer
set INSTALLER_VERSION=%INSTALLER_VERSION_FROM_STEP%
set INSTALLER_FILENAME=inference-%INSTALLER_VERSION%-installer.exe
set UNSIGNED_INSTALLER_PATH=%INSTALLER_BASE_PATH%\%INSTALLER_FILENAME%
echo "Debug: Listing contents of app_bundles\windows"
dir app_bundles\windows
echo "Debug: Listing contents of %INSTALLER_BASE_PATH%"
dir "%INSTALLER_BASE_PATH%"
echo "Looking for installer at: %UNSIGNED_INSTALLER_PATH%"
if not exist "%UNSIGNED_INSTALLER_PATH%" (
echo "Error: Unsigned installer not found at '%UNSIGNED_INSTALLER_PATH%'"
echo "Current directory for this step: "
cd
exit /b 1
)
echo "Looking for PKCS11 config at: %PKCS11_CONFIG_PATH%"
if not exist "%PKCS11_CONFIG_PATH%" (
echo "Error: DigiCert PKCS11 config file not found..."
exit /b 1
)
echo "Attempting to sign: %UNSIGNED_INSTALLER_PATH%"
set SMCTL_CLI_OUTPUT_TEMP_FILE=%TEMP%\smctl_cli_output.txt
smctl sign --fingerprint ${{ secrets.SM_CODE_SIGNING_CERT_SHA1_HASH }} --input "%UNSIGNED_INSTALLER_PATH%" --config-file "%PKCS11_CONFIG_PATH%" > "%SMCTL_CLI_OUTPUT_TEMP_FILE%" 2>&1
set SMCTL_EXIT_CODE=%ERRORLEVEL%
echo "--- smctl CLI output ---"
type "%SMCTL_CLI_OUTPUT_TEMP_FILE%"
echo "--- end of smctl CLI output ---"
echo "smctl CLI exit code: %SMCTL_EXIT_CODE%"
REM Temporarily store CLI output for findstr before deleting the file
set TEMP_CLI_OUTPUT_FOR_FINDSTR=%SMCTL_CLI_OUTPUT_TEMP_FILE%
REM Display the detailed smctl.log file
set SMCTL_LOG_FILE_PATH=%USERPROFILE%\.signingmanager\logs\smctl.log
echo "--- smctl.log content (from %SMCTL_LOG_FILE_PATH%) ---"
if exist "%SMCTL_LOG_FILE_PATH%" (
type "%SMCTL_LOG_FILE_PATH%"
) else (
echo "smctl.log not found at %SMCTL_LOG_FILE_PATH%."
)
echo "--- end of smctl.log content ---"
REM We need to re-capture CLI output or pass it if we want to search it again here.
REM Let's assume for a moment that with TRACE logging, the exit code becomes more reliable,
REM OR that the TRACE log itself will contain an undeniable error message we could search for.
REM The original findstr logic was based on the brief "FAILED" message.
REM Check for "FAILED" in the originally captured CLI output
findstr /I /C:"FAILED" "%TEMP_CLI_OUTPUT_FOR_FINDSTR%"
set FINDSTR_EXIT_CODE=%ERRORLEVEL%
REM findstr returns 0 if string is found, 1 if not found (or error)
REM Now delete the temp CLI output file
del "%TEMP_CLI_OUTPUT_FOR_FINDSTR%"
if %SMCTL_EXIT_CODE% neq 0 (
echo "Error: smctl sign command failed with exit code %SMCTL_EXIT_CODE% (see smctl.log above for details)."
exit /b 1
)
if %FINDSTR_EXIT_CODE% equ 0 (
REM String "FAILED" was found in the CLI output
echo "Error: smctl CLI output indicates failure (found 'FAILED' in output). Review smctl.log for details."
exit /b 1
)
echo "Installer signed successfully (based on smctl exit code and absence of 'FAILED' in CLI output). Review logs carefully."
- name: Upload Windows Installer
uses: actions/upload-artifact@v7
with:
name: Roboflow-Inference-Windows-Installer
# The build.py script creates the installer in installer/inference-{version}-installer.exe
# We'll upload the whole installer directory, or use a wildcard if preferred.
path: app_bundles/windows/installer/ # Adjusted path
if-no-files-found: error # Fail the step if the installer directory is not found or empty
- name: Upload Installer to Release
if: github.event_name == 'release'
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ github.event.release.upload_url }}
asset_path: ./app_bundles/windows/installer/inference-${{ steps.determine_version.outputs.installer_version_out }}-installer.exe
asset_name: inference-${{ steps.determine_version.outputs.installer_version_out }}-gpu-installer.exe
asset_content_type: application/vnd.microsoft.portable-executable