mit neuen venv und exe-Files
This commit is contained in:
562
venv3_12/Lib/site-packages/cx_Freeze/command/bdist_rpm.py
Normal file
562
venv3_12/Lib/site-packages/cx_Freeze/command/bdist_rpm.py
Normal file
@@ -0,0 +1,562 @@
|
||||
"""Implements the 'bdist_rpm' command (create RPM binary distributions).
|
||||
|
||||
Borrowed from distutils.command.bdist_rpm of Python 3.10 and merged with
|
||||
bdist_rpm subclass of cx_Freeze 6.10.
|
||||
|
||||
https://rpm.org/documentation.html
|
||||
https://rpm-packaging-guide.github.io/
|
||||
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import os
|
||||
import platform
|
||||
import shutil
|
||||
import sys
|
||||
import tarfile
|
||||
from subprocess import CalledProcessError, check_output
|
||||
from sysconfig import get_python_version
|
||||
from typing import ClassVar
|
||||
|
||||
from setuptools import Command
|
||||
|
||||
from cx_Freeze._compat import IS_CONDA
|
||||
from cx_Freeze.exception import ExecError, FileError, PlatformError
|
||||
|
||||
__all__ = ["bdist_rpm"]
|
||||
|
||||
|
||||
class bdist_rpm(Command):
|
||||
"""Create an RPM distribution."""
|
||||
|
||||
description = "create an RPM distribution"
|
||||
|
||||
user_options: ClassVar[list[tuple[str, str | None, str]]] = [
|
||||
(
|
||||
"bdist-base=",
|
||||
None,
|
||||
"base directory for creating built distributions",
|
||||
),
|
||||
(
|
||||
"rpm-base=",
|
||||
None,
|
||||
"base directory for creating RPMs "
|
||||
'[defaults to "rpm" under "--bdist-base"]',
|
||||
),
|
||||
(
|
||||
"dist-dir=",
|
||||
"d",
|
||||
"directory to put final RPM files in "
|
||||
"(and .spec files if --spec-only)",
|
||||
),
|
||||
("spec-only", None, "only regenerate spec file"),
|
||||
# More meta-data: too RPM-specific to put in the setup script,
|
||||
# but needs to go in the .spec file -- so we make these options
|
||||
# to "bdist_rpm". The idea is that packagers would put this
|
||||
# info in pyproject.toml or setup.cfg, although they are of course free
|
||||
# to supply it on the command line.
|
||||
(
|
||||
"distribution-name=",
|
||||
None,
|
||||
"name of the (Linux) distribution to which this "
|
||||
"RPM applies (*not* the name of the module distribution!)",
|
||||
),
|
||||
(
|
||||
"group=",
|
||||
None,
|
||||
'package classification [default: "Development/Libraries"]',
|
||||
),
|
||||
("release=", None, "RPM release number"),
|
||||
("serial=", None, "RPM serial number"),
|
||||
(
|
||||
"vendor=",
|
||||
None,
|
||||
'RPM "vendor" (eg. "Joe Blow <joe@example.com>") '
|
||||
"[default: maintainer or author from setup script]",
|
||||
),
|
||||
(
|
||||
"packager=",
|
||||
None,
|
||||
'RPM packager (eg. "Jane Doe <jane@example.net>") '
|
||||
"[default: same as vendor]",
|
||||
),
|
||||
(
|
||||
"doc-files=",
|
||||
None,
|
||||
"list of documentation files (space or comma-separated)",
|
||||
),
|
||||
("changelog=", None, "RPM changelog"),
|
||||
("icon=", None, "name of icon file"),
|
||||
("provides=", None, "capabilities provided by this package"),
|
||||
("requires=", None, "capabilities required by this package"),
|
||||
("conflicts=", None, "capabilities which conflict with this package"),
|
||||
(
|
||||
"build-requires=",
|
||||
None,
|
||||
"capabilities required to build this package",
|
||||
),
|
||||
("obsoletes=", None, "capabilities made obsolete by this package"),
|
||||
("no-autoreq", None, "do not automatically calculate dependencies"),
|
||||
# Actions to take when building RPM
|
||||
("keep-temp", "k", "don't clean up RPM build directory"),
|
||||
("no-keep-temp", None, "clean up RPM build directory [default]"),
|
||||
# Add the hooks necessary for specifying custom scripts
|
||||
(
|
||||
"prep-script=",
|
||||
None,
|
||||
"Specify a script for the PREP phase of RPM building",
|
||||
),
|
||||
(
|
||||
"build-script=",
|
||||
None,
|
||||
"Specify a script for the BUILD phase of RPM building",
|
||||
),
|
||||
(
|
||||
"pre-install=",
|
||||
None,
|
||||
"Specify a script for the pre-INSTALL phase of RPM building",
|
||||
),
|
||||
(
|
||||
"install-script=",
|
||||
None,
|
||||
"Specify a script for the INSTALL phase of RPM building",
|
||||
),
|
||||
(
|
||||
"post-install=",
|
||||
None,
|
||||
"Specify a script for the post-INSTALL phase of RPM building",
|
||||
),
|
||||
(
|
||||
"pre-uninstall=",
|
||||
None,
|
||||
"Specify a script for the pre-UNINSTALL phase of RPM building",
|
||||
),
|
||||
(
|
||||
"post-uninstall=",
|
||||
None,
|
||||
"Specify a script for the post-UNINSTALL phase of RPM building",
|
||||
),
|
||||
(
|
||||
"clean-script=",
|
||||
None,
|
||||
"Specify a script for the CLEAN phase of RPM building",
|
||||
),
|
||||
(
|
||||
"verify-script=",
|
||||
None,
|
||||
"Specify a script for the VERIFY phase of the RPM build",
|
||||
),
|
||||
("quiet", "q", "Run the INSTALL phase of RPM building in quiet mode"),
|
||||
("debug", "g", "Run in debug mode"),
|
||||
]
|
||||
|
||||
boolean_options: ClassVar[list[str]] = [
|
||||
"keep-temp",
|
||||
"no-autoreq",
|
||||
"quiet",
|
||||
"debug",
|
||||
]
|
||||
|
||||
negative_opt: ClassVar[dict[str, str]] = {
|
||||
"no-keep-temp": "keep-temp",
|
||||
}
|
||||
|
||||
def initialize_options(self) -> None:
|
||||
self.bdist_base = None
|
||||
self.dist_dir = None
|
||||
|
||||
self.rpm_base = None
|
||||
self.spec_only = None
|
||||
|
||||
self.distribution_name = None
|
||||
self.group = None
|
||||
self.release = None
|
||||
self.serial = None
|
||||
self.vendor = None
|
||||
self.packager = None
|
||||
self.doc_files = None
|
||||
self.changelog = None
|
||||
self.icon = None
|
||||
|
||||
self.prep_script = None
|
||||
self.build_script = None
|
||||
self.install_script = None
|
||||
self.clean_script = None
|
||||
self.verify_script = None
|
||||
self.pre_install = None
|
||||
self.post_install = None
|
||||
self.pre_uninstall = None
|
||||
self.post_uninstall = None
|
||||
self.prep = None
|
||||
self.provides = None
|
||||
self.requires = None
|
||||
self.conflicts = None
|
||||
self.build_requires = None
|
||||
self.obsoletes = None
|
||||
|
||||
self.keep_temp = 0
|
||||
self.no_autoreq = 0
|
||||
|
||||
self.quiet = 0
|
||||
self.debug = 0
|
||||
|
||||
def finalize_options(self) -> None:
|
||||
if os.name != "posix":
|
||||
msg = (
|
||||
"don't know how to create RPM "
|
||||
f"distributions on platform {os.name}"
|
||||
)
|
||||
raise PlatformError(msg)
|
||||
|
||||
self._rpm = shutil.which("rpm")
|
||||
self._rpmbuild = shutil.which("rpmbuild")
|
||||
if not self._rpmbuild:
|
||||
msg = "failed to find rpmbuild for this platform."
|
||||
raise PlatformError(msg)
|
||||
|
||||
self.set_undefined_options(
|
||||
"bdist",
|
||||
("bdist_base", "bdist_base"),
|
||||
("dist_dir", "dist_dir"),
|
||||
)
|
||||
if self.rpm_base is None:
|
||||
self.rpm_base = os.path.join(self.bdist_base, "rpm")
|
||||
|
||||
self.finalize_package_data()
|
||||
|
||||
def finalize_package_data(self) -> None:
|
||||
self.ensure_string("group", "Development/Libraries")
|
||||
contact = self.distribution.get_contact() or "UNKNOWN"
|
||||
contact_email = self.distribution.get_contact_email() or "UNKNOWN"
|
||||
self.ensure_string("vendor", f"{contact} <{contact_email}>")
|
||||
self.ensure_string("packager")
|
||||
self.ensure_string_list("doc_files")
|
||||
if isinstance(self.doc_files, list):
|
||||
doc_files = set(self.doc_files)
|
||||
for readme in ("README", "README.txt"):
|
||||
if os.path.exists(readme) and readme not in doc_files:
|
||||
self.doc_files.append(readme)
|
||||
|
||||
self.ensure_string("release", "1")
|
||||
self.ensure_string("serial") # should it be an int?
|
||||
|
||||
self.ensure_string("distribution_name")
|
||||
|
||||
self.ensure_string("changelog")
|
||||
# Format changelog correctly
|
||||
self.changelog = self._format_changelog(self.changelog)
|
||||
|
||||
self.ensure_filename("icon")
|
||||
|
||||
self.ensure_filename("prep_script")
|
||||
self.ensure_filename("build_script")
|
||||
self.ensure_filename("install_script")
|
||||
self.ensure_filename("clean_script")
|
||||
self.ensure_filename("verify_script")
|
||||
self.ensure_filename("pre_install")
|
||||
self.ensure_filename("post_install")
|
||||
self.ensure_filename("pre_uninstall")
|
||||
self.ensure_filename("post_uninstall")
|
||||
|
||||
# Now *this* is some meta-data that belongs in the setup script...
|
||||
self.ensure_string_list("provides")
|
||||
self.ensure_string_list("requires")
|
||||
self.ensure_string_list("conflicts")
|
||||
self.ensure_string_list("build_requires")
|
||||
self.ensure_string_list("obsoletes")
|
||||
|
||||
def run(self) -> None:
|
||||
if self.debug:
|
||||
print("before _get_package_data():")
|
||||
print("vendor =", self.vendor)
|
||||
print("packager =", self.packager)
|
||||
print("doc_files =", self.doc_files)
|
||||
print("changelog =", self.changelog)
|
||||
|
||||
# make directories
|
||||
if self.spec_only:
|
||||
spec_dir = self.dist_dir
|
||||
else:
|
||||
rpm_dir = {}
|
||||
for data in ("SOURCES", "SPECS", "BUILD", "RPMS", "SRPMS"):
|
||||
rpm_dir[data] = os.path.join(self.rpm_base, data)
|
||||
self.mkpath(rpm_dir[data])
|
||||
spec_dir = rpm_dir["SPECS"]
|
||||
self.mkpath(self.dist_dir)
|
||||
|
||||
# Spec file goes into 'dist_dir' if '--spec-only specified',
|
||||
# build/rpm.<plat> otherwise.
|
||||
distribution_name = self.distribution.get_name()
|
||||
spec_path = os.path.join(spec_dir, f"{distribution_name}.spec")
|
||||
self.execute(
|
||||
write_file,
|
||||
(spec_path, self._make_spec_file()),
|
||||
f"writing '{spec_path}'",
|
||||
)
|
||||
|
||||
if self.spec_only: # stop if requested
|
||||
return
|
||||
|
||||
# Make a source distribution and copy to SOURCES directory with
|
||||
# optional icon.
|
||||
def exclude_filter(info: tarfile.TarInfo) -> tarfile.TarInfo | None:
|
||||
if (
|
||||
os.path.basename(info.name) in ("build", "dist")
|
||||
and info.isdir()
|
||||
):
|
||||
return None
|
||||
return info
|
||||
|
||||
name = self.distribution.get_name()
|
||||
version = self.distribution.get_version()
|
||||
source = f"{name}-{version}"
|
||||
source_dir = rpm_dir["SOURCES"]
|
||||
source_fullname = os.path.join(source_dir, source + ".tar.gz")
|
||||
with tarfile.open(source_fullname, "w:gz") as tar:
|
||||
tar.add(".", source, filter=exclude_filter)
|
||||
if self.icon:
|
||||
if os.path.exists(self.icon):
|
||||
self.copy_file(self.icon, source_dir)
|
||||
else:
|
||||
msg = f"icon file {self.icon!r} does not exist"
|
||||
raise FileError(msg)
|
||||
|
||||
# build package, binary only (-bb)
|
||||
logging.info("building RPMs")
|
||||
rpm_cmd = [self._rpmbuild, "-bb"]
|
||||
if not self.keep_temp:
|
||||
rpm_cmd.append("--clean")
|
||||
|
||||
if self.quiet:
|
||||
rpm_cmd.append("--quiet")
|
||||
|
||||
rpm_cmd.append(spec_path)
|
||||
# Determine the binary rpm names that should be built out of this spec
|
||||
# file
|
||||
# Note that some of these may not be really built (if the file
|
||||
# list is empty)
|
||||
nvr_string = "%{name}-%{version}-%{release}"
|
||||
src_rpm = nvr_string + ".src.rpm"
|
||||
non_src_rpm = "%{arch}/" + nvr_string + ".%{arch}.rpm"
|
||||
q_cmd = [
|
||||
self._rpm,
|
||||
"-q",
|
||||
"--qf",
|
||||
rf"{src_rpm} {non_src_rpm}\n",
|
||||
"--specfile",
|
||||
spec_path,
|
||||
]
|
||||
try:
|
||||
out = check_output(q_cmd, text=True)
|
||||
except CalledProcessError as exc:
|
||||
msg = f"Failed to execute: {' '.join(q_cmd)!r}"
|
||||
raise ExecError(msg) from exc
|
||||
|
||||
binary_rpms = []
|
||||
for line in out.splitlines():
|
||||
rows = line.split()
|
||||
assert len(rows) == 2 # noqa: S101
|
||||
binary_rpms.append(rows[1])
|
||||
|
||||
self.spawn(rpm_cmd)
|
||||
|
||||
if not self.dry_run:
|
||||
pyversion = get_python_version()
|
||||
|
||||
for binary_rpm in binary_rpms:
|
||||
rpm = os.path.join(rpm_dir["RPMS"], binary_rpm)
|
||||
if os.path.exists(rpm):
|
||||
self.move_file(rpm, self.dist_dir)
|
||||
filename = os.path.join(
|
||||
self.dist_dir, os.path.basename(rpm)
|
||||
)
|
||||
self.distribution.dist_files.append(
|
||||
("bdist_rpm", pyversion, filename)
|
||||
)
|
||||
|
||||
def _make_spec_file(self) -> list[str]:
|
||||
"""Generate the text of an RPM spec file and return it as a
|
||||
list of strings (one per line).
|
||||
"""
|
||||
# definitions and headers
|
||||
dist = self.distribution
|
||||
spec_file = [
|
||||
f"%define _topdir {os.path.abspath(self.rpm_base)}",
|
||||
# cx_Freeze specific
|
||||
"%define __prelink_undo_cmd %{nil}",
|
||||
"%define __strip /bin/true",
|
||||
"",
|
||||
f"%define name {dist.get_name()}",
|
||||
f"%define version {dist.get_version().replace('-', '_')}",
|
||||
f"%define unmangled_version {dist.get_version()}",
|
||||
f"%define release {self.release.replace('-', '_')}",
|
||||
"",
|
||||
f"Summary: {dist.get_description() or 'UNKNOWN'}",
|
||||
"Name: %{name}",
|
||||
"Version: %{version}",
|
||||
"Release: %{release}",
|
||||
f"License: {dist.get_license() or 'UNKNOWN'}",
|
||||
f"Group: {self.group}",
|
||||
"BuildRoot: %{buildroot}",
|
||||
"Prefix: %{_prefix}",
|
||||
f"BuildArch: {platform.machine()}",
|
||||
]
|
||||
|
||||
# Fix for conda
|
||||
if IS_CONDA:
|
||||
spec_file.append("%define debug_package %{nil}")
|
||||
|
||||
# Workaround for #14443 which affects some RPM based systems such as
|
||||
# RHEL6 (and probably derivatives)
|
||||
vendor_hook = check_output(
|
||||
[self._rpm, "--eval", "%{__os_install_post}"], text=True
|
||||
)
|
||||
# Generate a potential replacement value for __os_install_post (whilst
|
||||
# normalizing the whitespace to simplify the test for whether the
|
||||
# invocation of brp-python-bytecompile passes in __python):
|
||||
vendor_hook = "\n".join(
|
||||
[f" {line.strip()} \\" for line in vendor_hook.splitlines()]
|
||||
)
|
||||
problem = "brp-python-bytecompile \\\n"
|
||||
fixed = "brp-python-bytecompile %{__python} \\\n"
|
||||
fixed_hook = vendor_hook.replace(problem, fixed)
|
||||
if fixed_hook != vendor_hook:
|
||||
spec_file += [
|
||||
"# Workaround for http://bugs.python.org/issue14443",
|
||||
f"%define __python {sys.executable}",
|
||||
f"%define __os_install_post {fixed_hook}",
|
||||
"",
|
||||
]
|
||||
|
||||
# we create the spec file before running 'tar' in case of --spec-only.
|
||||
spec_file.append("Source0: %{name}-%{unmangled_version}.tar.gz")
|
||||
|
||||
for field in (
|
||||
"Vendor",
|
||||
"Packager",
|
||||
"Provides",
|
||||
"Requires",
|
||||
"Conflicts",
|
||||
"Obsoletes",
|
||||
):
|
||||
val = getattr(self, field.lower())
|
||||
if isinstance(val, list):
|
||||
join_val = " ".join(val)
|
||||
spec_file.append(f"{field}: {join_val}")
|
||||
elif val is not None:
|
||||
spec_file.append(f"{field}: {val}")
|
||||
|
||||
if dist.get_url() not in (None, "UNKNOWN"):
|
||||
spec_file.append(f"Url: {dist.get_url()}")
|
||||
|
||||
if self.distribution_name:
|
||||
spec_file.append(f"Distribution: {self.distribution_name}")
|
||||
|
||||
if self.build_requires:
|
||||
spec_file.append("BuildRequires: " + " ".join(self.build_requires))
|
||||
|
||||
if self.icon:
|
||||
spec_file.append("Icon: " + os.path.basename(self.icon))
|
||||
|
||||
if self.no_autoreq:
|
||||
spec_file.append("AutoReq: 0")
|
||||
|
||||
spec_file += [
|
||||
"",
|
||||
"%description",
|
||||
dist.get_long_description() or dist.get_description() or "UNKNOWN",
|
||||
]
|
||||
|
||||
# rpm scripts - figure out default build script
|
||||
if dist.script_name == "cxfreeze":
|
||||
def_setup_call = shutil.which(dist.script_name)
|
||||
else:
|
||||
def_setup_call = f"{sys.executable} {dist.script_name}"
|
||||
def_build = f"{def_setup_call} build_exe --optimize=1 --silent"
|
||||
def_build = 'env CFLAGS="$RPM_OPT_FLAGS" ' + def_build
|
||||
|
||||
# insert contents of files
|
||||
|
||||
# this is kind of misleading: user-supplied options are files
|
||||
# that we open and interpolate into the spec file, but the defaults
|
||||
# are just text that we drop in as-is.
|
||||
|
||||
install_cmd = (
|
||||
f"{def_setup_call} install --skip-build"
|
||||
" --prefix=%{_prefix} --root=%{buildroot}"
|
||||
)
|
||||
|
||||
script_options = [
|
||||
("prep", "prep_script", "%setup -n %{name}-%{unmangled_version}"),
|
||||
("build", "build_script", def_build),
|
||||
("install", "install_script", install_cmd),
|
||||
("clean", "clean_script", "rm -rf %{buildroot}"),
|
||||
("verifyscript", "verify_script", None),
|
||||
("pre", "pre_install", None),
|
||||
("post", "post_install", None),
|
||||
("preun", "pre_uninstall", None),
|
||||
("postun", "post_uninstall", None),
|
||||
]
|
||||
|
||||
for rpm_opt, attr, default in script_options:
|
||||
# Insert contents of file referred to, if no file is referred to
|
||||
# use 'default' as contents of script
|
||||
val = getattr(self, attr)
|
||||
if val or default:
|
||||
spec_file.extend(["", "%" + rpm_opt])
|
||||
if val:
|
||||
with open(val, encoding="utf_8") as file:
|
||||
spec_file.extend(file.read().split("\n"))
|
||||
else:
|
||||
spec_file.append(default)
|
||||
|
||||
# files section
|
||||
spec_file += [
|
||||
"",
|
||||
"%files",
|
||||
"%dir %{_prefix}/lib/%{name}-%{unmangled_version}",
|
||||
"%{_prefix}/lib/%{name}-%{unmangled_version}/*",
|
||||
"%{_bindir}/%{name}",
|
||||
"%defattr(-,root,root)",
|
||||
]
|
||||
|
||||
if self.doc_files:
|
||||
spec_file.append("%doc " + " ".join(self.doc_files))
|
||||
|
||||
if self.changelog:
|
||||
spec_file.extend(["", "%changelog"])
|
||||
spec_file.extend(self.changelog)
|
||||
|
||||
return spec_file
|
||||
|
||||
@staticmethod
|
||||
def _format_changelog(changelog) -> list[str]:
|
||||
"""Format the changelog correctly and convert it to a string list."""
|
||||
if not changelog:
|
||||
return changelog
|
||||
new_changelog = []
|
||||
for raw_line in changelog.strip().split("\n"):
|
||||
line = raw_line.strip()
|
||||
if line[0] == "*":
|
||||
new_changelog.extend(["", line])
|
||||
elif line[0] == "-":
|
||||
new_changelog.append(line)
|
||||
else:
|
||||
new_changelog.append(" " + line)
|
||||
|
||||
# strip trailing newline inserted by first changelog entry
|
||||
if not new_changelog[0]:
|
||||
del new_changelog[0]
|
||||
|
||||
return new_changelog
|
||||
|
||||
|
||||
def write_file(filename, contents) -> None:
|
||||
"""Create a file with the specified name and write 'contents'
|
||||
(a sequence of strings without line terminators) to it.
|
||||
"""
|
||||
with open(filename, "w", encoding="utf_8") as file:
|
||||
for line in contents:
|
||||
file.write(line + "\n")
|
||||
Reference in New Issue
Block a user