Skip to content

Commit 08775ad

Browse files
Ilanlidoclaude
andcommitted
CM-68554: Build and sign Windows onedir executable
Enable the windows-2022/onedir matrix entry, parametrize the signing and test steps for the onedir exe path, and sign only the unsigned PE files under _internal (vendor-signed binaries keep their original signatures). The onedir archive is named cycode-win-onedir.zip (no .exe infix). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 1342add commit 08775ad

2 files changed

Lines changed: 39 additions & 12 deletions

File tree

.github/workflows/build_executable.yml

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,6 @@ jobs:
2626
exclude:
2727
- os: ubuntu-22.04
2828
mode: onedir
29-
- os: windows-2022
30-
mode: onedir
3129

3230
runs-on: ${{ matrix.os }}
3331

@@ -249,18 +247,46 @@ jobs:
249247
C:\Windows\System32\certutil.exe -csp "DigiCert Signing Manager KSP" -key -user
250248
smctl windows certsync --keypair-alias=%SM_KEYPAIR_ALIAS%
251249
252-
:: sign executable
253-
signtool.exe sign /sha1 %SM_CODE_SIGNING_CERT_SHA1_HASH% /tr http://timestamp.digicert.com /td SHA256 /fd SHA256 ".\dist\cycode-cli.exe"
250+
:: sign executable (in onedir mode the exe lives inside the collected directory)
251+
set "EXE_PATH=.\dist\cycode-cli.exe"
252+
if "${{ matrix.mode }}"=="onedir" set "EXE_PATH=.\dist\cycode-cli\cycode-cli.exe"
253+
signtool.exe sign /sha1 %SM_CODE_SIGNING_CERT_SHA1_HASH% /tr http://timestamp.digicert.com /td SHA256 /fd SHA256 "%EXE_PATH%"
254+
255+
- name: Sign unsigned onedir binaries (Windows)
256+
if: runner.os == 'Windows' && matrix.mode == 'onedir'
257+
shell: powershell
258+
env:
259+
SM_HOST: ${{ secrets.SM_HOST }}
260+
SM_API_KEY: ${{ secrets.SM_API_KEY }}
261+
SM_CLIENT_CERT_PASSWORD: ${{ secrets.SM_CLIENT_CERT_PASSWORD }}
262+
SM_CODE_SIGNING_CERT_SHA1_HASH: ${{ secrets.SM_CODE_SIGNING_CERT_SHA1_HASH }}
263+
run: |
264+
# Vendor binaries (PSF-signed stdlib .pyds, python3xx.dll, Microsoft VC runtime) already
265+
# carry valid signatures; re-signing would replace them with ours. Sign only the unsigned
266+
# ones (PyInstaller-generated and third-party wheel binaries).
267+
$files = Get-ChildItem -Path dist\cycode-cli\_internal -Recurse -Include *.dll,*.pyd,*.exe |
268+
Where-Object { (Get-AuthenticodeSignature $_.FullName).Status -eq 'NotSigned' }
269+
if (-not $files) {
270+
Write-Host 'No unsigned binaries found'
271+
exit 0
272+
}
273+
Write-Host "Signing $($files.Count) unsigned binaries:"
274+
$files.FullName | Write-Host
275+
signtool.exe sign /sha1 $env:SM_CODE_SIGNING_CERT_SHA1_HASH /tr http://timestamp.digicert.com /td SHA256 /fd SHA256 @($files.FullName)
276+
exit $LASTEXITCODE
254277
255278
- name: Test Windows signed executable
256279
if: runner.os == 'Windows'
257280
shell: cmd
258281
run: |
282+
set "EXE_PATH=.\dist\cycode-cli.exe"
283+
if "${{ matrix.mode }}"=="onedir" set "EXE_PATH=.\dist\cycode-cli\cycode-cli.exe"
284+
259285
:: call executable and expect correct output
260-
.\dist\cycode-cli.exe status
286+
"%EXE_PATH%" status
261287
262288
:: verify signature
263-
signtool.exe verify /v /pa ".\dist\cycode-cli.exe"
289+
signtool.exe verify /v /pa "%EXE_PATH%"
264290
265291
- name: Prepare files for artifact and release (rename and calculate sha256)
266292
run: echo "ARTIFACT_NAME=$(./process_executable_file.py dist/cycode-cli)" >> $GITHUB_ENV

process_executable_file.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
_OS_TO_CLI_DIST_TEMPLATE = {
2323
'darwin': Template('cycode-mac$suffix$ext'),
2424
'linux': Template('cycode-linux$suffix$ext'),
25-
'windows': Template('cycode-win$suffix.exe$ext'),
25+
'windows': Template('cycode-win$suffix$ext'),
2626
}
2727
_WINDOWS = 'windows'
2828
_WINDOWS_EXECUTABLE_SUFFIX = '.exe'
@@ -87,8 +87,7 @@ def get_cli_file_name(suffix: str = '', ext: str = '') -> str:
8787
if os_name not in _OS_TO_CLI_DIST_TEMPLATE:
8888
raise Exception(f'Unsupported OS: {os_name}')
8989

90-
template = _OS_TO_CLI_DIST_TEMPLATE[os_name]
91-
return template.substitute(suffix=suffix, ext=ext)
90+
return _OS_TO_CLI_DIST_TEMPLATE[os_name].substitute(suffix=suffix, ext=ext)
9291

9392

9493
def get_cli_file_suffix(is_onedir: bool) -> str:
@@ -117,23 +116,25 @@ def write_hashes_db_to_file(hashes: DirHashes, output_path: str) -> None:
117116

118117

119118
def get_cli_filename(is_onedir: bool) -> str:
120-
return get_cli_file_name(get_cli_file_suffix(is_onedir))
119+
# onedir is distributed as an archive of a directory, so only onefile carries .exe
120+
ext = _WINDOWS_EXECUTABLE_SUFFIX if get_os_name() == _WINDOWS and not is_onedir else ''
121+
return get_cli_file_name(suffix=get_cli_file_suffix(is_onedir), ext=ext)
121122

122123

123124
def get_cli_path(output_path: Path, is_onedir: bool) -> str:
124125
return os.path.join(output_path, get_cli_filename(is_onedir))
125126

126127

127128
def get_cli_hash_filename(is_onedir: bool) -> str:
128-
return get_cli_file_name(suffix=get_cli_file_suffix(is_onedir), ext=_HASH_FILE_EXT)
129+
return get_cli_filename(is_onedir) + _HASH_FILE_EXT
129130

130131

131132
def get_cli_hash_path(output_path: Path, is_onedir: bool) -> str:
132133
return os.path.join(output_path, get_cli_hash_filename(is_onedir))
133134

134135

135136
def get_cli_archive_filename(is_onedir: bool) -> str:
136-
return get_cli_file_name(suffix=get_cli_file_suffix(is_onedir))
137+
return get_cli_filename(is_onedir)
137138

138139

139140
def get_cli_archive_path(output_path: Path, is_onedir: bool) -> str:

0 commit comments

Comments
 (0)