Skip to content

Commit ccdf3e9

Browse files
authored
Add MSVC build script, fix build for MSVC and macOS, add GitHub Actions
1 parent 62ee1f0 commit ccdf3e9

8 files changed

Lines changed: 395 additions & 13 deletions

File tree

.github/workflows/build.yml

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
name: build-and-test
2+
3+
on:
4+
push:
5+
pull_request:
6+
workflow_dispatch:
7+
8+
jobs:
9+
build-test:
10+
name: ${{ matrix.os }}
11+
runs-on: ${{ matrix.os }}
12+
strategy:
13+
fail-fast: false
14+
matrix:
15+
os: [ubuntu-latest, windows-latest, macos-latest]
16+
17+
steps:
18+
- name: Checkout
19+
uses: actions/checkout@v6
20+
21+
- name: Build and test (Linux)
22+
if: runner.os == 'Linux'
23+
run: |
24+
make check
25+
make initrom userom
26+
27+
# macOS: Apple's linker has no -lrt and rejects the Makefile's -s flag, so
28+
# override CFLAGS/LDFLAGS. initrom/userom are skipped: they need SysV
29+
# shared memory, and OpenMP isn't bundled with Apple's clang.
30+
- name: Build and test (macOS)
31+
if: runner.os == 'macOS'
32+
run: |
33+
make check \
34+
CFLAGS="-Wall -O2 -fomit-frame-pointer -DSKIP_MEMZERO" \
35+
OMPFLAGS_MAYBE="" LDFLAGS=""
36+
37+
# Windows: build and test with the MSVC toolchain via build.ps1. The
38+
# "check" target builds tests.exe and phc-test.exe, runs them, and
39+
# verifies their output against TESTS-OK / PHC-TEST-OK-SHA256
40+
- name: Build and test (Windows)
41+
if: runner.os == 'Windows'
42+
shell: powershell
43+
run: .\build.ps1 -Target check

.gitignore

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Build artifacts (Unix Makefile)
2+
*.o
3+
/tests
4+
/phc-test
5+
/initrom
6+
/userom
7+
8+
# Build artifacts (MSVC / build.ps1)
9+
*.obj
10+
*.exe
11+
*.pdb
12+
*.ilk
13+
14+
# Test output
15+
TESTS-OUT
16+
PHC-TEST-OUT

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ all: $(PROJ)
4242
check: tests phc-test
4343
@echo 'Running main tests'
4444
@time ./tests | tee TESTS-OUT
45-
@diff -U0 TESTS-OK TESTS-OUT && echo PASSED || echo FAILED
45+
@diff -U0 TESTS-OK TESTS-OUT && echo PASSED || { echo FAILED; exit 1; }
4646
@if [ -f PHC-TEST-OK-SHA256 ]; then \
4747
echo 'Running PHC tests'; \
4848
time ./phc-test > PHC-TEST-OUT; \

README

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,23 @@ We do most of our testing on Linux systems with gcc. The supplied
125125
Makefile assumes that you use gcc.
126126

127127

128+
On Windows.
129+
130+
A PowerShell script, build.ps1, is provided to build yescrypt with the
131+
MSVC toolchain. It requires Visual Studio Community 2026 with the
132+
"Desktop development with C++" workload installed (this also provides
133+
vswhere.exe, which the script uses to locate the compiler automatically).
134+
From a PowerShell prompt in this directory, run:
135+
136+
powershell -ExecutionPolicy Bypass -File .\build.ps1 -Target check
137+
138+
This builds and runs tests.exe and phc-test.exe, printing "PASSED" and
139+
"PHC PASSED" on success. Other targets mirror the Makefile: "all"
140+
(default), "tests", "phc-test", "ref", "check-ref", and "clean". The
141+
"initrom" and "userom" programs are not built on Windows, as they rely on
142+
POSIX shared memory and mmap.
143+
144+
128145
ROM in SysV shared memory demo and benchmark.
129146

130147
Also included with this version of yescrypt are "initrom" and "userom"

build.ps1

