diff --git a/main/desktop/win32/source/setup/setup.cpp b/main/desktop/win32/source/setup/setup.cpp index 6e62b818d5d..bd589b391d8 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 19506683803..b0f326d96c9 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." diff --git a/main/external/vcredist/README b/main/external/vcredist/README index 1c81afe604d..9ee16b48df9 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". 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 7f443b19495..9ac0306ab3b 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 3fe0c206217..0ab3f179aa8 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 diff --git a/main/python/makefile.mk b/main/python/makefile.mk index 0919f12ac22..72a4f0d20df 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 00000000000..2296e1792bd --- /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 @@ + + + ++ + + + diff --git a/main/sal/systools/win32/uwinapi/makefile.mk b/main/sal/systools/win32/uwinapi/makefile.mk index d81a02fa026..50419c4c8d7 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. diff --git a/main/shell/source/tools/lngconvex/lngconvex.cxx b/main/shell/source/tools/lngconvex/lngconvex.cxx index ad3681c9396..bb91ba79366 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( diff --git a/main/solenv/bin/modules/installer/windows/file.pm b/main/solenv/bin/modules/installer/windows/file.pm index 18f87405e46..f2b083f46df 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/gbuild/platform/windows.mk b/main/solenv/gbuild/platform/windows.mk index 4ad02c32310..606f127ef61 100644 --- a/main/solenv/gbuild/platform/windows.mk +++ b/main/solenv/gbuild/platform/windows.mk @@ -54,9 +54,11 @@ 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 \ -DNT351 \ -DWIN32 \ -DWNT \ diff --git a/main/solenv/inc/minor.mk b/main/solenv/inc/minor.mk index e4292755d1c..fa821a1612c 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 diff --git a/main/solenv/inc/wntmsci11.mk b/main/solenv/inc/wntmsci11.mk index e9268595359..1011d87f576 100644 --- a/main/solenv/inc/wntmsci11.mk +++ b/main/solenv/inc/wntmsci11.mk @@ -218,7 +218,18 @@ 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 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 b61b99e574a..1d96bccb100 100644 --- a/main/solenv/inc/wntmscx.mk +++ b/main/solenv/inc/wntmscx.mk @@ -225,7 +225,18 @@ 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 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 7be63697165..083ac8dc7ae 100644 --- a/win10-msvc/README.md +++ b/win10-msvc/README.md @@ -247,6 +247,68 @@ 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` | 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" +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