From f74c7b08f2b4db7a7456d3fa39a106023a040b1c Mon Sep 17 00:00:00 2001 From: Peter Date: Wed, 4 Nov 2026 22:54:23 +0100 Subject: [PATCH 01/10] solenv: raise the Windows API floor to Windows 7 (0x0601) Trunk builds only with a VS2015-or-newer toolset -- Python 3.11 needs one -- and that toolset's runtime does not install on anything older than Windows 7 SP1. The dmake and gbuild platform settings still declared Windows 2000 (WINVER/_WIN32_WINNT/_WIN32_IE 0x0500), and gbuild declared no _WIN32_WINNT at all. Declare the floor the result actually has, with the values the Bazel build has compiled and run at: WINVER/_WIN32_WINNT 0x0601 and the _WIN32_IE 0x0800 the Windows SDK derives from them. Unchanged on purpose: the GUI subsystem version pin in wntmsc14.mk (5.02/5.01 -- 6.00 switches Windows to the brokered drag-and-drop path our drop targets fail), and the modules that state their own value. Co-Authored-By: Claude Opus 5.5 --- main/solenv/gbuild/platform/windows.mk | 7 +++++-- main/solenv/inc/wntmsci11.mk | 10 +++++++++- main/solenv/inc/wntmscx.mk | 10 +++++++++- 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/main/solenv/gbuild/platform/windows.mk b/main/solenv/gbuild/platform/windows.mk index 4ad02c3231..07b6a61cce 100644 --- a/main/solenv/gbuild/platform/windows.mk +++ b/main/solenv/gbuild/platform/windows.mk @@ -54,9 +54,12 @@ ifneq ($(origin CXX),default) gb_CXX := $(CXX) endif +# The Windows API floor, Windows 7 SP1 -- the same values as +# solenv/inc/wntmsci11.mk and wntmscx.mk, see there. gb_OSDEFS := \ - -DWINVER=0x0500 \ - -D_WIN32_IE=0x0500 \ + -DWINVER=0x0601 \ + -D_WIN32_WINNT=0x0601 \ + -D_WIN32_IE=0x0800 \ -DNT351 \ -DWIN32 \ -DWNT \ diff --git a/main/solenv/inc/wntmsci11.mk b/main/solenv/inc/wntmsci11.mk index e926859535..9b3a923529 100644 --- a/main/solenv/inc/wntmsci11.mk +++ b/main/solenv/inc/wntmsci11.mk @@ -218,7 +218,15 @@ CFLAGSWERRCC=-WX # settings.mk): MODULES_WITH_WARNINGS := -CDEFS+=-D_MT -DWINVER=0x0500 -D_WIN32_WINNT=0x0500 -D_WIN32_IE=0x0500 +# The Windows API floor: Windows 7 SP1. Trunk builds only with a VS2015-or- +# newer toolset (Python 3.11 needs one, and its runtime does not install on +# anything older than Windows 7 SP1), so the old Windows 2000 values (0x0500) +# described a platform the result cannot run on. _WIN32_IE is the value the +# Windows SDK derives from _WIN32_WINNT 0x0601. A few modules state their own +# value (fpicker and sal/systools/win32/kill replace it with -U/-D, shell adds +# a second -D, openssl builds with its own flags). Python builds with its own +# project files and gets the same floor from python-3.11.15-win7.patch. +CDEFS+=-D_MT -DWINVER=0x0601 -D_WIN32_WINNT=0x0601 -D_WIN32_IE=0x0800 .IF "$(COMEX)" == "11" _VC_MANIFEST_BASENAME=__VC80 .ELSE diff --git a/main/solenv/inc/wntmscx.mk b/main/solenv/inc/wntmscx.mk index b61b99e574..6e216c319c 100644 --- a/main/solenv/inc/wntmscx.mk +++ b/main/solenv/inc/wntmscx.mk @@ -225,7 +225,15 @@ CFLAGSWERRCC=-WX # settings.mk): MODULES_WITH_WARNINGS := -CDEFS+=-D_MT -DWINVER=0x0500 -D_WIN32_WINNT=0x0500 -D_WIN32_IE=0x0500 +# The Windows API floor: Windows 7 SP1. Trunk builds only with a VS2015-or- +# newer toolset (Python 3.11 needs one, and its runtime does not install on +# anything older than Windows 7 SP1), so the old Windows 2000 values (0x0500) +# described a platform the result cannot run on. _WIN32_IE is the value the +# Windows SDK derives from _WIN32_WINNT 0x0601. A few modules state their own +# value (fpicker and sal/systools/win32/kill replace it with -U/-D, shell adds +# a second -D, openssl builds with its own flags). Python builds with its own +# project files and gets the same floor from python-3.11.15-win7.patch. +CDEFS+=-D_MT -DWINVER=0x0601 -D_WIN32_WINNT=0x0601 -D_WIN32_IE=0x0800 .IF "$(COMEX)" == "11" _VC_MANIFEST_BASENAME=__VC80 .ELSE From 3bcca9603af1cee536e0cced7dfe7f0e8fce0a9a Mon Sep 17 00:00:00 2001 From: Peter Date: Wed, 4 Nov 2026 22:57:38 +0100 Subject: [PATCH 02/10] python: build Python 3.11 for Windows 7 SP1 (python-3.11.15-win7.patch) Upstream Python 3.9+ targets Windows 8: PC/pyconfig.h sets Py_WINVER 0x0602 and pythoncore links pathcch.lib, whose API set (api-ms-win-core-path-l1-1-0) does not exist on Windows 7 -- python311.dll would not even load there. PCbuild compiles with its own settings, so the solenv floor does not reach it. The patch is the Windows 7 support the Bazel build has compiled and run with (ext_libraries/modules/python/3.11.15/overlay on bazel-migration): PC/pyconfig.h Py_WINVER 0x0601, Py_NTDDI NTDDI_WIN7 PC/pathcch.h shadows the SDK header (PC/ is ahead on the path) PC/pathcch_win7.c PathCchSkipRoot/FindExtension/CombineEx: the real ones from KernelBase.dll where exported (Windows 8+), documented-behaviour fallbacks on Windows 7 PC/aoo_win7_compat.h the two Windows 8 LCMAP_* constants _winapi.c uses only to reject them, force-included everywhere pythoncore.vcxproj compiles pathcch_win7.c, links shlwapi.lib instead of pathcch.lib _freeze_module keeps pathcch.lib: it only runs on the build machine. The patch is applied for the Windows build only. Verified to apply with tg_ext.mk's own `patch --binary -p2' against the pristine tarball and to reproduce the intended tree byte for byte. On Windows 7 SP1 Python's dynload needs KB2533623 (LOAD_LIBRARY_SEARCH_*), the same class of prerequisite as the UCRT update the VS2015+ runtime needs. Co-Authored-By: Claude Opus 5.5 --- main/python/makefile.mk | 11 + main/python/python-3.11.15-win7.patch | 414 ++++++++++++++++++++++++++ 2 files changed, 425 insertions(+) create mode 100644 main/python/python-3.11.15-win7.patch diff --git a/main/python/makefile.mk b/main/python/makefile.mk index 0919f12ac2..72a4f0d20d 100644 --- a/main/python/makefile.mk +++ b/main/python/makefile.mk @@ -53,6 +53,17 @@ PATCH_FILES=\ # through the compiler include/lib search paths instead of the old hard-coded # source edit; see python_CFLAGS/python_LDFLAGS in the UNX branch below. +# Windows 7 SP1, the office's floor, where upstream Python 3.9+ wants +# Windows 8: PC/pyconfig.h's Py_WINVER/Py_NTDDI lowered to Windows 7, and +# pythoncore no longer imports pathcch.lib (its API set does not exist on +# Windows 7, so python311.dll would not even load there) -- PC/pathcch_win7.c +# calls the real PathCch* functions where KernelBase exports them and falls +# back to their documented behaviour on Windows 7. _freeze_module keeps +# pathcch.lib: it only runs on the build machine. +.IF "$(GUI)"=="WNT" +PATCH_FILES += python-$(PYVERSION)-win7.patch +.ENDIF + # Windows build deferred — Python 3 requires modern MSVC (VS2008/msvs9 not supported). # The msvs9 patch files below are retained for reference but are NOT applied until # the Windows toolchain is modernised. See python3 migration notes. diff --git a/main/python/python-3.11.15-win7.patch b/main/python/python-3.11.15-win7.patch new file mode 100644 index 0000000000..2296e1792b --- /dev/null +++ b/main/python/python-3.11.15-win7.patch @@ -0,0 +1,414 @@ +diff -ruN misc/Python-3.11.15/PC/aoo_win7_compat.h misc/build/Python-3.11.15/PC/aoo_win7_compat.h +--- misc/Python-3.11.15/PC/aoo_win7_compat.h 1970-01-01 00:00:00.000000000 +0000 ++++ misc/build/Python-3.11.15/PC/aoo_win7_compat.h 2025-01-01 00:00:00.000000000 +0000 +@@ -0,0 +1,48 @@ ++/* ++ * Licensed to the Apache Software Foundation (ASF) under one ++ * or more contributor license agreements. See the NOTICE file ++ * distributed with this work for additional information ++ * regarding copyright ownership. The ASF licenses this file ++ * to you under the Apache License, Version 2.0 (the ++ * "License"); you may not use this file except in compliance ++ * with the License. You may obtain a copy of the License at ++ * ++ * http://www.apache.org/licenses/LICENSE-2.0 ++ * ++ * Unless required by applicable law or agreed to in writing, ++ * software distributed under the License is distributed on an ++ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY ++ * KIND, either express or implied. See the License for the ++ * specific language governing permissions and limitations ++ * under the License. ++ */ ++ ++/* ++ * Force-included into every CPython translation unit (PCbuild/pyproject.props, ++ * ForcedIncludeFiles). ++ * ++ * CPython 3.11 is compiled here at the Windows 7 API level (Py_WINVER 0x0601 ++ * in PC/pyconfig.h) instead of upstream's Windows 8 one, so the SDK hides what it ++ * gates on Windows 8. Most of that would be a real dependency and must stay a ++ * compile error. What is defined here is only what CPython uses WITHOUT ++ * depending on Windows 8 -- plain constants, never functions -- each with the ++ * use that makes it safe. The values are the SDK's. ++ * ++ * Found by matching every name the 10.0.19041.0 SDK declares under a Windows 8 ++ * or later guard against the sources this module compiles. ++ */ ++ ++#ifndef AOO_PYTHON_WIN7_COMPAT_H ++#define AOO_PYTHON_WIN7_COMPAT_H ++ ++/* WinNls.h, WINVER >= _WIN32_WINNT_WIN8. Modules/_winapi.c ++ (_winapi.LCMapStringEx) only uses them to reject these flags with ++ ValueError before calling LCMapStringEx, which itself exists from Vista. */ ++#ifndef LCMAP_SORTHANDLE ++#define LCMAP_SORTHANDLE 0x20000000 ++#endif ++#ifndef LCMAP_HASH ++#define LCMAP_HASH 0x00040000 ++#endif ++ ++#endif /* AOO_PYTHON_WIN7_COMPAT_H */ +diff -ruN misc/Python-3.11.15/PC/pathcch.h misc/build/Python-3.11.15/PC/pathcch.h +--- misc/Python-3.11.15/PC/pathcch.h 1970-01-01 00:00:00.000000000 +0000 ++++ misc/build/Python-3.11.15/PC/pathcch.h 2025-01-01 00:00:00.000000000 +0000 +@@ -0,0 +1,63 @@ ++/* ++ * Licensed to the Apache Software Foundation (ASF) under one ++ * or more contributor license agreements. See the NOTICE file ++ * distributed with this work for additional information ++ * regarding copyright ownership. The ASF licenses this file ++ * to you under the Apache License, Version 2.0 (the ++ * "License"); you may not use this file except in compliance ++ * with the License. You may obtain a copy of the License at ++ * ++ * http://www.apache.org/licenses/LICENSE-2.0 ++ * ++ * Unless required by applicable law or agreed to in writing, ++ * software distributed under the License is distributed on an ++ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY ++ * KIND, either express or implied. See the License for the ++ * specific language governing permissions and limitations ++ * under the License. ++ */ ++ ++/* ++ * Stand-in for the Windows SDK's , for a Python that must load on ++ * Windows 7 SP1. ++ * ++ * The SDK header declares the PathCch* functions as imports from ++ * api-ms-win-core-path-l1-1-0.dll, an API set that exists from Windows 8 on. ++ * A python311.dll importing it statically cannot be loaded on Windows 7 at ++ * all. This file lives in PC/, which is on the include path ahead of the SDK, ++ * so `#include ` in getpath.c, posixmodule.c, fileutils.c and ++ * pathconfig.c resolves here instead and the SDK header is never seen. ++ * ++ * Only what those four files use is declared. The definitions in ++ * PC/pathcch_win7.c call the real functions from KernelBase.dll when it ++ * exports them (Windows 8 and later), so behaviour there is the system's own; ++ * the fallbacks only ever run on Windows 7. ++ */ ++ ++#ifndef AOO_PYTHON_PATHCCH_H ++#define AOO_PYTHON_PATHCCH_H ++ ++#include ++ ++#ifdef __cplusplus ++extern "C" { ++#endif ++ ++#define PATHCCH_NONE 0x00000000 ++#define PATHCCH_ALLOW_LONG_PATHS 0x00000001 ++ ++/* Largest buffer, in characters, the PathCch functions accept. */ ++#define PATHCCH_MAX_CCH 0x8000 ++ ++HRESULT WINAPI PathCchSkipRoot(PCWSTR pszPath, PCWSTR *ppszRootEnd); ++HRESULT WINAPI PathCchFindExtension(PCWSTR pszPath, size_t cchPath, ++ PCWSTR *ppszExt); ++HRESULT WINAPI PathCchCombineEx(PWSTR pszPathOut, size_t cchPathOut, ++ PCWSTR pszPathIn, PCWSTR pszMore, ++ ULONG dwFlags); ++ ++#ifdef __cplusplus ++} ++#endif ++ ++#endif /* AOO_PYTHON_PATHCCH_H */ +diff -ruN misc/Python-3.11.15/PC/pathcch_win7.c misc/build/Python-3.11.15/PC/pathcch_win7.c +--- misc/Python-3.11.15/PC/pathcch_win7.c 1970-01-01 00:00:00.000000000 +0000 ++++ misc/build/Python-3.11.15/PC/pathcch_win7.c 2025-01-01 00:00:00.000000000 +0000 +@@ -0,0 +1,241 @@ ++/* ++ * Licensed to the Apache Software Foundation (ASF) under one ++ * or more contributor license agreements. See the NOTICE file ++ * distributed with this work for additional information ++ * regarding copyright ownership. The ASF licenses this file ++ * to you under the Apache License, Version 2.0 (the ++ * "License"); you may not use this file except in compliance ++ * with the License. You may obtain a copy of the License at ++ * ++ * http://www.apache.org/licenses/LICENSE-2.0 ++ * ++ * Unless required by applicable law or agreed to in writing, ++ * software distributed under the License is distributed on an ++ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY ++ * KIND, either express or implied. See the License for the ++ * specific language governing permissions and limitations ++ * under the License. ++ */ ++ ++/* ++ * PathCch* for a python311.dll that must load on Windows 7 SP1. See ++ * PC/pathcch.h for why this exists. ++ * ++ * Each function first asks KernelBase.dll for the real implementation. ++ * KernelBase is mapped into every process from Windows 7 on, so this loads ++ * nothing and searches no directory; from Windows 8 on it exports the PathCch ++ * family and the system's own code runs. Only when the export is missing -- ++ * i.e. on Windows 7 -- does the fallback below run. ++ * ++ * The fallbacks are written from the documented contract of each function, ++ * and cover what Python 3.11 asks of them: ++ * PathCchSkipRoot os._path_splitroot (posixmodule.c), _Py_isabs ++ * (fileutils.c) ++ * PathCchFindExtension the ".exe" test in getpath.c's isxfile ++ * PathCchCombineEx _Py_join_relfile (fileutils.c), used by getpath ++ * PathCchCombineEx falls back to shlwapi's PathCombineW and so is limited to ++ * MAX_PATH there -- which is the limit Windows 7 itself imposes on the Win32 ++ * file API without a \\?\ prefix. ++ */ ++ ++#include "pathcch.h" ++ ++#include ++#include ++#include ++ ++typedef HRESULT (WINAPI *skip_root_fn)(PCWSTR, PCWSTR *); ++typedef HRESULT (WINAPI *find_extension_fn)(PCWSTR, size_t, PCWSTR *); ++typedef HRESULT (WINAPI *combine_ex_fn)(PWSTR, size_t, PCWSTR, PCWSTR, ULONG); ++ ++/* Looks NAME up in KernelBase.dll once and remembers the answer in *SLOT: ++ 0 = not looked up yet, -1 = not exported, otherwise the address. Two ++ threads racing here store the same value, and a pointer-sized aligned store ++ is atomic on every Windows target, so no lock is needed. */ ++static FARPROC ++kernelbase_export(volatile LONG_PTR *slot, const char *name) ++{ ++ LONG_PTR value = *slot; ++ if (value == 0) { ++ HMODULE kernelbase = GetModuleHandleW(L"kernelbase.dll"); ++ FARPROC proc = kernelbase ? GetProcAddress(kernelbase, name) : NULL; ++ value = proc ? (LONG_PTR)proc : (LONG_PTR)-1; ++ *slot = value; ++ } ++ return value == (LONG_PTR)-1 ? NULL : (FARPROC)value; ++} ++ ++/* Advances past one path segment and the backslash ending it, if any. */ ++static PCWSTR ++skip_segment(PCWSTR p) ++{ ++ while (*p != L'\0' && *p != L'\\') { ++ ++p; ++ } ++ if (*p == L'\\') { ++ ++p; ++ } ++ return p; ++} ++ ++static int ++is_drive_letter(WCHAR c) ++{ ++ return (c >= L'A' && c <= L'Z') || (c >= L'a' && c <= L'z'); ++} ++ ++/* "X:" or "X:\" -> the character after it. */ ++static PCWSTR ++skip_drive(PCWSTR p) ++{ ++ p += 2; ++ if (*p == L'\\') { ++ ++p; ++ } ++ return p; ++} ++ ++/* Root forms, as documented for PathCchSkipRoot: ++ C:\path drive-absolute root "C:\" ++ C:path drive-relative root "C:" ++ \path rooted root "\" ++ \\server\share\path root "\\server\share\" ++ \\?\C:\path root "\\?\C:\" ++ \\?\UNC\server\share\path root "\\?\UNC\server\share\" ++ \\?\Volume{guid}\path root "\\?\Volume{guid}\" ++ Anything else -- a relative path -- has no root and is E_INVALIDARG. */ ++static HRESULT ++skip_root_fallback(PCWSTR path, PCWSTR *root_end) ++{ ++ PCWSTR p; ++ ++ if (path == NULL || root_end == NULL || path[0] == L'\0') { ++ return E_INVALIDARG; ++ } ++ *root_end = NULL; ++ ++ if (path[0] == L'\\' && path[1] == L'\\') { ++ if (path[2] == L'?') { ++ if (path[3] != L'\\') { ++ return E_INVALIDARG; ++ } ++ p = path + 4; ++ if (_wcsnicmp(p, L"UNC\\", 4) == 0) { ++ p = skip_segment(p + 4); /* server */ ++ p = skip_segment(p); /* share */ ++ } ++ else if (is_drive_letter(p[0]) && p[1] == L':') { ++ p = skip_drive(p); ++ } ++ else if (_wcsnicmp(p, L"Volume{", 7) == 0) { ++ p = skip_segment(p); ++ } ++ else { ++ return E_INVALIDARG; ++ } ++ } ++ else { ++ p = skip_segment(path + 2); /* server */ ++ p = skip_segment(p); /* share */ ++ } ++ } ++ else if (is_drive_letter(path[0]) && path[1] == L':') { ++ p = skip_drive(path); ++ } ++ else if (path[0] == L'\\') { ++ p = path + 1; ++ } ++ else { ++ return E_INVALIDARG; ++ } ++ ++ *root_end = p; ++ return S_OK; ++} ++ ++/* The extension is the last '.' in the final path segment; with none, ++ *ext is the terminating NUL. The string must end within cch characters. */ ++static HRESULT ++find_extension_fallback(PCWSTR path, size_t cch, PCWSTR *ext) ++{ ++ PCWSTR p; ++ PCWSTR dot = NULL; ++ ++ if (ext == NULL) { ++ return E_INVALIDARG; ++ } ++ *ext = NULL; ++ if (path == NULL || cch == 0 || cch > PATHCCH_MAX_CCH) { ++ return E_INVALIDARG; ++ } ++ for (p = path; p < path + cch && *p != L'\0'; ++p) { ++ if (*p == L'.') { ++ dot = p; ++ } ++ else if (*p == L'\\') { ++ dot = NULL; ++ } ++ } ++ if (p == path + cch) { ++ return E_INVALIDARG; /* not terminated */ ++ } ++ *ext = dot != NULL ? dot : p; ++ return S_OK; ++} ++ ++static HRESULT ++combine_ex_fallback(PWSTR out, size_t cch, PCWSTR in, PCWSTR more, ++ ULONG flags) ++{ ++ WCHAR combined[MAX_PATH]; ++ size_t len; ++ ++ (void)flags; /* long paths do not exist on Windows 7 */ ++ if (out == NULL || cch == 0 || cch > PATHCCH_MAX_CCH) { ++ return E_INVALIDARG; ++ } ++ out[0] = L'\0'; ++ if (PathCombineW(combined, in, more) == NULL) { ++ return HRESULT_FROM_WIN32(ERROR_FILENAME_EXCED_RANGE); ++ } ++ len = wcslen(combined); ++ if (len >= cch) { ++ return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); ++ } ++ memcpy(out, combined, (len + 1) * sizeof(WCHAR)); ++ return S_OK; ++} ++ ++static volatile LONG_PTR skip_root_slot; ++static volatile LONG_PTR find_extension_slot; ++static volatile LONG_PTR combine_ex_slot; ++ ++HRESULT WINAPI ++PathCchSkipRoot(PCWSTR pszPath, PCWSTR *ppszRootEnd) ++{ ++ skip_root_fn real = (skip_root_fn)kernelbase_export( ++ &skip_root_slot, "PathCchSkipRoot"); ++ return real != NULL ? real(pszPath, ppszRootEnd) ++ : skip_root_fallback(pszPath, ppszRootEnd); ++} ++ ++HRESULT WINAPI ++PathCchFindExtension(PCWSTR pszPath, size_t cchPath, PCWSTR *ppszExt) ++{ ++ find_extension_fn real = (find_extension_fn)kernelbase_export( ++ &find_extension_slot, "PathCchFindExtension"); ++ return real != NULL ? real(pszPath, cchPath, ppszExt) ++ : find_extension_fallback(pszPath, cchPath, ppszExt); ++} ++ ++HRESULT WINAPI ++PathCchCombineEx(PWSTR pszPathOut, size_t cchPathOut, PCWSTR pszPathIn, ++ PCWSTR pszMore, ULONG dwFlags) ++{ ++ combine_ex_fn real = (combine_ex_fn)kernelbase_export( ++ &combine_ex_slot, "PathCchCombineEx"); ++ return real != NULL ++ ? real(pszPathOut, cchPathOut, pszPathIn, pszMore, dwFlags) ++ : combine_ex_fallback(pszPathOut, cchPathOut, pszPathIn, pszMore, ++ dwFlags); ++} +diff -ruN misc/Python-3.11.15/PC/pyconfig.h misc/build/Python-3.11.15/PC/pyconfig.h +--- misc/Python-3.11.15/PC/pyconfig.h 2025-01-01 00:00:00.000000000 +0000 ++++ misc/build/Python-3.11.15/PC/pyconfig.h 2025-01-01 00:00:00.000000000 +0000 +@@ -141,9 +141,12 @@ + #endif /* MS_WIN64 */ + + /* set the version macros for the windows headers */ +-/* Python 3.9+ requires Windows 8 or greater */ +-#define Py_WINVER 0x0602 /* _WIN32_WINNT_WIN8 */ +-#define Py_NTDDI NTDDI_WIN8 ++/* Upstream Python 3.9+ requires Windows 8 or greater. Apache OpenOffice ++ builds it for Windows 7 SP1, its own floor: PC/pathcch_win7.c stands in for ++ the PathCch* functions Windows 7 lacks, and PC/aoo_win7_compat.h supplies ++ the few Windows 8 constants used without depending on Windows 8. */ ++#define Py_WINVER 0x0601 /* _WIN32_WINNT_WIN7 */ ++#define Py_NTDDI NTDDI_WIN7 + + /* We only set these values when building Python - we don't want to force + these values on extensions, as that will affect the prototypes and +diff -ruN misc/Python-3.11.15/PCbuild/pyproject.props misc/build/Python-3.11.15/PCbuild/pyproject.props +--- misc/Python-3.11.15/PCbuild/pyproject.props 2025-01-01 00:00:00.000000000 +0000 ++++ misc/build/Python-3.11.15/PCbuild/pyproject.props 2025-01-01 00:00:00.000000000 +0000 +@@ -39,6 +39,7 @@ + + $(PySourcePath)Include;$(PySourcePath)Include\internal;$(PySourcePath)PC;$(IntDir);%(AdditionalIncludeDirectories) + WIN32;$(_Py3NamePreprocessorDefinition);$(_PlatformPreprocessorDefinition)$(_DebugPreprocessorDefinition)$(_PydPreprocessorDefinition)%(PreprocessorDefinitions) ++ aoo_win7_compat.h;%(ForcedIncludeFiles) + + MaxSpeed + true +diff -ruN misc/Python-3.11.15/PCbuild/pythoncore.vcxproj misc/build/Python-3.11.15/PCbuild/pythoncore.vcxproj +--- misc/Python-3.11.15/PCbuild/pythoncore.vcxproj 2025-01-01 00:00:00.000000000 +0000 ++++ misc/build/Python-3.11.15/PCbuild/pythoncore.vcxproj 2025-01-01 00:00:00.000000000 +0000 +@@ -106,7 +106,7 @@ + _Py_HAVE_ZLIB;%(PreprocessorDefinitions) + + +- version.lib;ws2_32.lib;pathcch.lib;bcrypt.lib;%(AdditionalDependencies) ++ version.lib;ws2_32.lib;shlwapi.lib;bcrypt.lib;%(AdditionalDependencies) + + + +@@ -473,6 +473,7 @@ + + + ++ + + + From adc56a221cbcdccda410a0215692c37e64e90023 Mon Sep 17 00:00:00 2001 From: Peter Date: Wed, 4 Nov 2026 22:58:32 +0100 Subject: [PATCH 03/10] instsetoo_native: refuse to install below Windows 7 SP1 The office is now built for Windows 7 SP1 (WINVER/_WIN32_WINNT 0x0601, the VS2015+ runtime, Python 3.11), but the MSI would still install on Vista or Windows 7 RTM and leave an office that cannot start. Add a launch condition so the installer stops with a clear message instead: (VersionNT > 601) or (VersionNT = 601 and ServicePackLevel >= 1) or Installed The "or Installed" keeps repair and removal of an existing installation working. Office MSI only: the language pack, SDK and URE packages install next to an office and carry only the privilege check. The new string OOO_LAUNCH_3 is en-US until translated. Co-Authored-By: Claude Opus 5.5 --- .../inc_openoffice/windows/msi_languages/LaunchCo.ulf | 2 ++ .../inc_openoffice/windows/msi_templates/LaunchCo.idt | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/main/instsetoo_native/inc_openoffice/windows/msi_languages/LaunchCo.ulf b/main/instsetoo_native/inc_openoffice/windows/msi_languages/LaunchCo.ulf index 7f443b1949..9ac0306ab3 100644 --- a/main/instsetoo_native/inc_openoffice/windows/msi_languages/LaunchCo.ulf +++ b/main/instsetoo_native/inc_openoffice/windows/msi_languages/LaunchCo.ulf @@ -23,3 +23,5 @@ en-US = "The Installation Wizard cannot be run properly because you are logged in as a user without sufficient administrator rights for this system." [OOO_LAUNCH_2] en-US = "Please use the file setup.exe to start the installation." +[OOO_LAUNCH_3] +en-US = "[ProductName] requires Windows 7 Service Pack 1 or later." diff --git a/main/instsetoo_native/inc_openoffice/windows/msi_templates/LaunchCo.idt b/main/instsetoo_native/inc_openoffice/windows/msi_templates/LaunchCo.idt index 3fe0c20621..0ab3f179aa 100644 --- a/main/instsetoo_native/inc_openoffice/windows/msi_templates/LaunchCo.idt +++ b/main/instsetoo_native/inc_openoffice/windows/msi_templates/LaunchCo.idt @@ -2,4 +2,5 @@ Condition Description s255 l255 WINDOWSENCODINGTEMPLATE LaunchCondition Condition Privileged OOO_LAUNCH_1 -(VersionNT < 600) or (SETUP_USED=1) or Installed OOO_LAUNCH_2 \ No newline at end of file +(VersionNT < 600) or (SETUP_USED=1) or Installed OOO_LAUNCH_2 +(VersionNT > 601) or (VersionNT = 601 and ServicePackLevel >= 1) or Installed OOO_LAUNCH_3 \ No newline at end of file From 00e9c6a6e75d6d95ddd76ff692954c8eebdfb2b9 Mon Sep 17 00:00:00 2001 From: Peter Date: Wed, 4 Nov 2026 22:59:03 +0100 Subject: [PATCH 04/10] docs(win10-msvc): the Windows floor is Windows 7 SP1 Record the floor raise (solenv WINVER/_WIN32_WINNT 0x0601), why Python is where the floor is actually decided (python-3.11.15-win7.patch), the installer's launch condition, what was deliberately left alone (the GUI subsystem pin, per-module values), and the Windows 7 SP1 install-time prerequisites (KB2999226, KB2533623, a Windows 7-capable VC++ runtime). Co-Authored-By: Claude Opus 5.5 --- win10-msvc/README.md | 55 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/win10-msvc/README.md b/win10-msvc/README.md index 7be6369716..ac7bf01e07 100644 --- a/win10-msvc/README.md +++ b/win10-msvc/README.md @@ -247,6 +247,61 @@ Delivered as new `*.patch.ucrt` files listed after the existing `libxml2` needed nothing — 2.9.10 already guards its own `snprintf` shim on `_MSC_VER < 1900`. `nss` needed nothing either. +## The Windows floor: Windows 7 SP1 + +What a build runs on is no longer chosen by the old platform files: the VS2015+ +runtime does not install below Windows 7 SP1, and Python 3.11 cannot be built +by VS2008, so trunk has no Windows XP or Windows 2000 build left. The platform +settings still declared Windows 2000 (`WINVER`/`_WIN32_WINNT`/`_WIN32_IE` +`0x0500`; gbuild declared no `_WIN32_WINNT` at all). They now declare what the +result actually needs, with the values the Bazel build compiles and runs at: + +| | dmake (`wntmsci11.mk`, `wntmscx.mk`) and gbuild (`platform/windows.mk`) | +| --- | --- | +| `WINVER`, `_WIN32_WINNT` | `0x0601` (Windows 7) | +| `_WIN32_IE` | `0x0800`, the value the SDK derives from `0x0601` | + +The floor is in the base files, not in `wntmsc14.mk`: there is no VC9 build of +trunk left for a conditional to protect, so the "VC9 stays byte-identical" +property of the overlay no longer covers these three defines. + +Kept as they were, on purpose: + +- **The GUI subsystem version** stays pinned to 5.02 (x64) / 5.01 (x86) in + `wntmsc14.mk`. It is *not* a floor setting: 6.00 makes Windows route drops + through the brokered drag-and-drop path, which AOO's drop targets fail + (drag-and-drop dead in Gallery, Calc and Draw). +- **Modules that state their own value.** `fpicker` and + `sal/systools/win32/kill` replace it with `-U`/`-D`, `shell` adds a second + `-D`, `openssl` builds with its own flags — all lower than the floor, so + their code keeps using only what it did. + +**Python is where the floor is actually decided.** It builds from its own +`PCbuild` projects, which no solenv define reaches, and upstream 3.9+ targets +Windows 8: `PC/pyconfig.h` sets `Py_WINVER 0x0602`, and pythoncore links +`pathcch.lib`, whose API set does not exist on Windows 7 — `python311.dll` +would not even load there. `python-3.11.15-win7.patch` (Windows only) is the +support the Bazel build already runs with: `Py_WINVER`/`Py_NTDDI` lowered to +Windows 7; `PC/pathcch_win7.c` calls the real `PathCch*` functions from +KernelBase where they exist and falls back to their documented behaviour on +Windows 7; `PC/aoo_win7_compat.h` supplies the two Windows 8 `LCMAP_*` +constants `_winapi.c` uses only to reject them. `_freeze_module` keeps +`pathcch.lib`, since it only runs on the build machine. + +**The installer** refuses Windows 7 RTM and anything older +(`LaunchCo.idt`, `OOO_LAUNCH_3`): `(VersionNT > 601) or (VersionNT = 601 and +ServicePackLevel >= 1) or Installed`, the last term so that repair and removal +still work. + +What Windows 7 SP1 additionally needs at install time is outside the build: +the Universal C Runtime update (KB2999226) for the VS2015+ runtime, and +KB2533623 for the `LOAD_LIBRARY_SEARCH_*` flags Python loads its modules with. +And the runtime that ships must itself still support Windows 7 — the newest +VC++ redistributables do not, so the toolset should be no newer than the +runtime release shipped with it (see the staged-CRT gap below). Nothing here +has been run on a real Windows 7 machine yet; only the compile-time floor is +enforced. + ## The old Platform SDK does not go away entirely `--with-frame-home` must keep pointing at the **Platform SDK v7.0**, whatever From c8734844d258af7c4e13e7e5b3ba90e5c857a02c Mon Sep 17 00:00:00 2001 From: Peter Date: Wed, 4 Nov 2026 23:16:26 +0100 Subject: [PATCH 05/10] solenv: do not pin _WIN32_IE -- let the SDK derive it f74c7b08f2 set _WIN32_IE=0x0800 next to _WIN32_WINNT=0x0601. That is the value the Windows SDK derives at 0x0601, but a fixed value is not the same thing as a derived one: sal/systools/win32/uwinapi's CopyFileEx{A,W}.cpp define _WIN32_WINNT 0x0400 themselves, and sdkddkver.h rejects an NT4 _WIN32_WINNT next to an IE above 6.0 SP1: sdkddkver.h(296): fatal error C1189: _WIN32_WINNT settings conflicts with _WIN32_IE setting Leave _WIN32_IE unset, as the Bazel build does, so each translation unit gets the value matching its own _WIN32_WINNT. Everything at the floor still compiles at 0x0800. Co-Authored-By: Claude Opus 5.5 --- main/solenv/gbuild/platform/windows.mk | 1 - main/solenv/inc/wntmsci11.mk | 15 +++++++++------ main/solenv/inc/wntmscx.mk | 15 +++++++++------ win10-msvc/README.md | 9 ++++++++- 4 files changed, 26 insertions(+), 14 deletions(-) diff --git a/main/solenv/gbuild/platform/windows.mk b/main/solenv/gbuild/platform/windows.mk index 07b6a61cce..606f127ef6 100644 --- a/main/solenv/gbuild/platform/windows.mk +++ b/main/solenv/gbuild/platform/windows.mk @@ -59,7 +59,6 @@ endif gb_OSDEFS := \ -DWINVER=0x0601 \ -D_WIN32_WINNT=0x0601 \ - -D_WIN32_IE=0x0800 \ -DNT351 \ -DWIN32 \ -DWNT \ diff --git a/main/solenv/inc/wntmsci11.mk b/main/solenv/inc/wntmsci11.mk index 9b3a923529..1011d87f57 100644 --- a/main/solenv/inc/wntmsci11.mk +++ b/main/solenv/inc/wntmsci11.mk @@ -221,12 +221,15 @@ MODULES_WITH_WARNINGS := # The Windows API floor: Windows 7 SP1. Trunk builds only with a VS2015-or- # newer toolset (Python 3.11 needs one, and its runtime does not install on # anything older than Windows 7 SP1), so the old Windows 2000 values (0x0500) -# described a platform the result cannot run on. _WIN32_IE is the value the -# Windows SDK derives from _WIN32_WINNT 0x0601. A few modules state their own -# value (fpicker and sal/systools/win32/kill replace it with -U/-D, shell adds -# a second -D, openssl builds with its own flags). Python builds with its own -# project files and gets the same floor from python-3.11.15-win7.patch. -CDEFS+=-D_MT -DWINVER=0x0601 -D_WIN32_WINNT=0x0601 -D_WIN32_IE=0x0800 +# described a platform the result cannot run on. _WIN32_IE is deliberately not +# set: the SDK derives it per translation unit from the _WIN32_WINNT that unit +# ends up with (0x0800 at 0x0601), and a fixed value breaks the files that +# lower _WIN32_WINNT to NT4 themselves (sal uwinapi, C1189 in sdkddkver.h). +# A few modules state their own _WIN32_WINNT: fpicker and +# sal/systools/win32/kill replace it with -U/-D, shell adds a second -D, +# openssl builds with its own flags. Python builds with its own project files +# and gets the same floor from python-3.11.15-win7.patch. +CDEFS+=-D_MT -DWINVER=0x0601 -D_WIN32_WINNT=0x0601 .IF "$(COMEX)" == "11" _VC_MANIFEST_BASENAME=__VC80 .ELSE diff --git a/main/solenv/inc/wntmscx.mk b/main/solenv/inc/wntmscx.mk index 6e216c319c..1d96bccb10 100644 --- a/main/solenv/inc/wntmscx.mk +++ b/main/solenv/inc/wntmscx.mk @@ -228,12 +228,15 @@ MODULES_WITH_WARNINGS := # The Windows API floor: Windows 7 SP1. Trunk builds only with a VS2015-or- # newer toolset (Python 3.11 needs one, and its runtime does not install on # anything older than Windows 7 SP1), so the old Windows 2000 values (0x0500) -# described a platform the result cannot run on. _WIN32_IE is the value the -# Windows SDK derives from _WIN32_WINNT 0x0601. A few modules state their own -# value (fpicker and sal/systools/win32/kill replace it with -U/-D, shell adds -# a second -D, openssl builds with its own flags). Python builds with its own -# project files and gets the same floor from python-3.11.15-win7.patch. -CDEFS+=-D_MT -DWINVER=0x0601 -D_WIN32_WINNT=0x0601 -D_WIN32_IE=0x0800 +# described a platform the result cannot run on. _WIN32_IE is deliberately not +# set: the SDK derives it per translation unit from the _WIN32_WINNT that unit +# ends up with (0x0800 at 0x0601), and a fixed value breaks the files that +# lower _WIN32_WINNT to NT4 themselves (sal uwinapi, C1189 in sdkddkver.h). +# A few modules state their own _WIN32_WINNT: fpicker and +# sal/systools/win32/kill replace it with -U/-D, shell adds a second -D, +# openssl builds with its own flags. Python builds with its own project files +# and gets the same floor from python-3.11.15-win7.patch. +CDEFS+=-D_MT -DWINVER=0x0601 -D_WIN32_WINNT=0x0601 .IF "$(COMEX)" == "11" _VC_MANIFEST_BASENAME=__VC80 .ELSE diff --git a/win10-msvc/README.md b/win10-msvc/README.md index ac7bf01e07..083ac8dc7a 100644 --- a/win10-msvc/README.md +++ b/win10-msvc/README.md @@ -259,7 +259,14 @@ result actually needs, with the values the Bazel build compiles and runs at: | | dmake (`wntmsci11.mk`, `wntmscx.mk`) and gbuild (`platform/windows.mk`) | | --- | --- | | `WINVER`, `_WIN32_WINNT` | `0x0601` (Windows 7) | -| `_WIN32_IE` | `0x0800`, the value the SDK derives from `0x0601` | +| `_WIN32_IE` | not set — the SDK derives it per translation unit (`0x0800` at `0x0601`) | + +Not setting `_WIN32_IE` is load-bearing, and it is what the Bazel build does +too. `sal/systools/win32/uwinapi`'s `CopyFileEx{A,W}.cpp` define +`_WIN32_WINNT 0x0400` themselves, and the SDK rejects an NT4 `_WIN32_WINNT` +next to an IE above 6.0 SP1 (`sdkddkver.h`: C1189 "_WIN32_WINNT settings +conflicts with _WIN32_IE setting"); a derived value follows whatever the file +ends up with. The floor is in the base files, not in `wntmsc14.mk`: there is no VC9 build of trunk left for a conditional to protect, so the "VC9 stays byte-identical" From f67b92869476fba82395f7b2c37f5de00523887d Mon Sep 17 00:00:00 2001 From: Peter Date: Thu, 5 Nov 2026 03:35:24 +0100 Subject: [PATCH 06/10] sal/uwinapi: build the psapi thunks with PSAPI_VERSION 1 At the Windows 7 floor (_WIN32_WINNT 0x0601) psapi.h defaults to PSAPI_VERSION 2 and maps GetModuleFileNameExA/W to the K32 names, so the uwinapi thunks were defined as K32GetModuleFileNameExA/W while uwinapi.dxp exports the classic names: LNK2001 + LNK1120. Pin version 1 for this module only; callers elsewhere keep the default and bind to kernel32. Co-Authored-By: Claude Opus 5.5 --- main/sal/systools/win32/uwinapi/makefile.mk | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/main/sal/systools/win32/uwinapi/makefile.mk b/main/sal/systools/win32/uwinapi/makefile.mk index d81a02fa02..50419c4c8d 100644 --- a/main/sal/systools/win32/uwinapi/makefile.mk +++ b/main/sal/systools/win32/uwinapi/makefile.mk @@ -43,6 +43,13 @@ CXXFLAGS+= $(LFS_CFLAGS) CFLAGSCXX+=-Wno-unused-parameter -Wno-return-type .ENDIF +# The thunks define the psapi entry points under their classic names, which +# uwinapi.dxp exports. From _WIN32_WINNT 0x0601 on psapi.h defaults to +# PSAPI_VERSION 2 and maps GetModuleFileNameExA/W to K32GetModuleFileNameExA/W, +# so the definitions would land under the K32 names and the export would not +# resolve (LNK2001). Callers elsewhere keep the default and bind to kernel32. +CDEFS+=-DPSAPI_VERSION=1 + .IF "$(COMEX)"=="9" .IF "$(PSDK_HOME)"!="" # Since the 02/2003 PSDK the "new" linker is needed here. From 7eb04ab85b0464f31f49780a1bdcd8cf13504649 Mon Sep 17 00:00:00 2001 From: Peter Date: Sat, 19 Sep 2026 18:19:03 +0200 Subject: [PATCH 07/10] Keep the build id within the range Windows Installer allows Commit 805d7ece19 raised BUILD from 9900 to 99000. get_fileversion() in solenv/bin/modules/installer/windows/file.pm splices that value into the third field of every file's version, so each File.Version row became 10.0.99000.0. Windows Installer stores each version field in 16 bits and rejects anything above 65535, so the whole database is judged invalid and every install aborts at InstallValidate with Internal Error 2725 -- Invalid database tables That hit every package the tree produces -- office, SDK and language pack, x86 and x64 alike -- and the message names neither a table nor the value responsible. Lower BUILD to 9900, and derive RSCREVISION from RSCVERSION, LAST_MINOR and BUILD so the build id is written in exactly one place instead of twice. Reject an out-of-range build id in get_fileversion() as well: a package that no Windows machine can install should fail while it is being built, not after it ships. Co-Authored-By: Claude Opus 5 (cherry picked from commit ca673c293cf2eb5f1c8f982dc62180981fbaa8b0) --- main/solenv/bin/modules/installer/windows/file.pm | 9 +++++++++ main/solenv/inc/minor.mk | 11 +++++++++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/main/solenv/bin/modules/installer/windows/file.pm b/main/solenv/bin/modules/installer/windows/file.pm index 18f87405e4..f2b083f46d 100644 --- a/main/solenv/bin/modules/installer/windows/file.pm +++ b/main/solenv/bin/modules/installer/windows/file.pm @@ -525,6 +525,15 @@ sub get_fileversion { $vendornumber = $allvariables->{'VENDORPATCHVERSION'}; } + # Windows Installer stores each field of a file version in 16 bits, so a + # build id above 65535 produces a package that every Windows machine + # rejects at InstallValidate with "Internal Error 2725 -- Invalid + # database tables". The failure gives no hint of its cause, so refuse to + # build such a package rather than shipping one that cannot be installed. + if (( $installer::globals::buildid =~ /^\d+$/ ) && ( $installer::globals::buildid > 65535 )) + { + installer::exiter::exit_program("ERROR: build id $installer::globals::buildid exceeds 65535, the largest value Windows Installer accepts in a version field. Lower BUILD in solenv/inc/minor.mk.", "get_fileversion"); + } $fileversion = $libraryversion . "\." . $installer::globals::buildid . "\." . $vendornumber; if ( $onefile->{'FileVersion'} ) { diff --git a/main/solenv/inc/minor.mk b/main/solenv/inc/minor.mk index e4292755d1..fa821a1612 100644 --- a/main/solenv/inc/minor.mk +++ b/main/solenv/inc/minor.mk @@ -19,7 +19,14 @@ # # ************************************************************* RSCVERSION=510 -RSCREVISION=510m1(Build:99000) -BUILD=99000 +# The build id ends up in the third field of every file's version in the +# Windows installer (see get_fileversion() in +# solenv/bin/modules/installer/windows/file.pm). Windows Installer limits +# each version field to 65535, and a package that exceeds it fails at +# InstallValidate with "Internal Error 2725 -- Invalid database tables", +# so BUILD must stay <= 65535. +BUILD=9900 LAST_MINOR=m1 +# Derived, so the build id is written in exactly one place. +RSCREVISION=$(RSCVERSION)$(LAST_MINOR)(Build:$(BUILD)) SOURCEVERSION=AOO510 From 9333ae3cba6b71516544b9d66ae0d02091282d61 Mon Sep 17 00:00:00 2001 From: Peter Date: Fri, 6 Nov 2026 23:28:08 +0100 Subject: [PATCH 08/10] lngconvex: fall back to en-US for untranslated strings lngconvex substitutes each %PLACEHOLDER% in an .rc template from the string table of the language being written. A string that has an en-US entry but no translation yet -- every string added since the last translation round -- was left as the raw placeholder in all other language sections. setup.exe shows this: on a non-English system a failed runtime install reported "%RUNTIME_FAILED%" instead of the message, and %OS_TO_OLD% and %USER_CANCELED% had the same problem. Fall back to the en-US text. The fallback looks the en-US table up with find() so that it cannot create an empty table, which would add an extra language section to the output. Co-Authored-By: Claude Opus 5.5 --- .../shell/source/tools/lngconvex/lngconvex.cxx | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/main/shell/source/tools/lngconvex/lngconvex.cxx b/main/shell/source/tools/lngconvex/lngconvex.cxx index ad3681c939..bb91ba7936 100644 --- a/main/shell/source/tools/lngconvex/lngconvex.cxx +++ b/main/shell/source/tools/lngconvex/lngconvex.cxx @@ -273,7 +273,25 @@ class Substitutor OSL_ASSERT(prt); replacement_table_t::iterator iter = prt->find(Text); if (iter != prt->end()) + { Text = iter->second; + return; + } + + // Not translated into this language (typically a string newer than the + // last translation round): fall back to en-US. Without this the raw + // placeholder, e.g. "%RUNTIME_FAILED%", ends up in the resource and is + // what a non-English user sees. Looked up with find(), not + // get_replacement_table(), which would create a table and with it an + // extra language section in the output. + iso_lang_replacement_table_t::iterator en = + iso_lang_replacement_table_.find("en-US"); + if (en != iso_lang_replacement_table_.end()) + { + iter = en->second->find(Text); + if (iter != en->second->end()) + Text = iter->second; + } } void add_substitution( From ae5464b6940990c9df896835e349bbb522d94434 Mon Sep 17 00:00:00 2001 From: Peter Date: Fri, 6 Nov 2026 23:28:09 +0100 Subject: [PATCH 09/10] setup.exe: require Windows 7 SP1, not Windows 10 The office is now built for Windows 7 SP1 (WINVER/_WIN32_WINNT 0x0601) and the MSI launch condition stops below Windows 7 SP1, but setup.exe still refused to run on anything older than Windows 10. Check for 6.1 SP1 instead, and say so in the message. The version still comes from RtlGetVersion, because GetVersionEx reports 6.3 on Windows 10 and 11 without a supportedOS manifest entry. The service pack level needs OSVERSIONINFOEXW. Co-Authored-By: Claude Opus 5.5 --- main/desktop/win32/source/setup/setup.cpp | 51 +++++++++++++---------- main/desktop/win32/source/setup/setup.ulf | 2 +- 2 files changed, 29 insertions(+), 24 deletions(-) diff --git a/main/desktop/win32/source/setup/setup.cpp b/main/desktop/win32/source/setup/setup.cpp index 6e62b818d5..bd589b391d 100644 --- a/main/desktop/win32/source/setup/setup.cpp +++ b/main/desktop/win32/source/setup/setup.cpp @@ -67,10 +67,13 @@ #define ERROR_OS_TO_OLD -5 #define ERROR_RUNTIME_FAILED -6 -// Lowest Windows this office can run on. Not an arbitrary policy: the VC v14 -// redistributable that supplies the runtime the binaries need installs only on -// Windows 10/11 and Server 2016 and later, so the floor comes with the toolset. -#define REQUIRED_WINDOWS_MAJOR 10 +// Lowest Windows this office can run on: Windows 7 SP1 (6.1 SP1), matching the +// WINVER/_WIN32_WINNT 0x0601 the office is compiled for and the MSI launch +// condition. The bundled VC v14 redistributable must still install there -- +// current releases do not; see external/vcredist/README. +#define REQUIRED_WINDOWS_MAJOR 6 +#define REQUIRED_WINDOWS_MINOR 1 +#define REQUIRED_WINDOWS_SP 1 #define PARAM_SETUP_USED TEXT( " SETUP_USED=1 " ) #define PARAM_PACKAGE TEXT( "/I " ) @@ -1861,9 +1864,9 @@ boolean SetupApp::IsPatchInstalled( TCHAR* pBaseDir, TCHAR* pFileName ) // as an MSI LaunchCondition, and lives here instead. // // RtlGetVersion is not subject to that shim. -static bool GetRealWindowsVersion( DWORD *pMajor, DWORD *pBuild ) +static bool GetRealWindowsVersion( OSVERSIONINFOEXW *pInfo ) { - typedef LONG ( WINAPI *pfnRtlGetVersion_t )( OSVERSIONINFOW * ); + typedef LONG ( WINAPI *pfnRtlGetVersion_t )( OSVERSIONINFOEXW * ); HMODULE hNtdll = ::GetModuleHandle( TEXT( "ntdll.dll" ) ); if ( hNtdll == NULL ) @@ -1874,25 +1877,18 @@ static bool GetRealWindowsVersion( DWORD *pMajor, DWORD *pBuild ) if ( pRtlGetVersion == NULL ) return false; - OSVERSIONINFOW aInfo; - ZeroMemory( &aInfo, sizeof( aInfo ) ); - aInfo.dwOSVersionInfoSize = sizeof( aInfo ); + ZeroMemory( pInfo, sizeof( *pInfo ) ); + pInfo->dwOSVersionInfoSize = sizeof( *pInfo ); - if ( pRtlGetVersion( &aInfo ) != 0 ) - return false; - - *pMajor = aInfo.dwMajorVersion; - *pBuild = aInfo.dwBuildNumber; - return true; + return pRtlGetVersion( pInfo ) == 0; } //-------------------------------------------------------------------------- boolean SetupApp::CheckOSVersion() { - DWORD nMajor = 0; - DWORD nBuild = 0; + OSVERSIONINFOEXW aInfo; - if ( !GetRealWindowsVersion( &nMajor, &nBuild ) ) + if ( !GetRealWindowsVersion( &aInfo ) ) { // Could not determine the version. Let the install proceed rather than // refuse on a machine we simply failed to identify -- if the OS really is too @@ -1903,15 +1899,24 @@ boolean SetupApp::CheckOSVersion() TCHAR sBuf[ 128 ]; - StringCchPrintf( sBuf, 128, TEXT( " Windows major version %u, build %u\r\n" ), - nMajor, nBuild ); + StringCchPrintf( sBuf, 128, TEXT( " Windows version %u.%u build %u, service pack %u\r\n" ), + aInfo.dwMajorVersion, aInfo.dwMinorVersion, aInfo.dwBuildNumber, + (DWORD)aInfo.wServicePackMajor ); Log( sBuf ); - if ( nMajor < REQUIRED_WINDOWS_MAJOR ) + const DWORD nHave = ( aInfo.dwMajorVersion << 16 ) | ( aInfo.dwMinorVersion << 8 ) + | aInfo.wServicePackMajor; + const DWORD nNeed = ( REQUIRED_WINDOWS_MAJOR << 16 ) | ( REQUIRED_WINDOWS_MINOR << 8 ) + | REQUIRED_WINDOWS_SP; + + if ( nHave < nNeed ) { StringCchPrintf( sBuf, 128, - TEXT( "ERROR: Windows %u is older than the required Windows %u.\r\n" ), - nMajor, (DWORD)REQUIRED_WINDOWS_MAJOR ); + TEXT( "ERROR: Windows %u.%u SP%u is older than the required Windows %u.%u SP%u.\r\n" ), + aInfo.dwMajorVersion, aInfo.dwMinorVersion, + (DWORD)aInfo.wServicePackMajor, + (DWORD)REQUIRED_WINDOWS_MAJOR, (DWORD)REQUIRED_WINDOWS_MINOR, + (DWORD)REQUIRED_WINDOWS_SP ); Log( sBuf ); SetError( ERROR_OS_TO_OLD ); return false; diff --git a/main/desktop/win32/source/setup/setup.ulf b/main/desktop/win32/source/setup/setup.ulf index 1950668380..b0f326d96c 100644 --- a/main/desktop/win32/source/setup/setup.ulf +++ b/main/desktop/win32/source/setup/setup.ulf @@ -50,7 +50,7 @@ en-US = "This package requires at least the version '%s' of the Windows Installe en-US = "This package requires the Windows Installer. \nYou need at least Windows Installer '%s' on your system!" [%OS_TO_OLD%] -en-US = "This version of %PRODUCTNAME% requires Windows 10 or later. \nIt cannot be installed on this version of Windows." +en-US = "This version of %PRODUCTNAME% requires Windows 7 Service Pack 1 or later. \nIt cannot be installed on this version of Windows." [%RUNTIME_FAILED%] en-US = "The Microsoft Visual C++ runtime could not be installed. \n%PRODUCTNAME% cannot run without it, so the installation has been stopped." From 3c4b5ab63b4ab36f43577d0b90104b17573bad34 Mon Sep 17 00:00:00 2001 From: Peter Date: Fri, 6 Nov 2026 23:28:09 +0100 Subject: [PATCH 10/10] external/vcredist: bundle the VS2019 redistributable, not the latest With the Windows 7 SP1 floor, the bundled VC v14 redistributable must install on Windows 7 SP1. The current v14 releases do not, so the "latest" permalinks are wrong for this build. The 14.29 redistributable that ships with the VS2019 build tools still supports Windows 7 SP1 and matches the toolset. Also document how a wrong file shows up: setup.exe runs the package with the v14 switches "/install /quiet /norestart". The Visual C++ 2008 package does not understand them and fails, and setup.exe stops with "The Microsoft Visual C++ runtime could not be installed". Co-Authored-By: Claude Opus 5.5 --- main/external/vcredist/README | 37 +++++++++++++++++++++++------------ 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/main/external/vcredist/README b/main/external/vcredist/README index 1c81afe604..9ee16b48df 100644 --- a/main/external/vcredist/README +++ b/main/external/vcredist/README @@ -11,12 +11,16 @@ desktop/win32/source/setup/setup.cpp all refer to the vcredist_* spelling. Which ones ---------- -The Visual C++ v14 Redistributable (Visual Studio 2015-2022), latest version: +The Visual C++ v14 Redistributable that ships with the Visual Studio 2019 +build tools used to compile the office (14.29.x, VS2019 16.11). It is +already on the build machine: - x86 https://aka.ms/vc14/vc_redist.x86.exe - x64 https://aka.ms/vc14/vc_redist.x64.exe + /VC/Redist/MSVC/14.29.30133/vc_redist.x86.exe + /VC/Redist/MSVC/14.29.30133/vc_redist.x64.exe -These are permalinks that always resolve to the current v14 release. +Do NOT take the "latest" permalinks (https://aka.ms/vc14/vc_redist.x64.exe): +the current v14 releases no longer install on Windows 7 SP1, which is this +build's floor -- see below. The rule is that the redistributable must be the same version as, or newer than, the MSVC build tools used to compile the office. A v14 redistributable @@ -34,11 +38,20 @@ runtime. Supported Windows versions -------------------------- -The current v14 redistributable supports Windows 10 and 11, and Windows Server -2016 / 2019 / 2022 / 2025 -- it does not install on anything older. That is -what sets the minimum Windows version for a build made with a modern toolset; -it is not an independent choice. - -If an older floor is ever needed, an older v14 servicing release has to be -pinned here explicitly, and its own supported-OS list checked -- the v14 line -has raised its floor over time. +The office is built for Windows 7 SP1 (WINVER/_WIN32_WINNT 0x0601), and both +setup.exe and the MSI launch condition enforce that floor. The bundled +redistributable must therefore install on Windows 7 SP1 too. + +The current v14 redistributable supports only Windows 10 and 11 and Windows +Server 2016 and later -- it does not install on anything older. The VS2019 +16.11 redistributable (14.29.x) still supports Windows 7 SP1, and because it +matches the toolset it also satisfies "same version or newer". If the +toolset is ever raised, the redistributable has to move with it, and the new +release's supported-OS list has to be checked against the floor -- the v14 +line has raised its floor over time. + +Check what is in this directory before building: the file version of +vcredist_x64.exe must start with 14., not 9.0. setup.exe runs it with the v14 +switches "/install /quiet /norestart"; the 2008 package does not understand +them, fails, and setup.exe stops with "The Microsoft Visual C++ runtime could +not be installed".