-
Notifications
You must be signed in to change notification settings - Fork 6.7k
Embed Windows resources (VERSIONINFO) during build #11048
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Signed-off-by: Babak K. Shandiz <babakks@github.com>
Signed-off-by: Babak K. Shandiz <babakks@github.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR adds support for embedding Windows VERSIONINFO resources into the .exe
build by generating .syso
files from a static metadata file.
- Introduces
script/winres.json
to hold placeholder metadata for Windows resources. - Adds
script/gen-winres.ps1
to generate and move architecture-specific.syso
files. - Updates
.goreleaser.yml
to invoke the PowerShell script before Windows builds.
Reviewed Changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
File | Description |
---|---|
script/winres.json | Defines structured placeholders for RT_GROUP_ICON, RT_MANIFEST, and RT_VERSION entries |
script/gen-winres.ps1 | PowerShell script that runs go-winres make and moves generated .syso files |
.goreleaser.yml | Added a before hook to run the generation script for embedding resources |
Comments suppressed due to low confidence (2)
script/gen-winres.ps1:39
- Use the correct variable casing in the error message (
$_winresJson
) to match the variable name and avoid confusion.
Write-Host "error: winres.json file not found at '$_winresjson'"
script/gen-winres.ps1:54
- Verify that the import path (
github.com/tc-hib/go-winres
) matches the intended module (the PR description mentionsgc-hib
). Update this path if it was mistyped.
go run github.com/tc-hib/go-winres@v0.3.3 make `
.goreleaser.yml
Outdated
@@ -11,6 +11,8 @@ before: | |||
{{ if eq .Runtime.Goos "windows" }}echo{{ end }} make manpages GH_VERSION={{.Version}} | |||
- >- # On linux the completions are used in nfpms below, but on macos they are used outside in the deployment build. | |||
{{ if eq .Runtime.Goos "windows" }}echo{{ end }} make completions | |||
- >- # We need to create the `.syso` files (per architecture) to embed Windows resources (version info) | |||
pwsh .\script\gen-winres.ps1 386,amd64,arm64 '{{ .Version }}' .\script\winres.json .\cmd\gh\ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wrap the new pwsh .\script\gen-winres.ps1
hook in a Windows-only conditional (e.g. {{ if eq .Runtime.Goos "windows" }}
) to prevent it from running on non-Windows targets and failing the build.
pwsh .\script\gen-winres.ps1 386,amd64,arm64 '{{ .Version }}' .\script\winres.json .\cmd\gh\ | |
{{ if eq .Runtime.Goos "windows" }}pwsh .\script\gen-winres.ps1 386,amd64,arm64 '{{ .Version }}' .\script\winres.json .\cmd\gh\{{ end }} |
Copilot uses AI. Check for mistakes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The .Runtime.Goos
holds the platform where the Goreleaser is invoked. So, it's not the target.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@babakks : you're correct: GOOS
in this case is the operating system of the runner, which isn't the target per se but it is because we Windows releases are built on the Windows runner.
That said, I think the before
hooks above short circuit the make
commands on the Windows runner, which would / should result in just echo
ing the commands without running them.
You can look back into #7324 where completions were only built for Windows and Mac OS:
Lines 8 to 13 in 8b987e2
before: | 8000|
hooks: | |
- >- | |
{{ if eq .Runtime.Goos "windows" }}echo{{ end }} make manpages GH_VERSION={{.Version}} | |
- >- | |
{{ if ne .Runtime.Goos "linux" }}echo{{ end }} make completions |
If these should only be present on Windows builds, then maybe this should be conditionalized for anything not Windows
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I noticed that, and it's annoying to see CI environment details are leaked into .goreleaser.yml
. Anyway, I updated the command to only work on Windows.
Signed-off-by: Babak K. Shandiz <babakks@github.com>
Signed-off-by: Babak K. Shandiz <babakks@github.com>
Don't leave FileVersion as 0.0.0.0, both version fields are strings, but are in windows UI parsed to numeric only, even if the value is non numeric, 2.49.0.1, please ignore the UI. Example for git.exe: PS C:\> [System.Diagnostics.FileVersionInfo]::GetVersionInfo("C:\Program Files\Git\bin\git.exe").ProductVersion
2.49.0.windows.1
PS C:\> [System.Diagnostics.FileVersionInfo]::GetVersionInfo("C:\Program Files\Git\bin\git.exe").FileVersion
2.49.0.windows.1
PS C:\> .NET binaries instead have data such as:
Edit: But in this case follow output of |
Thanks for the note, @caspChristian! 🙏 I can confirm that was just the GUI behaviour. Regrading the
So, I think we should pick a format that works for us. As you pointed out, currently, the output of $ gh version
gh version 2.74.0 (2025-05-29)
https://github.com/cli/cli/releases/tag/v2.74.0 The version and the build date are the only release-specific variables that we bake into the binary. So, I agree with your suggestion about using the
|
@andyfeller Since you somewhat defined the A/C for this, can you please confirm the suggested format of |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
praise: excellent PR write up!
Handful of questions and concerns I'd like to follow up on regarding automation and the underlying tool. 🤔
.goreleaser.yml
Outdated
@@ -11,6 +11,8 @@ before: | |||
{{ if eq .Runtime.Goos "windows" }}echo{{ end }} make manpages GH_VERSION={{.Version}} | |||
- >- # On linux the completions are used in nfpms below, but on macos they are used outside in the deployment build. | |||
{{ if eq .Runtime.Goos "windows" }}echo{{ end }} make completions | |||
- >- # We need to create the `.syso` files (per architecture) to embed Windows resources (version info) | |||
pwsh .\script\gen-winres.ps1 386,amd64,arm64 '{{ .Version }}' .\script\winres.json .\cmd\gh\ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@babakks : you're correct: GOOS
in this case is the operating system of the runner, which isn't the target per se but it is because we Windows releases are built on the Windows runner.
That said, I think the before
hooks above short circuit the make
commands on the Windows runner, which would / should result in just echo
ing the commands without running them.
You can look back into #7324 where completions were only built for Windows and Mac OS:
Lines 8 to 13 in 8b987e2
before: | |
hooks: | |
- >- | |
{{ if eq .Runtime.Goos "windows" }}echo{{ end }} make manpages GH_VERSION={{.Version}} | |
- >- | |
{{ if ne .Runtime.Goos "linux" }}echo{{ end }} make completions |
If these should only be present on Windows builds, then maybe this should be conditionalized for anything not Windows
script/gen-winres.ps1
Outdated
# (3-component). If we populate the `--file-version` with our semver value, then | ||
# a zero component will be added to the end, which is not what we want. | ||
|
||
go run github.com/tc-hib/go-winres@v0.3.3 make ` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
issue: this project does not appear to be actively maintained if even go.mod
hasn't been changed in a year.
I know this was the tool that @iamazeem
used in experimenting on this issue. The only other suggestion I could offer is https://github.com/josephspurrier/goversioninfo, which has more recent releases and appears to keep up with Dependabot security updates.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The reason I picked this one was it's simpler interface, especially regarding the setting of minimum OS version requirements. But that's a fair point. We're now using the one you mentioned. Also, now I'm thinking I don't agree with setting the min OS version.
script/winres.json
Outdated
"version": "" | ||
}, | ||
"description": "", | ||
"minimum-os": "win7", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggest: if we continue using this dependency, then we should consider updating this to Win10
"minimum-os": "win7", | |
"minimum-os": "win10", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now I believe it's not a good idea to state the minimum OS version in the metadata. One reason is that might be breaking change to users who are already using gh
on older platforms, and it's not nice to break their experience. The other reason is that, now that we're using the other library/tool, we have to manually create the manifest file (in which the OS requirement is listed), which is something that is hard to follow/maintain over time, because we'd have to update a list of UUIDs associated with different Windows versions, and I'd rather leave it.
Signed-off-by: Babak K. Shandiz <babakks@github.com>
Signed-off-by: Babak K. Shandiz <babakks@github.com>
Signed-off-by: Babak K. Shandiz <babakks@github.com>
Signed-off-by: Babak K. Shandiz <babakks@github.com>
Signed-off-by: Babak K. Shandiz <babakks@github.com>
@andyfeller I've now switched to Via GUIVia CLI
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good, @babakks; had just a couple of questions to clarify and will ship!
@@ -12,7 +12,7 @@ before: | |||
- >- # On linux the completions are used in nfpms below, but on macos they are used outside in the deployment build. | |||
{{ if eq .Runtime.Goos "windows" }}echo{{ end }} make completions | |||
- >- # We need to create the `.syso` files (per architecture) to embed Windows resources (version info) | |||
pwsh .\script\gen-winres.ps1 386,amd64,arm64 '{{ .Version }}' .\script\winres.json .\cmd\gh\ | |||
{{ if ne .Runtime.Goos "windows" }}echo{{ end }} pwsh .\script\gen-winres.ps1 '{{ .Version }} ({{time "2006-01-02"}})' '{{ .Version }}' .\script\versioninfo.template.json .\cmd\gh\ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
question: Is the current timestamp meant to take the place of the Build
portion of the file version?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, as I suggested here, it will look like 2.74.0 (2025-05-29)
, which is similar to gh version
output:
~ gh version
gh version 2.74.1 (2025-06-10)
https://github.com/cli/cli/releases/tag/v2.74.1
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Realizing my major concern about the command continuation was already addressed, this looks good to ship 👍
Co-authored-by: Andy Feller <andyfeller@github.com>
Fixes #10601
This PR adds another
Before
hook to our Goreleaser config to prepare Windows resources information (version info) as.syso
files to be embedded into our.exe
binary during build.There are a couple of notes regarding this implementation:
A new file, named
script/winres.json
is added to hold the static metadata (e.g. company, product name, etc). The version fields are left empty in this file, which will be later populated during the call to the newly addedscript/gen-winres.ps1
script. I didn't include the icon, though.The
script/gen-winres.ps1
is PowerShell script that creates.syso
files and moves them tocmd/gh
to be picked up by Go compiler. Leaving them at the root of the repo has no effect. Under the hood the script uses thegithub.com/gc-hib/go-winres@v0.3.3
module'smake
command, to creates multiple.syso
files per architecture. Thanks to the right naming (e.g.rsrc_windows_amd64.syso
), the Go compiler only picks the one that matches the target architecture. So, we can simply create all.syso
files at the beginning of the goreleaser session and leave the rest to Go compiler. Not to mention, leaving the files will have no effect when building for other platforms; although, we actually build them as separete jobs.The
FileVersion
field of the Windows resources structure is meant to be a 4-component version (e.g.1.1.1.1
). I tried to assign it with our 3-component semver version but a zero 4th component appears in the GUI (See below). So, I think it's best to leave theFileVersion
empty (which will be displayed as0.0.0.0
) and just populate theProductVersion
field, which is fine. I've explained this in the script. Please let me know if you think it's worth having theFileVersion
set.Verifying the binaries
To make sure the implementation is correct, I tried it with a simplified workflow that goes through the same steps as our deployment workflow, and then uploads the content of the
dist
directory as artifacts so you can download it and check the.exe
files. The workflow file is included at the end. You can run it on your own forks. However, make sure you're building the tag created from the last commit, otherwise the latest changes to the.goreleaser.yml
file will not be used. By the way, this is the same workflow, ran on my own fork, and it's how the created.exe
looks:For checking the
.exe
files, make sure you do it on Windows (through GUI or PowerShell). I don't know about macOS, but if you try PowerShell on Linux it will return an empty data structure (I learned this the hard way, 😄). Anyway, this is the PowerShell command to check the metadata being embedded:Sample workflow