From 46d91625e9f8dcc20374428c5e4ce6d8da24fb32 Mon Sep 17 00:00:00 2001 From: Florian Piesche <79453505+florianpiesche@users.noreply.github.com> Date: Fri, 30 May 2025 17:14:54 +0100 Subject: [PATCH 01/11] Try and update Tcl to 9.0.1... (CQ-1915) --- build_conquest_python.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build_conquest_python.py b/build_conquest_python.py index a58fd3a..a14e64d 100755 --- a/build_conquest_python.py +++ b/build_conquest_python.py @@ -168,8 +168,8 @@ def verify(self): class TclPackage(AutoconfMixin, NoArchiveMixin, Package): name = 'tcl' - version = '8.6.16' - tclversion = '8.6' + version = '9.0.1' + tclversion = '9.0' @property def source_archives(self): From 03faa0b3253610d570822a6ea2e6f954e87aa311 Mon Sep 17 00:00:00 2001 From: Florian Piesche Date: Wed, 4 Jun 2025 11:16:50 +0100 Subject: [PATCH 02/11] Also echo output to build log (CQ-1519) --- .github/workflows/build-conquest-python.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-conquest-python.yml b/.github/workflows/build-conquest-python.yml index 7e58ec9..07f3821 100644 --- a/.github/workflows/build-conquest-python.yml +++ b/.github/workflows/build-conquest-python.yml @@ -9,7 +9,8 @@ on: # yamllint disable-line rule:truthy default: false type: boolean platforms: - description: 'Platform to build for, eg. ' + description: 'Platform to build for' + type: choice options: - 'rocky8' - 'ubuntu' @@ -94,6 +95,7 @@ jobs: archive_path=$(echo $output | awk '{print $NF}') echo "archive_filename=$archive_filename" >> $GITHUB_ENV echo "archive_path=$archive_path" >> $GITHUB_ENV + echo $output - name: Store conquest python as build artifact if: ${{ !inputs.artifactory-push }} From 888ea0744dc219e701ca944d66941ac334bf68d4 Mon Sep 17 00:00:00 2001 From: Florian Piesche Date: Tue, 10 Jun 2025 17:55:35 +0100 Subject: [PATCH 03/11] Store build logs as artifacts for inspection (CQ-1519) --- .github/workflows/build-conquest-python.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/build-conquest-python.yml b/.github/workflows/build-conquest-python.yml index 07f3821..7e577af 100644 --- a/.github/workflows/build-conquest-python.yml +++ b/.github/workflows/build-conquest-python.yml @@ -97,6 +97,13 @@ jobs: echo "archive_path=$archive_path" >> $GITHUB_ENV echo $output + - name: Store build logs as build artifact + uses: actions/upload-artifact@v4 + with: + retention-days: 1 + path: "/opt/ccdc/third-party-sources/logs/*" + name: "build_logs.zip" + - name: Store conquest python as build artifact if: ${{ !inputs.artifactory-push }} uses: actions/upload-artifact@v4 From 89da381c6ffac1350fd634d2c93b8da7b296ca89 Mon Sep 17 00:00:00 2001 From: Florian Piesche Date: Wed, 11 Jun 2025 14:46:04 +0100 Subject: [PATCH 04/11] Try and manually specify include library and include paths for tcp (CQ-1519) --- build_conquest_python.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/build_conquest_python.py b/build_conquest_python.py index a59684a..f3e7eae 100755 --- a/build_conquest_python.py +++ b/build_conquest_python.py @@ -217,8 +217,11 @@ def configuration_script(self): def arguments_to_configuration_script(self): args = super().arguments_to_configuration_script + [ '--enable-shared', - '--enable-threads', + # '--enable-threads', '--enable-64bit', + f"LDFLAGS={ConquestPythonPackage().python_base_directory / 'lib'}", + f"CFLAGS={ConquestPythonPackage().python_base_directory / 'include'}", + f"CPPFLAGS={ConquestPythonPackage().python_base_directory / 'include'}", ] if self.macos: args.extend([ From 4a2cb5feaf773d0b0ea40d285f407b689cc1186b Mon Sep 17 00:00:00 2001 From: Florian Piesche Date: Wed, 11 Jun 2025 16:20:26 +0100 Subject: [PATCH 05/11] Try and fix cppflags/cflags (CQ-1519) --- build_conquest_python.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/build_conquest_python.py b/build_conquest_python.py index f3e7eae..ed58719 100755 --- a/build_conquest_python.py +++ b/build_conquest_python.py @@ -219,9 +219,9 @@ def arguments_to_configuration_script(self): '--enable-shared', # '--enable-threads', '--enable-64bit', - f"LDFLAGS={ConquestPythonPackage().python_base_directory / 'lib'}", - f"CFLAGS={ConquestPythonPackage().python_base_directory / 'include'}", - f"CPPFLAGS={ConquestPythonPackage().python_base_directory / 'include'}", + f"LDFLAGS={ConquestPythonPackage().python_base_directory / 'lib'} $LDFLAGS", + f"CFLAGS={ConquestPythonPackage().python_base_directory / 'include'} $CFLAGS", + f"CPPFLAGS={ConquestPythonPackage().python_base_directory / 'include'} $CPPFLAGS", ] if self.macos: args.extend([ From 4acd58dd8f19a351182f0bb11bc4fec8fb7c9e80 Mon Sep 17 00:00:00 2001 From: Florian Piesche Date: Wed, 11 Jun 2025 16:43:25 +0100 Subject: [PATCH 06/11] use lincludedir/libdir instead (CQ-1519) --- build_conquest_python.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/build_conquest_python.py b/build_conquest_python.py index ed58719..c5ee6d8 100755 --- a/build_conquest_python.py +++ b/build_conquest_python.py @@ -219,9 +219,8 @@ def arguments_to_configuration_script(self): '--enable-shared', # '--enable-threads', '--enable-64bit', - f"LDFLAGS={ConquestPythonPackage().python_base_directory / 'lib'} $LDFLAGS", - f"CFLAGS={ConquestPythonPackage().python_base_directory / 'include'} $CFLAGS", - f"CPPFLAGS={ConquestPythonPackage().python_base_directory / 'include'} $CPPFLAGS", + f"--includedir={ConquestPythonPackage().python_base_directory / 'include'}", + f'--libdir={ConquestPythonPackage().python_base_directory / "lib"}', ] if self.macos: args.extend([ From 6628b1df576fb5795d0166254f466c9b5ba8ab9c Mon Sep 17 00:00:00 2001 From: Florian Piesche Date: Wed, 11 Jun 2025 17:12:26 +0100 Subject: [PATCH 07/11] Try and just set prefix (CQ-1519) --- build_conquest_python.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/build_conquest_python.py b/build_conquest_python.py index c5ee6d8..86c31f8 100755 --- a/build_conquest_python.py +++ b/build_conquest_python.py @@ -219,8 +219,9 @@ def arguments_to_configuration_script(self): '--enable-shared', # '--enable-threads', '--enable-64bit', - f"--includedir={ConquestPythonPackage().python_base_directory / 'include'}", - f'--libdir={ConquestPythonPackage().python_base_directory / "lib"}', + f"--prefix={ConquestPythonPackage().python_base_directory}", + # f"--includedir={ConquestPythonPackage().python_base_directory / 'include'}", + # f'--libdir={ConquestPythonPackage().python_base_directory / "lib"}', ] if self.macos: args.extend([ From 8d65d54ae1eb5ed49d5ef6f8e0cae3989ba2ae95 Mon Sep 17 00:00:00 2001 From: Florian Piesche Date: Thu, 12 Jun 2025 11:10:21 +0100 Subject: [PATCH 08/11] Fix linter complaints and reofrmat files with `black` (NO_JIRA) --- build_conquest_python.py | 741 ++++++++++++++++++++++--------------- ccdc/thirdparty/package.py | 386 ++++++++++--------- 2 files changed, 652 insertions(+), 475 deletions(-) diff --git a/build_conquest_python.py b/build_conquest_python.py index 86c31f8..102e168 100755 --- a/build_conquest_python.py +++ b/build_conquest_python.py @@ -1,117 +1,127 @@ #!/usr/bin/env python3 -import sys -import subprocess -import os -import shutil -import tempfile +from ccdc.thirdparty.package import ( + Package, + AutoconfMixin, + MakeInstallMixin, + NoArchiveMixin, + CMakeMixin, +) import multiprocessing - +import os from pathlib import Path -from ccdc.thirdparty.package import Package, AutoconfMixin, MakeInstallMixin, NoArchiveMixin, CMakeMixin +import shutil +import subprocess class InstallInConquestPythonBaseMixin(object): @property def install_directory(self): - '''The nuance with descendants of this class is that these libraries will be installed under the Python Framework on MacOS''' + """The nuance with descendants of this class is that these libraries will be installed under the Python Framework on MacOS""" return ConquestPythonPackage().python_base_directory -class ZlibPackage(InstallInConquestPythonBaseMixin, AutoconfMixin, NoArchiveMixin, Package): - '''Zlib''' - name = 'zlib' - version = '1.2.13' +class ZlibPackage( + InstallInConquestPythonBaseMixin, AutoconfMixin, NoArchiveMixin, Package +): + """Zlib""" + + name = "zlib" + version = "1.2.13" @property def source_archives(self): return { # older version of zlib no longer available on zlib.net, # so get it directly from Mark Adler's github repo - f'zlib-{self.version}.tar.xz': f'https://github.com/madler/zlib/releases/download/v{self.version}/zlib-{self.version}.tar.xz' + f"zlib-{self.version}.tar.xz": f"https://github.com/madler/zlib/releases/download/v{self.version}/zlib-{self.version}.tar.xz" } -class SqlitePackage(InstallInConquestPythonBaseMixin, AutoconfMixin, NoArchiveMixin, Package): - '''SQLite''' - name = 'sqlite' - version = '3.34.0' - tarversion = '3340000' +class SqlitePackage( + InstallInConquestPythonBaseMixin, AutoconfMixin, NoArchiveMixin, Package +): + """SQLite""" + + name = "sqlite" + version = "3.34.0" + tarversion = "3340000" @property def source_archives(self): return { - f'sqlite-autoconf-{self.tarversion}.tar.gz': f'https://www.sqlite.org/2020/sqlite-autoconf-{self.tarversion}.tar.gz' + f"sqlite-autoconf-{self.tarversion}.tar.gz": f"https://www.sqlite.org/2020/sqlite-autoconf-{self.tarversion}.tar.gz" } @property def main_source_directory_path(self): - return self.source_extracted / f'{self.name}-autoconf-{self.tarversion}' + return self.source_extracted / f"{self.name}-autoconf-{self.tarversion}" @property def cflags(self): return super().cflags + [ - '-DSQLITE_ENABLE_FTS3', - '-DSQLITE_ENABLE_FTS3_PARENTHESIS', - '-DSQLITE_ENABLE_FTS4', - '-DSQLITE_ENABLE_FTS5', - '-DSQLITE_ENABLE_EXPLAIN_COMMENTS', - '-DSQLITE_ENABLE_NULL_TRIM', - '-DSQLITE_MAX_COLUMN=10000', - '-DSQLITE_ENABLE_JSON1', - '-DSQLITE_ENABLE_RTREE', - '-DSQLITE_TCL=0', - '-fPIC', + "-DSQLITE_ENABLE_FTS3", + "-DSQLITE_ENABLE_FTS3_PARENTHESIS", + "-DSQLITE_ENABLE_FTS4", + "-DSQLITE_ENABLE_FTS5", + "-DSQLITE_ENABLE_EXPLAIN_COMMENTS", + "-DSQLITE_ENABLE_NULL_TRIM", + "-DSQLITE_MAX_COLUMN=10000", + "-DSQLITE_ENABLE_JSON1", + "-DSQLITE_ENABLE_RTREE", + "-DSQLITE_TCL=0", + "-fPIC", ] @property def ldflags(self): - return super().ldflags + [ - '-lm' - ] + return super().ldflags + ["-lm"] @property def arguments_to_configuration_script(self): return super().arguments_to_configuration_script + [ - '--enable-threadsafe', - '--enable-shared=no', - '--enable-static=yes', - '--disable-readline', - '--disable-dependency-tracking', + "--enable-threadsafe", + "--enable-shared=no", + "--enable-static=yes", + "--disable-readline", + "--disable-dependency-tracking", ] -class OpensslPackage(InstallInConquestPythonBaseMixin, AutoconfMixin, NoArchiveMixin, Package): - ''' Most of these settings have been based on the Homebrew Formula that can be found here for reference +class OpensslPackage( + InstallInConquestPythonBaseMixin, AutoconfMixin, NoArchiveMixin, Package +): + """Most of these settings have been based on the Homebrew Formula that can be found here for reference https://github.com/Homebrew/homebrew-core/blob/5ab9766e70776ef1702f8d40c8ef5159252c31be/Formula/openssl%401.1.rb more details were pilfered from Python.org's installer creation script - ''' - name = 'openssl' - version = '1.1.1i' + """ + + name = "openssl" + version = "1.1.1i" @property def source_archives(self): return { - f'openssl-{self.version}.tar.gz': f'https://www.openssl.org/source/openssl-{self.version}.tar.gz' + f"openssl-{self.version}.tar.gz": f"https://www.openssl.org/source/openssl-{self.version}.tar.gz" } @property def configuration_script(self): - return self.main_source_directory_path / 'Configure' + return self.main_source_directory_path / "Configure" @property def arguments_to_configuration_script(self): args = super().arguments_to_configuration_script + [ - '--release', - f'--openssldir={self.install_directory}/ssl', - 'no-ssl3', - 'no-ssl3-method', - 'no-zlib', + "--release", + f"--openssldir={self.install_directory}/ssl", + "no-ssl3", + "no-ssl3-method", + "no-zlib", ] if self.macos: - args += ['darwin64-x86_64-cc', 'enable-ec_nistp_64_gcc_128'] + args += ["darwin64-x86_64-cc", "enable-ec_nistp_64_gcc_128"] else: - args += ['linux-x86_64', 'enable-ec_nistp_64_gcc_128'] + args += ["linux-x86_64", "enable-ec_nistp_64_gcc_128"] return args @property @@ -119,28 +129,37 @@ def environment_for_configuration_script(self): env = super().environment_for_configuration_script if env.pop("OPENSSL_LOCAL_CONFIG_DIR", None): print( - 'Removed OPENSSL_LOCAL_CONFIG_DIR from the environment to avoid interference') + "Removed OPENSSL_LOCAL_CONFIG_DIR from the environment to avoid interference" + ) return env def run_configuration_script(self): - '''OpenSSL has it's own interesting configuration script that runs via Perl''' + """OpenSSL has it's own interesting configuration script that runs via Perl""" self.system( - ['perl', str(self.configuration_script)] + - self.arguments_to_configuration_script, - env=self.environment_for_configuration_script, cwd=self.build_directory_path) + ["perl", str(self.configuration_script)] + + self.arguments_to_configuration_script, + env=self.environment_for_configuration_script, + cwd=self.build_directory_path, + ) def run_install_command(self): # No need to install man pages etc - self.system(['make', 'install_sw'], - env=self.environment_for_build_command, cwd=self.build_directory_path) + self.system( + ["make", "install_sw"], + env=self.environment_for_build_command, + cwd=self.build_directory_path, + ) # python build needs .a files in a directory away from .dylib # TODO: this might be unnecessary - static_libs_path = self.install_directory / 'staticlibs' + static_libs_path = self.install_directory / "staticlibs" static_libs_path.mkdir(parents=True, exist_ok=True) - shutil.copyfile(self.install_directory / 'lib' / - 'libssl.a', static_libs_path / 'libssl.a') - shutil.copyfile(self.install_directory / 'lib' / - 'libcrypto.a', static_libs_path / 'libcrypto.a') + shutil.copyfile( + self.install_directory / "lib" / "libssl.a", static_libs_path / "libssl.a" + ) + shutil.copyfile( + self.install_directory / "lib" / "libcrypto.a", + static_libs_path / "libcrypto.a", + ) def verify(self): # If this fails, take a look here!!!! @@ -161,39 +180,40 @@ def verify(self): # env=env, cwd=self.build_directory_path) pass + # This can get very messy, very quickly, as I found out by following our custom scripts # So take a look here for inspiration # https://github.com/python/cpython/blob/2.7/Mac/BuildScript/build-installer.py class TclPackage(AutoconfMixin, NoArchiveMixin, Package): - name = 'tcl' - version = '9.0.1' - tclversion = '9.0' + name = "tcl" + version = "9.0.1" + tclversion = "9.0" @property def source_archives(self): return { # Canonica would be https://prdownloads.sourceforge.net/tcl but it's fetching truncated files - f'tcl{self.version}-src.tar.gz': f'https://freefr.dl.sourceforge.net/project/tcl/Tcl/{self.version}/tcl{self.version}-src.tar.gz' + f"tcl{self.version}-src.tar.gz": f"https://freefr.dl.sourceforge.net/project/tcl/Tcl/{self.version}/tcl{self.version}-src.tar.gz" } def extract_source_archives(self): super().extract_source_archives() # Remove packages we don't want to build - sp = Path(self.main_source_directory_path / 'pkgs') + sp = Path(self.main_source_directory_path / "pkgs") - sqlite_path = sp.glob('sqlite*') + sqlite_path = sp.glob("sqlite*") for file in sqlite_path: shutil.rmtree(file) - tbc_path = sp.glob('tdbc*') + tbc_path = sp.glob("tdbc*") for file in tbc_path: shutil.rmtree(file) @property def main_source_directory_path(self): - return self.source_extracted / f'{self.name}{self.version}' + return self.source_extracted / f"{self.name}{self.version}" @property def install_directory(self): @@ -205,53 +225,56 @@ def install_directory(self): @property def cflags(self): - return super().cflags + [ - '-DNDEBUG' - ] + return super().cflags + ["-DNDEBUG"] @property def configuration_script(self): - return self.main_source_directory_path / 'unix' / 'configure' + return self.main_source_directory_path / "unix" / "configure" @property def arguments_to_configuration_script(self): args = super().arguments_to_configuration_script + [ - '--enable-shared', + "--enable-shared", # '--enable-threads', - '--enable-64bit', + "--enable-64bit", f"--prefix={ConquestPythonPackage().python_base_directory}", # f"--includedir={ConquestPythonPackage().python_base_directory / 'include'}", # f'--libdir={ConquestPythonPackage().python_base_directory / "lib"}', ] if self.macos: - args.extend([ - f'--libdir={ConquestPythonPackage().python_base_directory / "lib"}', - ]) + args.extend( + [ + f'--libdir={ConquestPythonPackage().python_base_directory / "lib"}', + ] + ) return args def run_build_command(self): if self.macos: - self.system([ - 'make', - f'TCL_LIBRARY="{ ConquestPythonPackage().python_base_directory / "lib" / "tcl8.6" }"', - f'-j{multiprocessing.cpu_count()}' - ], - env=self.environment_for_build_command, cwd=self.build_directory_path) + self.system( + [ + "make", + f'TCL_LIBRARY="{ConquestPythonPackage().python_base_directory / "lib" / "tcl8.6"}"', + f"-j{multiprocessing.cpu_count()}", + ], + env=self.environment_for_build_command, + cwd=self.build_directory_path, + ) else: super().run_build_command() @property def tcl_versioned_framework_path(self): - return Path('Tcl.framework') / 'Versions' / '8.6' + return Path("Tcl.framework") / "Versions" / "8.6" @property def tcl_versioned_framework_path_in_library(self): - return Path('Library') / 'Frameworks' / self.tcl_versioned_framework_path + return Path("Library") / "Frameworks" / self.tcl_versioned_framework_path @property def include_directories(self): - '''Return the directories clients must add to their include path''' - return [self.install_directory / 'include'] + """Return the directories clients must add to their include path""" + return [self.install_directory / "include"] @property def bindir(self): @@ -259,22 +282,25 @@ def bindir(self): @property def tclsh(self): - return self.bindir / f"tclsh{ self.tclversion }" + return self.bindir / f"tclsh{self.tclversion}" def run_install_command(self): if self.macos: - self.system([ - 'make', - 'install', - f'TCL_LIBRARY="{ConquestPythonPackage().python_base_directory / "lib" / "tcl8.6"}"', - f'PACKAGE_DIR={ ConquestPythonPackage().python_base_directory / "lib" / "tcl8.6" }', - ], - env=self.environment_for_build_command, cwd=self.build_directory_path) + self.system( + [ + "make", + "install", + f'TCL_LIBRARY="{ConquestPythonPackage().python_base_directory / "lib" / "tcl8.6"}"', + f'PACKAGE_DIR={ConquestPythonPackage().python_base_directory / "lib" / "tcl8.6"}', + ], + env=self.environment_for_build_command, + cwd=self.build_directory_path, + ) try: - os.unlink(self.bindir/'tclsh') + os.unlink(self.bindir / "tclsh") except OSError: pass - os.symlink(self.bindir / 'tclsh8.6', self.bindir/'tclsh') + os.symlink(self.bindir / "tclsh8.6", self.bindir / "tclsh") # # copy msgcat.tcl because pyinstaller won't pick up modules # shutil.copytree( # self.main_source_directory_path / 'library' / 'msgcat', @@ -286,21 +312,26 @@ def run_install_command(self): def verify(self): honk_proc = subprocess.Popen( - [self.tclsh], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - honk_proc.stdin.write("puts honk\n".encode('utf-8')) + [self.tclsh], + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) + honk_proc.stdin.write("puts honk\n".encode("utf-8")) out = honk_proc.communicate() honk_proc.stdin.close() s0 = out[0].decode().rstrip() - if s0 != 'honk': + if s0 != "honk": print( - f'{self.tclsh} won\'t honk, stdout[{out[0].decode()}] stderr[{out[1].decode()}]') + f"{self.tclsh} won't honk, stdout[{out[0].decode()}] stderr[{out[1].decode()}]" + ) return False return True class TkPackage(AutoconfMixin, NoArchiveMixin, Package): - name = 'tk' - version = '8.6.16' + name = "tk" + version = "8.6.16" @property def source_archives(self): @@ -308,24 +339,24 @@ def source_archives(self): # Canonical would be https://prdownloads.sourceforge.net/tcl/ but it's fetching garbage # How lovely to have a different tcl and tk version.... # The url is a bit of a mess, but it's the only one that works? - f'tk{self.version}-src.tar.gz': f'https://sourceforge.net/projects/tcl/files/Tcl/{self.version}/tk{self.version}-src.tar.gz/download' + f"tk{self.version}-src.tar.gz": f"https://sourceforge.net/projects/tcl/files/Tcl/{self.version}/tk{self.version}-src.tar.gz/download" } @property def main_source_directory_path(self): - return self.source_extracted / f'{self.name}{self.version}' + return self.source_extracted / f"{self.name}{self.version}" @property def environment_for_configuration_script(self): env = super().environment_for_configuration_script - env['PATH'] = f"{TclPackage().bindir}:{env['PATH']}" + env["PATH"] = f"{TclPackage().bindir}:{env['PATH']}" if self.macos: - env['ac_cv_path_tclsh'] = f'{TclPackage().bindir / "tclsh8.6"}' + env["ac_cv_path_tclsh"] = f'{TclPackage().bindir / "tclsh8.6"}' return env @property def configuration_script(self): - return self.main_source_directory_path / 'unix' / 'configure' + return self.main_source_directory_path / "unix" / "configure" @property def install_directory(self): @@ -335,85 +366,98 @@ def install_directory(self): def arguments_to_configuration_script(self): if self.macos: return super().arguments_to_configuration_script + [ - f'--with-tcl={ ConquestPythonPackage().python_base_directory / "lib" }', - '--enable-aqua', - '--enable-shared', - '--enable-threads', - '--enable-64bit', + f'--with-tcl={ConquestPythonPackage().python_base_directory / "lib"}', + "--enable-aqua", + "--enable-shared", + "--enable-threads", + "--enable-64bit", f'--libdir={ConquestPythonPackage().python_base_directory / "lib"}', ] else: return super().arguments_to_configuration_script + [ - '--enable-shared', - '--enable-threads', - '--enable-64bit', - f'--with-tcl={ TclPackage().install_directory / "lib" }' + "--enable-shared", + "--enable-threads", + "--enable-64bit", + f'--with-tcl={TclPackage().install_directory / "lib"}', ] def run_build_command(self): if self.macos: - self.system([ - 'make', - f'TCL_LIBRARY="{ ConquestPythonPackage().python_base_directory / "lib" / "tcl8.6" }"', - f'TK_LIBRARY="{ ConquestPythonPackage().python_base_directory / "lib" / "tk8.6" }"', - f'-j{multiprocessing.cpu_count()}' - ], - env=self.environment_for_build_command, cwd=self.build_directory_path) + self.system( + [ + "make", + f'TCL_LIBRARY="{ConquestPythonPackage().python_base_directory / "lib" / "tcl8.6"}"', + f'TK_LIBRARY="{ConquestPythonPackage().python_base_directory / "lib" / "tk8.6"}"', + f"-j{multiprocessing.cpu_count()}", + ], + env=self.environment_for_build_command, + cwd=self.build_directory_path, + ) else: super().run_build_command() def run_install_command(self): if self.macos: - self.system([ - 'make', - 'install', - f'TCL_LIBRARY="{ConquestPythonPackage().python_base_directory / "lib" / "tcl8.6"}"', - f'TK_LIBRARY="{ConquestPythonPackage().python_base_directory / "lib" / "tk8.6"}"', - ], - env=self.environment_for_build_command, cwd=self.build_directory_path) + self.system( + [ + "make", + "install", + f'TCL_LIBRARY="{ConquestPythonPackage().python_base_directory / "lib" / "tcl8.6"}"', + f'TK_LIBRARY="{ConquestPythonPackage().python_base_directory / "lib" / "tk8.6"}"', + ], + env=self.environment_for_build_command, + cwd=self.build_directory_path, + ) else: super().run_install_command() @property def include_directories(self): - '''Return the directories clients must add to their include path''' - return [self.install_directory / 'include'] + """Return the directories clients must add to their include path""" + return [self.install_directory / "include"] def verify(self): - itcl_check = ''' + itcl_check = """ package require Itcl package require Tk puts "OK" exit -''' - itcl_proc = subprocess.Popen([TclPackage( - ).tclsh], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - itcl_proc.stdin.write(itcl_check.encode('utf-8')) +""" + itcl_proc = subprocess.Popen( + [TclPackage().tclsh], + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) + itcl_proc.stdin.write(itcl_check.encode("utf-8")) out = itcl_proc.communicate() itcl_proc.stdin.close() s0 = out[1].decode().rstrip() if "can't find package" in s0: print( - f'{TclPackage().tclsh} won\'t do, stdout[{out[0].decode()}] stderr[{out[1].decode()}]') + f"{TclPackage().tclsh} won't do, stdout[{out[0].decode()}] stderr[{out[1].decode()}]" + ) return False return True -class ToglPackage(InstallInConquestPythonBaseMixin, AutoconfMixin, NoArchiveMixin, Package): - name = 'togl' - version = 'windows-ci' +class ToglPackage( + InstallInConquestPythonBaseMixin, AutoconfMixin, NoArchiveMixin, Package +): + name = "togl" + version = "windows-ci" @property def source_archives(self): return { - f'Togl-{self.version}.zip': f'https://github.com/rockdreamer/togl/archive/{self.version}.zip' + f"Togl-{self.version}.zip": f"https://github.com/rockdreamer/togl/archive/{self.version}.zip" } @property def main_source_directory_path(self): - return self.source_extracted / f'{self.name}-{self.version}' + return self.source_extracted / f"{self.name}-{self.version}" @property def install_directory(self): @@ -421,7 +465,7 @@ def install_directory(self): @property def build_directory_path(self): - '''The autoconf script is a bit rubbish, only works in-source :(''' + """The autoconf script is a bit rubbish, only works in-source :(""" return self.main_source_directory_path @property @@ -429,118 +473,130 @@ def arguments_to_configuration_script(self): args = super().arguments_to_configuration_script if self.macos: args += [ - f'--with-tcl={ ConquestPythonPackage().python_base_directory / "lib" }', + f'--with-tcl={ConquestPythonPackage().python_base_directory / "lib"}', f'--libdir={ConquestPythonPackage().python_base_directory / "lib"}', - f'--with-tclinclude={ TclPackage().include_directories[0]}', - f'--with-tk={ ConquestPythonPackage().python_base_directory / "lib" }', - f'--with-tkinclude={ TkPackage().include_directories[0]}', + f"--with-tclinclude={TclPackage().include_directories[0]}", + f'--with-tk={ConquestPythonPackage().python_base_directory / "lib"}', + f"--with-tkinclude={TkPackage().include_directories[0]}", ] else: args += [ - f'--with-tcl={ TclPackage().install_directory / "lib" }', - f'--with-tk={ TkPackage().install_directory / "lib" }', - '--with-Xmu', + f'--with-tcl={TclPackage().install_directory / "lib"}', + f'--with-tk={TkPackage().install_directory / "lib"}', + "--with-Xmu", ] return args def verify(self): - itcl_check = ''' + itcl_check = """ package require Togl wm title . "Togl runs" -togl .o1 -width 200 -height 200 -rgba true -double true -depth true -create double::create_cb -display double::display_cb -reshape double::reshape_cb -multisample false -ident Aliased +togl .o1 -width 200 -height 200 -rgba true -double true -depth true -create double::create_cb -display double::display_cb -reshape double::reshape_cb -multisample false -ident Aliased puts "OK" exit -''' - itcl_proc = subprocess.Popen([TclPackage( - ).tclsh], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - itcl_proc.stdin.write(itcl_check.encode('utf-8')) +""" + itcl_proc = subprocess.Popen( + [TclPackage().tclsh], + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) + itcl_proc.stdin.write(itcl_check.encode("utf-8")) out = itcl_proc.communicate() itcl_proc.stdin.close() s0 = out[1].decode().rstrip() if "can't find package" in s0: print( - f'{TclPackage().tclsh} won\'t do, stdout[{out[0].decode()}] stderr[{out[1].decode()}]') + f"{TclPackage().tclsh} won't do, stdout[{out[0].decode()}] stderr[{out[1].decode()}]" + ) return False return True -class DbPackage(InstallInConquestPythonBaseMixin, MakeInstallMixin, NoArchiveMixin, Package): - '''AKA BSDDb. This is made by Oracle now. So, DO NOT update this version.''' - name = 'db' - version = '5.3.28' +class DbPackage( + InstallInConquestPythonBaseMixin, MakeInstallMixin, NoArchiveMixin, Package +): + """AKA BSDDb. This is made by Oracle now. So, DO NOT update this version.""" + + name = "db" + version = "5.3.28" @property def source_archives(self): return { - f'db-{self.version}.tar.gz': f'https://download.oracle.com/berkeley-db/db-{self.version}.tar.gz' + f"db-{self.version}.tar.gz": f"https://download.oracle.com/berkeley-db/db-{self.version}.tar.gz" } def patch_sources(self): # Fails to build on recent Clang and gcc versions without this patch. # See https://github.com/narkoleptik/os-x-berkeleydb-patch self.patch( - self.main_source_directory_path / 'src' / 'dbinc' / 'atomic.h', - ('\t__atomic_compare_exchange((p), (o), (n))', - '\t__atomic_compare_exchange_db((p), (o), (n))'), - ('static inline int __atomic_compare_exchange(', - 'static inline int __atomic_compare_exchange_db(') + self.main_source_directory_path / "src" / "dbinc" / "atomic.h", + ( + "\t__atomic_compare_exchange((p), (o), (n))", + "\t__atomic_compare_exchange_db((p), (o), (n))", + ), + ( + "static inline int __atomic_compare_exchange(", + "static inline int __atomic_compare_exchange_db(", + ), ) @property def configuration_script(self): - return self.main_source_directory_path / 'dist' / 'configure' + return self.main_source_directory_path / "dist" / "configure" @property def arguments_to_configuration_script(self): - args = [ - '--enable-compat185', - '--enable-shared', - '--enable-dbm' - ] + args = ["--enable-compat185", "--enable-shared", "--enable-dbm"] if self.macos: # XCode 12 causes a different mutex implementation to be loaded - # so we impose use of posix mutexes and avoid the presence of + # so we impose use of posix mutexes and avoid the presence of # private between pthreads and x86_64 - args.append('--with-mutex=POSIX/pthreads/x86_64/gcc-assembly') - args.append('--enable-mutex-support') - args.append('--enable-posixmutexes') + args.append("--with-mutex=POSIX/pthreads/x86_64/gcc-assembly") + args.append("--enable-mutex-support") + args.append("--enable-posixmutexes") return super().arguments_to_configuration_script + args -class JpegPackage(InstallInConquestPythonBaseMixin, AutoconfMixin, NoArchiveMixin, Package): - name = 'jpeg' - version = '9f' +class JpegPackage( + InstallInConquestPythonBaseMixin, AutoconfMixin, NoArchiveMixin, Package +): + name = "jpeg" + version = "9f" @property def source_archives(self): return { - f'jpegsrc.v{self.version}.tar.gz': f'https://fossies.org/linux/misc/jpegsrc.v{self.version}.tar.gz' + f"jpegsrc.v{self.version}.tar.gz": f"https://fossies.org/linux/misc/jpegsrc.v{self.version}.tar.gz" } -class TiffPackage(InstallInConquestPythonBaseMixin, CMakeMixin, NoArchiveMixin, Package): - name = 'tiff' - version = '4.2.0' +class TiffPackage( + InstallInConquestPythonBaseMixin, CMakeMixin, NoArchiveMixin, Package +): + name = "tiff" + version = "4.2.0" @property def source_archives(self): return { - f'tiff-{self.version}.tar.gz': f'http://download.osgeo.org/libtiff/tiff-{self.version}.tar.gz' + f"tiff-{self.version}.tar.gz": f"http://download.osgeo.org/libtiff/tiff-{self.version}.tar.gz" } @property def arguments_to_configuration_script(self): return [ - f'-DCMAKE_INSTALL_PREFIX={self.install_directory}', - f'-DCMAKE_PREFIX_PATH={ ConquestPythonPackage().python_base_directory }', + f"-DCMAKE_INSTALL_PREFIX={self.install_directory}", + f"-DCMAKE_PREFIX_PATH={ConquestPythonPackage().python_base_directory}", self.main_source_directory_path, ] class ConquestPythonPackage(AutoconfMixin, MakeInstallMixin, Package): - name = 'conquest_python' - version = '2.7.18' + name = "conquest_python" + version = "2.7.18" def __init__(self): self.use_vs_version_in_base_name = False @@ -549,35 +605,36 @@ def __init__(self): @property def source_archives(self): return { - f'Python-{self.version}.tar.xz': f'https://www.python.org/ftp/python/{self.version}/Python-{self.version}.tar.xz' + f"Python-{self.version}.tar.xz": f"https://www.python.org/ftp/python/{self.version}/Python-{self.version}.tar.xz" } def patch_sources(self): self.patch( - self.main_source_directory_path / 'setup.py', - ('/usr/contrib/ssl/include/', - f'{OpensslPackage().install_directory}/include'), - ('/usr/contrib/ssl/lib/', - f'{OpensslPackage().install_directory}/lib'), + self.main_source_directory_path / "setup.py", + ( + "/usr/contrib/ssl/include/", + f"{OpensslPackage().install_directory}/include", + ), + ("/usr/contrib/ssl/lib/", f"{OpensslPackage().install_directory}/lib"), ) @property def main_source_directory_path(self): - return self.source_extracted / f'Python-{self.version}' + return self.source_extracted / f"Python-{self.version}" # Todo, might be required in cppflags @property def cflags(self): cflags = super().cflags + [ - f'-I{SqlitePackage().include_directories[0]}', - f'-I{OpensslPackage().include_directories[0]}', + f"-I{SqlitePackage().include_directories[0]}", + f"-I{OpensslPackage().include_directories[0]}", ] if self.macos: # It's important to keep the TclPackage includes ahead of the main usr/inlude - cflags.append(f'-I{TclPackage().include_directories[0]}') - cflags.append(f'-I{self.macos_sdkroot}/usr/include') + cflags.append(f"-I{TclPackage().include_directories[0]}") + cflags.append(f"-I{self.macos_sdkroot}/usr/include") else: - cflags.append('-fPIC') + cflags.append("-fPIC") return cflags @@ -585,26 +642,31 @@ def cflags(self): def ldflags(self): ldflags = super().ldflags if self.macos: - ldflags.extend([ - f'-L{self.python_base_directory / "lib"}', - f'-rpath { self.python_base_directory / "lib" }', - ]) + ldflags.extend( + [ + f'-L{self.python_base_directory / "lib"}', + f'-rpath {self.python_base_directory / "lib"}', + ] + ) else: - wanted_rpath = ':'.join(str(x) for x in - SqlitePackage().library_link_directories - + OpensslPackage().library_link_directories - + self.library_link_directories - # + DbPackage().library_link_directories - ) - ldflags.extend([ - f'-L{self.library_link_directories[0]}', - f'-L{SqlitePackage().library_link_directories[0]}', - f'-L{OpensslPackage().library_link_directories[0]}', - '-lsqlite3', - '-lssl', - '-lcrypto', - f'-Wl,-rpath={wanted_rpath}', - ]) + wanted_rpath = ":".join( + str(x) + for x in SqlitePackage().library_link_directories + + OpensslPackage().library_link_directories + + self.library_link_directories + # + DbPackage().library_link_directories + ) + ldflags.extend( + [ + f"-L{self.library_link_directories[0]}", + f"-L{SqlitePackage().library_link_directories[0]}", + f"-L{OpensslPackage().library_link_directories[0]}", + "-lsqlite3", + "-lssl", + "-lcrypto", + f"-Wl,-rpath={wanted_rpath}", + ] + ) return ldflags @@ -612,57 +674,76 @@ def ldflags(self): def environment_for_configuration_script(self): env = super().environment_for_configuration_script if self.macos: - env['DYLD_LIBRARY_PATH'] = f"{self.python_base_directory / 'lib'}" + env["DYLD_LIBRARY_PATH"] = f"{self.python_base_directory / 'lib'}" return env @property def arguments_to_configuration_script(self): args = super().arguments_to_configuration_script args += [ - '--enable-optimizations', - '--with-lto', - '--disable-ipv6', - '--enable-unicode=ucs4', + "--enable-optimizations", + "--with-lto", + "--disable-ipv6", + "--enable-unicode=ucs4", ] if self.macos: args += [ f'--enable-framework={self.install_directory / "Library" / "Frameworks"}', # Tcl and Tk are on the same path - f'--with-tcltk-includes=-I{TclPackage().include_directories[0]}', + f"--with-tcltk-includes=-I{TclPackage().include_directories[0]}", f'--with-tcltk-libs=-L{self.python_base_directory / "lib"} -ltcl8.6 -ltk8.6', f"LDFLAGS={self.environment_for_configuration_script['LDFLAGS']}", f"CFLAGS={self.environment_for_configuration_script['CFLAGS']}", f"CPPFLAGS={self.environment_for_configuration_script['CFLAGS']}", f"DYLD_LIBRARY_PATH={self.python_base_directory / 'lib'}", - f"PKG_CONFIG_PATH={self.python_base_directory / 'lib'/ 'pkgconfig'}", + f"PKG_CONFIG_PATH={self.python_base_directory / 'lib' / 'pkgconfig'}", ] else: args += [ - '--enable-shared', + "--enable-shared", ] return args @property def python_base_directory(self): - '''On MacOS, we must use what goes inside the framework as a base directory''' + """On MacOS, we must use what goes inside the framework as a base directory""" if self.macos: - return self.install_directory / 'Library' / 'Frameworks' / 'Python.framework' / 'Versions' / '2.7' + return ( + self.install_directory + / "Library" + / "Frameworks" + / "Python.framework" + / "Versions" + / "2.7" + ) return self.install_directory @property def python_exe(self): if self.windows: - return self.python_base_directory / 'python.exe' - return self.python_base_directory / 'bin' / 'python' + return self.python_base_directory / "python.exe" + return self.python_base_directory / "bin" / "python" def ensure_pip(self): - self.system([str(self.python_exe), '-m', 'ensurepip']) - self.system([str(self.python_exe), '-m', 'pip', 'install', - '--upgrade', 'pip', 'setuptools'], append_log=True) + self.system([str(self.python_exe), "-m", "ensurepip"]) + self.system( + [ + str(self.python_exe), + "-m", + "pip", + "install", + "--upgrade", + "pip", + "setuptools", + ], + append_log=True, + ) def pip_install(self, *args, env=dict(os.environ)): - self.system([str(self.python_exe), '-m', 'pip', - 'install'] + [arg for arg in args], env=env) + self.system( + [str(self.python_exe), "-m", "pip", "install"] + [arg for arg in args], + env=env, + ) # not building archive, this will come later def build(self): @@ -681,26 +762,39 @@ def run_install_command(self): super().run_install_command() return # Use the already downloaded package - installer = self.source_downloads_base / \ - 'conquest-windows-build-requirements' / \ - f'python-{self.version}.amd64.msi' + installer = ( + self.source_downloads_base + / "conquest-windows-build-requirements" + / f"python-{self.version}.amd64.msi" + ) - self.system(['msiexec', '/qn', '/passive', '/a', f'{installer}', f'TARGETDIR={self.install_directory}', 'ADDLOCAL=TclTk,Tools,Testsuite'], - env=self.environment_for_build_command, cwd=self.build_directory_path) + self.system( + [ + "msiexec", + "/qn", + "/passive", + "/a", + f"{installer}", + f"TARGETDIR={self.install_directory}", + "ADDLOCAL=TclTk,Tools,Testsuite", + ], + env=self.environment_for_build_command, + cwd=self.build_directory_path, + ) def smoke_test(self): - subprocess.check_call([f'{ self.python_exe }', 'smoke_test.py']) + subprocess.check_call([self.python_exe, "smoke_test.py"]) class ApswPackage(Package): - name = 'apsw' - version = '3.34.0' - fullversion = '3.34.0-r1' + name = "apsw" + version = "3.34.0" + fullversion = "3.34.0-r1" @property def source_archives(self): return { - f'{self.name}-{self.fullversion}.zip': f'https://github.com/rogerbinns/apsw/releases/download/{self.fullversion}/{self.name}-{self.fullversion}.zip' + f"{self.name}-{self.fullversion}.zip": f"https://github.com/rogerbinns/apsw/releases/download/{self.fullversion}/{self.name}-{self.fullversion}.zip" } @@ -711,15 +805,11 @@ def main(): pass if Package().linux: - subprocess.run('sudo dnf update -y ', shell=True, check=True) - #subprocess.run("sudo dnf install -y 'dnf-command(config-manager)'", shell=True, check=True) - #subprocess.run('sudo dnf config-manager --enable powertools', shell=True, check=True) - #subprocess.run('sudo dnf install -y epel-release', shell=True, check=True) - subprocess.run( - 'sudo dnf install -y libXmu-devel', - shell=True, - check=True - ) + subprocess.run("sudo dnf update -y ", shell=True, check=True) + # subprocess.run("sudo dnf install -y 'dnf-command(config-manager)'", shell=True, check=True) + # subprocess.run('sudo dnf config-manager --enable powertools', shell=True, check=True) + # subprocess.run('sudo dnf install -y epel-release', shell=True, check=True) + subprocess.run("sudo dnf install -y libXmu-devel", shell=True, check=True) if not Package().windows: ZlibPackage().build() @@ -735,79 +825,116 @@ def main(): ConquestPythonPackage().build() ConquestPythonPackage().ensure_pip() ConquestPythonPackage().pip_install( - 'Pmw==2.1.1', - 'numpy==1.16.6', - 'PyInstaller==3.5', - 'PyOpenGL==3.1.0', - 'Pillow==6.2.2', - 'funcsigs==1.0.2', - 'mock==3.0.5', - 'six==1.15.0', - 'pylint==1.9.5', - 'flake8==3.8.4', - 'pytest==4.6.11', - 'pytest-xdist==1.34.0', - 'pytest-instafail==0.4.2', - 'pytest-cov==2.10.1', + "Pmw==2.1.1", + "numpy==1.16.6", + "PyInstaller==3.5", + "PyOpenGL==3.1.0", + "Pillow==6.2.2", + "funcsigs==1.0.2", + "mock==3.0.5", + "six==1.15.0", + "pylint==1.9.5", + "flake8==3.8.4", + "pytest==4.6.11", + "pytest-xdist==1.34.0", + "pytest-instafail==0.4.2", + "pytest-cov==2.10.1", ) # Apply kludge based on https://stackoverflow.com/questions/63475461/unable-to-import-opengl-gl-in-python-on-macos # to support macos 11 # Hopefully, they won't change the path until we replace conquest :) if Package().macos: ConquestPythonPackage().patch( - ConquestPythonPackage().python_base_directory / 'lib' / 'python2.7' / - 'site-packages' / 'OpenGL' / 'platform' / 'ctypesloader.py', - ('fullName = util.find_library( name )', - "fullName = '/System/Library/Frameworks/OpenGL.framework/OpenGL'"), + ConquestPythonPackage().python_base_directory + / "lib" + / "python2.7" + / "site-packages" + / "OpenGL" + / "platform" + / "ctypesloader.py", + ( + "fullName = util.find_library( name )", + "fullName = '/System/Library/Frameworks/OpenGL.framework/OpenGL'", + ), ) # Patch Pyinstaller to fix https://github.com/pyinstaller/pyinstaller/issues/5540 # The actual fix for this isn't in any of the 3.x versions, and 4.x onwards require Python 3 :( if Package().linux: - command = ["patch", "-p1", "-i", os.path.join(os.path.dirname(os.path.abspath(__file__)), "pyinstaller.patch")] + command = [ + "patch", + "-p1", + "-i", + os.path.join( + os.path.dirname(os.path.abspath(__file__)), "pyinstaller.patch" + ), + ] print(f"Applying patch with {' '.join(command)}") - package_path = ConquestPythonPackage().python_base_directory / "lib" / "python2.7" / "site-packages" + package_path = ( + ConquestPythonPackage().python_base_directory + / "lib" + / "python2.7" + / "site-packages" + ) subprocess.run(command, cwd=package_path) if not Package().windows: bdb_env = dict(os.environ) - bdb_env['BERKELEYDB_DIR'] = f'{ConquestPythonPackage().python_base_directory}' - ConquestPythonPackage().pip_install('-v', - 'bsddb3==6.2.7', - f'--install-option=--berkeley-db={ConquestPythonPackage().python_base_directory}', - f'--install-option=--lflags=-L{ConquestPythonPackage().python_base_directory}/lib', - env=bdb_env) + bdb_env["BERKELEYDB_DIR"] = f"{ConquestPythonPackage().python_base_directory}" + ConquestPythonPackage().pip_install( + "-v", + "bsddb3==6.2.7", + f"--install-option=--berkeley-db={ConquestPythonPackage().python_base_directory}", + f"--install-option=--lflags=-L{ConquestPythonPackage().python_base_directory}/lib", + env=bdb_env, + ) ConquestPythonPackage().pip_install( - f'https://github.com/rogerbinns/apsw/releases/download/{ ApswPackage().fullversion }/apsw-{ ApswPackage().fullversion }.zip', - '--global-option=fetch', '--global-option=--version', f'--global-option={ ApswPackage().version }', '--global-option=--all', - '--global-option=build', '--global-option=--enable-all-extensions' + f"https://github.com/rogerbinns/apsw/releases/download/{ApswPackage().fullversion}/apsw-{ApswPackage().fullversion}.zip", + "--global-option=fetch", + "--global-option=--version", + f"--global-option={ApswPackage().version}", + "--global-option=--all", + "--global-option=build", + "--global-option=--enable-all-extensions", ) else: # install apsw - site_packages_dir = ConquestPythonPackage().install_directory / \ - 'Lib' / 'site-packages' - apsw_installer = ApswPackage().source_downloads_base / 'conquest-windows-build-requirements' / \ - f'apsw-{ApswPackage().version}.win-amd64-py2.7.exe' - tmpdir = Path('apswtmp') + site_packages_dir = ( + ConquestPythonPackage().install_directory / "Lib" / "site-packages" + ) + apsw_installer = ( + ApswPackage().source_downloads_base + / "conquest-windows-build-requirements" + / f"apsw-{ApswPackage().version}.win-amd64-py2.7.exe" + ) + tmpdir = Path("apswtmp") ConquestPythonPackage().system( - ['7z', 'x', '-aoa', f'-o{tmpdir}', f'{apsw_installer}']) - files = os.listdir(tmpdir / 'PLATLIB') + ["7z", "x", "-aoa", f"-o{tmpdir}", f"{apsw_installer}"] + ) + files = os.listdir(tmpdir / "PLATLIB") for f in files: - shutil.move(str(tmpdir / 'PLATLIB' / f), str(site_packages_dir)) + shutil.move(str(tmpdir / "PLATLIB" / f), str(site_packages_dir)) # install precompiled Togl - tk85_dir = ConquestPythonPackage().install_directory / 'tcl' / 'tk8.5' - togl_zip = ToglPackage().source_downloads_base / \ - 'conquest-windows-build-requirements' / f'Togl-2.2-pre.zip' + tk85_dir = ConquestPythonPackage().install_directory / "tcl" / "tk8.5" + togl_zip = ( + ToglPackage().source_downloads_base + / "conquest-windows-build-requirements" + / "Togl-2.2-pre.zip" + ) ConquestPythonPackage().system( - ['7z', 'x', '-aoa', f'-o{tk85_dir}', f'{togl_zip}']) + ["7z", "x", "-aoa", f"-o{tk85_dir}", f"{togl_zip}"] + ) # install precompiled bsddb3 - precompiled_bsddb3 = DbPackage().source_downloads_base / \ - 'conquest-windows-build-requirements' / f'bsddb3-6.2.6-cp27-cp27m-win_amd64.whl' + precompiled_bsddb3 = ( + DbPackage().source_downloads_base + / "conquest-windows-build-requirements" + / "bsddb3-6.2.6-cp27-cp27m-win_amd64.whl" + ) ConquestPythonPackage().pip_install(str(precompiled_bsddb3)) # install precompiled pywin32 - ConquestPythonPackage().pip_install('pywin32==225') + ConquestPythonPackage().pip_install("pywin32==225") ConquestPythonPackage().smoke_test() ConquestPythonPackage().create_archive() diff --git a/ccdc/thirdparty/package.py b/ccdc/thirdparty/package.py index 3dbf34d..4023444 100644 --- a/ccdc/thirdparty/package.py +++ b/ccdc/thirdparty/package.py @@ -1,19 +1,18 @@ #!/usr/bin/env python3 -import sys -import subprocess -import os -import stat -import shutil -import tempfile -import multiprocessing import getpass +import multiprocessing +import os from pathlib import Path -from distutils.version import StrictVersion +import shutil +import stat +import subprocess +import sys class Package(object): - '''Base for anything installable''' + """Base for anything installable""" + name = None version = None _cached_sdkroot = None @@ -24,48 +23,62 @@ def __init__(self): @property def macos(self): - return sys.platform == 'darwin' + return sys.platform == "darwin" @property def windows(self): - return sys.platform == 'win32' + return sys.platform == "win32" @property def linux(self): - return sys.platform.startswith('linux') + return sys.platform.startswith("linux") @property def centos(self): - return self.linux and Path('/etc/centos-release').exists() + return self.linux and Path("/etc/centos-release").exists() @property def centos_major_version(self): - return subprocess.check_output('rpm -E %{rhel}', shell=True).decode('utf-8').strip() + return ( + subprocess.check_output("rpm -E %{rhel}", shell=True) + .decode("utf-8") + .strip() + ) @property def debian(self): - return self.linux and Path('/etc/debian_version').exists() + return self.linux and Path("/etc/debian_version").exists() @property def ubuntu(self): - return self.debian and subprocess.check_output('lsb_release -i -s', shell=True).decode('utf-8').strip() == 'Ubuntu' + return ( + self.debian + and subprocess.check_output("lsb_release -i -s", shell=True) + .decode("utf-8") + .strip() + == "Ubuntu" + ) @property def ubuntu_version(self): - return subprocess.check_output('lsb_release -r -s', shell=True).decode('utf-8').strip() + return ( + subprocess.check_output("lsb_release -r -s", shell=True) + .decode("utf-8") + .strip() + ) @property def platform(self): - if sys.platform.startswith('linux'): - return 'linux' + if sys.platform.startswith("linux"): + return "linux" if not self.use_distribution_in_base_name: return sys.platform if not self.linux: return sys.platform if self.centos: - return f'centos{self.centos_major_version}' + return f"centos{self.centos_major_version}" if self.ubuntu: - return f'ubuntu{self.ubuntu_version}' + return f"ubuntu{self.ubuntu_version}" @property def macos_sdkroot(self): @@ -73,19 +86,21 @@ def macos_sdkroot(self): return None if not self._cached_sdkroot: self._cached_sdkroot = subprocess.check_output( - ['xcrun', '--show-sdk-path'])[:-1].decode('utf8') + ["xcrun", "--show-sdk-path"] + )[:-1].decode("utf8") return self._cached_sdkroot @property def macos_deployment_target(self): - '''The minimum macos version the pagkage will work on''' - return '10.12' + """The minimum macos version the pagkage will work on""" + return "10.12" def prepare_directories(self): if not self.toolbase.exists() and not self.windows: - subprocess.check_output(['sudo', 'mkdir', '-p', '/opt/ccdc']) + subprocess.check_output(["sudo", "mkdir", "-p", "/opt/ccdc"]) subprocess.check_output( - ['sudo', 'chown', f'{getpass.getuser()}', '/opt/ccdc']) + ["sudo", "chown", f"{getpass.getuser()}", "/opt/ccdc"] + ) self.toolbase.mkdir(parents=True, exist_ok=True) self.source_downloads_base.mkdir(parents=True, exist_ok=True) self.source_extracted_base.mkdir(parents=True, exist_ok=True) @@ -94,45 +109,45 @@ def prepare_directories(self): @property def toolbase(self): - '''Return the base directory where tools are installed''' + """Return the base directory where tools are installed""" if self.windows: - return Path('D:\\x_mirror\\buildman\\tools') + return Path("D:\\x_mirror\\buildman\\tools") else: - return Path('/opt/ccdc/third-party') + return Path("/opt/ccdc/third-party") @property def source_downloads_base(self): - '''Return the directory where sources are downloaded''' + """Return the directory where sources are downloaded""" if self.windows: - if 'SYSTEM_ARTIFACTSDIRECTORY' in os.environ: - return Path(os.environ['SYSTEM_ARTIFACTSDIRECTORY']) - return Path('D:\\tp\\downloads') + if "SYSTEM_ARTIFACTSDIRECTORY" in os.environ: + return Path(os.environ["SYSTEM_ARTIFACTSDIRECTORY"]) + return Path("D:\\tp\\downloads") else: - return Path('/opt/ccdc/third-party-sources/downloads') + return Path("/opt/ccdc/third-party-sources/downloads") @property def source_extracted_base(self): - '''Return the base directory where sources are extracted''' + """Return the base directory where sources are extracted""" if self.windows: - return Path('D:\\tp\\extracted') + return Path("D:\\tp\\extracted") else: - return Path('/opt/ccdc/third-party-sources/extracted') + return Path("/opt/ccdc/third-party-sources/extracted") @property def source_builds_base(self): - '''Return the base directory where sources are built''' + """Return the base directory where sources are built""" if self.windows: - return Path('D:\\tp\\builds') + return Path("D:\\tp\\builds") else: - return Path('/opt/ccdc/third-party-sources/builds') + return Path("/opt/ccdc/third-party-sources/builds") @property def build_logs(self): - '''Return the directory where build logs are stored''' + """Return the directory where build logs are stored""" if self.windows: - return Path('D:\\tp\\logs') + return Path("D:\\tp\\logs") else: - return Path('/opt/ccdc/third-party-sources/logs') + return Path("/opt/ccdc/third-party-sources/logs") @property def output_base_name(self): @@ -140,36 +155,37 @@ def output_base_name(self): self.name, self.version, ] - if 'GITHUB_RUN_NUMBER' in os.environ: - components.append(os.environ['GITHUB_RUN_NUMBER']) + if "GITHUB_RUN_NUMBER" in os.environ: + components.append(os.environ["GITHUB_RUN_NUMBER"]) else: - components.append('do-not-use-me-developer-version') + components.append("do-not-use-me-developer-version") components.append(self.platform) - if self.use_vs_version_in_base_name and 'BUILD_VS_VERSION' in os.environ: + if self.use_vs_version_in_base_name and "BUILD_VS_VERSION" in os.environ: components.append(f'vs{os.environ["BUILD_VS_VERSION"]}') - return '-'.join(components) - + return "-".join(components) + @property def install_directory(self): - '''Return the canonical installation directory''' + """Return the canonical installation directory""" return self.toolbase / self.name / self.output_base_name @property def output_archive_filename(self): - return f'{self.output_base_name}.tar.gz' + return f"{self.output_base_name}.tar.gz" def create_archive(self): - if 'BUILD_ARTIFACTSTAGINGDIRECTORY' in os.environ: + if "BUILD_ARTIFACTSTAGINGDIRECTORY" in os.environ: archive_output_directory = Path( - os.environ['BUILD_ARTIFACTSTAGINGDIRECTORY']) + os.environ["BUILD_ARTIFACTSTAGINGDIRECTORY"] + ) else: archive_output_directory = self.source_builds_base - print(f'Creating {self.output_archive_filename} in {archive_output_directory}') + print(f"Creating {self.output_archive_filename} in {archive_output_directory}") command = [ - 'tar', - '-zcf', - f'{ archive_output_directory / self.output_archive_filename }', # the tar filename - f'{ self.install_directory.relative_to(self.toolbase / self.name) }', + "tar", + "-zcf", + f"{archive_output_directory / self.output_archive_filename}", # the tar filename + f"{self.install_directory.relative_to(self.toolbase / self.name)}", ] try: # keep the name + version directory in the archive, but not the package name directory @@ -177,71 +193,74 @@ def create_archive(self): except subprocess.CalledProcessError as e: if not self.windows: raise e - command.insert(1, '--force-local') + command.insert(1, "--force-local") # keep the name + version directory in the archive, but not the package name directory self.system(command, cwd=self.toolbase / self.name) @property def include_directories(self): - '''Return the directories clients must add to their include path''' - return [self.install_directory / 'include'] + """Return the directories clients must add to their include path""" + return [self.install_directory / "include"] @property def library_link_directories(self): - '''Return the directories clients must add to their library link path''' - return [self.install_directory / 'lib'] + """Return the directories clients must add to their library link path""" + return [self.install_directory / "lib"] @property def source_archives(self): - '''Map of archive file/url to fetch''' + """Map of archive file/url to fetch""" return {} def fetch_source_archives(self): import urllib.request + for filename, url in self.source_archives.items(): if (self.source_downloads_base / filename).exists(): print( - f'Skipping download of existing {self.source_downloads_base / filename}') + f"Skipping download of existing {self.source_downloads_base / filename}" + ) continue - print(f'Fetching {url} to {self.source_downloads_base / filename}') + print(f"Fetching {url} to {self.source_downloads_base / filename}") with urllib.request.urlopen(url) as response: - with open(self.source_downloads_base / filename, 'wb') as final_file: + with open(self.source_downloads_base / filename, "wb") as final_file: shutil.copyfileobj(response, final_file) def extract_source_archives(self): for source_archive_filename in self.source_archives.keys(): - self.extract_archive(self.source_downloads_base / - source_archive_filename, self.source_extracted) + self.extract_archive( + self.source_downloads_base / source_archive_filename, + self.source_extracted, + ) def extract_archive(self, path, where): - '''untar a file with any reasonable suffix''' - print(f'Extracting {path} to {where}') - if '.zip' in path.suffixes: - self.system(['unzip', '-q', '-o', str(path)], cwd=where) + """untar a file with any reasonable suffix""" + print(f"Extracting {path} to {where}") + if ".zip" in path.suffixes: + self.system(["unzip", "-q", "-o", str(path)], cwd=where) return - if '.bz2' in path.suffixes: - flags = 'jxf' - elif '.gz' in path.suffixes: - flags = 'zxf' - elif '.tgz' in path.suffixes: - flags = 'zxf' - elif '.xz' in path.suffixes: - flags = 'xf' + if ".bz2" in path.suffixes: + flags = "jxf" + elif ".gz" in path.suffixes: + flags = "zxf" + elif ".tgz" in path.suffixes: + flags = "zxf" + elif ".xz" in path.suffixes: + flags = "xf" else: raise AttributeError(f"Can't extract {path}") if self.windows: - flags = f'-{flags}' + flags = f"-{flags}" try: - self.system(['tar', '--force-local', - flags, str(path)], cwd=where) + self.system(["tar", "--force-local", flags, str(path)], cwd=where) except subprocess.CalledProcessError: - self.system(['tar', flags, str(path)], cwd=where) + self.system(["tar", flags, str(path)], cwd=where) else: - self.system(['tar', flags, str(path)], cwd=where) + self.system(["tar", flags, str(path)], cwd=where) def patch_sources(self): - '''Override to patch source code after extraction''' + """Override to patch source code after extraction""" pass @property @@ -258,7 +277,7 @@ def source_extracted(self): @property def main_source_directory_path(self): - return self.source_extracted / f'{self.name}-{self.version}' + return self.source_extracted / f"{self.name}-{self.version}" @property def build_directory_path(self): @@ -269,12 +288,12 @@ def build_directory_path(self): def cleanup(self): try: shutil.rmtree(self.source_extracted, ignore_errors=True) - print(f'Cleaned up {self.source_extracted}') + print(f"Cleaned up {self.source_extracted}") except OSError: pass try: shutil.rmtree(self.build_directory_path, ignore_errors=True) - print(f'Cleaned up {self.build_directory_path}') + print(f"Cleaned up {self.build_directory_path}") except OSError: pass @@ -284,116 +303,131 @@ def configuration_script(self): @property def arguments_to_configuration_script(self): - return [f'--prefix={self.install_directory}'] + return [f"--prefix={self.install_directory}"] @property def cxxflags(self): - flags = [ - '-O2' - ] + flags = ["-O2"] if self.macos: - flags.extend([ - '-arch', 'x86_64', - '-isysroot', self.macos_sdkroot, - f'-mmacosx-version-min={self.macos_deployment_target}', - ]) + flags.extend( + [ + "-arch", + "x86_64", + "-isysroot", + self.macos_sdkroot, + f"-mmacosx-version-min={self.macos_deployment_target}", + ] + ) return flags @property def ldflags(self): flags = [] if self.macos: - flags.extend([ - '-arch', 'x86_64', - '-isysroot', self.macos_sdkroot, - f'-mmacosx-version-min={self.macos_deployment_target}', - ]) + flags.extend( + [ + "-arch", + "x86_64", + "-isysroot", + self.macos_sdkroot, + f"-mmacosx-version-min={self.macos_deployment_target}", + ] + ) return flags @property def cflags(self): - flags = [ - '-O2' - ] + flags = ["-O2"] if self.macos: - flags.extend([ - '-arch', 'x86_64', - '-isysroot', self.macos_sdkroot, - f'-mmacosx-version-min={self.macos_deployment_target}', - ]) + flags.extend( + [ + "-arch", + "x86_64", + "-isysroot", + self.macos_sdkroot, + f"-mmacosx-version-min={self.macos_deployment_target}", + ] + ) return flags @property def environment_for_configuration_script(self): env = dict(os.environ) if self.cflags: - env['CFLAGS'] = ' '.join(self.cflags) + env["CFLAGS"] = " ".join(self.cflags) if self.cxxflags: - env['CXXFLAGS'] = ' '.join(self.cxxflags) + env["CXXFLAGS"] = " ".join(self.cxxflags) if self.ldflags: - env['LDFLAGS'] = ' '.join(self.ldflags) + env["LDFLAGS"] = " ".join(self.ldflags) if self.macos: - env['MACOSX_DEPLOYMENT_TARGET'] = self.macos_deployment_target + env["MACOSX_DEPLOYMENT_TARGET"] = self.macos_deployment_target return env def run_configuration_script(self): - '''run the required commands to configure a package''' + """run the required commands to configure a package""" if not self.configuration_script: - print(f'Skipping configuration script for {self.name}') + print(f"Skipping configuration script for {self.name}") return st = os.stat(self.configuration_script) - if '/usr/' not in str(self.configuration_script): + if "/usr/" not in str(self.configuration_script): os.chmod(self.configuration_script, st.st_mode | stat.S_IEXEC) self.system( - [str(self.configuration_script)] + - self.arguments_to_configuration_script, - env=self.environment_for_configuration_script, cwd=self.build_directory_path) + [str(self.configuration_script)] + self.arguments_to_configuration_script, + env=self.environment_for_configuration_script, + cwd=self.build_directory_path, + ) @property def environment_for_build_command(self): return self.environment_for_configuration_script def run_build_command(self): - '''run the required commands to build a package after configuration''' + """run the required commands to build a package after configuration""" pass def run_install_command(self): - '''run the required commands to install a package''' + """run the required commands to install a package""" pass def logfile_path(self, task): - '''Canonical log file for a particular task''' - return self.build_logs / f'{self.name}-{self.version}-{task}.log' + """Canonical log file for a particular task""" + return self.build_logs / f"{self.name}-{self.version}-{task}.log" def system(self, command, cwd=None, env=None, append_log=False): - '''execute command, logging in the appropriate logfile''' + """execute command, logging in the appropriate logfile""" task = sys._getframe(1).f_code.co_name - print(f'{self.name} {task}') + print(f"{self.name} {task}") if isinstance(command, str): command = [command] - print(f'Running {command}') - openmode = 'a' if append_log else 'w' + print(f"Running {command}") + openmode = "a" if append_log else "w" with open(self.logfile_path(task), openmode) as f: - output = '' + output = "" p = subprocess.Popen( - command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, cwd=cwd, env=env) + command, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + cwd=cwd, + env=env, + ) while True: retcode = p.poll() - l = p.stdout.readline().decode('utf-8') - print(l.rstrip()) - output += l - f.write(l) + line = p.stdout.readline().decode("utf-8") + print(line.rstrip()) + output += line + f.write(line) if retcode is not None: break assert p.returncode is not None if p.returncode != 0: - print(f'Failed process environment was {env}') + print(f"Failed process environment was {env}") raise subprocess.CalledProcessError( - returncode=p.returncode, cmd=command, output=output) + returncode=p.returncode, cmd=command, output=output + ) def verify(self): - '''Override this function to verify that the install has - produced something functional.''' + """Override this function to verify that the install has + produced something functional.""" pass def build(self): @@ -408,20 +442,21 @@ def build(self): self.create_archive() def update_dylib_id(self, library_path, new_id): - '''MacOS helper to change a library's identifier''' - self.system(['install_name_tool', '-id', new_id, str(library_path)]) + """MacOS helper to change a library's identifier""" + self.system(["install_name_tool", "-id", new_id, str(library_path)]) def change_dylib_lookup(self, library_path, from_path, to_path): - '''MacOS helper to change the path where libraries and executables look for other libraries''' - self.system(['install_name_tool', '-change', - from_path, to_path, str(library_path)]) + """MacOS helper to change the path where libraries and executables look for other libraries""" + self.system( + ["install_name_tool", "-change", from_path, to_path, str(library_path)] + ) def patch(self, fname, *subs): with open(fname) as read_file: txt = read_file.read() - for (old, new) in subs: + for old, new in subs: txt = txt.replace(old, new) - with open(fname, 'w') as out: + with open(fname, "w") as out: out.write(txt) @@ -433,51 +468,66 @@ def patch(self, fname, *subs): class GnuMakeMixin(object): - '''Make based build''' + """Make based build""" def run_build_command(self): - self.system(['make', f'-j{multiprocessing.cpu_count()}'], - env=self.environment_for_build_command, cwd=self.build_directory_path) + self.system( + ["make", f"-j{multiprocessing.cpu_count()}"], + env=self.environment_for_build_command, + cwd=self.build_directory_path, + ) class MakeInstallMixin(object): - '''Make install (rather than the default do nothing install)''' + """Make install (rather than the default do nothing install)""" def run_install_command(self): - self.system(['make', 'install'], - env=self.environment_for_build_command, cwd=self.build_directory_path) + self.system( + ["make", "install"], + env=self.environment_for_build_command, + cwd=self.build_directory_path, + ) class AutoconfMixin(GnuMakeMixin, MakeInstallMixin, object): - '''Autoconf based configure script''' + """Autoconf based configure script""" + @property def configuration_script(self): - return self.main_source_directory_path / 'configure' + return self.main_source_directory_path / "configure" class CMakeMixin(Package): @property def configuration_script(self): - return shutil.which('cmake') + return shutil.which("cmake") def run_build_command(self): - self.system([self.configuration_script, '--build', '.', '--config', 'Release'], - env=self.environment_for_build_command, cwd=self.build_directory_path) + self.system( + [self.configuration_script, "--build", ".", "--config", "Release"], + env=self.environment_for_build_command, + cwd=self.build_directory_path, + ) def run_install_command(self): - self.system([self.configuration_script, '--install', '.'], - env=self.environment_for_build_command, cwd=self.build_directory_path) + self.system( + [self.configuration_script, "--install", "."], + env=self.environment_for_build_command, + cwd=self.build_directory_path, + ) @property def visual_studio_generator_for_build(self): - if not "BUILD_VS_VERSION" in os.environ: - print('BUILD_VS_VERSION not set, defaulting to VS 2019') - return 'Visual Studio 16 2019' - if os.environ["BUILD_VS_VERSION"] == '2019': - return 'Visual Studio 16 2019' - if os.environ["BUILD_VS_VERSION"] == '2017': - return 'Visual Studio 15 2017' - raise Exception(f'Invalid value for BUILD_VS_VERSION: {os.environ["BUILD_VS_VERSION"]}') + if "BUILD_VS_VERSION" not in os.environ: + print("BUILD_VS_VERSION not set, defaulting to VS 2019") + return "Visual Studio 16 2019" + if os.environ["BUILD_VS_VERSION"] == "2019": + return "Visual Studio 16 2019" + if os.environ["BUILD_VS_VERSION"] == "2017": + return "Visual Studio 15 2017" + raise Exception( + f'Invalid value for BUILD_VS_VERSION: {os.environ["BUILD_VS_VERSION"]}' + ) class NoArchiveMixin(Package): From 3501eaa199633b574a27524fa1c9816896e69391 Mon Sep 17 00:00:00 2001 From: Florian Piesche Date: Thu, 12 Jun 2025 11:12:57 +0100 Subject: [PATCH 09/11] Try and use cflags property (CQ-1519) --- build_conquest_python.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/build_conquest_python.py b/build_conquest_python.py index 102e168..c48ec07 100755 --- a/build_conquest_python.py +++ b/build_conquest_python.py @@ -225,7 +225,7 @@ def install_directory(self): @property def cflags(self): - return super().cflags + ["-DNDEBUG"] + return super().cflags + ["-DNDEBUG", f"-I{ZlibPackage().include_directories[0]}"] @property def configuration_script(self): @@ -238,8 +238,6 @@ def arguments_to_configuration_script(self): # '--enable-threads', "--enable-64bit", f"--prefix={ConquestPythonPackage().python_base_directory}", - # f"--includedir={ConquestPythonPackage().python_base_directory / 'include'}", - # f'--libdir={ConquestPythonPackage().python_base_directory / "lib"}', ] if self.macos: args.extend( From 568045aeaed83397ca41f64cc0d821d79a08eaca Mon Sep 17 00:00:00 2001 From: Florian Piesche Date: Thu, 12 Jun 2025 11:22:05 +0100 Subject: [PATCH 10/11] Output build logs to terminal too (JIRA CQ-1519) --- .github/workflows/build-conquest-python.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/build-conquest-python.yml b/.github/workflows/build-conquest-python.yml index 7e577af..ca9dad6 100644 --- a/.github/workflows/build-conquest-python.yml +++ b/.github/workflows/build-conquest-python.yml @@ -97,6 +97,14 @@ jobs: echo "archive_path=$archive_path" >> $GITHUB_ENV echo $output + - name: Output logs + run: | + for filename in /opt/ccdc/third-party-sources/logs/*; do + echo "::group::${filename}" + cat "$filename" + echo "::endgroup::" + done + - name: Store build logs as build artifact uses: actions/upload-artifact@v4 with: From 4463960963d8e4812b23c477c5724ab927089267 Mon Sep 17 00:00:00 2001 From: Florian Piesche Date: Thu, 12 Jun 2025 11:38:52 +0100 Subject: [PATCH 11/11] Update Tk to 9.0.1 also (CQ-1519) --- build_conquest_python.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build_conquest_python.py b/build_conquest_python.py index c48ec07..f254cfc 100755 --- a/build_conquest_python.py +++ b/build_conquest_python.py @@ -329,7 +329,7 @@ def verify(self): class TkPackage(AutoconfMixin, NoArchiveMixin, Package): name = "tk" - version = "8.6.16" + version = "9.0.1" @property def source_archives(self):