Lines changed: 248 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,248 @@
1+
#requires -Version 5.1
2+
<#
3+
.COPYRIGHT
4+
Copyright 2013-2026 Alexander Peslyak
5+
Copyright 2026 CPUchain
6+
All rights reserved.
7+
8+
Redistribution and use in source and binary forms, with or without
9+
modification, are permitted.
10+
11+
THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
12+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
13+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
14+
ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
15+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
16+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
17+
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
18+
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
19+
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
20+
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
21+
SUCH DAMAGE.
22+
23+
.SYNOPSIS
24+
Build yescrypt with the MSVC toolchain (port of the supplied GNU Makefile).
25+
26+
.DESCRIPTION
27+
Locates a Visual Studio / Build Tools installation with the C++ compiler
28+
using vswhere.exe, imports its x64 environment, and compiles the yescrypt
29+
"tests" and "phc-test" programs with cl.exe / link.exe.
30+
31+
The Makefile's "initrom" and "userom" programs are intentionally NOT built:
32+
they rely on POSIX shared memory (sys/shm.h) and mmap, which have no MSVC
33+
equivalent. "tests" and "phc-test" are what "make check" exercises.
34+
35+
Targets (mirroring the Makefile):
36+
build.ps1 # build tests.exe and phc-test.exe (optimized)
37+
build.ps1 -Target check # build and run both, verify against known-good
38+
build.ps1 -Target ref # build using the reference implementation
39+
build.ps1 -Target check-ref
40+
build.ps1 -Target clean # remove build artifacts
41+
42+
.NOTES
43+
Requires Visual Studio 2026 (Community is fine) with the
44+
"Desktop development with C++" workload installed. See the README section
45+
"How to test yescrypt for proper operation." for details.
46+
#>
47+
[CmdletBinding()]
48+
param(
49+
[ValidateSet('all', 'tests', 'phc-test', 'check', 'ref', 'check-ref', 'clean')]
50+
[string]$Target = 'all'
51+
)
52+
53+
$ErrorActionPreference = 'Stop'
54+
Set-Location -LiteralPath $PSScriptRoot
55+
56+
# --- Toolchain detection via vswhere.exe ------------------------------------
57+
58+
function Find-VsWhere {
59+
$candidates = @(
60+
(Join-Path ${env:ProgramFiles(x86)} 'Microsoft Visual Studio\Installer\vswhere.exe'),
61+
(Join-Path $env:ProgramFiles 'Microsoft Visual Studio\Installer\vswhere.exe')
62+
)
63+
foreach ($c in $candidates) {
64+
if ($c -and (Test-Path -LiteralPath $c)) { return $c }
65+
}
66+
$cmd = Get-Command vswhere.exe -ErrorAction SilentlyContinue
67+
if ($cmd) { return $cmd.Source }
68+
throw "vswhere.exe not found. Install Visual Studio 2026 (it ships vswhere)."
69+
}
70+
71+
function Get-VcVarsPath {
72+
$vswhere = Find-VsWhere
73+
# Require the x64/x86 C++ compiler toolset.
74+
$installPath = & $vswhere -latest -products * `
75+
-requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 `
76+
-property installationPath
77+
if (-not $installPath) {
78+
throw "No Visual Studio installation with the C++ toolset was found. " +
79+
"Install the 'Desktop development with C++' workload."
80+
}
81+
$vcvars = Join-Path $installPath 'VC\Auxiliary\Build\vcvars64.bat'
82+
if (-not (Test-Path -LiteralPath $vcvars)) {
83+
throw "vcvars64.bat not found under '$installPath'."
84+
}
85+
return $vcvars
86+
}
87+
88+
# Import the MSVC x64 environment (PATH, INCLUDE, LIB, ...) into this session.
89+
function Import-VcEnvironment {
90+
$vcvars = Get-VcVarsPath
91+
Write-Host "Using MSVC environment: $vcvars"
92+
$marker = '___VCVARS_ENV___'
93+
# vcvars64.bat may emit benign stderr noise; don't let it abort the script.
94+
$lines = & cmd /c "`"$vcvars`" 2>nul && echo $marker && set"
95+
$seen = $false
96+
foreach ($line in $lines) {
97+
if (-not $seen) {
98+
if ($line.Trim() -eq $marker) { $seen = $true }
99+
continue
100+
}
101+
if ($line -match '^([^=]+)=(.*)$') {
102+
Set-Item -Path ("Env:" + $matches[1]) -Value $matches[2]
103+
}
104+
}
105+
if (-not $seen) { throw "Failed to import the MSVC environment." }
106+
}
107+
108+
# --- Compiler / linker settings (port of Makefile CFLAGS) -------------------
109+
110+
# /O2 -> -O2
111+
# /D__SSE2__ -> enable the SSE2 intrinsic code path (MSVC x64 supports
112+
# <emmintrin.h>; this path is free of GCC inline asm).
113+
# /DSKIP_MEMZERO -> matches the Makefile's -DSKIP_MEMZERO.
114+
# OpenMP is intentionally not enabled: it is only used by userom, which is not
115+
# built here.
116+
$CFLAGS = @('/nologo', '/O2', '/MT', '/D__SSE2__', '/DSKIP_MEMZERO', '/wd4146', '/wd4244')
117+
118+
$OBJS_COMMON = @('yescrypt-common.obj', 'sha256.obj', 'insecure_memzero.obj')
119+
$OBJS_CORE_OPT = 'yescrypt-opt.obj'
120+
$OBJS_CORE_REF = 'yescrypt-ref.obj'
121+
122+
function Invoke-Tool {
123+
param([string]$Exe, [string[]]$Arguments)
124+
Write-Host ">> $Exe $($Arguments -join ' ')"
125+
# Send tool output to the host so it doesn't pollute function return values.
126+
& $Exe @Arguments | Out-Host
127+
if ($LASTEXITCODE -ne 0) {
128+
throw "$Exe failed with exit code $LASTEXITCODE"
129+
}
130+
}
131+
132+
function Compile-One {
133+
param([string]$Source, [string]$Obj, [string[]]$ExtraFlags = @())
134+
if (-not $Obj) {
135+
$Obj = [System.IO.Path]::GetFileNameWithoutExtension($Source) + '.obj'
136+
}
137+
Invoke-Tool 'cl' (@('/c') + $CFLAGS + $ExtraFlags + @("/Fo$Obj", $Source))
138+
return $Obj
139+
}
140+
141+
function Link-Exe {
142+
param([string]$Out, [string[]]$Objs)
143+
Invoke-Tool 'link' (@('/nologo', "/OUT:$Out") + $Objs)
144+
}
145+
146+
# Compile the core + common objects shared by every program.
147+
function Build-Common {
148+
param([string]$Core = $OBJS_CORE_OPT)
149+
$coreSrc = if ($Core -eq $OBJS_CORE_REF) { 'yescrypt-ref.c' } else { 'yescrypt-opt.c' }
150+
Compile-One $coreSrc $Core | Out-Null
151+
Compile-One 'yescrypt-common.c' 'yescrypt-common.obj' | Out-Null
152+
Compile-One 'sha256.c' 'sha256.obj' | Out-Null
153+
Compile-One 'insecure_memzero.c' 'insecure_memzero.obj' | Out-Null
154+
}
155+
156+
function Build-Tests {
157+
param([string]$Core = $OBJS_CORE_OPT)
158+
Build-Common -Core $Core
159+
Compile-One 'tests.c' 'tests.obj' | Out-Null
160+
Link-Exe 'tests.exe' (@($Core) + $OBJS_COMMON + @('tests.obj'))
161+
Write-Host "Built tests.exe"
162+
}
163+
164+
function Build-PhcTest {
165+
param([string]$Core = $OBJS_CORE_OPT)
166+
Build-Common -Core $Core
167+
# phc-test is phc.c compiled with -DTEST (Makefile: phc-test.o).
168+
Compile-One 'phc.c' 'phc-test.obj' @('/DTEST') | Out-Null
169+
Link-Exe 'phc-test.exe' (@($Core) + $OBJS_COMMON + @('phc-test.obj'))
170+
Write-Host "Built phc-test.exe"
171+
}
172+
173+
# Read a file as text with CRLF normalized to LF (MSVC's text-mode output uses
174+
# CRLF; the known-good files use LF).
175+
function Get-NormalizedText {
176+
param([string]$Path)
177+
return (Get-Content -Raw -LiteralPath $Path) -replace "`r`n", "`n"
178+
}
179+
180+
function Invoke-Check {
181+
param([string]$Core = $OBJS_CORE_OPT)
182+
Build-Tests -Core $Core
183+
Build-PhcTest -Core $Core
184+
185+
Write-Host 'Running main tests'
186+
& .\tests.exe | Out-File -Encoding ascii -FilePath 'TESTS-OUT'
187+
if ($LASTEXITCODE -ne 0) { throw "tests.exe failed with exit code $LASTEXITCODE" }
188+
$expected = (Get-NormalizedText 'TESTS-OK').TrimEnd("`n")
189+
$actual = (Get-NormalizedText 'TESTS-OUT').TrimEnd("`n")
190+
if ($expected -eq $actual) {
191+
Write-Host 'PASSED' -ForegroundColor Green
192+
} else {
193+
Write-Host 'FAILED' -ForegroundColor Red
194+
Compare-Object ($expected -split "`n") ($actual -split "`n") |
195+
Format-Table -AutoSize | Out-String | Write-Host
196+
exit 1
197+
}
198+
199+
if (Test-Path -LiteralPath 'PHC-TEST-OK-SHA256') {
200+
Write-Host 'Running PHC tests'
201+
# Capture raw stdout (text mode -> CRLF), then verify the LF-normalized
202+
# SHA-256 against the known-good digest in PHC-TEST-OK-SHA256.
203+
& cmd /c ".\phc-test.exe > PHC-TEST-OUT 2>nul"
204+
if ($LASTEXITCODE -ne 0) { throw "phc-test.exe failed with exit code $LASTEXITCODE" }
205+
206+
$bytes = [System.Text.Encoding]::ASCII.GetBytes((Get-NormalizedText 'PHC-TEST-OUT'))
207+
$sha = [System.Security.Cryptography.SHA256]::Create()
208+
try {
209+
$got = -join ($sha.ComputeHash($bytes) | ForEach-Object { $_.ToString('x2') })
210+
} finally {
211+
$sha.Dispose()
212+
}
213+
$expectedHash = ((Get-Content -Raw 'PHC-TEST-OK-SHA256').Trim() -split '\s+')[0].ToLower()
214+
if ($got -eq $expectedHash) {
215+
Write-Host 'PHC PASSED' -ForegroundColor Green
216+
} else {
217+
Write-Host "PHC FAILED (got $got, expected $expectedHash)" -ForegroundColor Red
218+
exit 1
219+
}
220+
}
221+
}
222+
223+
function Invoke-Clean {
224+
$patterns = @('*.obj', 'tests.exe', 'phc-test.exe', 'TESTS-OUT', 'PHC-TEST-OUT',
225+
'*.ilk', '*.pdb')
226+
foreach ($p in $patterns) {
227+
Get-ChildItem -LiteralPath $PSScriptRoot -Filter $p -ErrorAction SilentlyContinue |
228+
Remove-Item -Force -ErrorAction SilentlyContinue
229+
}
230+
Write-Host 'Cleaned build artifacts.'
231+
}
232+
233+
# --- Dispatch ---------------------------------------------------------------
234+
235+
switch ($Target) {
236+
'clean' { Invoke-Clean; break }
237+
default {
238+
Import-VcEnvironment
239+
switch ($Target) {
240+
'all' { Build-Tests; Build-PhcTest }
241+
'tests' { Build-Tests }
242+
'phc-test' { Build-PhcTest }
243+
'check' { Invoke-Check }
244+
'ref' { Build-Tests -Core $OBJS_CORE_REF; Build-PhcTest -Core $OBJS_CORE_REF }
245+
'check-ref' { Invoke-Check -Core $OBJS_CORE_REF }
246+
}
247+
}
248+
}

0 commit comments

Comments
 (0)