Client: build Apache.Thrift.Compiler targets net8.0 and set no RollForward, so on a machine with only .NET 9 or 10 the tool installed and then refused to start, asking for .NET 8. With RollForward=Major the launcher uses the next newer .NET when 8 is missing; where 8 is installed, nothing changes. test-dotnet-tool.ps1 now checks the runtimeconfig.json in the package. The CI runners have .NET 8 next to newer versions, so installing and running the tool there could not show the problem. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Windows helpers
Scripts used by the Windows compiler build in
.github/workflows/cmake.yml and by the
Windows packaging workflows, and usable on their own from a developer machine.
check-compiler-imports.ps1
Checks what a Windows executable imports. The thrift.exe published with a
release must depend only on Windows itself and on the Visual C++ runtime -
anything else, such as Boost, OpenSSL, zlib or libevent, would have to be
shipped alongside it, and the compiler is handed out as a single file.
PS C:\thrift> .\build\windows\check-compiler-imports.ps1 -Path C:\install\bin\thrift.exe
It finds dumpbin.exe itself through vswhere.exe, so it does not have to run
from a Visual Studio developer prompt. Pass -DumpBin to point it somewhere
else, and -AllowedImports to change the permitted list; wildcards are allowed,
which is how one entry covers the whole UCRT.
The executable must also not be statically linked. The project moved away
from a statically linked compiler in dd8ecde06, and
doc/ReleaseManagement.md states that the
released compiler requires the Visual C++ redistributable. A static build
imports strictly fewer DLLs, every one of them permitted, so the allow list on
its own would wave it through; -RequiredImport is what catches it. Pass
-RequiredImport '' when a static build really is what you want.
The exit code is 0 when everything is in order and 1 otherwise, so it can be used as a build step.
check-compiler-imports-tests.ps1
Tests for the above. They feed canned dumpbin output through the parser, so
they need neither a compiler nor Windows - any PowerShell will do:
$ pwsh build/windows/check-compiler-imports-tests.ps1
They exist because a check like this fails open in both directions: a parser that matches nothing reports every executable as clean, and an allow list that grew far enough rejects nothing. The tests pin down that a release build passes, that a static build and a third-party dependency are both rejected, and that the wildcard matching does what the default allow list relies on.
get-voted-compiler.ps1
Fetches the thrift-<version>.exe of a release candidate or a release from
dist.apache.org and checks it before anything is built from it: every
checksum file next to it (.sha512, .sha256) must match, at least one must be
there, and its detached signature must be good and made with a key from the
project's KEYS. Only dist.apache.org URLs naming a thrift-<version>.exe
are accepted.
PS C:\thrift> .\build\windows\get-voted-compiler.ps1 -Url https://dist.apache.org/repos/dist/release/thrift/0.25.0/thrift-0.25.0.exe -OutputDir voted
The installer and the .NET tool of a release have to contain the executable the
vote covered, and a new build of the same source is not the same bytes. The
Windows packages and
.NET tool workflows use this to get
that executable. -Path and -KeysPath run the same checks on files that are
already local. It needs gpg.
get-voted-compiler-tests.ps1
Tests for the above. They sign stand-in executables with a throwaway GPG key, so
they need gpg but no network:
$ pwsh build/windows/get-voted-compiler-tests.ps1
A signed executable with matching checksums passes. Refused are: one changed
after signing, also when its checksums were redone; one with a wrong checksum;
one signed by a key that is not in KEYS; one without a signature or without a
checksum; and URLs that are not on dist.apache.org or do not name a
thrift-<version>.exe.
build-installer.ps1
Packages a built thrift.exe into a Windows installer with
Inno Setup 6.3 or later, which is
preinstalled on the GitHub Windows runner images.
PS C:\thrift> .\build\windows\build-installer.ps1 -Version 0.26.0 -Compiler C:\install\bin\thrift.exe -OutputDir dist
It writes thrift-<version>-setup.exe into the output directory and prints its
path. ISCC.exe is located automatically; pass -Iscc to override. The
installer also carries LICENSE and NOTICE, taken from the checkout unless
-SourceRoot names another directory.
installer/thrift.iss
The Inno Setup script itself. It takes the version, the compiler and the output
directory on the command line, so no version number is stored in it and
build/veralign.sh has nothing extra to keep in step.
The installer installs per user by default and needs no elevation; an
administrator can install for all users from the wizard or with /ALLUSERS. It
optionally puts the install directory on PATH, and takes that entry out again
on uninstall.
The compiler links the shared runtime, so it needs the Microsoft Visual C++
redistributable, which the ASF cannot ship. An interactive install says so when
it cannot find vcruntime140.dll, rather than letting the first run fail with a
missing-DLL dialog. It never blocks, and a silent install - which is what a
package manager does - shows nothing; the WinGet and Chocolatey packages declare
the redistributable as a dependency instead, so their users never see it. They
ask for version 14.51 or later, because it has to be at least as new as the build
tools the compiler was built with; a version already installed only counts from
there on.
installer/test-installer.ps1
Installs and uninstalls a built installer silently and checks what it did:
that the compiler is there and runs, that the install directory landed on
PATH, and that uninstalling removed exactly that one PATH entry and nothing
else. Inno Setup can append to PATH on its own but cannot undo it, so the
removal is hand-written code in thrift.iss - the part of the installer most
worth testing.
It changes the PATH of the user it runs as, so run it in CI or on a throwaway
machine.
PS C:\thrift> .\build\windows\installer\test-installer.ps1 -Installer dist\thrift-0.26.0-setup.exe
-ExpectedVersion checks what the installed compiler reports, and
-ExpectedSha256 that the installed thrift.exe is the very file the installer
was built from.
build-dotnet-tool.ps1
Packs a built thrift.exe as a .NET tool,
published to NuGet as Apache.Thrift.Compiler.
PS C:\thrift> .\build\windows\build-dotnet-tool.ps1 -Version 0.26.0 -Compiler C:\install\bin\thrift.exe -OutputDir nupkg
As with the installer, the version is passed in rather than stored in the
project file, so build/veralign.sh gains nothing to keep in
step. Building without -Compiler fails rather than producing a package with no
compiler in it. LICENSE and NOTICE come from the checkout unless
-SourceRoot names another directory.
For a release, the .NET tool
workflow packs the voted thrift-<version>.exe from dist/release, fetched
and checked with get-voted-compiler.ps1, together with the LICENSE and
NOTICE of the release tag. Started by hand with compiler_url, it does the
same with the executable at that URL and publishes nothing.
dotnet-tool/
The tool project. A .NET tool package needs a managed entry point, so
Program.cs is a launcher that finds thrift.exe next to itself and hands over
to it - arguments, the standard streams and the exit code all pass straight
through.
.NET 10 can declare a native executable as the entry point directly, with no managed code, but a package built that way cannot be installed by the .NET 8 or .NET 9 SDK at all, and the MSBuild properties involved are undocumented. The launcher costs one small assembly and works everywhere.
The launcher targets .NET 8 and rolls forward to a newer major version when .NET 8 is not installed. Otherwise a machine with only .NET 9 or later would install the tool and then refuse to start it, asking for .NET 8.
The package carries the Windows compiler only. NuGet installs a tool package on any platform, so the launcher checks and reports that rather than failing to start a Windows executable.
dotnet-tool/test-dotnet-tool.ps1
Looks inside a packed .nupkg, then installs it into a throwaway directory and
runs it.
$ pwsh build/windows/dotnet-tool/test-dotnet-tool.ps1 -Package nupkg/Apache.Thrift.Compiler.0.26.0.nupkg -Version 0.26.0
Two things can go wrong with a tool package that wraps a native executable, and
neither shows up before somebody installs it: the payload can be missing, and
the layout can be such that the tool installs but does not run. So the test
checks the package contents - including that LICENSE and NOTICE really are
in there and are not empty - and then proves the installed command works.
It also checks that the launcher may roll forward to a newer .NET, because
running the tool on a machine that has .NET 8 as well cannot show that.
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. -ExpectedSha256 checks that the
bundled thrift.exe is the very file the package was built from.
build-chocolatey-package.ps1 and chocolatey/
Builds the Chocolatey package, so that the compiler
can be installed with choco install thrift.
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
the voted compiler, 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. For a release whose vote did not
cover the installer, -InstallerSource github takes the copy on the GitHub
release instead.
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, or an
unknown installer source, 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.
build-winget-manifests.ps1 and winget/
Renders the three manifest files a submission to
microsoft/winget-pkgs needs, from
the templates in winget/, so that the compiler can be installed with
winget install Apache.Thrift.
$ pwsh build/windows/build-winget-manifests.ps1 -Version 0.26.0
The manifest points at archive.apache.org, which keeps every release.
downloads.apache.org only carries the current one, so a manifest naming it
would stop working at the next release and take every older version in
winget-pkgs with it.
An installer the release vote did not cover is not on dist.apache.org, and so
not in the archive either. For such a release, -InstallerSource github points
the manifest at the copy on the GitHub release, whose URL is just as permanent.
Unless -Sha256 is given, the installer is downloaded from the very URL that
goes into the manifest and hashed, so the manifest cannot claim a checksum the
published file does not have. That also means this cannot be run before the
installer is there: for the archive, before the release has reached it.
winget/test-winget-manifests.ps1
Tests the renderer: that the values land where they belong, that no placeholder survives into a file that would be submitted verbatim, that the output is UTF-8 without a BOM and with LF endings, and that a malformed version, checksum or date, or an unknown installer source, is refused.
winget/validate_manifests.py
Validates rendered manifests against the WinGet JSON schemas, fetched from Microsoft for the schema version each manifest names. A manifest that does not match is otherwise rejected only after the pull request has been opened.
$ python3 -m pip install pyyaml jsonschema
$ python3 build/windows/winget/validate_manifests.py winget-manifests/manifests/a/Apache/Thrift/0.26.0