Skip to content

Commit 804968a

Browse files
committed
resolve verstamp bootstraping problem
1 parent 377a3ef commit 804968a

7 files changed

Lines changed: 254 additions & 59 deletions

File tree

.github/workflows/main.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ jobs:
4040
4141
- name: Build and install
4242
run: |
43-
python setup.py --skip-verstamp install --user
43+
python setup.py install --user
4444
4545
- name: Run tests
4646
# Run the tests directly from the source dir so support files (eg, .wav files etc)
@@ -91,7 +91,7 @@ jobs:
9191
python .github\workflows\download-arm64-libs.py .\arm64libs
9292
9393
- name: Build wheels
94-
run: python setup.py --skip-verstamp build_ext -L .\arm64libs --plat-name win-arm64 build --plat-name win-arm64 bdist_wheel --plat-name win-arm64
94+
run: python setup.py build_ext -L .\arm64libs --plat-name win-arm64 build --plat-name win-arm64 bdist_wheel --plat-name win-arm64
9595

9696
- uses: actions/upload-artifact@v3
9797
if: ${{ always() }}

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ to form a checklist so @mhammond doesn't forget what to do :)
141141

142142
* Update setup.py with the new build number.
143143

144-
* Execute `make.bat`, wait forever, test the artifacts.
144+
* Execute `make_all.bat`, wait forever, test the artifacts.
145145

146146
* Upload .whl artifacts to pypi - we do this before pushing the tag because they might be
147147
rejected for an invalid `README.md`. Done via `py -3.? -m twine upload dist/*XXX*.whl`.

