Building on Windows¶
A generated project builds, tests and imports on Windows exactly as generated,
with clang-cl and no flag or manifest key (gh-1368). CI builds every
bundled example with clang-cl on windows-latest on every pull request that
changes code, and that job gates the merge.
There are two ways in: PowerShell, from a terminal, and the Visual Studio
IDE, opening the project folder. Both configure through the same generated
CMakePresets.json (gh-1376), so they build the same thing.
What you need¶
- Visual Studio 2022 or later, or the standalone Build Tools, with the Desktop development with C++ workload: the MSVC libraries and Windows SDK that clang-cl links against. CMake and Ninja ship with it.
- clang-cl: Visual Studio's C++ Clang Compiler for Windows component,
or LLVM (
winget install LLVM.LLVM). - Python 3.9+, x64.
Why clang-cl and not MSVC's cl.exe: jm generates C99 float _Complex,
which cl.exe does not have at all; clib_common.h stops such a build with
an #error naming the fix. clang-cl has _Complex while targeting the same
MSVC ABI that Python itself is built with.
The project's .venv¶
Both ways find Python the same way: the presets name the project's own
virtual environment, .venv\Scripts\python.exe, rather than letting CMake
search for an interpreter. A search on a machine with more than one Python
can configure against one numpy and import against another (gh-814), so
the venv has to exist before the first configure. From the project folder:
or, with uv, uv venv then uv pip install numpy.
PowerShell¶
Open a Developer PowerShell for VS, so the MSVC environment clang-cl needs
is loaded, and cd into the project:
cmake --preset windows-release
cmake --build --preset windows-release
ctest --preset windows-release
.venv\Scripts\python -m unittest discover -s src -p "test_*.py" -v
The build writes the extension (.pyd) into src\<package>\, next to its
.pyi, so the last line imports it from there. cmake --list-presets shows
the rest: windows-debug and windows-relwithdebinfo. Preset builds go to
out\build\<preset>\, which the generated .gitignore ignores.
With GNU make installed, make build and make test work too. The
generated Makefile selects clang-cl and Ninja on Windows by itself, and
CC or CMAKE_GENERATOR override either. It builds into build\, so it
never shares a CMake cache with the presets.
Visual Studio¶
- File > Open > Folder, and pick the project folder. Visual Studio reads
CMakePresets.jsonand loads the Microsoft environment itself; no Developer PowerShell is needed. - Pick a configuration in the toolbar's preset list: Release (clang-cl), Debug (clang-cl) or RelWithDebInfo (clang-cl). The Linux and macOS presets are hidden on Windows.
- Build > Build All.
- Test > Test Explorer lists the CTest tests; Run All runs them.
If the configure step fails with Could NOT find Python3, the .venv is
missing or has no numpy; create it as above, then Project > Delete Cache
and Reconfigure.
VS Code's CMake Tools extension reads the same file, on every platform.
Changing the presets¶
CMakePresets.json is jm's: jm status reports it outdated when a newer jm
renders it differently, the same as the Makefile and .gitignore. Your own
presets go in CMakeUserPresets.json beside it. CMake and Visual Studio
read both, the user file may inherit from jm's presets, and it is
gitignored, so a different interpreter path, extra cache variables or a
personal build directory stay on your machine:
{
"version": 3,
"configurePresets": [
{
"name": "my-release",
"inherits": "windows-release",
"cacheVariables": {
"Python3_EXECUTABLE": "C:/Python312/python.exe"
}
}
]
}
A project created before the file existed gets it from jm apply, which adds
missing files and changes nothing else.
Line endings¶
jm writes every file with LF line endings, on Windows too (gh-1368), and
jm status compares the tree against that render. Git for Windows checks text
files out CRLF by default (core.autocrlf=true), so jm new writes a
.gitattributes that keeps every checkout LF:
A project created before the file existed gets it from jm apply, like any
other file it adds; until then jm status --check lists it as MISSING. It
is jm's content and create-only, as .gitignore is: add your own attributes
to it, and jm status reports it OUTDATED when a newer jm renders it
differently. Adding it to a repository that already holds CRLF files changes
nothing on disk until git renormalises them, which you do once, on any
platform:
A CRLF file whose content is current is not drift (gh-1641): jm status lists
it under LINE ENDINGS, uncounted, rather than as STALE or OUTDATED, so
jm status --check passes on a Windows checkout either way. A real change
under a CRLF checkout is still STALE, and --diff shows that change alone.
jm apply rewrites the files it regenerates as LF and lists each as eol.
A module that cannot build on Windows¶
A module over a core that only exists on Linux and macOS (a POSIX-only
library you guard with if(NOT WIN32) yourself) declares it:
On Windows its extension is not built, and its names are absent from the package that re-exports them, while everything else imports normally. See A module built on some platforms only.
Not supported¶
- MinGW (gcc on Windows), retired in 0.77.0. A
"windows"entry in[project] platforms, andjm new --windows, now only print a notice;jm applyremoves the MinGW blocks they used to emit. (That is not the per-module key above.) - The
makebuild backend (build = "make") is POSIX-only, and gets no presets; use the CMake backend on Windows. - Windows on ARM64 natively. The MSVC ARM64 libraries are a separate Build Tools component, and CI covers x64 only.
Behaviour that differs from Linux¶
Complex multiply and divide are built with -fcx-limited-range, so they skip
C99 Annex G's inf/NaN corner cases (the alternative is an MSVC link that
cannot resolve __mulsc3). Python extensions always use the release C
runtime (/MD), including in Debug builds, so they link against the ordinary
python3X.lib.