diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 9e9adccf3..0ba8a60d1 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -8,14 +8,31 @@ on: jobs: test: - name: ./test.sh + name: ./test.sh (${{ matrix.os }}) runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: - os: ['macos-latest', 'ubuntu-latest', 'ubuntu-24.04-arm'] + include: + - os: macos-latest + - os: ubuntu-latest + - os: ubuntu-24.04-arm + - os: windows-latest + shell: msys2 {0} + timeout: 5 # the build and the install test take longer on the 4-core GitHub-hosted runner + defaults: + run: + shell: ${{ matrix.shell || 'bash -e {0}' }} timeout-minutes: 10 steps: - uses: actions/checkout@v7 + - uses: msys2/setup-msys2@v2 + if: runner.os == 'Windows' + with: + msystem: CLANG64 + path-type: inherit # uv and Git for Windows stay visible + install: mingw-w64-clang-x86_64-clang # for scons + - uses: astral-sh/setup-uv@v5 + if: runner.os == 'Windows' - run: ./test.sh - timeout-minutes: 1 + timeout-minutes: ${{ matrix.timeout || 1 }} diff --git a/SConstruct b/SConstruct index b7e1a4c76..d9330a517 100644 --- a/SConstruct +++ b/SConstruct @@ -1,14 +1,17 @@ import os import platform import subprocess +import sys import sysconfig +WINDOWS = platform.system() == "Windows" arch = subprocess.check_output(["uname", "-m"], encoding='utf8').rstrip() if platform.system() == "Darwin": arch = "Darwin" -# shm_open/shm_unlink live in librt on older glibc versions (including manylinux). -common = [] if arch == "Darwin" else ["rt"] +# shm_open/shm_unlink live in librt on older glibc versions (including manylinux); +# on Windows visionipc's sockets come from winsock +common = {"Linux": ["rt"], "Windows": ["ws2_32"]}.get(platform.system(), []) cpppath = [ "#/", @@ -63,8 +66,11 @@ env = Environment( CXXFLAGS="-std=c++1z", CPPPATH=cpppath, CYTHONCFILESUFFIX=".cpp", - tools=["default", "cython"] + tools=["mingw" if WINDOWS else "default", "cython"], # the default tool picks MSVC on Windows ) +if WINDOWS: + env["CC"], env["CXX"] = "clang", "clang++" # the mingw tool assumes gcc + env.Append(LINKFLAGS=["-static"]) # libc++ into the binaries so they run outside the MSYS2 shell Export('env', 'arch', 'common') @@ -73,6 +79,10 @@ envCython["CCFLAGS"] += ["-Wno-#warnings", "-Wno-cpp", "-Wno-shadow", "-Wno-depr envCython["CCFLAGS"].remove('-Werror') if arch == "Darwin": envCython["LINKFLAGS"] = ["-bundle", "-undefined", "dynamic_lookup"] +elif WINDOWS: + envCython["LINKFLAGS"] = ["-shared", "-static"] + envCython.Append(LIBPATH=[os.path.join(sys.base_prefix, "libs")]) + envCython["LIBS"] = [f"python{sys.version_info.major}{sys.version_info.minor}"] else: envCython["LINKFLAGS"] = ["-pthread", "-shared"] diff --git a/setup.py b/setup.py index 45e2c172d..1bb741ec6 100644 --- a/setup.py +++ b/setup.py @@ -11,7 +11,7 @@ class SConsBuildExt(build_ext): def build_extensions(self): subprocess.check_call([sys.executable, "-m", "SCons", "--minimal", "-j", str(self.parallel or os.cpu_count() or 1)]) for ext in self.extensions: - source = Path(*ext.name.split(".")).with_suffix(".so") + source = Path(*ext.name.split(".")).with_suffix(".pyd" if sys.platform == "win32" else ".so") target = Path(self.get_ext_fullpath(ext.name)) target.parent.mkdir(parents=True, exist_ok=True) self.copy_file(str(source), str(target)) diff --git a/setup.sh b/setup.sh index 09e3af82c..a5429e5cd 100755 --- a/setup.sh +++ b/setup.sh @@ -14,6 +14,15 @@ if ! command -v uv &>/dev/null; then set -e fi +VENV_BIN=bin +EXE="" +case "$(uname -s)" in MINGW*|MSYS*) + VENV_BIN=Scripts # a native Python's venv keeps its scripts in Scripts/ + EXE=".exe" + export UV_PYTHON="${UV_PYTHON:-3.12}" # not the MSYS2 toolchain's own python, whose wheels are incompatible + ;; +esac + export UV_PROJECT_ENVIRONMENT="$DIR/.venv" uv sync --all-extras -source "$DIR/.venv/bin/activate" +source "$DIR/.venv/$VENV_BIN/activate" diff --git a/test.sh b/test.sh index bf3a0e0ef..df4688c0b 100755 --- a/test.sh +++ b/test.sh @@ -16,10 +16,11 @@ lefthook run test ( TEST_DIR=$(mktemp -d) trap 'rm -rf "$TEST_DIR"' EXIT - uv venv --python "$DIR/.venv/bin/python" "$TEST_DIR/.venv" - uv pip install --python "$TEST_DIR/.venv/bin/python" "$DIR" + uv venv --python "$DIR/.venv/$VENV_BIN/python$EXE" "$TEST_DIR/.venv" + TEST_PYTHON="$TEST_DIR/.venv/$VENV_BIN/python$EXE" + uv pip install --python "$TEST_PYTHON" "$DIR" cd "$TEST_DIR" - "$TEST_DIR/.venv/bin/python" -m unittest msgq.tests.test_messaging + "$TEST_PYTHON" -m unittest msgq.tests.test_messaging ) # *** all done ***