Build OpenSSL 3.5.7 via workflow_dispatch #101
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Build OpenSSL | |
| run-name: Build OpenSSL ${{ inputs.version }} via ${{ github.event_name }} | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| build_type: | |
| description: 'Build Source: OpenSSL Release or OpenSSL Branch/OpenSSL fork' | |
| required: true | |
| type: choice | |
| options: | |
| - release | |
| - branch | |
| default: release | |
| version: | |
| description: 'OpenSSL Release Version, OpenSSL Branch Name or OpenSSL Fork Repo(e.g. 3.4.0, master, or user/repo/branch)' | |
| required: true | |
| type: string | |
| ignore_eol: | |
| description: 'Ignore EOL Check' | |
| required: false | |
| type: boolean | |
| default: false | |
| keep_raw_artifacts: | |
| description: 'Keep raw build artifacts' | |
| required: false | |
| type: boolean | |
| default: false | |
| workflow_call: | |
| inputs: | |
| build_type: | |
| required: false | |
| type: string | |
| default: release | |
| version: | |
| required: true | |
| type: string | |
| ignore_eol: | |
| required: false | |
| type: boolean | |
| default: false | |
| keep_raw_artifacts: | |
| required: false | |
| type: boolean | |
| default: false | |
| jobs: | |
| # ========================================================================= | |
| # 0. VALIDATE VERSION & EOL | |
| # ========================================================================= | |
| validate-version: | |
| name: Validate Inputs | |
| runs-on: ubuntu-latest | |
| permissions: | |
| actions: write | |
| outputs: | |
| version: ${{ steps.check.outputs.version }} | |
| target_repo: ${{ steps.check.outputs.target_repo }} | |
| ref: ${{ steps.check.outputs.ref }} | |
| sha: ${{ steps.check.outputs.sha }} | |
| artifact_version: ${{ steps.check.outputs.artifact_version }} | |
| slugified_version: ${{ steps.check.outputs.slugified_version }} | |
| is_fork: ${{ steps.check.outputs.is_fork }} | |
| steps: | |
| - name: Check EOL or Branch Existence | |
| id: check | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| VERSION="${{ inputs.version }}" | |
| BUILD_TYPE="${{ inputs.build_type }}" | |
| IGNORE_EOL="${{ inputs.ignore_eol }}" | |
| TARGET_REPO="openssl/openssl" | |
| IS_FORK="false" | |
| if [ "$BUILD_TYPE" == "release" ]; then | |
| MAJOR_MINOR=$(echo "$VERSION" | cut -d. -f1,2) | |
| EOL_DATE=$(curl -s https://raw.githubusercontent.com/openssl/release-metadata/refs/heads/main/data.json | jq -r ".[\"$MAJOR_MINOR\"].eol") | |
| EOL_DATE=$(echo "$EOL_DATE" | date -d "$EOL_DATE" +%s 2>/dev/null || date -d "01 $EOL_DATE" +%s 2>/dev/null || echo 0) | |
| if [ -z "$EOL_DATE" ] || [ "$EOL_DATE" == "null" ]; then | |
| echo "⚠️ Could not determine EOL date for $MAJOR_MINOR. Proceeding cautiously." | |
| else | |
| TODAY=$(date +%s) | |
| if [[ "$TODAY" > "$EOL_DATE" ]]; then | |
| echo "❌ OpenSSL $MAJOR_MINOR reached EOL on $(date -d "@$EOL_DATE" +%Y-%m-%d) ." | |
| if [ "$IGNORE_EOL" != "true" ]; then | |
| echo "Aborting build. Check 'Ignore EOL' to override." | |
| exit 1 | |
| fi | |
| echo "⚠️ Ignore EOL is checked. Proceeding anyway." | |
| fi | |
| fi | |
| TARGET_REF="openssl-$VERSION" | |
| echo "🔍 Resolving SHA for tag '$TARGET_REF'..." | |
| SHA=$(git ls-remote --tags https://github.com/$TARGET_REPO.git "$TARGET_REF" | awk '{print $1}') | |
| if [ -z "$SHA" ]; then | |
| echo "❌ Tag '$TARGET_REF' not found in $TARGET_REPO." | |
| exit 1 | |
| fi | |
| ARTIFACT_VERSION="$VERSION" | |
| SLUGIFIED_VERSION="$VERSION" | |
| else | |
| # Branch Mode | |
| if [[ "$VERSION" == */*/* ]]; then | |
| # Format: user/repo/branch | |
| TARGET_REPO=$(echo "$VERSION" | cut -d'/' -f1,2) | |
| TARGET_REF=$(echo "$VERSION" | cut -d'/' -f3-) | |
| IS_FORK="true" | |
| else | |
| TARGET_REF="$VERSION" | |
| fi | |
| echo "🔍 Resolving SHA for branch/ref '$TARGET_REF' in repository '$TARGET_REPO'..." | |
| SHA=$(git ls-remote https://github.com/$TARGET_REPO.git "$TARGET_REF" | awk '{print $1}') | |
| if [ -z "$SHA" ]; then | |
| # Check if it's already a SHA | |
| if [[ "$TARGET_REF" =~ ^[0-9a-f]{40}$ ]]; then | |
| SHA="$TARGET_REF" | |
| else | |
| echo "❌ Reference '$TARGET_REF' does not exist in $TARGET_REPO." | |
| exit 1 | |
| fi | |
| fi | |
| TIMESTAMP=$(date -u +%Y%m%dT%H%M%SZ) | |
| # Slugify the whole version input (e.g. user/repo/branch -> user_repo_branch) | |
| SLUGIFIED_VERSION=$(echo "$VERSION" | sed 's/\//_/g') | |
| # Truncate for filename safety | |
| SAFE_PART=$(echo "$SLUGIFIED_VERSION" | cut -c 1-100) | |
| ARTIFACT_VERSION="${SAFE_PART}_${TIMESTAMP}" | |
| fi | |
| # Output resolved information | |
| echo "target_repo=$TARGET_REPO" >> $GITHUB_OUTPUT | |
| echo "is_fork=$IS_FORK" >> $GITHUB_OUTPUT | |
| echo "ref=$TARGET_REF" >> $GITHUB_OUTPUT | |
| echo "sha=$SHA" >> $GITHUB_OUTPUT | |
| echo "artifact_version=$ARTIFACT_VERSION" >> $GITHUB_OUTPUT | |
| echo "slugified_version=$SLUGIFIED_VERSION" >> $GITHUB_OUTPUT | |
| echo "version=$VERSION" >> $GITHUB_OUTPUT | |
| # ========================================================================= | |
| # 1. BUILD COMMON ASSETS (Headers & Docs) | |
| # ========================================================================= | |
| build-common-assets: | |
| name: Build Headers & Docs | |
| needs: validate-version | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| repository: ${{ needs.validate-version.outputs.target_repo }} | |
| ref: ${{ needs.validate-version.outputs.sha }} | |
| - name: Build Common Assets | |
| run: | | |
| ./Configure linux-x86_64 no-tests --prefix=/usr/local | |
| make -j$(nproc) install_dev DESTDIR=$PWD/common-assets | |
| make install_html_docs DESTDIR=$PWD/common-assets || true | |
| # Include License | |
| cp LICENSE.txt common-assets/usr/local/ | |
| # --- Generate README --- | |
| cat << 'EOF' > common-assets/usr/local/README.txt | |
| OpenSSL Distribution Package | |
| ============================ | |
| This package contains the OpenSSL executable, shared libraries, static libraries (stripped), C headers, and documentation. | |
| Package Layout: | |
| --------------- | |
| * version.txt - OpenSSL version in this package [non-redistributable] | |
| * openssl - The OpenSSL command-line utility [redistributable/optional] | |
| * libcrypto / libssl - Shared libraries [redistributable/required] | |
| * install_symlinks.sh - (POSIX only) Script to restore shared library symlinks [redistributable/optional] | |
| * engines/ - OpenSSL engines [redistributable/optional] | |
| * providers/ - OpenSSL providers [redistributable/optional] | |
| * doc/ - Developers Documentation [non-redistributable] | |
| * include/ - C Header files [non-redistributable] | |
| * lib/import/ - Import libraries (Windows only) [non-redistributable] | |
| * lib/static/ - Static libraries (.lib / .a) [non-redistributable] | |
| Linking Instructions: | |
| --------------------- | |
| * Windows Dynamic: Link against the import libraries in `lib/import/` (which point to the DLLs in the root). | |
| * Windows Static: Link against the static libraries in `lib/static/` (Compiled with /MD Dynamic CRT). | |
| * POSIX Dynamic: Link directly against the shared libraries (.so / .dylib) in the root directory. | |
| * POSIX Static: Link against the static archives (.a) in `lib/static/`. | |
| Deployment Instructions (Linux / macOS / Unix): | |
| ----------------------------------------------- | |
| Windows file systems fail to extract Unix symbolic links. To ensure cross-platform compatibility, this archive contains only the physical shared library files. | |
| If this package includes the 'install_symlinks.sh' script, you MUST run it from the root of the extracted directory to recreate the required library symlinks (e.g., libcrypto.so -> libcrypto.so.X). | |
| $ cd <extracted_directory> | |
| $ sh ./install_symlinks.sh | |
| Windows Users: | |
| -------------- | |
| Windows does not use symlinks for OpenSSL DLLs. You can safely ignore or delete the shell script. | |
| EOF | |
| # Remove unnecessary large directories | |
| rm -rf common-assets/usr/local/lib common-assets/usr/local/lib64 common-assets/usr/local/bin common-assets/usr/local/ssl | |
| - name: Upload Common Assets | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: openssl-common-assets-${{ github.run_id }} | |
| path: common-assets/usr/local | |
| retention-days: 1 | |
| # ========================================================================= | |
| # 2. COMPILE BINARIES (Fan-Out Matrix) | |
| # ========================================================================= | |
| compile-binaries: | |
| name: Compile (${{ matrix.platform.label }} ${{ matrix.platform.arch }} ${{ matrix.linkage }}) | |
| needs: validate-version | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| linkage: [shared, static] | |
| platform: | |
| - { label: Windows, os: windows-latest, arch: x64, target: VC-WIN64A, vcvars: amd64 } | |
| - { label: Windows, os: windows-latest, arch: x86, target: VC-WIN32, vcvars: x86 } | |
| - { label: Windows, os: windows-latest, arch: arm64ec, target: VC-ARM64EC, vcvars: amd64_arm64 } | |
| - { label: Linux, os: ubuntu-latest, arch: x64, target: linux-x86_64 } | |
| - { label: Linux, os: ubuntu-latest, arch: arm64, target: linux-aarch64 } | |
| - { label: macOS, os: macos-14, arch: x64, target: darwin64-x86_64-cc, minos: "10.14" } | |
| - { label: macOS, os: macos-14, arch: arm64, target: darwin64-arm64-cc, minos: "11.0" } | |
| - { label: Android, os: ubuntu-latest, arch: arm64, target: android-arm64, api: 21 } | |
| - { label: Android, os: ubuntu-latest, arch: arm, target: android-arm, api: 21 } | |
| - { label: iOS, os: macos-14, arch: arm64, target: ios64-cross } | |
| - { label: iOS, os: macos-14, arch: sim-arm64, target: iossimulator-xcrun, minos: "11.0" } | |
| exclude: | |
| - linkage: shared | |
| platform: { label: iOS } # iOS is static only | |
| runs-on: ${{ matrix.platform.os }} | |
| permissions: | |
| id-token: write | |
| contents: read | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| repository: ${{ needs.validate-version.outputs.target_repo }} | |
| ref: ${{ needs.validate-version.outputs.sha }} | |
| - name: Validate Azure Code Signing Secrets | |
| if: matrix.platform.label == 'Windows' | |
| shell: pwsh | |
| run: | | |
| $missing = @() | |
| if ([string]::IsNullOrWhiteSpace('${{ secrets.AZURE_CLIENT_ID }}')) { $missing += "AZURE_CLIENT_ID" } | |
| if ([string]::IsNullOrWhiteSpace('${{ secrets.AZURE_CLIENT_SECRET }}')) { $missing += "AZURE_CLIENT_SECRET" } | |
| if ([string]::IsNullOrWhiteSpace('${{ secrets.AZURE_TENANT_ID }}')) { $missing += "AZURE_TENANT_ID" } | |
| if ([string]::IsNullOrWhiteSpace('${{ secrets.AZURE_SUBSCRIPTION_ID }}')) { $missing += "AZURE_SUBSCRIPTION_ID" } | |
| if ([string]::IsNullOrWhiteSpace('${{ secrets.AZURE_SIGNING_ACCOUNT_NAME }}')) { $missing += "AZURE_SIGNING_ACCOUNT_NAME" } | |
| if ([string]::IsNullOrWhiteSpace('${{ secrets.AZURE_CERTIFICATE_PROFILE_NAME }}')) { $missing += "AZURE_CERTIFICATE_PROFILE_NAME" } | |
| if ($missing.Count -gt 0) { | |
| Write-Error "Required Azure Code Signing secret(s) missing: $($missing -join ', ')" | |
| exit 1 | |
| } | |
| - name: Prepare Windows Targets | |
| if: matrix.platform.label == 'Windows' | |
| shell: bash | |
| run: | | |
| cat << 'EOF' > Configurations/99-win-hybridcrt.conf | |
| my %targets = ( | |
| # ========================================================================= | |
| # x64 HybridCRT Targets | |
| # ========================================================================= | |
| "VC-WIN64A-SHARED" => { | |
| inherit_from => [ "VC-WIN64A" ], | |
| disable => [ "tests", "makedepend", "__DOCS__" ], | |
| cflags => sub { | |
| my $f = join(" ", @_); | |
| $f =~ s/\/MDd?\b//g; # Remove Dynamic CRT | |
| return "$f /MT /Zi"; # Force Static CRT + Debug Symbols | |
| }, | |
| lflags => sub { | |
| my $f = join(" ", @_); | |
| return "$f /debug /NODEFAULTLIB:libucrt.lib /DEFAULTLIB:ucrt.lib"; | |
| }, | |
| }, | |
| "VC-WIN64A-STATIC" => { | |
| inherit_from => [ "VC-WIN64A" ], | |
| disable => [ "shared", "module", "tests", "makedepend", "__DOCS__" ], | |
| cflags => sub { | |
| my $f = join(" ", @_); | |
| $f =~ s/\/MDd?\b//g; | |
| return "$f /MT /Zi"; | |
| }, | |
| lflags => sub { | |
| my $f = join(" ", @_); | |
| return "$f /debug /NODEFAULTLIB:libucrt.lib /DEFAULTLIB:ucrt.lib"; | |
| }, | |
| }, | |
| # ========================================================================= | |
| # x86 HybridCRT Targets | |
| # ========================================================================= | |
| "VC-WIN32-SHARED" => { | |
| inherit_from => [ "VC-WIN32" ], | |
| disable => [ "tests", "makedepend", "__DOCS__" ], | |
| cflags => sub { | |
| my $f = join(" ", @_); | |
| $f =~ s/\/MDd?\b//g; | |
| return "$f /MT /Zi"; | |
| }, | |
| lflags => sub { | |
| my $f = join(" ", @_); | |
| return "$f /debug /NODEFAULTLIB:libucrt.lib /DEFAULTLIB:ucrt.lib"; | |
| }, | |
| }, | |
| "VC-WIN32-STATIC" => { | |
| inherit_from => [ "VC-WIN32" ], | |
| disable => [ "shared", "module", "tests", "makedepend", "__DOCS__" ], | |
| cflags => sub { | |
| my $f = join(" ", @_); | |
| $f =~ s/\/MDd?\b//g; | |
| return "$f /MT /Zi"; | |
| }, | |
| lflags => sub { | |
| my $f = join(" ", @_); | |
| return "$f /debug /NODEFAULTLIB:libucrt.lib /DEFAULTLIB:ucrt.lib"; | |
| }, | |
| }, | |
| # ========================================================================= | |
| # ARM64EC HybridCRT Targets | |
| # ========================================================================= | |
| "VC-ARM64EC-SHARED" => { | |
| inherit_from => [ "VC-WIN64-ARM" ], | |
| disable => [ "asm", "tests", "makedepend", "__DOCS__" ], | |
| CFLAGS => "/W3 /wd4090 /wd4267 /wd4244 /nologo /O1 /Zi", | |
| ARFLAGS => "/nologo /MACHINE:ARM64EC", | |
| cflags => sub { | |
| my $f = join(" ", @_); | |
| $f =~ s/\/MDd?\b//g; # Remove Dynamic CRT | |
| $f =~ s/\/arm64\b//ig; # Remove standard ARM64 flag | |
| return "$f /MT /arm64EC /D_WIN32_WINNT=0x0A00"; | |
| }, | |
| lflags => sub { | |
| my $f = join(" ", @_); | |
| $f =~ s/\/MACHINE:ARM64\b//ig; # Remove conflicting machine type | |
| return "$f /debug /MACHINE:ARM64EC /NODEFAULTLIB:libucrt.lib /DEFAULTLIB:ucrt.lib"; | |
| }, | |
| }, | |
| "VC-ARM64EC-STATIC" => { | |
| inherit_from => [ "VC-WIN64-ARM" ], | |
| disable => [ "shared", "module", "asm", "tests", "makedepend", "__DOCS__" ], | |
| CFLAGS => "/W3 /wd4090 /wd4267 /wd4244 /nologo /O1 /Zi", | |
| ARFLAGS => "/nologo /MACHINE:ARM64EC", | |
| cflags => sub { | |
| my $f = join(" ", @_); | |
| $f =~ s/\/MDd?\b//g; | |
| $f =~ s/\/arm64\b//ig; | |
| return "$f /MT /arm64EC /D_WIN32_WINNT=0x0A00"; | |
| }, | |
| lflags => sub { | |
| my $f = join(" ", @_); | |
| $f =~ s/\/MACHINE:ARM64\b//ig; | |
| return "$f /debug /MACHINE:ARM64EC /NODEFAULTLIB:libucrt.lib /DEFAULTLIB:ucrt.lib"; | |
| }, | |
| }, | |
| ); | |
| EOF | |
| # Check if the 'no-docs' option is documented in INSTALL.md (introduced in 3.2.0) | |
| if grep -q "no-docs" INSTALL.md; then | |
| echo "Feature 'docs' is supported. Disabling it in config." | |
| sed -i 's/"__DOCS__"/"docs"/g' Configurations/99-win-hybridcrt.conf | |
| else | |
| echo "Feature 'docs' is NOT supported (likely OpenSSL 3.0.x or 3.1.x). Removing from config." | |
| sed -i 's/"__DOCS__"//g' Configurations/99-win-hybridcrt.conf | |
| fi | |
| - name: Compile and Stage Windows Binaries | |
| if: matrix.platform.label == 'Windows' | |
| shell: cmd | |
| run: | | |
| :: Dynamically locate the latest Visual Studio installation path | |
| for /f "usebackq tokens=*" %%i in (`"%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe" -latest -property installationPath`) do set "VS_PATH=%%i" | |
| :: Call vcvarsall.bat using the dynamically discovered path | |
| call "%VS_PATH%\VC\Auxiliary\Build\vcvarsall.bat" ${{ matrix.platform.vcvars }} | |
| set "INSTALL_TEMP=%GITHUB_WORKSPACE%\temp_install" | |
| :: Use the original raw_artifact\dist path so it matches POSIX perfectly | |
| set "STAGED=%GITHUB_WORKSPACE%\raw_artifact\dist" | |
| mkdir "%STAGED%\lib\static" "%STAGED%\lib\import" "%STAGED%\engines" "%STAGED%\providers" | |
| :: Construct Target Name dynamically (e.g., VC-WIN64A-SHARED) | |
| set "LINK_UPPER=${{ matrix.linkage }}" | |
| if "%LINK_UPPER%"=="shared" (set "TARGET_NAME=${{ matrix.platform.target }}-SHARED") else (set "TARGET_NAME=${{ matrix.platform.target }}-STATIC") | |
| cd openssl-src | |
| perl Configure %TARGET_NAME% --prefix="%INSTALL_TEMP%" | |
| nmake && nmake install_sw | |
| :: Organize into Staged Structure (Matching the ORIGINAL layout) | |
| if "${{ matrix.linkage }}"=="shared" ( | |
| :: Copy exe and dlls to the ROOT of the artifact, not a bin/ folder! | |
| if exist "%INSTALL_TEMP%\bin\openssl.exe" copy "%INSTALL_TEMP%\bin\openssl.exe" "%STAGED%\" | |
| copy "%INSTALL_TEMP%\bin\*.dll" "%STAGED%\" | |
| copy "%INSTALL_TEMP%\lib\*.lib" "%STAGED%\lib\import\" | |
| if exist "%INSTALL_TEMP%\lib\engines-3" copy "%INSTALL_TEMP%\lib\engines-3\*.dll" "%STAGED%\engines\" | |
| if exist "%INSTALL_TEMP%\lib\ossl-modules" copy "%INSTALL_TEMP%\lib\ossl-modules\*.dll" "%STAGED%\providers\" | |
| ) else ( | |
| copy "%INSTALL_TEMP%\lib\*.lib" "%STAGED%\lib\static\" | |
| ) | |
| - name: Check for Windows Binaries to Sign | |
| if: matrix.platform.label == 'Windows' | |
| id: check_binaries | |
| shell: pwsh | |
| run: | | |
| $files = Get-ChildItem -Path "${{ github.workspace }}\raw_artifact\dist" -Recurse -Include *.exe,*.dll | |
| if ($files.Count -gt 0) { | |
| Write-Host "Found $($files.Count) binary file(s) to sign." | |
| Add-Content -Path $env:GITHUB_OUTPUT -Value "has_binaries=true" | |
| } else { | |
| Write-Host "No .exe or .dll files found to sign." | |
| Add-Content -Path $env:GITHUB_OUTPUT -Value "has_binaries=false" | |
| } | |
| - name: Azure Login | |
| if: matrix.platform.label == 'Windows' && steps.check_binaries.outputs.has_binaries == 'true' | |
| uses: azure/login@v3 | |
| with: | |
| creds: '{"clientId":"${{ secrets.AZURE_CLIENT_ID }}","clientSecret":"${{ secrets.AZURE_CLIENT_SECRET }}","subscriptionId":"${{ secrets.AZURE_SUBSCRIPTION_ID }}","tenantId":"${{ secrets.AZURE_TENANT_ID }}"}' | |
| - name: Sign Windows Binaries (Azure Artifact Signing) | |
| if: matrix.platform.label == 'Windows' && steps.check_binaries.outputs.has_binaries == 'true' | |
| uses: azure/artifact-signing-action@v2 | |
| with: | |
| endpoint: ${{ secrets.AZURE_CODESIGNING_ENDPOINT || 'https://eus.codesigning.azure.net/' }} | |
| signing-account-name: ${{ secrets.AZURE_SIGNING_ACCOUNT_NAME }} | |
| certificate-profile-name: ${{ secrets.AZURE_CERTIFICATE_PROFILE_NAME }} | |
| files-folder: ${{ github.workspace }}\raw_artifact\dist | |
| files-folder-filter: exe,dll | |
| files-folder-recurse: true | |
| file-digest: SHA256 | |
| timestamp-rfc3161: http://timestamp.acs.microsoft.com | |
| timestamp-digest: SHA256 | |
| - name: Verify Signed Windows Binaries | |
| if: matrix.platform.label == 'Windows' && steps.check_binaries.outputs.has_binaries == 'true' | |
| shell: pwsh | |
| run: | | |
| $files = Get-ChildItem -Path "${{ github.workspace }}\raw_artifact\dist" -Recurse -Include *.exe,*.dll | |
| if ($files.Count -eq 0) { | |
| Write-Host "No .exe or .dll files found to verify." | |
| exit 0 | |
| } | |
| $failed = $false | |
| foreach ($file in $files) { | |
| Write-Host "Verifying $($file.FullName)..." | |
| $sig = Get-AuthenticodeSignature -FilePath $file.FullName | |
| Write-Host " Status: $($sig.Status)" | |
| Write-Host " StatusMessage: $($sig.StatusMessage)" | |
| if ($sig.SignerCertificate) { | |
| Write-Host " Subject: $($sig.SignerCertificate.Subject)" | |
| Write-Host " Issuer: $($sig.SignerCertificate.Issuer)" | |
| } | |
| if ($sig.Status -ne 'Valid') { | |
| Write-Error "Signature status for $($file.Name) is invalid: $($sig.Status)" | |
| $failed = $true | |
| } | |
| } | |
| if ($failed) { | |
| exit 1 | |
| } | |
| - name: Install Linux Dependencies | |
| if: matrix.platform.label == 'Linux' | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y libsctp-dev | |
| if [ "${{ matrix.platform.arch }}" == "arm64" ]; then | |
| sudo apt-get install -y gcc-aarch64-linux-gnu libc6-dev-arm64-cross | |
| fi | |
| - name: Compile Unix (POSIX) | |
| if: matrix.platform.label != 'Windows' | |
| shell: bash | |
| run: | | |
| TARGET="${{ matrix.platform.target }}" | |
| LINKAGE="${{ matrix.linkage }}" | |
| LABEL="${{ matrix.platform.label }}" | |
| PREFIX="${{ github.workspace }}/raw_artifact/usr/local" | |
| EXTRA_FLAGS="" | |
| # --- Platform Specific Setup --- | |
| if [ "$LABEL" == "macOS" ] ; then | |
| EXTRA_FLAGS="-mmacosx-version-min=${{ matrix.platform.minos }}" | |
| elif [ "$LABEL" == "Linux" ]; then | |
| EXTRA_FLAGS="enable-sctp -Wl,-rpath,'\$\$ORIGIN'" | |
| if [ "$TARGET" == "linux-aarch64" ]; then | |
| export CROSS_COMPILE=aarch64-linux-gnu- | |
| fi | |
| elif [ "$LABEL" == "Android" ]; then | |
| sed -i 's/-fPIC/-fPIC -Wl,-z,max-page-size=16384/g' Configurations/15-android.conf | |
| export ANDROID_NDK_ROOT=$ANDROID_NDK_LATEST_HOME | |
| export PATH=$ANDROID_NDK_ROOT/toolchains/llvm/prebuilt/linux-x86_64/bin:$PATH | |
| elif [ "$LABEL" == "iOS" ]; then | |
| if [[ "$TARGET" == *"simulator"* ]]; then | |
| export CROSS_TOP=$(xcode-select -print-path)/Platforms/iPhoneSimulator.platform/Developer | |
| export CROSS_SDK=iPhoneSimulator.sdk | |
| EXTRA_FLAGS="-mios-simulator-version-min=${{ matrix.platform.minos }}" | |
| else | |
| export CROSS_TOP=$(xcode-select -print-path)/Platforms/iPhoneOS.platform/Developer | |
| export CROSS_SDK=iPhoneOS.sdk | |
| fi | |
| fi | |
| # --- Configure & Build --- | |
| if [ "$LINKAGE" == "shared" ]; then | |
| ./Configure $TARGET shared no-tests $EXTRA_FLAGS --prefix="$PREFIX" | |
| else | |
| if ! ./Configure $TARGET no-shared no-apps no-module no-tests $EXTRA_FLAGS --prefix="$PREFIX"; then | |
| ./Configure $TARGET no-shared no-module no-tests $EXTRA_FLAGS --prefix="$PREFIX" | |
| fi | |
| fi | |
| make -j$(nproc 2>/dev/null || sysctl -n hw.ncpu) | |
| make install_sw DESTDIR="/" | |
| - name: Organize Unix Binaries | |
| if: matrix.platform.label != 'Windows' | |
| shell: bash | |
| run: | | |
| LABEL="${{ matrix.platform.label }}" | |
| LINKAGE="${{ matrix.linkage }}" | |
| PREFIX="${{ github.workspace }}/raw_artifact/usr/local" | |
| mkdir -p raw_artifact/dist/{engines,providers,lib/static} | |
| if [ "$LINKAGE" == "shared" ]; then | |
| # Executable | |
| find "$PREFIX/bin" -type f -name "openssl" -exec cp {} raw_artifact/dist/ \; 2>/dev/null || true | |
| # Shared Libs | |
| if [ "$LABEL" == "Android" ]; then | |
| find "$PREFIX/lib" "$PREFIX/lib64" -maxdepth 1 -type f -name "*.so" -exec cp {} raw_artifact/dist/ \; 2>/dev/null || true | |
| else | |
| # Versioned libs only: libcrypto.so.X, libssl.so.X or libcrypto.X.dylib, libssl.X.dylib | |
| find "$PREFIX/lib" "$PREFIX/lib64" -maxdepth 1 -type f -name "libcrypto.so.*" -exec cp {} raw_artifact/dist/ \; 2>/dev/null || true | |
| find "$PREFIX/lib" "$PREFIX/lib64" -maxdepth 1 -type f -name "libssl.so.*" -exec cp {} raw_artifact/dist/ \; 2>/dev/null || true | |
| find "$PREFIX/lib" "$PREFIX/lib64" -maxdepth 1 -type f -name "libcrypto.*.dylib" -exec cp {} raw_artifact/dist/ \; 2>/dev/null || true | |
| find "$PREFIX/lib" "$PREFIX/lib64" -maxdepth 1 -type f -name "libssl.*.dylib" -exec cp {} raw_artifact/dist/ \; 2>/dev/null || true | |
| fi | |
| # Engines & Providers | |
| find "$PREFIX" -path "*/engines-*/*.so" -exec cp {} raw_artifact/dist/engines/ \; 2>/dev/null || true | |
| find "$PREFIX" -path "*/engines-*/*.dylib" -exec cp {} raw_artifact/dist/engines/ \; 2>/dev/null || true | |
| find "$PREFIX" -path "*/ossl-modules*/*.so" -exec cp {} raw_artifact/dist/providers/ \; 2>/dev/null || true | |
| find "$PREFIX" -path "*/ossl-modules*/*.dylib" -exec cp {} raw_artifact/dist/providers/ \; 2>/dev/null || true | |
| # Stripping (Non-macOS) | |
| if [ "$LABEL" != "macOS" ]; then | |
| find raw_artifact/dist -maxdepth 1 -type f \( -name "openssl" -o -name "*.so*" \) -exec strip {} + 2>/dev/null || true | |
| find raw_artifact/dist/engines raw_artifact/dist/providers -type f -name "*.so" -exec strip {} + 2>/dev/null || true | |
| fi | |
| # Symlinks script (Linux/macOS) | |
| if [ "$LABEL" == "Linux" ] || [ "$LABEL" == "macOS" ]; then | |
| echo "#!/bin/sh" > raw_artifact/dist/install_symlinks.sh | |
| echo "echo \"Restoring shared library symlinks...\"" >> raw_artifact/dist/install_symlinks.sh | |
| cd raw_artifact/dist | |
| for lib in libcrypto libssl; do | |
| if [ "$LABEL" == "Linux" ]; then | |
| REAL_FILE=$(ls ${lib}.so.* 2>/dev/null | head -n 1) | |
| if [ -n "$REAL_FILE" ]; then | |
| echo "ln -sf $REAL_FILE ${lib}.so" >> install_symlinks.sh | |
| fi | |
| else | |
| REAL_FILE=$(ls ${lib}.*.dylib 2>/dev/null | head -n 1) | |
| if [ -n "$REAL_FILE" ]; then | |
| echo "ln -sf $REAL_FILE ${lib}.dylib" >> install_symlinks.sh | |
| fi | |
| fi | |
| done | |
| chmod +x install_symlinks.sh | |
| cd - > /dev/null | |
| fi | |
| else | |
| # Static linkage | |
| mkdir -p raw_artifact/dist/lib/static | |
| find "$PREFIX" -type f -name "*.a" -exec cp {} raw_artifact/dist/lib/static/ \; 2>/dev/null || true | |
| # Stripping (Non-macOS) | |
| if [ "$LABEL" != "macOS" ]; then | |
| find raw_artifact/dist/lib/static -type f -name "*.a" -exec strip -S {} + 2>/dev/null || true | |
| fi | |
| fi | |
| find raw_artifact/dist -type d -empty -delete || true | |
| - name: Upload Raw Artifact | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: raw-${{ matrix.platform.label }}-${{ matrix.platform.arch }}-${{ matrix.linkage }}-${{ github.run_id }} | |
| path: raw_artifact/dist | |
| retention-days: 1 | |
| # ========================================================================= | |
| # 3a. BUILD WINDOWS MULTI-ARCH INSTALLER — Release Builds Only | |
| # ========================================================================= | |
| InnoSetup-windows-installer: | |
| name: Inno Setup Windows Multi-Arch Installer | |
| needs: [validate-version, build-common-assets, compile-binaries] | |
| if: needs.validate-version.outputs.is_fork == 'false' && inputs.build_type == 'release' | |
| runs-on: windows-latest | |
| permissions: | |
| id-token: write | |
| contents: read | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Validate Signing Secrets | |
| shell: pwsh | |
| run: | | |
| $missing = @() | |
| if ([string]::IsNullOrWhiteSpace('${{ secrets.AZURE_CLIENT_ID }}')) { $missing += 'AZURE_CLIENT_ID' } | |
| if ([string]::IsNullOrWhiteSpace('${{ secrets.AZURE_CLIENT_SECRET }}')) { $missing += 'AZURE_CLIENT_SECRET' } | |
| if ([string]::IsNullOrWhiteSpace('${{ secrets.AZURE_TENANT_ID }}')) { $missing += 'AZURE_TENANT_ID' } | |
| if ([string]::IsNullOrWhiteSpace('${{ secrets.AZURE_SUBSCRIPTION_ID }}')) { $missing += 'AZURE_SUBSCRIPTION_ID' } | |
| if ([string]::IsNullOrWhiteSpace('${{ secrets.AZURE_SIGNING_ACCOUNT_NAME }}')) { $missing += 'AZURE_SIGNING_ACCOUNT_NAME' } | |
| if ([string]::IsNullOrWhiteSpace('${{ secrets.AZURE_CERTIFICATE_PROFILE_NAME }}')) { $missing += 'AZURE_CERTIFICATE_PROFILE_NAME' } | |
| if ($missing.Count -gt 0) { | |
| Write-Error "Missing required secret(s): $($missing -join ', ')" | |
| exit 1 | |
| } | |
| - name: Download x64 Shared Artifact | |
| uses: actions/download-artifact@v8 | |
| with: | |
| name: raw-Windows-x64-shared-${{ github.run_id }} | |
| path: raw-shared-x64 | |
| - name: Download x86 Shared Artifact | |
| uses: actions/download-artifact@v8 | |
| with: | |
| name: raw-Windows-x86-shared-${{ github.run_id }} | |
| path: raw-shared-x86 | |
| - name: Download ARM64EC Shared Artifact | |
| uses: actions/download-artifact@v8 | |
| with: | |
| name: raw-Windows-arm64ec-shared-${{ github.run_id }} | |
| path: raw-shared-arm64ec | |
| - name: Download Common Assets | |
| uses: actions/download-artifact@v8 | |
| with: | |
| name: openssl-common-assets-${{ github.run_id }} | |
| path: common-assets | |
| - name: Stage Multi-Arch Redistributables | |
| shell: pwsh | |
| run: | | |
| $ErrorActionPreference = 'Stop' | |
| $redist = "$env:GITHUB_WORKSPACE\redist" | |
| foreach ($arch in @('x64', 'x86', 'arm64ec')) { | |
| $archDir = "$redist\$arch" | |
| New-Item -ItemType Directory -Force -Path $archDir, "$archDir\providers", "$archDir\engines" | Out-Null | |
| $src = "$env:GITHUB_WORKSPACE\raw-shared-$arch" | |
| Copy-Item "$src\openssl.exe" $archDir -ErrorAction SilentlyContinue | |
| Get-ChildItem $src -Filter 'libcrypto-*.dll' | Copy-Item -Destination $archDir | |
| Get-ChildItem $src -Filter 'libssl-*.dll' | Copy-Item -Destination $archDir | |
| if (Test-Path "$src\providers") { | |
| Get-ChildItem "$src\providers" -Filter '*.dll' | Copy-Item -Destination "$archDir\providers" | |
| } | |
| if (Test-Path "$src\engines") { | |
| Get-ChildItem "$src\engines" -Filter '*.dll' | Copy-Item -Destination "$archDir\engines" | |
| } | |
| } | |
| Copy-Item "common-assets\LICENSE.txt" $redist -ErrorAction SilentlyContinue | |
| Write-Host "=== Multi-Arch Redistributables Staged ===" | |
| Get-ChildItem $redist -Recurse | Select-Object -ExpandProperty FullName | |
| - name: Install InnoSetup | |
| shell: pwsh | |
| run: | | |
| choco install innosetup --no-progress -y | |
| if ($LASTEXITCODE -ne 0) { Write-Error 'Failed to install InnoSetup'; exit 1 } | |
| - name: Generate and Compile Multi-Arch Installer | |
| shell: pwsh | |
| env: | |
| APP_PUBLISHER: ${{ vars.PUBLISHER_DISPLAY_NAME || 'TaurusTLS Developers' }} | |
| APP_PUBLISHER_URL: ${{ vars.PUBLISHER_URL || 'https://github.com/TaurusTLS-Developers/OpenSSL-Distribution' }} | |
| run: | | |
| $ErrorActionPreference = 'Stop' | |
| $version = '${{ needs.validate-version.outputs.version }}' | |
| $appId = 'B3E8F2A1-C4D7-4E9F-B6A2-F7E9D4C7B0A5' | |
| $wsDir = $env:GITHUB_WORKSPACE | |
| $redistDir = "$wsDir\redist" | |
| $assetsDir = "$wsDir\assets" | |
| $pubName = $env:APP_PUBLISHER | |
| $pubUrl = $env:APP_PUBLISHER_URL | |
| New-Item -ItemType Directory -Force -Path "$wsDir\installers" | Out-Null | |
| # Substitute template parameters | |
| $templatePath = "$wsDir\config\openssl-installer.iss.template" | |
| $outFilename = "openssl-$version-Windows-installer" | |
| $iss = Get-Content $templatePath -Raw | |
| $iss = $iss.Replace('{{APP_ID}}', "{{$appId}") | |
| $iss = $iss.Replace('{{VERSION}}', $version) | |
| $iss = $iss.Replace('{{APP_PUBLISHER}}', $pubName) | |
| $iss = $iss.Replace('{{PUBLISHER_DISPLAY_NAME}}', $pubName) | |
| $iss = $iss.Replace('{{APP_PUBLISHER_URL}}', $pubUrl) | |
| $iss = $iss.Replace('{{OUTPUT_DIR}}', "$wsDir\installers") | |
| $iss = $iss.Replace('{{OUTPUT_BASE_FILENAME}}', $outFilename) | |
| $iss = $iss.Replace('{{REDIST_DIR}}', $redistDir) | |
| $iss = $iss.Replace('{{ASSETS_DIR}}', $assetsDir) | |
| $issPath = "$wsDir\openssl-installer.iss" | |
| [IO.File]::WriteAllText($issPath, $iss, [Text.UTF8Encoding]::new($false)) | |
| Write-Host "=== Generated Multi-Arch .iss ===" | |
| Get-Content $issPath | |
| $iscc = Get-ChildItem 'C:\Program Files (x86)\Inno Setup*' -Filter 'ISCC.exe' -Recurse -ErrorAction SilentlyContinue | | |
| Select-Object -ExpandProperty FullName -First 1 | |
| if (-not $iscc) { | |
| $isccCmd = Get-Command iscc.exe -ErrorAction SilentlyContinue | |
| if ($isccCmd) { $iscc = $isccCmd.Source } | |
| } | |
| if (-not $iscc) { Write-Error 'ISCC.exe not found'; exit 1 } | |
| Write-Host "ISCC: $iscc" | |
| & $iscc $issPath | |
| if ($LASTEXITCODE -ne 0) { Write-Error "InnoSetup failed (exit $LASTEXITCODE)"; exit 1 } | |
| Write-Host "✅ Multi-Arch InnoSetup installer compiled." | |
| - name: Azure Login | |
| uses: azure/login@v3 | |
| with: | |
| creds: '{"clientId":"${{ secrets.AZURE_CLIENT_ID }}","clientSecret":"${{ secrets.AZURE_CLIENT_SECRET }}","subscriptionId":"${{ secrets.AZURE_SUBSCRIPTION_ID }}","tenantId":"${{ secrets.AZURE_TENANT_ID }}"}' | |
| - name: Sign Installer (Azure Artifact Signing) | |
| uses: azure/artifact-signing-action@v2 | |
| with: | |
| endpoint: ${{ secrets.AZURE_CODESIGNING_ENDPOINT || 'https://eus.codesigning.azure.net/' }} | |
| signing-account-name: ${{ secrets.AZURE_SIGNING_ACCOUNT_NAME }} | |
| certificate-profile-name: ${{ secrets.AZURE_CERTIFICATE_PROFILE_NAME }} | |
| files-folder: ${{ github.workspace }}\installers | |
| files-folder-filter: exe | |
| files-folder-recurse: false | |
| file-digest: SHA256 | |
| timestamp-rfc3161: http://timestamp.acs.microsoft.com | |
| timestamp-digest: SHA256 | |
| - name: Verify Signed Installer | |
| shell: pwsh | |
| run: | | |
| $files = Get-ChildItem "$env:GITHUB_WORKSPACE\installers\*" -Include *.exe | |
| if ($files.Count -eq 0) { Write-Error 'No installer file found to verify.'; exit 1 } | |
| foreach ($file in $files) { | |
| Write-Host "Verifying $($file.FullName)..." | |
| $sig = Get-AuthenticodeSignature -FilePath $file.FullName | |
| Write-Host " Status: $($sig.Status)" | |
| Write-Host " Subject: $($sig.SignerCertificate.Subject)" | |
| if ($sig.Status -ne 'Valid') { Write-Error "Invalid signature: $($sig.Status)"; exit 1 } | |
| } | |
| - name: Upload Multi-Arch Installer | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: openssl-${{ needs.validate-version.outputs.artifact_version }}-Windows-installer | |
| path: ${{ github.workspace }}\installers\openssl-${{ needs.validate-version.outputs.version }}-Windows-installer.exe | |
| archive: false | |
| retention-days: 5 | |
| # ========================================================================= | |
| # 3b. BUILD WINDOWS MSIX FRAMEWORK PACKAGES (Matrix) — Release Builds Only | |
| # ========================================================================= | |
| msix-windows-installers: | |
| name: Windows MSIX Framework (${{ matrix.arch }}) | |
| needs: [validate-version, build-common-assets, compile-binaries] | |
| if: needs.validate-version.outputs.is_fork == 'false' && inputs.build_type == 'release' | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - { arch: x64, msix_arch: x64 } | |
| - { arch: x86, msix_arch: x86 } | |
| - { arch: arm64ec, msix_arch: arm64 } | |
| runs-on: windows-latest | |
| permissions: | |
| id-token: write | |
| contents: read | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Validate Signing Secrets | |
| shell: pwsh | |
| run: | | |
| $missing = @() | |
| if ([string]::IsNullOrWhiteSpace('${{ secrets.AZURE_CLIENT_ID }}')) { $missing += 'AZURE_CLIENT_ID' } | |
| if ([string]::IsNullOrWhiteSpace('${{ secrets.AZURE_CLIENT_SECRET }}')) { $missing += 'AZURE_CLIENT_SECRET' } | |
| if ([string]::IsNullOrWhiteSpace('${{ secrets.AZURE_TENANT_ID }}')) { $missing += 'AZURE_TENANT_ID' } | |
| if ([string]::IsNullOrWhiteSpace('${{ secrets.AZURE_SUBSCRIPTION_ID }}')) { $missing += 'AZURE_SUBSCRIPTION_ID' } | |
| if ([string]::IsNullOrWhiteSpace('${{ secrets.AZURE_SIGNING_ACCOUNT_NAME }}')) { $missing += 'AZURE_SIGNING_ACCOUNT_NAME' } | |
| if ([string]::IsNullOrWhiteSpace('${{ secrets.AZURE_CERTIFICATE_PROFILE_NAME }}')) { $missing += 'AZURE_CERTIFICATE_PROFILE_NAME' } | |
| if ([string]::IsNullOrWhiteSpace('${{ secrets.AZURE_MSIX_PUBLISHER }}')) { $missing += 'AZURE_MSIX_PUBLISHER' } | |
| if ($missing.Count -gt 0) { | |
| Write-Error "Missing required secret(s): $($missing -join ', ')" | |
| exit 1 | |
| } | |
| - name: Download Shared Artifact | |
| uses: actions/download-artifact@v8 | |
| with: | |
| name: raw-Windows-${{ matrix.arch }}-shared-${{ github.run_id }} | |
| path: raw-shared | |
| - name: Download Common Assets | |
| uses: actions/download-artifact@v8 | |
| with: | |
| name: openssl-common-assets-${{ github.run_id }} | |
| path: common-assets | |
| - name: Build Framework MSIX Package | |
| shell: powershell | |
| env: | |
| MSIX_PUBLISHER: ${{ secrets.AZURE_MSIX_PUBLISHER }} | |
| APP_PUBLISHER: ${{ vars.PUBLISHER_DISPLAY_NAME || 'TaurusTLS Developers' }} | |
| run: | | |
| $ErrorActionPreference = 'Stop' | |
| $version = '${{ needs.validate-version.outputs.version }}' | |
| $arch = '${{ matrix.arch }}' | |
| $msixArch = '${{ matrix.msix_arch }}' | |
| $publisher = $env:MSIX_PUBLISHER | |
| $pubName = $env:APP_PUBLISHER | |
| $parts = $version -split '\.' | |
| $msixVer = "$($parts[0]).$($parts[1]).$($parts[2]).0" | |
| $wsDir = $env:GITHUB_WORKSPACE | |
| $staging = "$wsDir\msix-staging" | |
| $src = "$wsDir\raw-shared" | |
| # Locate MakeAppx.exe from Windows SDK | |
| $makeAppx = Get-ChildItem 'C:\Program Files (x86)\Windows Kits\10\bin' ` | |
| -Filter 'MakeAppx.exe' -Recurse -ErrorAction SilentlyContinue | | |
| Where-Object { $_.DirectoryName -match '\\x64$' } | | |
| Select-Object -ExpandProperty FullName -First 1 | |
| if (-not $makeAppx) { Write-Error 'MakeAppx.exe not found in Windows SDK'; exit 1 } | |
| New-Item -ItemType Directory -Force -Path "$staging\Assets", "$wsDir\installers" | Out-Null | |
| # Copy assets folder directly from repository (openssl-150x150.png, openssl-44x44.png, openssl-50x50.png) | |
| if (Test-Path "$wsDir\assets") { | |
| Copy-Item -Recurse -Force "$wsDir\assets" "$staging\" | |
| } else { | |
| Write-Error "Assets folder not found at $wsDir\assets"; exit 1 | |
| } | |
| # Copy redistributable binaries & license | |
| Copy-Item "$src\openssl.exe" $staging -ErrorAction SilentlyContinue | |
| Get-ChildItem $src -Filter 'libcrypto-*.dll' | Copy-Item -Destination $staging | |
| Get-ChildItem $src -Filter 'libssl-*.dll' | Copy-Item -Destination $staging | |
| if (Test-Path "$src\providers") { | |
| New-Item -ItemType Directory -Force -Path "$staging\providers" | Out-Null | |
| Get-ChildItem "$src\providers" -Filter '*.dll' | Copy-Item -Destination "$staging\providers" | |
| } | |
| if (Test-Path "$src\engines") { | |
| New-Item -ItemType Directory -Force -Path "$staging\engines" | Out-Null | |
| Get-ChildItem "$src\engines" -Filter '*.dll' | Copy-Item -Destination "$staging\engines" | |
| } | |
| Copy-Item "common-assets\LICENSE.txt" $staging -ErrorAction SilentlyContinue | |
| # Substitute AppxManifest template tokens | |
| $xmlPublisher = [System.Security.SecurityElement]::Escape($publisher) | |
| $xmlPubName = [System.Security.SecurityElement]::Escape($pubName) | |
| $templatePath = "$wsDir\config\AppxManifest.xml.template" | |
| $xml = Get-Content $templatePath -Raw | |
| $xml = $xml.Replace('{{MSIX_PUBLISHER}}', $xmlPublisher) | |
| $xml = $xml.Replace('{{MSIX_VERSION}}', $msixVer) | |
| $xml = $xml.Replace('{{MSIX_ARCH}}', $msixArch) | |
| $xml = $xml.Replace('{{VERSION}}', $version) | |
| $xml = $xml.Replace('{{ARCH}}', $arch) | |
| $xml = $xml.Replace('{{PUBLISHER_DISPLAY_NAME}}', $xmlPubName) | |
| [IO.File]::WriteAllText("$staging\AppxManifest.xml", $xml, [Text.UTF8Encoding]::new($false)) | |
| # Pack individual Framework .msix | |
| $msixOut = "$wsDir\installers\openssl-$version-Windows-$arch.msix" | |
| & $makeAppx pack /d $staging /p $msixOut /o | |
| if ($LASTEXITCODE -ne 0) { Write-Error "MakeAppx pack failed for $arch"; exit 1 } | |
| Write-Host "✅ Framework MSIX created: $msixOut" | |
| - name: Azure Login | |
| uses: azure/login@v3 | |
| with: | |
| creds: '{"clientId":"${{ secrets.AZURE_CLIENT_ID }}","clientSecret":"${{ secrets.AZURE_CLIENT_SECRET }}","subscriptionId":"${{ secrets.AZURE_SUBSCRIPTION_ID }}","tenantId":"${{ secrets.AZURE_TENANT_ID }}"}' | |
| - name: Sign MSIX Package (Azure Artifact Signing) | |
| uses: azure/artifact-signing-action@v2 | |
| with: | |
| endpoint: ${{ secrets.AZURE_CODESIGNING_ENDPOINT || 'https://eus.codesigning.azure.net/' }} | |
| signing-account-name: ${{ secrets.AZURE_SIGNING_ACCOUNT_NAME }} | |
| certificate-profile-name: ${{ secrets.AZURE_CERTIFICATE_PROFILE_NAME }} | |
| files-folder: ${{ github.workspace }}\installers | |
| files-folder-filter: msix | |
| files-folder-recurse: false | |
| file-digest: SHA256 | |
| timestamp-rfc3161: http://timestamp.acs.microsoft.com | |
| timestamp-digest: SHA256 | |
| - name: Verify Signed MSIX Package | |
| shell: pwsh | |
| run: | | |
| $files = Get-ChildItem "$env:GITHUB_WORKSPACE\installers\*" -Include *.msix | |
| if ($files.Count -eq 0) { Write-Error 'No .msix file found to verify.'; exit 1 } | |
| foreach ($file in $files) { | |
| Write-Host "Verifying $($file.FullName)..." | |
| $sig = Get-AuthenticodeSignature -FilePath $file.FullName | |
| Write-Host " Status: $($sig.Status)" | |
| Write-Host " Subject: $($sig.SignerCertificate.Subject)" | |
| if ($sig.Status -ne 'Valid') { Write-Error "Invalid signature on $($file.Name): $($sig.Status)"; exit 1 } | |
| } | |
| - name: Upload MSIX Framework Artifact | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: openssl-${{ needs.validate-version.outputs.artifact_version }}-Windows-${{ matrix.arch }}-msix | |
| path: ${{ github.workspace }}\installers\openssl-${{ needs.validate-version.outputs.version }}-Windows-${{ matrix.arch }}.msix | |
| archive: false | |
| retention-days: 5 | |
| # ========================================================================= | |
| # 4. PACKAGE RELEASE (Fan-In Matrix) | |
| # ========================================================================= | |
| package-release: | |
| name: Package (${{ matrix.label }} ${{ matrix.arch }}) | |
| needs: [validate-version, build-common-assets, compile-binaries] | |
| if: always() && !contains(needs.*.result, 'failure') && !contains(needs.*.result, 'cancelled') | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| # Windows packaging moved to ubuntu-latest for speed and native 'zip' support | |
| - { label: Windows, arch: x64, runner: ubuntu-latest } | |
| - { label: Windows, arch: x86, runner: ubuntu-latest } | |
| - { label: Windows, arch: arm64ec, runner: ubuntu-latest } | |
| # Linux & Android | |
| - { label: Linux, arch: x64, runner: ubuntu-latest } | |
| - { label: Linux, arch: arm64, runner: ubuntu-latest } | |
| - { label: Android, arch: arm64, runner: ubuntu-latest } | |
| - { label: Android, arch: arm, runner: ubuntu-latest } | |
| # Apple platforms MUST use macOS runners for lipo, otool, install_name_tool, and Apple strip | |
| - { label: macOS, arch: universal, runner: macos-14 } | |
| - { label: iOS, arch: arm64, runner: macos-14 } | |
| - { label: iOS, arch: sim-arm64, runner: macos-14 } | |
| runs-on: ${{ matrix.runner }} | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Download Common Assets | |
| uses: actions/download-artifact@v8 | |
| with: | |
| name: openssl-common-assets-${{ github.run_id }} | |
| path: common-assets | |
| - name: Download Raw Binaries (Standard) | |
| if: matrix.label != 'macOS' | |
| uses: actions/download-artifact@v8 | |
| with: | |
| pattern: raw-${{ matrix.label }}-${{ matrix.arch }}-*-${{ github.run_id }} | |
| path: raw-binaries | |
| merge-multiple: false | |
| - name: Download Raw Binaries (macOS Universal) | |
| if: matrix.label == 'macOS' | |
| uses: actions/download-artifact@v8 | |
| with: | |
| pattern: raw-macOS-*-${{ github.run_id }} | |
| path: raw-binaries | |
| merge-multiple: false | |
| - name: Merge Binaries and Assets | |
| if: matrix.label != 'macOS' | |
| shell: bash | |
| run: | | |
| # Merge contents of downloaded raw artifacts into dist/ | |
| # Standard behavior: download-artifact creates subdirectories named after artifacts. | |
| # Flattened behavior: If only one artifact exists, it may be extracted directly. | |
| HAS_RAW_SUBDIRS=false | |
| for d in raw-binaries/raw-*/; do | |
| if [ -d "$d" ]; then | |
| HAS_RAW_SUBDIRS=true | |
| break | |
| fi | |
| done | |
| if [ "$HAS_RAW_SUBDIRS" = true ]; then | |
| echo "Detected nested artifact structure. Merging subdirectories..." | |
| for d in raw-binaries/raw-*/; do | |
| if [ -d "$d" ]; then | |
| echo "Merging $d contents into dist/" | |
| cp -R "$d". dist/ | |
| fi | |
| done | |
| else | |
| echo "Detected flattened artifact structure. Merging raw-binaries/ directly..." | |
| cp -R raw-binaries/. dist/ | |
| fi | |
| - name: Rename macOS artifacts (remove run_id suffix) | |
| if: matrix.label == 'macOS' | |
| shell: bash | |
| run: | | |
| cd raw-binaries | |
| for d in raw-macOS-*-${{ github.run_id }}; do | |
| if [ -d "$d" ]; then | |
| mv "$d" "${d%-*}" | |
| fi | |
| done | |
| - name: Organize & Lipo (macOS Universal) | |
| if: matrix.label == 'macOS' | |
| shell: bash | |
| run: | | |
| # 1. Relocate and Strip individual architectures | |
| for arch in x64 arm64; do | |
| BASE_DIR="raw-binaries/raw-macOS-$arch-shared" | |
| find "$BASE_DIR" -type f \( -name "*.dylib" -o -name "*.so" -o -name "openssl" \) | while read -r target_file; do | |
| if [[ "$target_file" == *"openssl" ]]; then | |
| strip "$target_file" || true | |
| else | |
| strip -x "$target_file" || true | |
| fi | |
| if [[ "$target_file" == *".dylib" ]]; then | |
| install_name_tool -id "@rpath/$(basename "$target_file")" "$target_file" || true | |
| fi | |
| DEPS=$(otool -L "$target_file" 2>/dev/null | awk '/\/usr\/local\/lib\/lib/ {print $1}') | |
| for dep in $DEPS; do | |
| depname=$(basename "$dep") | |
| if [[ "$target_file" == *"engines"* ]] || [[ "$target_file" == *"providers"* ]]; then | |
| install_name_tool -change "$dep" "@loader_path/../$depname" "$target_file" || true | |
| else | |
| install_name_tool -change "$dep" "@loader_path/$depname" "$target_file" || true | |
| fi | |
| done | |
| install_name_tool -add_rpath "@executable_path" "$target_file" 2>/dev/null || true | |
| install_name_tool -add_rpath "@loader_path" "$target_file" 2>/dev/null || true | |
| done | |
| find "raw-binaries/raw-macOS-$arch-static" -type f -name "*.a" -exec strip -S {} + 2>/dev/null || true | |
| done | |
| # 2. Combine with Lipo | |
| mkdir -p dist/{engines,providers,lib/static} | |
| lipo_file() { | |
| local rel_path=$1 | |
| local dest_path=$2 | |
| local f_x86="raw-binaries/raw-macOS-x64-shared/$rel_path" | |
| local f_arm="raw-binaries/raw-macOS-arm64-shared/$rel_path" | |
| if [ ! -f "$f_x86" ]; then | |
| f_x86="raw-binaries/raw-macOS-x64-static/$rel_path" | |
| f_arm="raw-binaries/raw-macOS-arm64-static/$rel_path" | |
| fi | |
| if [ ! -f "$f_x86" ] || [ ! -f "$f_arm" ]; then return 0; fi | |
| lipo -create -output "$dest_path" "$f_x86" "$f_arm" | |
| } | |
| lipo_file "openssl" "dist/openssl" | |
| find raw-binaries/raw-macOS-x64-shared -maxdepth 1 -type f -name "*.dylib" | while read -r f; do | |
| lipo_file "$(basename "$f")" "dist/$(basename "$f")" | |
| done | |
| [ -d "raw-binaries/raw-macOS-x64-shared/engines" ] && find raw-binaries/raw-macOS-x64-shared/engines -type f -name "*.dylib" | while read -r f; do | |
| lipo_file "engines/$(basename "$f")" "dist/engines/$(basename "$f")" | |
| done | |
| [ -d "raw-binaries/raw-macOS-x64-shared/providers" ] && find raw-binaries/raw-macOS-x64-shared/providers -type f -name "*.dylib" | while read -r f; do | |
| lipo_file "providers/$(basename "$f")" "dist/providers/$(basename "$f")" | |
| done | |
| find raw-binaries/raw-macOS-x64-static/lib/static -type f -name "*.a" | while read -r f; do | |
| lipo_file "lib/static/$(basename "$f")" "dist/lib/static/$(basename "$f")" | |
| done | |
| cp raw-binaries/raw-macOS-x64-shared/install_symlinks.sh dist/ 2>/dev/null || true | |
| - name: Finalize Package | |
| shell: bash | |
| run: | | |
| # Copy Common Assets | |
| mkdir -p dist/include dist/doc | |
| cp -R common-assets/include/* dist/include/ 2>/dev/null || true | |
| cp -R common-assets/share/doc/* dist/doc/ 2>/dev/null || true | |
| # Copy License and README from common assets | |
| cp common-assets/LICENSE.txt dist/ 2>/dev/null || true | |
| cp common-assets/README.txt dist/ 2>/dev/null || true | |
| # Create Metadata | |
| if [ "${{ inputs.build_type }}" == "branch" ]; then | |
| echo "branch: ${{ needs.validate-version.outputs.slugified_version }}" > dist/version.txt | |
| else | |
| echo "${{ needs.validate-version.outputs.slugified_version }}" > dist/version.txt | |
| fi | |
| # Create Symlink Script (POSIX only, if not already present) | |
| if [ ! -f dist/install_symlinks.sh ]; then | |
| if [ "${{ matrix.label }}" == "Linux" ] || [ "${{ matrix.label }}" == "macOS" ]; then | |
| echo "#!/bin/sh" > dist/install_symlinks.sh | |
| echo "echo \"Restoring shared library symlinks...\"" >> dist/install_symlinks.sh | |
| cd dist | |
| for lib in libcrypto libssl; do | |
| if [ "${{ matrix.label }}" == "Linux" ]; then | |
| REAL_FILE=$(ls ${lib}.so.* 2>/dev/null | head -n 1) | |
| if [ -n "$REAL_FILE" ]; then | |
| echo "ln -sf $REAL_FILE ${lib}.so" >> install_symlinks.sh | |
| fi | |
| else | |
| REAL_FILE=$(ls ${lib}.*.dylib 2>/dev/null | head -n 1) | |
| if [ -n "$REAL_FILE" ]; then | |
| echo "ln -sf $REAL_FILE ${lib}.dylib" >> install_symlinks.sh | |
| fi | |
| fi | |
| done | |
| chmod +x install_symlinks.sh | |
| cd - > /dev/null | |
| fi | |
| fi | |
| find dist -type d -empty -delete | |
| ARCHIVE_NAME="openssl-${{ needs.validate-version.outputs.artifact_version }}-${{ matrix.label }}-${{ matrix.arch }}.zip" | |
| cd dist | |
| zip -r -y "../$ARCHIVE_NAME" . | |
| cd .. | |
| echo "ARCHIVE_FILE=$ARCHIVE_NAME" >> $GITHUB_ENV | |
| - name: Upload Final Archive | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: openssl-${{ needs.validate-version.outputs.artifact_version }}-${{ matrix.label }}-${{ matrix.arch }} | |
| path: ${{ env.ARCHIVE_FILE }} | |
| archive: false | |
| retention-days: 5 | |
| - name: Upload Build Metadata (Once) | |
| if: matrix.label == 'Linux' && matrix.arch == 'x64' && needs.validate-version.outputs.is_fork == 'false' | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: build-metadata | |
| path: dist/version.txt | |
| retention-days: 5 | |
| # ========================================================================= | |
| # 5. CLEANUP RAW ARTIFACTS | |
| # ========================================================================= | |
| cleanup-artifacts: | |
| name: Cleanup Intermediate Artifacts | |
| needs: [package-release, InnoSetup-windows-installer, msix-windows-installers] | |
| runs-on: ubuntu-latest | |
| if: | | |
| always() && | |
| inputs.keep_raw_artifacts != true && | |
| !contains(needs.*.result, 'failure') && | |
| !contains(needs.*.result, 'cancelled') | |
| permissions: | |
| actions: write | |
| steps: | |
| - name: Delete Raw and Common Artifacts | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| echo "Fetching artifacts for run ${{ github.run_id }}..." | |
| # Use gh api to list artifacts specifically for THIS run to avoid conflicts | |
| ARTIFACTS=$(gh api repos/${{ github.repository }}/actions/runs/${{ github.run_id }}/artifacts --paginate) | |
| # Filter for IDs of artifacts that start with 'raw-' or are 'openssl-common-assets' | |
| # We also verify the name ends with the run_id for triple-redundancy | |
| IDS=$(echo "$ARTIFACTS" | jq -r ".artifacts[] | select (.name | (startswith(\"raw-\") and endswith(\"-${{ github.run_id }}\")) or . == \"openssl-common-assets-${{ github.run_id }}\") | .id") | |
| if [ -z "$IDS" ] || [ "$IDS" == "null" ]; then | |
| echo "No intermediate artifacts found to delete." | |
| exit 0 | |
| fi | |
| for id in $IDS; do | |
| echo "Deleting artifact ID: $id" | |
| gh api -X DELETE repos/${{ github.repository }}/actions/artifacts/$id || echo "Failed to delete artifact ID: $id" ; true # Continue even if deletion fails | |
| done | |
| echo "✅ Cleanup complete." |