mypy.ini

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,10 @@ exclude = (?x)(
4848
[mypy-adsi.*,dde,exchange,exchdapi,mapi,perfmon,servicemanager,win32api,win32console,win32clipboard,win32comext.adsi.adsi,win32event,win32evtlog,win32file,win32gui,win32help,win32pdh,win32process,win32ras,win32security,win32service,win32trace,win32ui,win32uiole,win32wnet,_win32sysloader,_winxptheme]
4949
ignore_missing_imports = True
5050

51-
; verstamp is installed from win32verstamp.py called in setup.py
5251
; Most of win32com re-exports win32comext
5352
; Test is a local untyped module in win32comext.axdebug
5453
; pywin32_system32 is an empty module created in setup.py to store dlls
55-
[mypy-verstamp,win32com.*,Test,pywin32_system32]
54+
[mypy-win32com.*,Test,pywin32_system32]
5655
ignore_missing_imports = True
5756

5857
; Distutils being removed from stdlib currently causes some issues on Python 3.12

setup.py

Lines changed: 13 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
__doc__ = """This is a distutils setup-script for the pywin32 extensions.
66
77
The canonical source of truth for supported versions and build environments
8-
is [the github CI](https://github.com/mhammond/pywin32/tree/main/.github/workflows).
8+
is [the GitHub CI](https://github.com/mhammond/pywin32/tree/main/.github/workflows).
99
1010
To build and install locally for testing etc, you need a build environment
1111
which is capable of building the version of Python you are targeting, then:
@@ -62,12 +62,6 @@
6262
)
6363
print("Building pywin32", pywin32_version)
6464

65-
try:
66-
sys.argv.remove("--skip-verstamp")
67-
skip_verstamp = True
68-
except ValueError:
69-
skip_verstamp = False
70-
7165
try:
7266
this_file = __file__
7367
except NameError:
@@ -985,35 +979,18 @@ def link(
985979
# target. Do this externally to avoid suddenly dragging in the
986980
# modules needed by this process, and which we will soon try and
987981
# update.
988-
# Further, we don't really want to use sys.executable, because that
989-
# means the build environment must have a current pywin32 installed
990-
# in every version, which is a bit of a burden only for this.
991-
# So we assume the "default" Python version (ie, the version run by
992-
# py.exe) has pywin32 installed.
993-
# (This creates a chicken-and-egg problem though! We used to work around
994-
# this by ignoring failure to verstamp, but that's easy to miss. So now
995-
# allow --skip-verstamp on the cmdline - but if it's not there, the
996-
# verstamp must work.)
997-
if not skip_verstamp:
998-
args = ["py.exe", "-m", "win32verstamp"]
999-
args.append(f"--version={pywin32_version}")
1000-
args.append("--comments=https://github.com/mhammond/pywin32")
1001-
args.append(f"--original-filename={os.path.basename(output_filename)}")
1002-
args.append("--product=PyWin32")
1003-
if "-v" not in sys.argv:
1004-
args.append("--quiet")
1005-
args.append(output_filename)
1006-
try:
1007-
self.spawn(args)
1008-
except Exception:
1009-
print("** Failed to versionstamp the binaries.")
1010-
# py.exe is not yet available for windows-arm64 so version stamp will fail
1011-
# ignore it for now
1012-
if platform.machine() != "ARM64":
1013-
print(
1014-
"** If you want to skip this step, pass '--skip-verstamp' on the setup.py command-line"
1015-
)
1016-
raise
982+
args = [
983+
sys.executable,
984+
# NOTE: On Python 3.7, all args must be str
985+
str(Path(__file__).parent / "win32" / "Lib" / "win32verstamp.py"),
986+
f"--version={pywin32_version}",
987+
"--comments=https://github.com/mhammond/pywin32",
988+
f"--original-filename={os.path.basename(output_filename)}",
989+
"--product=PyWin32",
990+
"--quiet" if "-v" not in sys.argv else "",
991+
output_filename,
992+
]
993+
self.spawn(args)
1017994

1018995
# Work around bpo-36302/bpo-42009 - it sorts sources but this breaks
1019996
# support for building .mc files etc :(
Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
"""
2+
A pure-python re-implementation of methods used by win32verstamp.
3+
This is to avoid a bootstraping problem where win32verstamp is used during build,
4+
but requires an installation of pywin32 to be present.
5+
We used to work around this by ignoring failure to verstamp, but that's easy to miss.
6+
7+
Implementations adapted, simplified and typed from:
8+
- https://github.com/enthought/pywin32-ctypes/blob/main/win32ctypes/core/ctypes/_util.py
9+
- https://github.com/enthought/pywin32-ctypes/blob/main/win32ctypes/core/cffi/_resource.py
10+
- https://github.com/enthought/pywin32-ctypes/blob/main/win32ctypes/pywin32/win32api.py
11+
12+
---
13+
14+
(C) Copyright 2014 Enthought, Inc., Austin, TX
15+
All right reserved.
16+
17+
This file is open source software distributed according to the terms in
18+
https://github.com/enthought/pywin32-ctypes/blob/main/LICENSE.txt
19+
"""
20+
21+
from __future__ import annotations
22+
23+
from collections.abc import Callable, Iterable
24+
from ctypes import FormatError, WinDLL, _SimpleCData, get_last_error
25+
from ctypes.wintypes import (
26+
BOOL,
27+
DWORD,
28+
HANDLE,
29+
LPCWSTR,
30+
LPVOID,
31+
WORD,
32+
)
33+
from typing import TYPE_CHECKING, Any
34+
35+
if TYPE_CHECKING:
36+
from ctypes import _NamedFuncPointer
37+
38+
from _typeshed import ReadableBuffer
39+
from typing_extensions import Literal, SupportsBytes, SupportsIndex
40+
41+
kernel32 = WinDLL("kernel32", use_last_error=True)
42+
43+
###
44+
# https://github.com/enthought/pywin32-ctypes/blob/main/win32ctypes/core/ctypes/_util.py
45+
###
46+
47+
48+
def function_factory(
49+
function: _NamedFuncPointer,
50+
argument_types: list[type[_SimpleCData[Any]]],
51+
return_type: type[_SimpleCData[Any]],
52+
error_checking: Callable[..., Any], # Simplified over errcheck's signature
53+
) -> _NamedFuncPointer:
54+
function.argtypes = argument_types
55+
function.restype = return_type
56+
function.errcheck = error_checking
57+
return function
58+
59+
60+
def make_error(function: _NamedFuncPointer) -> OSError:
61+
code = get_last_error()
62+
description = FormatError(code).strip()
63+
function_name = function.__name__
64+
exception = OSError()
65+
exception.winerror = code
66+
exception.function = function_name
67+
exception.strerror = description
68+
return exception
69+
70+
71+
def check_null(result: int | None, function: _NamedFuncPointer, *_) -> int:
72+
if result is None:
73+
raise make_error(function)
74+
return result
75+
76+
77+
def check_false(result: int | None, function: _NamedFuncPointer, *_) -> Literal[True]:
78+
if not bool(result):
79+
raise make_error(function)
80+
else:
81+
return True
82+
83+
84+
###
85+
# https://github.com/enthought/pywin32-ctypes/blob/main/win32ctypes/core/cffi/_resource.py
86+
###
87+
88+
89+
def _UpdateResource(
90+
hUpdate: int,
91+
lpType: str | int,
92+
lpName: str | int,
93+
wLanguage: int,
94+
lpData: bytes,
95+
cbData: int,
96+
):
97+
lp_type = LPCWSTR(lpType)
98+
lp_name = LPCWSTR(lpName)
99+
_BaseUpdateResource(hUpdate, lp_type, lp_name, wLanguage, lpData, cbData)
100+
101+
102+
_BeginUpdateResource = function_factory(
103+
kernel32.BeginUpdateResourceW,
104+
[LPCWSTR, BOOL],
105+
HANDLE,
106+
check_null,
107+
)
108+
109+
110+
_EndUpdateResource = function_factory(
111+
kernel32.EndUpdateResourceW,
112+
[HANDLE, BOOL],
113+
BOOL,
114+
check_false,
115+
)
116+
117+
_BaseUpdateResource = function_factory(
118+
kernel32.UpdateResourceW,
119+
[HANDLE, LPCWSTR, LPCWSTR, WORD, LPVOID, DWORD],
120+
BOOL,
121+
check_false,
122+
)
123+
124+
125+
###
126+
# https://github.com/enthought/pywin32-ctypes/blob/main/win32ctypes/pywin32/win32api.py
127+
###
128+
129+
LANG_NEUTRAL = 0x00
130+
131+
132+
def BeginUpdateResource(filename: str, delete: bool):
133+
"""Get a handle that can be used by the :func:`UpdateResource`.
134+
135+
Parameters
136+
----------
137+
fileName : unicode
138+
The filename of the module to load.
139+
delete : bool
140+
When true all existing resources are deleted
141+
142+
Returns
143+
-------
144+
result : hModule
145+
Handle of the resource.
146+
147+
"""
148+
return _BeginUpdateResource(filename, delete)
149+
150+
151+
def EndUpdateResource(handle: int, discard: bool) -> None:
152+
"""End the update resource of the handle.
153+
154+
Parameters
155+
----------
156+
handle : hModule
157+
The handle of the resource as it is returned
158+
by :func:`BeginUpdateResource`
159+
160+
discard : bool
161+
When True all writes are discarded.
162+
163+
"""
164+
_EndUpdateResource(handle, discard)
165+
166+
167+
def UpdateResource(
168+
handle: int,
169+
type: str | int,
170+
name: str | int,
171+
data: Iterable[SupportsIndex] | SupportsIndex | SupportsBytes | ReadableBuffer,
172+
language=LANG_NEUTRAL,
173+
) -> None:
174+
"""Update a resource.
175+
176+
Parameters
177+
----------
178+
handle : hModule
179+
The handle of the resource file as returned by
180+
:func:`BeginUpdateResource`.
181+
182+
type : str : int
183+
The type of resource to update.
184+
185+
name : str : int
186+
The name or Id of the resource to update.
187+
188+
data : bytes
189+
A bytes like object is expected.
190+
191+
.. note::
192+
PyWin32 version 219, on Python 2.7, can handle unicode inputs.
193+
However, the data are stored as bytes and it is not really
194+
possible to convert the information back into the original
195+
unicode string. To be consistent with the Python 3 behaviour
196+
of PyWin32, we raise an error if the input cannot be
197+
converted to `bytes`.
198+
199+
language : int
200+
Language to use, default is LANG_NEUTRAL.
201+
202+
"""
203+
try:
204+
lp_data = bytes(data)
205+
except UnicodeEncodeError:
206+
raise TypeError("a bytes-like object is required, not a 'unicode'")
207+
_UpdateResource(handle, type, name, language, lp_data, len(lp_data))

win32/Lib/win32verstamp.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
1-
""" Stamp a Win32 binary with version information.
2-
"""
1+
"""Stamp a Win32 binary with version information."""
32

43
import glob
54
import optparse
65
import os
76
import struct
87

9-
from win32api import BeginUpdateResource, EndUpdateResource, UpdateResource
8+
from _win32verstamp_pywin32ctypes import (
9+
BeginUpdateResource,
10+
EndUpdateResource,
11+
UpdateResource,
12+
)
1013

1114
VS_FFI_SIGNATURE = -17890115 # 0xFEEF04BD
1215
VS_FFI_STRUCVERSION = 0x00010000

0 commit comments

Comments
 (0)