Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
190 changes: 190 additions & 0 deletions .github/workflows/chocolatey.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#
# Packages the Windows Thrift compiler for Chocolatey, so that it can be
# installed with "choco install thrift".
#
# The package downloads the installer from archive.apache.org rather than
# carrying a copy, so it cannot be built until the release has been promoted
# and the archive has picked it up. If the release run is too early, run it
# again from the Actions tab once the archive has the file.
#
# Nothing is pushed until the CHOCO_API_KEY secret exists. The "thrift" id on
# the Chocolatey community repository currently belongs to an abandoned third
# party package, and a maintainer takeover has to be granted first. See
# doc/ReleaseManagement.md.

name: "Chocolatey"

on:
release:
types: [published]
pull_request:
branches:
- master
paths:
- ".github/workflows/chocolatey.yml"
- "build/windows/chocolatey/**"
- "build/windows/build-chocolatey-package.ps1"
- "doc/ReleaseManagement.md"
workflow_dispatch:
inputs:
version:
description: "Released version to package, for example 0.26.0. Taken from CMakeLists.txt when empty."
required: false
type: string

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

permissions:
contents: read

jobs:
package:
name: Package
# choco pack needs Chocolatey, which is only on the Windows images.
runs-on: windows-2025
timeout-minutes: 30
permissions:
contents: read
outputs:
version: ${{ steps.version.outputs.version }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

- name: Test the package builder
shell: pwsh
run: ./build/windows/chocolatey/test-chocolatey-package.ps1

- name: Determine the version
id: version
shell: pwsh
env:
RELEASE_TAG: ${{ github.event.release.tag_name }}
INPUT_VERSION: ${{ inputs.version }}
run: |
$version = ''
if ($env:INPUT_VERSION) {
$version = $env:INPUT_VERSION
}
elseif ($env:RELEASE_TAG) {
$version = $env:RELEASE_TAG -replace '^v', ''
}
else {
foreach ($line in Get-Content CMakeLists.txt) {
if ($line -match '^\s*set\(thrift_VERSION\s+"(\d+\.\d+\.\d+)"\)') {
$version = $Matches[1]
break
}
}
}
if ($version -notmatch '^\d+\.\d+\.\d+$') {
Write-Error "Could not determine a major.minor.patch version (got '$version')."
exit 1
}

Write-Host "Version: $version"
"version=$version" | Out-File -FilePath $env:GITHUB_OUTPUT -Append

- name: Build the package
shell: pwsh
env:
VERSION: ${{ steps.version.outputs.version }}
IS_RELEASE: ${{ github.event_name == 'release' }}
run: |
if ($env:IS_RELEASE -eq 'true') {
# No checksum given, so the published installer is downloaded and
# hashed. The package then cannot record a checksum the published
# file does not have.
./build/windows/build-chocolatey-package.ps1 -Version $env:VERSION -OutputDir chocolatey-package
}
else {
# Nothing is published for a pull request, so this packages an
# obviously fake checksum. It proves the templates render and that
# choco pack accepts the result; it is not pushable, and the job
# does not upload it.
Write-Host '::notice::Packaging with a placeholder checksum; this run cannot push.'
./build/windows/build-chocolatey-package.ps1 -Version $env:VERSION `
-Sha256 ('DEADBEEF' * 8) -OutputDir chocolatey-package
}

- name: Check the packed package
shell: pwsh
env:
VERSION: ${{ steps.version.outputs.version }}
run: |
./build/windows/chocolatey/test-chocolatey-package.ps1 `
-Package "chocolatey-package/thrift.$env:VERSION.nupkg"

- name: Archive the package
if: ${{ github.event_name != 'pull_request' }}
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: chocolatey-package
if-no-files-found: error
path: chocolatey-package/*.nupkg
retention-days: 14

push:
name: Push to Chocolatey
needs: package
if: ${{ github.event_name == 'release' && !github.event.release.prerelease }}
runs-on: windows-2025
timeout-minutes: 20
environment: release
permissions:
contents: read
steps:
- name: Download the package
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: chocolatey-package
path: chocolatey-package

- name: Push
shell: pwsh
env:
CHOCO_API_KEY: ${{ secrets.CHOCO_API_KEY }}
VERSION: ${{ needs.package.outputs.version }}
run: |
$package = "chocolatey-package/thrift.$env:VERSION.nupkg"

if (-not $env:CHOCO_API_KEY) {
# Expected until the "thrift" id has been handed over. The package
# is built and attached to the run either way.
Write-Host '::warning::CHOCO_API_KEY is not set, so nothing was pushed to Chocolatey.'
@(
'### Chocolatey push skipped',
'',
'The `CHOCO_API_KEY` secret is not set in the `release` environment, so the',
'package was built but not pushed. It is attached to this run as the',
'`chocolatey-package` artifact.',
'',
'The `thrift` id on the Chocolatey community repository still belongs to an',
'abandoned third party package; a maintainer takeover has to be granted before',
'anything can be pushed under it. See `doc/ReleaseManagement.md`.'
) | Out-File -FilePath $env:GITHUB_STEP_SUMMARY -Append
exit 0
}

choco push $package --source https://push.chocolatey.org/ --api-key $env:CHOCO_API_KEY
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
29 changes: 29 additions & 0 deletions build/windows/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -160,3 +160,32 @@ in there and are not empty - and then proves the installed command works.
It runs anywhere PowerShell and the .NET SDK do. Off Windows it checks the
launcher's refusal instead of the compiler's output, which is the behaviour that
keeps a Linux user from a confusing failure.

## `build-chocolatey-package.ps1` and `chocolatey/`

Builds the [Chocolatey](https://chocolatey.org/) package, so that the compiler
can be installed with `choco install thrift`.

```powershell
PS C:\thrift> .\build\windows\build-chocolatey-package.ps1 -Version 0.26.0
```

The package does not carry the compiler. It downloads the installer published
with the release and runs it silently for all users, so what a user installs is
what was voted on and signed, and no binary is redistributed through a third
party CDN. As with the WinGet manifest, the URL is `archive.apache.org` and the
checksum is computed from the file at that very URL, so the package cannot
record a checksum the published file does not have.

Pass `-StageOnly` to render the package without packing it, which works on a
machine that has no Chocolatey.

### `chocolatey/test-chocolatey-package.ps1`

Tests the builder: that the URL, checksum and silent install arguments reach
the install script, that no placeholder survives, that `LICENSE` and `NOTICE`
are staged and not empty, and that a malformed version or checksum is refused.
Given `-Package`, it also looks inside a packed `.nupkg` - including that it
carries no executable, since the compiler is downloaded at install time.

Runs anywhere PowerShell does; only the `choco pack` step needs Windows.
172 changes: 172 additions & 0 deletions build/windows/build-chocolatey-package.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#

<#
.SYNOPSIS
Builds the Chocolatey package for the Windows Thrift compiler.

.DESCRIPTION
Renders the templates in build/windows/chocolatey and runs "choco pack".

The package does not carry the compiler. It downloads the installer
published with the release and runs it, so that what a user installs is
what was voted on and signed. Unless a checksum is given, it is computed
from the file at that very URL, which means this cannot run before the
release has reached the archive.

.PARAMETER Version
The released version, for example 0.26.0.

.PARAMETER InstallerUrl
Where the installer is published. Defaults to the Apache archive, which
keeps every release; downloads.apache.org only carries the current one.

.PARAMETER Sha256
The installer's SHA-256. Computed from the downloaded file when omitted.

.PARAMETER OutputDir
Where the staged package and the .nupkg are written.

.PARAMETER SourceRoot
Root of the Thrift source tree, used for LICENSE and NOTICE. Defaults to
the checkout this script lives in.

.PARAMETER StageOnly
Render the package without packing it, so that the generated files can be
reviewed on a machine that has no Chocolatey.

.EXAMPLE
pwsh build/windows/build-chocolatey-package.ps1 -Version 0.26.0
#>

[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
[string] $Version,
[string] $InstallerUrl = '',
[string] $Sha256 = '',
[string] $OutputDir = 'chocolatey-package',
[string] $SourceRoot = '',
[switch] $StageOnly
)

$ErrorActionPreference = 'Stop'

if ($Version -notmatch '^\d+\.\d+\.\d+$') {
throw "Version '$Version' is not major.minor.patch."
}
if (-not $InstallerUrl) {
$InstallerUrl = "https://archive.apache.org/dist/thrift/$Version/thrift-$Version-setup.exe"
}
if (-not $SourceRoot) {
$SourceRoot = (Resolve-Path (Join-Path $PSScriptRoot '..\..')).Path
}
foreach ($required in @('LICENSE', 'NOTICE')) {
if (-not (Test-Path -LiteralPath (Join-Path $SourceRoot $required))) {
throw "$required not found below -SourceRoot $SourceRoot"
}
}

if ($Sha256) {
if ($Sha256 -notmatch '^[0-9a-fA-F]{64}$') {
throw "Sha256 '$Sha256' is not 64 hexadecimal digits."
}
$Sha256 = $Sha256.ToUpperInvariant()
}
else {
Write-Host "Downloading $InstallerUrl"
$download = Join-Path ([System.IO.Path]::GetTempPath()) ([System.Guid]::NewGuid().ToString('N') + '.exe')
try {
try {
Invoke-WebRequest -Uri $InstallerUrl -OutFile $download -MaximumRedirection 5
}
catch {
throw ("Could not download $InstallerUrl : " + $_.Exception.Message + "`n" +
'If the release was just promoted, the Apache archive may not have picked it up yet. Wait and try again.')
}
$Sha256 = (Get-FileHash -Algorithm SHA256 -LiteralPath $download).Hash.ToUpperInvariant()
Write-Host " sha256: $Sha256"
}
finally {
Remove-Item -LiteralPath $download -Force -ErrorAction SilentlyContinue
}
}

$templateDir = Join-Path $PSScriptRoot 'chocolatey'
$stageDir = Join-Path $OutputDir 'staging'
if (Test-Path -LiteralPath $stageDir) {
Remove-Item -LiteralPath $stageDir -Recurse -Force
}
New-Item -Path (Join-Path $stageDir 'tools') -ItemType Directory -Force | Out-Null

$substitutions = @{
'__VERSION__' = $Version
'__INSTALLER_URL__' = $InstallerUrl
'__INSTALLER_SHA256__' = $Sha256
}

foreach ($template in (Get-ChildItem -Path $templateDir -Filter '*.in' -Recurse)) {
$relative = $template.FullName.Substring($templateDir.Length).TrimStart('\', '/')
$target = Join-Path $stageDir ($relative -replace '\.in$', '')

$content = Get-Content -LiteralPath $template.FullName -Raw
foreach ($key in $substitutions.Keys) {
$content = $content.Replace($key, $substitutions[$key])
}

# A placeholder that survived would be published verbatim.
$leftovers = [regex]::Matches($content, '__[A-Z0-9_]+__')
if ($leftovers.Count -gt 0) {
throw ("$relative still holds placeholders after substitution: " +
(($leftovers | ForEach-Object { $_.Value } | Sort-Object -Unique) -join ', '))
}

New-Item -Path (Split-Path -Parent $target) -ItemType Directory -Force | Out-Null
[System.IO.File]::WriteAllText($target, $content, [System.Text.UTF8Encoding]::new($false))
Write-Host "Rendered $target"
}

# Chocolatey shows LICENSE.txt from the tools directory, and the ASF wants the
# notice to travel with the package either way.
Copy-Item -LiteralPath (Join-Path $SourceRoot 'LICENSE') -Destination (Join-Path $stageDir 'tools\LICENSE.txt') -Force
Copy-Item -LiteralPath (Join-Path $SourceRoot 'NOTICE') -Destination (Join-Path $stageDir 'tools\NOTICE.txt') -Force

if ($StageOnly) {
Write-Host "Staged in $stageDir"
Write-Output $stageDir
return
}

if (-not (Get-Command 'choco' -ErrorAction SilentlyContinue)) {
throw 'Chocolatey was not found. Install it from https://chocolatey.org/install, or pass -StageOnly.'
}

$outputPath = (Resolve-Path -LiteralPath $OutputDir).Path
& choco pack (Join-Path $stageDir 'thrift.nuspec') --output-directory $outputPath
if ($LASTEXITCODE -ne 0) {
throw "choco pack failed with exit code $LASTEXITCODE"
}

$package = Join-Path $outputPath "thrift.$Version.nupkg"
if (-not (Test-Path -LiteralPath $package)) {
throw "choco pack reported success but $package is not there."
}

Write-Host "Built $package"
Write-Output $package
Loading
Loading