Skip to content

Commit

Permalink
resolve verstamp bootstraping problem
Browse files Browse the repository at this point in the history
  • Loading branch information
Avasam committed Jul 30, 2024
1 parent 377a3ef commit 19e99ff
Show file tree
Hide file tree
Showing 7 changed files with 254 additions and 59 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ jobs:
- name: Build and install
run: |
python setup.py --skip-verstamp install --user
python setup.py install --user
- name: Run tests
# Run the tests directly from the source dir so support files (eg, .wav files etc)
Expand Down Expand Up @@ -91,7 +91,7 @@ jobs:
python .github\workflows\download-arm64-libs.py .\arm64libs
- name: Build wheels
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
run: python setup.py build_ext -L .\arm64libs --plat-name win-arm64 build --plat-name win-arm64 bdist_wheel --plat-name win-arm64

- uses: actions/upload-artifact@v3
if: ${{ always() }}
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ to form a checklist so @mhammond doesn't forget what to do :)

* Update setup.py with the new build number.

* Execute `make.bat`, wait forever, test the artifacts.
* Execute `make_all.bat`, wait forever, test the artifacts.

* Upload .whl artifacts to pypi - we do this before pushing the tag because they might be
rejected for an invalid `README.md`. Done via `py -3.? -m twine upload dist/*XXX*.whl`.
Expand Down
3 changes: 1 addition & 2 deletions mypy.ini
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,10 @@ exclude = (?x)(
[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]
ignore_missing_imports = True

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

; Distutils being removed from stdlib currently causes some issues on Python 3.12
Expand Down
49 changes: 13 additions & 36 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
__doc__ = """This is a distutils setup-script for the pywin32 extensions.
The canonical source of truth for supported versions and build environments
is [the github CI](https://github.com/mhammond/pywin32/tree/main/.github/workflows).
is [the GitHub CI](https://github.com/mhammond/pywin32/tree/main/.github/workflows).
To build and install locally for testing etc, you need a build environment
which is capable of building the version of Python you are targeting, then:
Expand Down Expand Up @@ -62,12 +62,6 @@
)
print("Building pywin32", pywin32_version)

try:
sys.argv.remove("--skip-verstamp")
skip_verstamp = True
except ValueError:
skip_verstamp = False

try:
this_file = __file__
except NameError:
Expand Down Expand Up @@ -985,35 +979,18 @@ def link(
# target. Do this externally to avoid suddenly dragging in the
# modules needed by this process, and which we will soon try and
# update.
# Further, we don't really want to use sys.executable, because that
# means the build environment must have a current pywin32 installed
# in every version, which is a bit of a burden only for this.
# So we assume the "default" Python version (ie, the version run by
# py.exe) has pywin32 installed.
# (This creates a chicken-and-egg problem though! We used to work around
# this by ignoring failure to verstamp, but that's easy to miss. So now
# allow --skip-verstamp on the cmdline - but if it's not there, the
# verstamp must work.)
if not skip_verstamp:
args = ["py.exe", "-m", "win32verstamp"]
args.append(f"--version={pywin32_version}")
args.append("--comments=https://github.com/mhammond/pywin32")
args.append(f"--original-filename={os.path.basename(output_filename)}")
args.append("--product=PyWin32")
if "-v" not in sys.argv:
args.append("--quiet")
args.append(output_filename)
try:
self.spawn(args)
except Exception:
print("** Failed to versionstamp the binaries.")
# py.exe is not yet available for windows-arm64 so version stamp will fail
# ignore it for now
if platform.machine() != "ARM64":
print(
"** If you want to skip this step, pass '--skip-verstamp' on the setup.py command-line"
)
raise
args = [
sys.executable,
# NOTE: On Python 3.7, all args must be str
str(Path(__file__).parent / "win32" / "Lib" / "win32verstamp.py"),
f"--version={pywin32_version}",
"--comments=https://github.com/mhammond/pywin32",
f"--original-filename={os.path.basename(output_filename)}",
"--product=PyWin32",
"--quiet" if "-v" not in sys.argv else "",
output_filename,
]
self.spawn(args)

# Work around bpo-36302/bpo-42009 - it sorts sources but this breaks
# support for building .mc files etc :(
Expand Down
207 changes: 207 additions & 0 deletions win32/Lib/_win32verstamp_pywin32ctypes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
"""
A pure-python re-implementation of methods used by win32verstamp.
This is to avoid a bootstraping problem where win32verstamp is used during build,
but requires an installation of pywin32 to be present.
We used to work around this by ignoring failure to verstamp, but that's easy to miss.
Implementations adapted, simplified and typed from:
- https://github.com/enthought/pywin32-ctypes/blob/main/win32ctypes/core/ctypes/_util.py
- https://github.com/enthought/pywin32-ctypes/blob/main/win32ctypes/core/cffi/_resource.py
- https://github.com/enthought/pywin32-ctypes/blob/main/win32ctypes/pywin32/win32api.py
---
(C) Copyright 2014 Enthought, Inc., Austin, TX
All right reserved.
This file is open source software distributed according to the terms in
https://github.com/enthought/pywin32-ctypes/blob/main/LICENSE.txt
"""

from __future__ import annotations

from collections.abc import Callable, Iterable
from ctypes import FormatError, WinDLL, _SimpleCData, get_last_error
from ctypes.wintypes import (
BOOL,
DWORD,
HANDLE,
LPCWSTR,
LPVOID,
WORD,
)
from typing import TYPE_CHECKING, Any, SupportsBytes, SupportsIndex

if TYPE_CHECKING:
from ctypes import _NamedFuncPointer

from _typeshed import ReadableBuffer
from typing_extensions import Literal

kernel32 = WinDLL("kernel32", use_last_error=True)

###
# https://github.com/enthought/pywin32-ctypes/blob/main/win32ctypes/core/ctypes/_util.py
###


def function_factory(
function: _NamedFuncPointer,
argument_types: list[type[_SimpleCData[Any]]],
return_type: type[_SimpleCData[Any]],
error_checking: Callable[..., Any], # Simplified over errcheck's signature
) -> _NamedFuncPointer:
function.argtypes = argument_types
function.restype = return_type
function.errcheck = error_checking
return function


def make_error(function: _NamedFuncPointer) -> OSError:
code = get_last_error()
description = FormatError(code).strip()
function_name = function.__name__
exception = OSError()
exception.winerror = code
exception.function = function_name
exception.strerror = description
return exception


def check_null(result: int | None, function: _NamedFuncPointer, *_) -> int:
if result is None:
raise make_error(function)
return result


def check_false(result: int | None, function: _NamedFuncPointer, *_) -> Literal[True]:
if not bool(result):
raise make_error(function)
else:
return True


###
# https://github.com/enthought/pywin32-ctypes/blob/main/win32ctypes/core/cffi/_resource.py
###


def _UpdateResource(
hUpdate: int,
lpType: str | int,
lpName: str | int,
wLanguage: int,
lpData: bytes,
cbData: int,
):
lp_type = LPCWSTR(lpType)
lp_name = LPCWSTR(lpName)
_BaseUpdateResource(hUpdate, lp_type, lp_name, wLanguage, lpData, cbData)


_BeginUpdateResource = function_factory(
kernel32.BeginUpdateResourceW,
[LPCWSTR, BOOL],
HANDLE,
check_null,
)


_EndUpdateResource = function_factory(
kernel32.EndUpdateResourceW,
[HANDLE, BOOL],
BOOL,
check_false,
)

_BaseUpdateResource = function_factory(
kernel32.UpdateResourceW,
[HANDLE, LPCWSTR, LPCWSTR, WORD, LPVOID, DWORD],
BOOL,
check_false,
)


###
# https://github.com/enthought/pywin32-ctypes/blob/main/win32ctypes/pywin32/win32api.py
###

LANG_NEUTRAL = 0x00


def BeginUpdateResource(filename: str, delete: bool):
"""Get a handle that can be used by the :func:`UpdateResource`.
Parameters
----------
fileName : unicode
The filename of the module to load.
delete : bool
When true all existing resources are deleted
Returns
-------
result : hModule
Handle of the resource.
"""
return _BeginUpdateResource(filename, delete)


def EndUpdateResource(handle: int, discard: bool) -> None:
"""End the update resource of the handle.
Parameters
----------
handle : hModule
The handle of the resource as it is returned
by :func:`BeginUpdateResource`
discard : bool
When True all writes are discarded.
"""
_EndUpdateResource(handle, discard)


def UpdateResource(
handle: int,
type: str | int,
name: str | int,
data: Iterable[SupportsIndex] | SupportsIndex | SupportsBytes | ReadableBuffer,
language=LANG_NEUTRAL,
) -> None:
"""Update a resource.
Parameters
----------
handle : hModule
The handle of the resource file as returned by
:func:`BeginUpdateResource`.
type : str : int
The type of resource to update.
name : str : int
The name or Id of the resource to update.
data : bytes
A bytes like object is expected.
.. note::
PyWin32 version 219, on Python 2.7, can handle unicode inputs.
However, the data are stored as bytes and it is not really
possible to convert the information back into the original
unicode string. To be consistent with the Python 3 behaviour
of PyWin32, we raise an error if the input cannot be
converted to `bytes`.
language : int
Language to use, default is LANG_NEUTRAL.
"""
try:
lp_data = bytes(data)
except UnicodeEncodeError:
raise TypeError("a bytes-like object is required, not a 'unicode'")
_UpdateResource(handle, type, name, language, lp_data, len(lp_data))
9 changes: 6 additions & 3 deletions win32/Lib/win32verstamp.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
""" Stamp a Win32 binary with version information.
"""
"""Stamp a Win32 binary with version information."""

import glob
import optparse
import os
import struct

from win32api import BeginUpdateResource, EndUpdateResource, UpdateResource
from _win32verstamp_pywin32ctypes import (
BeginUpdateResource,
EndUpdateResource,
UpdateResource,
)

VS_FFI_SIGNATURE = -17890115 # 0xFEEF04BD
VS_FFI_STRUCVERSION = 0x00010000
Expand Down
Loading

0 comments on commit 19e99ff

Please sign in to comment.