mit neuen venv und exe-Files
This commit is contained in:
383
venv3_12/Lib/site-packages/cx_Freeze/winversioninfo.py
Normal file
383
venv3_12/Lib/site-packages/cx_Freeze/winversioninfo.py
Normal file
@@ -0,0 +1,383 @@
|
||||
"""Module for the VersionInfo base class."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
from importlib import import_module
|
||||
from pathlib import Path
|
||||
from struct import calcsize, pack
|
||||
from typing import ClassVar
|
||||
|
||||
from packaging.version import Version
|
||||
|
||||
__all__ = ["VersionInfo"]
|
||||
|
||||
# types
|
||||
CHAR = "c"
|
||||
WCHAR = "ss"
|
||||
WORD = "=H"
|
||||
DWORD = "=L"
|
||||
|
||||
# constants
|
||||
RT_VERSION = 16
|
||||
ID_VERSION = 1
|
||||
|
||||
VS_FFI_SIGNATURE = 0xFEEF04BD
|
||||
VS_FFI_STRUCVERSION = 0x00010000
|
||||
VS_FFI_FILEFLAGSMASK = 0x0000003F
|
||||
VOS_NT_WINDOWS32 = 0x00040004
|
||||
|
||||
KEY_VERSION_INFO = "VS_VERSION_INFO"
|
||||
KEY_STRING_FILE_INFO = "StringFileInfo"
|
||||
KEY_STRING_TABLE = "040904E4"
|
||||
KEY_VAR_FILE_INFO = "VarFileInfo"
|
||||
|
||||
COMMENTS_MAX_LEN = (64 - 2) * 1024 // calcsize(WCHAR)
|
||||
|
||||
# To disable the experimental feature in Windows:
|
||||
# set CX_FREEZE_STAMP=pywin32
|
||||
# pip install -U pywin32
|
||||
if os.environ.get("CX_FREEZE_STAMP", "") == "pywin32":
|
||||
CX_FREEZE_STAMP = "pywin32"
|
||||
else:
|
||||
CX_FREEZE_STAMP = "internal"
|
||||
|
||||
|
||||
class Structure:
|
||||
"""Abstract base class for structures in native byte order. Concrete
|
||||
structure and union types must be created by subclassing one of these
|
||||
types, and at least define a _fields class variable.
|
||||
"""
|
||||
|
||||
def __init__(self, *args) -> None:
|
||||
if not hasattr(self, "_fields"):
|
||||
self._fields: list[tuple[str, str]] = []
|
||||
for i, (field, _) in enumerate(self._fields):
|
||||
setattr(self, field, args[i])
|
||||
|
||||
def __str__(self) -> str:
|
||||
dump = json.dumps(self.as_dict(), indent=2)
|
||||
return self.__class__.__name__ + ": " + dump
|
||||
|
||||
def as_dict(self) -> dict[str, str]:
|
||||
"""Return the field values as dictionary."""
|
||||
fields = {}
|
||||
for fieldname, _ in self._fields:
|
||||
data = getattr(self, fieldname)
|
||||
if hasattr(data, "as_dict"):
|
||||
data = data.as_dict()
|
||||
elif isinstance(data, bytes):
|
||||
data = data.decode()
|
||||
fields[fieldname] = data
|
||||
return fields
|
||||
|
||||
def to_buffer(self) -> bytes:
|
||||
"""Return the field values to a buffer."""
|
||||
buffer = b""
|
||||
for fieldname, fmt in self._fields:
|
||||
data = getattr(self, fieldname)
|
||||
if hasattr(data, "to_buffer"):
|
||||
data = data.to_buffer()
|
||||
elif isinstance(data, str):
|
||||
data = data.encode("utf-16le")
|
||||
elif isinstance(data, int):
|
||||
data = pack(fmt, data)
|
||||
buffer += data
|
||||
return buffer
|
||||
|
||||
|
||||
class VS_FIXEDFILEINFO(Structure):
|
||||
"""Version information for a Win32 file."""
|
||||
|
||||
_fields: ClassVar[list[tuple[str, str]]] = [
|
||||
("dwSignature", DWORD),
|
||||
("dwStrucVersion", DWORD),
|
||||
("dwFileVersionMS", DWORD),
|
||||
("dwFileVersionLS", DWORD),
|
||||
("dwProductVersionMS", DWORD),
|
||||
("dwProductVersionLS", DWORD),
|
||||
("dwFileFlagsMask", DWORD),
|
||||
("dwFileFlags", DWORD),
|
||||
("dwFileOS", DWORD),
|
||||
("dwFileType", DWORD),
|
||||
("dwFileSubtype", DWORD),
|
||||
("dwFileDateMS", DWORD),
|
||||
("dwFileDateLS", DWORD),
|
||||
]
|
||||
|
||||
|
||||
class String(Structure):
|
||||
"""File version resource representation of the data."""
|
||||
|
||||
def __init__(
|
||||
self, key: str, value: int | str | Structure | None = None
|
||||
) -> None:
|
||||
key = key + "\0"
|
||||
key_len = len(key)
|
||||
fields = [
|
||||
("wLength", WORD),
|
||||
("wValueLength", WORD),
|
||||
("wType", WORD),
|
||||
("szKey", WCHAR * key_len),
|
||||
]
|
||||
key_len = calcsize(WCHAR) * key_len
|
||||
pad_len = (4 - ((calcsize(WORD) * 3 + key_len) & 3)) & 3
|
||||
if 0 < pad_len < 4:
|
||||
fields.append(("Padding", f"{pad_len}s"))
|
||||
value_len = 0
|
||||
value_type = 1
|
||||
value_size = 1
|
||||
if isinstance(value, int):
|
||||
value_len = calcsize(DWORD)
|
||||
value_type = 0
|
||||
fields.append(("Value", DWORD))
|
||||
elif isinstance(value, str):
|
||||
value = value + "\0"
|
||||
value_len = len(value)
|
||||
value_size = calcsize(WCHAR)
|
||||
fields.append(("Value", WCHAR * value_len))
|
||||
elif hasattr(value, "wLength"): # instance of String
|
||||
value_len = value.wLength
|
||||
fields.append(("Value", type(value)))
|
||||
elif isinstance(value, Structure):
|
||||
value_len = 0
|
||||
for field in value._fields:
|
||||
value_len += calcsize(field[1])
|
||||
value_type = 0
|
||||
fields.append(("Value", type(value)))
|
||||
|
||||
self._fields = fields
|
||||
self.wValueLength = value_len
|
||||
self.wType = value_type
|
||||
self.szKey = key
|
||||
self.Padding = b"\0" * pad_len
|
||||
self.Value = value
|
||||
self.wLength = (
|
||||
calcsize(WORD) * 3 + key_len + pad_len + value_size * value_len
|
||||
)
|
||||
self._children = 0
|
||||
|
||||
def children(self, value: String) -> None:
|
||||
"""Represents the child String object."""
|
||||
pad_len = 4 - (self.wLength & 3)
|
||||
if 0 < pad_len < 4:
|
||||
field = f"Padding{self._children}"
|
||||
self._fields.append((field, f"{pad_len}s"))
|
||||
setattr(self, field, b"\0" * pad_len)
|
||||
self.wLength += calcsize(CHAR) * pad_len
|
||||
field = f"Children{self._children}"
|
||||
self._fields.append((field, type(value)))
|
||||
setattr(self, field, value)
|
||||
self._children += 1
|
||||
self.wLength += value.wLength
|
||||
|
||||
|
||||
class VersionInfo:
|
||||
"""Organizes the version information (resource) data of a file."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
version: str,
|
||||
internal_name: str | None = None,
|
||||
original_filename: str | None = None,
|
||||
comments: str | None = None,
|
||||
company: str | None = None,
|
||||
description: str | None = None,
|
||||
copyright: str | None = None, # noqa: A002
|
||||
trademarks: str | None = None,
|
||||
product: str | None = None,
|
||||
dll: bool | None = None,
|
||||
debug: bool | None = None,
|
||||
verbose: bool = True,
|
||||
) -> None:
|
||||
valid_version = Version(version)
|
||||
parts = list(valid_version.release)
|
||||
while len(parts) < 4:
|
||||
parts.append(0)
|
||||
self.version: str = ".".join(map(str, parts))
|
||||
self.valid_version: Version = valid_version
|
||||
self.internal_name: str | None = internal_name
|
||||
self.original_filename: str | None = original_filename
|
||||
# comments length must be limited to 31kb
|
||||
self.comments: str = comments[:COMMENTS_MAX_LEN] if comments else None
|
||||
self.company: str | None = company
|
||||
self.description: str | None = description
|
||||
self.copyright: str | None = copyright
|
||||
self.trademarks: str | None = trademarks
|
||||
self.product: str | None = product
|
||||
self.dll: bool | None = dll
|
||||
self.debug: bool | None = debug
|
||||
self.verbose: bool = verbose
|
||||
|
||||
def stamp(self, path: str | Path) -> None:
|
||||
"""Stamp a Win32 binary with version information."""
|
||||
if isinstance(path, str):
|
||||
path = Path(path)
|
||||
if not path.is_file():
|
||||
raise FileNotFoundError(path)
|
||||
|
||||
if CX_FREEZE_STAMP == "pywin32":
|
||||
try:
|
||||
version_stamp = import_module("win32verstamp").stamp
|
||||
except ImportError as exc:
|
||||
msg = "install pywin32 extension first"
|
||||
raise RuntimeError(msg) from exc
|
||||
# comments length must be limited to 15kb (uses WORD='h')
|
||||
self.comments = (self.comments or "")[: COMMENTS_MAX_LEN // 2]
|
||||
version_stamp(os.fspath(path), self)
|
||||
return
|
||||
|
||||
# internal
|
||||
string_version_info = self.version_info(path)
|
||||
if CX_FREEZE_STAMP == "internal":
|
||||
try:
|
||||
util = import_module("cx_Freeze.util")
|
||||
except ImportError as exc:
|
||||
msg = "cx_Freeze.util extension not found"
|
||||
raise RuntimeError(msg) from exc
|
||||
handle = util.BeginUpdateResource(path, 0)
|
||||
util.UpdateResource(
|
||||
handle, RT_VERSION, ID_VERSION, string_version_info.to_buffer()
|
||||
)
|
||||
util.EndUpdateResource(handle, 0)
|
||||
|
||||
if self.verbose:
|
||||
print("Stamped:", path)
|
||||
|
||||
def version_info(self, path: Path) -> String:
|
||||
"""Returns the String version info used to stamp the version."""
|
||||
major = self.valid_version.major
|
||||
minor = self.valid_version.minor
|
||||
micro = self.valid_version.micro
|
||||
build = 0
|
||||
file_flags = 0
|
||||
if self.debug is None or path.stem.lower().endswith("_d"):
|
||||
file_flags += 1
|
||||
if self.valid_version.is_devrelease:
|
||||
file_flags += 8
|
||||
build = self.valid_version.dev
|
||||
elif self.valid_version.is_prerelease:
|
||||
file_flags += 2
|
||||
build = self.valid_version.pre[1]
|
||||
elif self.valid_version.is_postrelease:
|
||||
file_flags += 0x20
|
||||
build = self.valid_version.post
|
||||
elif len(self.valid_version.release) >= 4:
|
||||
build = self.valid_version.release[3]
|
||||
|
||||
# use the data in the order shown in 'pepper'
|
||||
data = {
|
||||
"FileDescription": self.description or "",
|
||||
"FileVersion": self.version,
|
||||
"InternalName": self.internal_name or path.name,
|
||||
"CompanyName": self.company or "",
|
||||
"LegalCopyright": self.copyright or "",
|
||||
"LegalTrademarks": self.trademarks or "",
|
||||
"OriginalFilename": self.original_filename or path.name,
|
||||
"ProductName": self.product or "",
|
||||
"ProductVersion": str(self.valid_version),
|
||||
"Comments": self.comments or "",
|
||||
}
|
||||
is_dll = self.dll
|
||||
if is_dll is None:
|
||||
is_dll = path.suffix.lower() in (".dll", ".pyd")
|
||||
fixed_file_info = VS_FIXEDFILEINFO(
|
||||
VS_FFI_SIGNATURE,
|
||||
VS_FFI_STRUCVERSION,
|
||||
(major << 16) | minor,
|
||||
(micro << 16) | build,
|
||||
(major << 16) | minor,
|
||||
(micro << 16) | build,
|
||||
VS_FFI_FILEFLAGSMASK,
|
||||
file_flags,
|
||||
VOS_NT_WINDOWS32,
|
||||
2 if is_dll else 1, # VFT_DLL or VFT_APP
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
)
|
||||
|
||||
# string table with its children
|
||||
string_table = String(KEY_STRING_TABLE)
|
||||
for key, value in data.items():
|
||||
string_table.children(String(key, value))
|
||||
|
||||
# create string file info and add string table as child
|
||||
string_file_info = String(KEY_STRING_FILE_INFO)
|
||||
string_file_info.children(string_table)
|
||||
|
||||
# var file info has a child
|
||||
var_file_info = String(KEY_VAR_FILE_INFO)
|
||||
var_file_info.children(String("Translation", 0x04E40409)) # 0x409,1252
|
||||
|
||||
# VS_VERSION_INFO is the first key and has two children
|
||||
string_version_info = String(KEY_VERSION_INFO, fixed_file_info)
|
||||
string_version_info.children(string_file_info)
|
||||
string_version_info.children(var_file_info)
|
||||
|
||||
return string_version_info
|
||||
|
||||
|
||||
def main_test(args=None) -> None:
|
||||
"""Command line test."""
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument(
|
||||
"filename",
|
||||
nargs="?",
|
||||
metavar="FILENAME",
|
||||
help="the name of the file (.dll, .pyd or .exe) to test version stamp",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--version",
|
||||
action="store",
|
||||
default="0.1",
|
||||
help="version to set as test",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--dict",
|
||||
action="store_true",
|
||||
dest="as_dict",
|
||||
help="show version info as dict",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--raw",
|
||||
action="store_true",
|
||||
dest="as_raw",
|
||||
help="show version info as raw bytes",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--pywin32",
|
||||
action="store_true",
|
||||
help="use pywin32 (win32verstamp) to set version information",
|
||||
)
|
||||
test_args = parser.parse_args(args)
|
||||
if test_args.filename is None:
|
||||
parser.error("filename must be specified")
|
||||
else:
|
||||
test_filename = Path(test_args.filename)
|
||||
if test_args.pywin32:
|
||||
global CX_FREEZE_STAMP # noqa: PLW0603
|
||||
CX_FREEZE_STAMP = "pywin32"
|
||||
|
||||
test_version = VersionInfo(
|
||||
test_args.version,
|
||||
comments="cx_Freeze comments",
|
||||
description="cx_Freeze description",
|
||||
company="cx_Freeze company",
|
||||
product="cx_Freeze product",
|
||||
copyright="(c) 2024, cx_Freeze",
|
||||
trademarks="cx_Freeze (TM)",
|
||||
)
|
||||
|
||||
if test_args.as_dict:
|
||||
print(test_version.version_info(test_filename))
|
||||
if test_args.as_raw:
|
||||
print(test_version.version_info(test_filename).to_buffer().hex(":"))
|
||||
test_version.stamp(test_filename)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# simple test
|
||||
main_test()
|
||||
Reference in New Issue
Block a user