diff --git a/recipes/cpython/all/conandata.yml b/recipes/cpython/all/conandata.yml index f9fd51067c8b3..0deb2c8f60363 100644 --- a/recipes/cpython/all/conandata.yml +++ b/recipes/cpython/all/conandata.yml @@ -1,72 +1,57 @@ sources: +# "3.10.13": +# url: "https://www.python.org/ftp/python/3.10.13/Python-3.10.13.tgz" +# sha256: "698ec55234c1363bd813b460ed53b0f108877c7a133d48bde9a50a1eb57b7e65" "3.10.0": url: "https://www.python.org/ftp/python/3.10.0/Python-3.10.0.tgz" sha256: "c4e0cbad57c90690cb813fb4663ef670b4d0f587d8171e2c42bd4c9245bd2758" - "3.9.7": - url: "https://www.python.org/ftp/python/3.9.7/Python-3.9.7.tgz" - sha256: "a838d3f9360d157040142b715db34f0218e535333696a5569dc6f854604eb9d1" - "3.8.12": - url: "https://www.python.org/ftp/python/3.8.12/Python-3.8.12.tgz" - sha256: "316aa33f3b7707d041e73f246efedb297a70898c4b91f127f66dc8d80c596f1a" - "3.7.12": - url: "https://www.python.org/ftp/python/3.7.12/Python-3.7.12.tgz" - sha256: "33b4daaf831be19219659466d12645f87ecec6eb21d4d9f9711018a7b66cce46" +# "3.9.18": +# url: "https://www.python.org/ftp/python/3.9.18/Python-3.9.18.tgz" +# sha256: "504ce8cfd59addc04c22f590377c6be454ae7406cb1ebf6f5a350149225a9354" +# "3.8.18": +# url: "https://www.python.org/ftp/python/3.8.18/Python-3.8.18.tgz" +# sha256: "7c5df68bab1be81a52dea0cc2e2705ea00553b67107a301188383d7b57320b16" + "3.7.17": + url: "https://www.python.org/ftp/python/3.7.17/Python-3.7.17.tgz" + sha256: "fd50161bc2a04f4c22a0971ff0f3856d98b4bf294f89740a9f06b520aae63b49" "2.7.18": url: "https://www.python.org/ftp/python/2.7.18/Python-2.7.18.tgz" sha256: "da3080e3b488f648a3d7a4560ddee895284c3380b11d6de75edb986526b9a814" patches: + "3.10.13": + - patch_file: "patches/3.10/3.10.0-0001-msvc.patch" + - patch_file: "patches/3.9/3.9.7-0002-_msi-vcxproj.patch" + - patch_file: "patches/3.10/3.10.0-0003-_ctypes-ffi.patch" + - patch_file: "patches/3.10/3.10.0-0004-setup.py-pass-CFLAGS-CPPFLAGS.patch" + - patch_file: "patches/3.10/3.10.0-0005-disable-macos-tcltk.patch" + - patch_file: "patches/3.x-0001-relocatable-python-config.patch" "3.10.0": - - patch_file: "patches/3.10.0-0001-msvc.patch" - base_path: "source_subfolder" - - patch_file: "patches/3.9.7-0002-_msi-vcxproj.patch" - base_path: "source_subfolder" - - patch_file: "patches/3.10.0-0003-_ctypes-ffi.patch" - base_path: "source_subfolder" - - patch_file: "patches/3.10.0-0004-setup.py-pass-CFLAGS-CPPFLAGS.patch" - base_path: "source_subfolder" - - patch_file: "patches/3.10.0-0005-disable-macos-tcltk.patch" - base_path: "source_subfolder" + - patch_file: "patches/3.10/3.10.0-0001-msvc.patch" + - patch_file: "patches/3.9/3.9.7-0002-_msi-vcxproj.patch" + - patch_file: "patches/3.10/3.10.0-0003-_ctypes-ffi.patch" + - patch_file: "patches/3.10/3.10.0-0004-setup.py-pass-CFLAGS-CPPFLAGS.patch" + - patch_file: "patches/3.10/3.10.0-0005-disable-macos-tcltk.patch" - patch_file: "patches/3.x-0001-relocatable-python-config.patch" - base_path: "source_subfolder" - "3.9.7": - - patch_file: "patches/3.9.7-0001-msvc.patch" - base_path: "source_subfolder" - - patch_file: "patches/3.9.7-0002-_msi-vcxproj.patch" - base_path: "source_subfolder" - - patch_file: "patches/3.9.7-0003-_ctypes-ffi.patch" - base_path: "source_subfolder" - - patch_file: "patches/3.9.7-0004-setup.py-pass-CFLAGS-CPPFLAGS.patch" - base_path: "source_subfolder" - - patch_file: "patches/3.9.7-0005-disable-macos-tcltk.patch" - base_path: "source_subfolder" + "3.9.18": + - patch_file: "patches/3.9/3.9.7-0001-msvc.patch" + - patch_file: "patches/3.9/3.9.7-0002-_msi-vcxproj.patch" + - patch_file: "patches/3.9/3.9.7-0003-_ctypes-ffi.patch" + - patch_file: "patches/3.9/3.9.7-0004-setup.py-pass-CFLAGS-CPPFLAGS.patch" + - patch_file: "patches/3.9/3.9.7-0005-disable-macos-tcltk.patch" - patch_file: "patches/3.x-0001-relocatable-python-config.patch" - base_path: "source_subfolder" - "3.8.12": - - patch_file: "patches/3.8.12-0001-msvc.patch" - base_path: "source_subfolder" - - patch_file: "patches/3.8.12-0002-_ctypes-ffi.patch" - base_path: "source_subfolder" - - patch_file: "patches/3.8.12-0003-setup.py-pass-CFLAGS-CPPFLAGS.patch" - base_path: "source_subfolder" - - patch_file: "patches/3.8.12-0004-disable-macos-tcltk.patch" - base_path: "source_subfolder" + "3.8.18": + - patch_file: "patches/3.8/3.8.12-0001-msvc.patch" + - patch_file: "patches/3.8/3.8.12-0002-_ctypes-ffi.patch" + - patch_file: "patches/3.8/3.8.12-0003-setup.py-pass-CFLAGS-CPPFLAGS.patch" + - patch_file: "patches/3.8/3.8.12-0004-disable-macos-tcltk.patch" - patch_file: "patches/3.x-0001-relocatable-python-config.patch" - base_path: "source_subfolder" - "3.7.12": - - patch_file: "patches/3.7.9-0001-msvc.patch" - base_path: "source_subfolder" - - patch_file: "patches/3.7.9-0002-setup.py-pass-CFLAGS-CPPFLAGS.patch" - base_path: "source_subfolder" - - patch_file: "patches/3.7.9-0003-disable-macos-tcltk.patch" - base_path: "source_subfolder" + "3.7.17": + - patch_file: "patches/3.7/3.7.9-0001-msvc.patch" + - patch_file: "patches/3.7/3.7.9-0002-setup.py-pass-CFLAGS-CPPFLAGS.patch" + - patch_file: "patches/3.7/3.7.9-0003-disable-macos-tcltk.patch" - patch_file: "patches/3.x-0001-relocatable-python-config.patch" - base_path: "source_subfolder" "2.7.18": - - patch_file: "patches/2.7.18-0001-msvc.patch" - base_path: "source_subfolder" - - patch_file: "patches/2.7.18-0002-add-support-msvc-14.patch" - base_path: "source_subfolder" - - patch_file: "patches/2.7.18-0003-msvc-fix-static.patch" - base_path: "source_subfolder" - - patch_file: "patches/2.7.18-0004-disable-macos-tcltk.patch" - base_path: "source_subfolder" + - patch_file: "patches/2.7/2.7.18-0001-msvc.patch" + - patch_file: "patches/2.7/2.7.18-0002-add-support-msvc-14.patch" + - patch_file: "patches/2.7/2.7.18-0003-msvc-fix-static.patch" + - patch_file: "patches/2.7/2.7.18-0004-disable-macos-tcltk.patch" diff --git a/recipes/cpython/all/conanfile.py b/recipes/cpython/all/conanfile.py index 4fa1d1da6334e..b05db11d574ae 100644 --- a/recipes/cpython/all/conanfile.py +++ b/recipes/cpython/all/conanfile.py @@ -1,21 +1,30 @@ -from conans import AutoToolsBuildEnvironment, ConanFile, MSBuild, tools -from conans.errors import ConanInvalidConfiguration -from io import StringIO import os import re import textwrap +from io import StringIO + +from conan import ConanFile +from conan.errors import ConanInvalidConfiguration +from conan.tools.apple import is_apple_os +from conan.tools.build import cross_building +from conan.tools.files import apply_conandata_patches, copy, export_conandata_patches, get, mkdir, rename, replace_in_file, rm, rmdir, unzip +from conan.tools.gnu import Autotools, AutotoolsToolchain, AutotoolsDeps +from conan.tools.layout import basic_layout +from conan.tools.microsoft import MSBuildDeps, MSBuildToolchain, MSBuild, is_msvc, is_msvc_static_runtime, msvc_runtime_flag +from conan.tools.scm import Version -required_conan_version = ">=1.33.0" +required_conan_version = ">=1.58.0" class CPythonConan(ConanFile): name = "cpython" + description = "Python is a programming language that lets you work quickly and integrate systems more effectively." + license = "Python-2.0" url = "https://github.com/conan-io/conan-center-index" homepage = "https://www.python.org" - description = "Python is a programming language that lets you work quickly and integrate systems more effectively." topics = ("python", "cpython", "language", "script") - license = ("Python-2.0",) - exports_sources = "patches/**" + + package_type = "library" settings = "os", "arch", "compiler", "build_type" options = { "shared": [True, False], @@ -64,44 +73,31 @@ class CPythonConan(ConanFile): "env_vars": True, } - _autotools = None - - @property - def _source_subfolder(self): - return "source_subfolder" - - @property - def _version_number_only(self): - return re.match(r"^([0-9.]+)", self.version).group(1) - - @property - def _version_tuple(self): - return tuple(self._version_number_only.split(".")) - @property def _supports_modules(self): - return self.settings.compiler != "Visual Studio" or self.options.shared + return not is_msvc(self) or self.options.shared @property def _version_suffix(self): - if self.settings.compiler == "Visual Studio": - joiner = "" - else: - joiner = "." - return joiner.join(self._version_tuple[:2]) + v = Version(self.version) + joiner = "" if is_msvc(self) else "." + return f"{v.major}{joiner}{v.minor}" @property def _is_py3(self): - return tools.Version(self._version_number_only).major == "3" + return Version(self.version).major == 3 @property def _is_py2(self): - return tools.Version(self._version_number_only).major == "2" + return Version(self.version).major == 2 + + def export_sources(self): + export_conandata_patches(self) def config_options(self): if self.settings.os == "Windows": del self.options.fPIC - if self.settings.compiler == "Visual Studio": + if is_msvc(self): del self.options.lto del self.options.docstrings del self.options.pymalloc @@ -121,100 +117,112 @@ def config_options(self): def configure(self): if self.options.shared: - del self.options.fPIC + self.options.rm_safe("fPIC") if not self._supports_modules: - del self.options.with_bz2 - del self.options.with_sqlite3 - del self.options.with_tkinter + self.options.rm_safe("with_bz2") + self.options.rm_safe("with_sqlite3") + self.options.rm_safe("with_tkinter") - del self.options.with_bsddb - del self.options.with_lzma - if self.settings.compiler == "Visual Studio": - # The msbuild generator only works with Visual Studio - self.generators.append("MSBuildDeps") + self.options.rm_safe("with_bsddb") + self.options.rm_safe("with_lzma") - def validate(self): - if self.options.shared: - if self.settings.compiler == "Visual Studio" and "MT" in self.settings.compiler.runtime: - raise ConanInvalidConfiguration("cpython does not support MT(d) runtime when building a shared cpython library") - if self.settings.compiler == "Visual Studio": - if self.options.optimizations: - raise ConanInvalidConfiguration("This recipe does not support optimized MSVC cpython builds (yet)") - # FIXME: should probably throw when cross building - # FIXME: optimizations for Visual Studio, before building the final `build_type`: - # 1. build the MSVC PGInstrument build_type, - # 2. run the instrumented binaries, (PGInstrument should have created a `python.bat` file in the PCbuild folder) - # 3. build the MSVC PGUpdate build_type - if self.settings.build_type == "Debug" and "d" not in self.settings.compiler.runtime: - raise ConanInvalidConfiguration("Building debug cpython requires a debug runtime (Debug cpython requires _CrtReportMode symbol, which only debug runtimes define)") - if self._is_py2: - if self.settings.compiler.version >= tools.Version("14"): - self.output.warn("Visual Studio versions 14 and higher were never officially supported by the CPython developers") - if str(self.settings.arch) not in self._msvc_archs: - raise ConanInvalidConfiguration("Visual Studio does not support this architecture") - - if not self.options.shared and tools.Version(self._version_number_only) >= "3.10": - raise ConanInvalidConfiguration("Static msvc build disabled (>=3.10) due to \"AttributeError: module 'sys' has no attribute 'winver'\"") - - if self.options.get_safe("with_curses", False) and not self.options["ncurses"].with_widec: - raise ConanInvalidConfiguration("cpython requires ncurses with wide character support") - - def package_id(self): - del self.info.options.env_vars - - def source(self): - tools.get(**self.conan_data["sources"][self.version], - destination=self._source_subfolder, strip_root=True) + def layout(self): + basic_layout(self, src_folder="src") @property def _with_libffi(self): # cpython 3.7.x on MSVC uses an ancient libffi 2.00-beta (which is not available at cci, and is API/ABI incompatible with current 3.2+) - return self._supports_modules \ - and (self.settings.compiler != "Visual Studio" or tools.Version(self._version_number_only) >= "3.8") + return self._supports_modules and (not is_msvc(self) or Version(self.version) >= "3.8") def requirements(self): - self.requires("zlib/1.2.11") + self.requires("zlib/[>=1.2.11 <2]", force=True) if self._supports_modules: - self.requires("openssl/1.1.1l") - self.requires("expat/2.4.1") + self.requires("openssl/[>=1.1 <4]") + self.requires("expat/2.5.0") if self._with_libffi: - self.requires("libffi/3.2.1") - if tools.Version(self._version_number_only) < "3.8": + self.requires("libffi/3.4.4") + if Version(self.version) < "3.8": self.requires("mpdecimal/2.4.2") - elif tools.Version(self._version_number_only) < "3.10": + elif Version(self.version) < "3.10": self.requires("mpdecimal/2.5.0") else: self.requires("mpdecimal/2.5.0") # FIXME: no 2.5.1 to troubleshoot apple if self.settings.os != "Windows": - if not tools.is_apple_os(self.settings.os): - self.requires("libuuid/1.0.3") - self.requires("libxcrypt/4.4.25") + if not is_apple_os(self): + self.requires("util-linux-libuuid/2.39") + self.requires("libxcrypt/4.4.35") if self.options.get_safe("with_bz2"): self.requires("bzip2/1.0.8") if self.options.get_safe("with_gdbm", False): - self.requires("gdbm/1.19") + self.requires("gdbm/1.23") if self.options.get_safe("with_nis", False): # TODO: Add nis when available. raise ConanInvalidConfiguration("nis is not available on CCI (yet)") if self.options.get_safe("with_sqlite3"): - self.requires("sqlite3/3.36.0") + self.requires("sqlite3/3.43.1") if self.options.get_safe("with_tkinter"): self.requires("tk/8.6.10") if self.options.get_safe("with_curses", False): - self.requires("ncurses/6.2") + # Used in a public header + # https://github.com/python/cpython/blob/v3.10.13/Include/py_curses.h#L34 + self.requires("ncurses/6.4", transitive_headers=True, transitive_libs=True) if self.options.get_safe("with_bsddb", False): self.requires("libdb/5.3.28") if self.options.get_safe("with_lzma", False): - self.requires("xz_utils/5.2.5") + self.requires("xz_utils/5.4.4") + + def package_id(self): + del self.info.settings.compiler + del self.info.options.env_vars - def _configure_autotools(self): - if self._autotools: - return self._autotools - self._autotools = AutoToolsBuildEnvironment(self, win_bash=tools.os_info.is_windows) - self._autotools.libs = [] + def validate(self): + if self.options.shared: + if is_msvc_static_runtime(self): + raise ConanInvalidConfiguration( + "cpython does not support MT(d) runtime when building a shared cpython library" + ) + if is_msvc(self): + if self.options.optimizations: + raise ConanInvalidConfiguration( + "This recipe does not support optimized MSVC cpython builds (yet)" + ) + # FIXME: should probably throw when cross building + # FIXME: optimizations for Visual Studio, before building the final `build_type`: + # 1. build the MSVC PGInstrument build_type, + # 2. run the instrumented binaries, (PGInstrument should have created a `python.bat` file in the PCbuild folder) + # 3. build the MSVC PGUpdate build_type + if self.settings.build_type == "Debug" and "d" not in msvc_runtime_flag(self): + raise ConanInvalidConfiguration( + "Building debug cpython requires a debug runtime (Debug cpython requires _CrtReportMode" + " symbol, which only debug runtimes define)" + ) + if str(self.settings.arch) not in self._msvc_archs: + raise ConanInvalidConfiguration("Visual Studio does not support this architecture") + if not self.options.shared and Version(self.version) >= "3.10": + raise ConanInvalidConfiguration("Static msvc build disabled (>=3.10) due to \"AttributeError: module 'sys' has no attribute 'winver'\"") + + if self.options.get_safe("with_curses", False) and not self.dependencies["ncurses"].options.with_widec: + raise ConanInvalidConfiguration("cpython requires ncurses with wide character support") + + if self._supports_modules: + if Version(self.version) < "3.8.0": + if self.dependencies["mpdecimal"].ref.version >= "2.5.0": + raise ConanInvalidConfiguration("cpython versions lesser then 3.8.0 require a mpdecimal lesser then 2.5.0") + elif Version(self.version) >= "3.9.0": + if self.dependencies["mpdecimal"].ref.version < "2.5.0": + raise ConanInvalidConfiguration("cpython 3.9.0 (and newer) requires (at least) mpdecimal 2.5.0") + + if self._with_libffi: + if self.dependencies["libffi"].ref.version >= "3.3" and is_msvc(self) and "d" in str(self.settings.compiler.runtime): + raise ConanInvalidConfiguration("libffi versions >= 3.3 cause 'read access violations' when using a debug runtime (MTd/MDd)") + + def source(self): + get(self, **self.conan_data["sources"][self.version], strip_root=True) + + def _generate_autotools(self): + tc = AutotoolsToolchain(self) yes_no = lambda v: "yes" if v else "no" - conf_args = [ - "--enable-shared={}".format(yes_no(self.options.shared)), + tc.configure_args += [ "--with-doc-strings={}".format(yes_no(self.options.docstrings)), "--with-pymalloc={}".format(yes_no(self.options.pymalloc)), "--with-system-expat", @@ -224,91 +232,130 @@ def _configure_autotools(self): "--with-pydebug={}".format(yes_no(self.settings.build_type == "Debug")), ] if self._is_py2: - conf_args.extend([ - "--enable-unicode={}".format(yes_no(self.options.unicode)), - ]) + tc.configure_args += ["--enable-unicode={}".format(yes_no(self.options.unicode))] if self._is_py3: - conf_args.extend([ + tc.configure_args += [ "--with-system-libmpdec", - "--with-openssl={}".format(self.deps_cpp_info["openssl"].rootpath), - "--enable-loadable-sqlite-extensions={}".format(yes_no(not self.options["sqlite3"].omit_load_extension)), - ]) - if self.settings.compiler == "intel": - conf_args.extend(["--with-icc"]) - if tools.get_env("CC") or self.settings.compiler != "gcc": - conf_args.append("--without-gcc") + "--with-openssl={}".format(self.dependencies["openssl"].package_folder), + "--enable-loadable-sqlite-extensions={}".format( + yes_no(not self.dependencies["sqlite3"].options.omit_load_extension) + ), + ] + if self.settings.compiler == "intel-cc": + tc.configure_args.append("--with-icc") + if os.environ.get("CC") or self.settings.compiler != "gcc": + tc.configure_args.append("--without-gcc") if self.options.with_tkinter: tcltk_includes = [] tcltk_libs = [] # FIXME: collect using some conan util (https://github.com/conan-io/conan/issues/7656) for dep in ("tcl", "tk", "zlib"): - tcltk_includes += ["-I{}".format(d) for d in self.deps_cpp_info[dep].include_paths] - tcltk_libs += ["-l{}".format(lib) for lib in self.deps_cpp_info[dep].libs] - if self.settings.os == "Linux" and not self.options["tk"].shared: + cpp_info = self.dependencies[dep].cpp_info.aggregated_components() + tcltk_includes += [f"-I{d}" for d in cpp_info.includedirs] + tcltk_libs += [f"-L{lib}" for lib in cpp_info.libdirs] + tcltk_libs += [f"-l{lib}" for lib in cpp_info.libs] + if self.settings.os in ["Linux", "FreeBSD"] and not self.dependencies["tk"].options.shared: # FIXME: use info from xorg.components (x11, xscrnsaver) - tcltk_libs.extend(["-l{}".format(lib) for lib in ("X11", "Xss")]) - conf_args.extend([ + tcltk_libs.extend([f"-l{lib}" for lib in ("X11", "Xss")]) + tc.configure_args += [ "--with-tcltk-includes={}".format(" ".join(tcltk_includes)), "--with-tcltk-libs={}".format(" ".join(tcltk_libs)), - ]) + ] if self.settings.os in ("Linux", "FreeBSD"): # Building _testembed fails due to missing pthread/rt symbols - self._autotools.link_flags.append("-lpthread") + tc.ldflags.append("-lpthread") + + tc.make_args += ["DESTDIR=", "prefix=", "exec_prefix="] build = None - if tools.cross_building(self) and not tools.cross_building(self, skip_x64_x86=True): + if cross_building(self) and not cross_building(self, skip_x64_x86=True): # Building from x86_64 to x86 is not a "real" cross build, so set build == host - build = tools.get_gnu_triplet(str(self.settings.os), str(self.settings.arch), str(self.settings.compiler)) - self._autotools.configure(args=conf_args, configure_dir=self._source_subfolder, build=build) - return self._autotools + build = get_gnu_triplet( + self, str(self.settings.os), str(self.settings.arch), str(self.settings.compiler) + ) + tc.generate() + + deps = AutotoolsDeps(self) + deps.generate() + + + def generate(self): + if is_msvc(self): + # The msbuild generator only works with Visual Studio + deps = MSBuildDeps(self) + deps.generate() + # The toolchain.props is not injected yet, but it also generates VCVars + toolchain = MSBuildToolchain(self) + toolchain.generate() + else: + self._generate_autotools() def _patch_sources(self): - for patch in self.conan_data.get("patches",{}).get(self.version, []): - tools.patch(**patch) - if self._is_py3 and tools.Version(self._version_number_only) < "3.10": - tools.replace_in_file(os.path.join(self._source_subfolder, "setup.py"), - ":libmpdec.so.2", "mpdec") - if self.settings.compiler == "Visual Studio": + apply_conandata_patches(self) + if self._is_py3 and Version(self.version) < "3.10": + replace_in_file(self, os.path.join(self.source_folder, "setup.py"), ":libmpdec.so.2", "mpdec") + if is_msvc(self): runtime_library = { "MT": "MultiThreaded", "MTd": "MultiThreadedDebug", "MD": "MultiThreadedDLL", "MDd": "MultiThreadedDebugDLL", - }[str(self.settings.compiler.runtime)] + }[msvc_runtime_flag(self)] self.output.info("Patching runtime") - tools.replace_in_file(os.path.join(self._source_subfolder, "PCbuild", "pyproject.props"), - "MultiThreadedDLL", runtime_library) - tools.replace_in_file(os.path.join(self._source_subfolder, "PCbuild", "pyproject.props"), - "MultiThreadedDebugDLL", runtime_library) + replace_in_file(self, os.path.join(self.source_folder, "PCbuild", "pyproject.props"), + "MultiThreadedDLL", runtime_library) + replace_in_file(self, os.path.join(self.source_folder, "PCbuild", "pyproject.props"), + "MultiThreadedDebugDLL", runtime_library) # Remove vendored packages - tools.rmdir(os.path.join(self._source_subfolder, "Modules", "_decimal", "libmpdec")) - tools.rmdir(os.path.join(self._source_subfolder, "Modules", "expat")) + rmdir(self, os.path.join(self.source_folder, "Modules", "_decimal", "libmpdec")) + rmdir(self, os.path.join(self.source_folder, "Modules", "expat")) if self.options.get_safe("with_curses", False): # FIXME: this will link to ALL libraries of ncurses. Only need to link to ncurses(w) (+ eventually tinfo) - tools.replace_in_file(os.path.join(self._source_subfolder, "setup.py"), - "curses_libs = ", - "curses_libs = {} #".format(repr(self.deps_cpp_info["ncurses"].libs + self.deps_cpp_info["ncurses"].system_libs))) + ncurses_info = self.dependencies["ncurses"].cpp_info.aggregated_components() + replace_in_file(self, os.path.join(self.source_folder, "setup.py"), + "curses_libs = ", + "curses_libs = {} #".format(repr(ncurses_info.libs + ncurses_info.system_libs))) + + if self._supports_modules: + openssl = self.dependencies["openssl"].cpp_info.aggregated_components() + zlib = self.dependencies["zlib"].cpp_info.aggregated_components() + replace_in_file(self, os.path.join(self.source_folder, "setup.py"), + "openssl_includes = ", + f"openssl_includes = {openssl.includedirs + zlib.includedirs} #") + replace_in_file(self, os.path.join(self.source_folder, "setup.py"), + "openssl_libdirs = ", + f"openssl_libdirs = {openssl.libdirs + zlib.libdirs} #") + replace_in_file(self, os.path.join(self.source_folder, "setup.py"), + "openssl_libs = ", + f"openssl_libs = {openssl.libs + zlib.libs} #") # Enable static MSVC cpython if not self.options.shared: - tools.replace_in_file(os.path.join(self._source_subfolder, "PCbuild", "pythoncore.vcxproj"), - "","Py_NO_BUILD_SHARED;") - tools.replace_in_file(os.path.join(self._source_subfolder, "PCbuild", "pythoncore.vcxproj"), - "Py_ENABLE_SHARED", "Py_NO_ENABLE_SHARED") - tools.replace_in_file(os.path.join(self._source_subfolder, "PCbuild", "pythoncore.vcxproj"), - "DynamicLibrary", "StaticLibrary") - - tools.replace_in_file(os.path.join(self._source_subfolder, "PCbuild", "python.vcxproj"), - "", "shlwapi.lib;ws2_32.lib;pathcch.lib;version.lib;%(AdditionalDependencies)") - tools.replace_in_file(os.path.join(self._source_subfolder, "PCbuild", "python.vcxproj"), - "", "Py_NO_ENABLE_SHARED;") - - tools.replace_in_file(os.path.join(self._source_subfolder, "PCbuild", "pythonw.vcxproj"), - "", "shlwapi.lib;ws2_32.lib;pathcch.lib;version.lib;%(AdditionalDependencies)") - tools.replace_in_file(os.path.join(self._source_subfolder, "PCbuild", "pythonw.vcxproj"), - "", "Py_NO_ENABLE_SHARED;%(PreprocessorDefinitions)") + replace_in_file(self, os.path.join(self.source_folder, "PCbuild", "pythoncore.vcxproj"), + "", + "Py_NO_BUILD_SHARED;") + replace_in_file(self, os.path.join(self.source_folder, "PCbuild", "pythoncore.vcxproj"), + "Py_ENABLE_SHARED", + "Py_NO_ENABLE_SHARED") + replace_in_file(self, os.path.join(self.source_folder, "PCbuild", "pythoncore.vcxproj"), + "DynamicLibrary", + "StaticLibrary") + + replace_in_file(self, os.path.join(self.source_folder, "PCbuild", "python.vcxproj"), + "", + "shlwapi.lib;ws2_32.lib;pathcch.lib;version.lib;%(AdditionalDependencies)") + replace_in_file(self, os.path.join(self.source_folder, "PCbuild", "python.vcxproj"), + "", + "Py_NO_ENABLE_SHARED;") + + replace_in_file(self, os.path.join(self.source_folder, "PCbuild", "pythonw.vcxproj"), + "", + "shlwapi.lib;ws2_32.lib;pathcch.lib;version.lib;%(AdditionalDependencies)") + replace_in_file(self, os.path.join(self.source_folder, "PCbuild", "pythonw.vcxproj"), + "", + "Py_NO_ENABLE_SHARED;%(PreprocessorDefinitions)") def _upgrade_single_project_file(self, project_file): """ @@ -317,22 +364,21 @@ def _upgrade_single_project_file(self, project_file): This is needed for static cpython or for disabled optional dependencies (e.g. tkinter=False) Restore it afterwards because it is needed to build some targets. """ - tools.rename(os.path.join(self._source_subfolder, "PCbuild", "pcbuild.sln"), - os.path.join(self._source_subfolder, "PCbuild", "pcbuild.sln.bak")) - tools.rename(os.path.join(self._source_subfolder, "PCbuild", "pcbuild.proj"), - os.path.join(self._source_subfolder, "PCbuild", "pcbuild.proj.bak")) - with tools.vcvars(self.settings): - self.run("devenv \"{}\" /upgrade".format(project_file), run_environment=True) - tools.rename(os.path.join(self._source_subfolder, "PCbuild", "pcbuild.sln.bak"), - os.path.join(self._source_subfolder, "PCbuild", "pcbuild.sln")) - tools.rename(os.path.join(self._source_subfolder, "PCbuild", "pcbuild.proj.bak"), - os.path.join(self._source_subfolder, "PCbuild", "pcbuild.proj")) + rename(self,os.path.join(self.source_folder, "PCbuild", "pcbuild.sln"), + os.path.join(self.source_folder, "PCbuild", "pcbuild.sln.bak")) + rename(self, os.path.join(self.source_folder, "PCbuild", "pcbuild.proj"), + os.path.join(self.source_folder, "PCbuild", "pcbuild.proj.bak")) + self.run(f'devenv "{project_file}" /upgrade') + rename(self, os.path.join(self.source_folder, "PCbuild", "pcbuild.sln.bak"), + os.path.join(self.source_folder, "PCbuild", "pcbuild.sln")) + rename(self, os.path.join(self.source_folder, "PCbuild", "pcbuild.proj.bak"), + os.path.join(self.source_folder, "PCbuild", "pcbuild.proj")) @property def _solution_projects(self): if self.options.shared: - solution_path = os.path.join(self._source_subfolder, "PCbuild", "pcbuild.sln") - projects = set(m.group(1) for m in re.finditer("\"([^\"]+)\\.vcxproj\"", open(solution_path).read())) + solution_path = os.path.join(self.source_folder, "PCbuild", "pcbuild.sln") + projects = set(m.group(1) for m in re.finditer('"([^"]+)\\.vcxproj"', open(solution_path).read())) def project_build(name): if os.path.basename(name) in self._msvc_discarded_projects: @@ -358,7 +404,10 @@ def sort_importance(key): @property def _msvc_discarded_projects(self): - discarded = {"python_uwp", "pythonw_uwp"} + discarded = { + "python_uwp", + "pythonw_uwp", + } if not self.options.with_bz2: discarded.add("bz2") if not self.options.with_sqlite3: @@ -367,11 +416,25 @@ def _msvc_discarded_projects(self): discarded.add("_tkinter") if self._is_py2: # Python 2 Visual Studio projects NOT to build - discarded = discarded.union({"bdist_wininst", "libeay", "ssleay", "sqlite3", "tcl", "tk", "tix"}) + discarded = discarded.union({ + "bdist_wininst", + "libeay", + "ssleay", + "sqlite3", + "tcl", + "tk", + "tix", + }) if not self.options.with_bsddb: discarded.add("_bsddb") elif self._is_py3: - discarded = discarded.union({"bdist_wininst", "liblzma", "openssl", "sqlite3", "xxlimited"}) + discarded = discarded.union({ + "bdist_wininst", + "liblzma", + "openssl", + "sqlite3", + "xxlimited", + }) if not self.options.with_lzma: discarded.add("_lzma") return discarded @@ -382,7 +445,7 @@ def _msvc_archs(self): "x86": "Win32", "x86_64": "x64", } - if tools.Version(self._version_number_only) >= "3.8": + if Version(self.version) >= "3.8": archs.update({ "armv7": "ARM", "armv8_32": "ARM", @@ -392,39 +455,28 @@ def _msvc_archs(self): def _msvc_build(self): msbuild = MSBuild(self) + msbuild.platform = self._msvc_archs[str(self.settings.arch)] + # TODO msbuild_properties = { "IncludeExternals": "true", } + projects = self._solution_projects - self.output.info("Building {} Visual Studio projects: {}".format(len(projects), projects)) + self.output.info(f"Building {len(projects)} Visual Studio projects: {projects}") - with tools.no_op(): - for project_i, project in enumerate(projects, 1): - self.output.info("[{}/{}] Building project '{}'...".format(project_i, len(projects), project)) - project_file = os.path.join(self._source_subfolder, "PCbuild", project + ".vcxproj") - self._upgrade_single_project_file(project_file) - msbuild.build(project_file, upgrade_project=False, build_type="Debug" if self.settings.build_type == "Debug" else "Release", - platforms=self._msvc_archs, properties=msbuild_properties) + for project_i, project in enumerate(projects, 1): + self.output.info(f"[{project_i}/{len(projects)}] Building project '{project}'...") + project_file = os.path.join(self.source_folder, "PCbuild", project + ".vcxproj") + self._upgrade_single_project_file(project_file) + msbuild.build(project_file) def build(self): - # FIXME: these checks belong in validate, but the versions of dependencies are not available there yet - if self._supports_modules: - if tools.Version(self._version_number_only) < "3.8.0": - if tools.Version(self.deps_cpp_info["mpdecimal"].version) >= "2.5.0": - raise ConanInvalidConfiguration("cpython versions lesser then 3.8.0 require a mpdecimal lesser then 2.5.0") - elif tools.Version(self._version_number_only) >= "3.9.0": - if tools.Version(self.deps_cpp_info["mpdecimal"].version) < "2.5.0": - raise ConanInvalidConfiguration("cpython 3.9.0 (and newer) requires (at least) mpdecimal 2.5.0") - - if self._with_libffi: - if tools.Version(self.deps_cpp_info["libffi"].version) >= "3.3" and self.settings.compiler == "Visual Studio" and "d" in str(self.settings.compiler.runtime): - raise ConanInvalidConfiguration("libffi versions >= 3.3 cause 'read access violations' when using a debug runtime (MTd/MDd)") - self._patch_sources() - if self.settings.compiler == "Visual Studio": + if is_msvc(self): self._msvc_build() else: - autotools = self._configure_autotools() + autotools = Autotools(self) + autotools.configure() autotools.make() @property @@ -433,43 +485,43 @@ def _msvc_artifacts_path(self): "x86_64": "amd64", "x86": "win32", } - if tools.Version(self._version_number_only) >= "3.8": + if Version(self.version) >= "3.8": build_subdir_lut.update({ "armv7": "arm32", "armv8_32": "arm32", "armv8": "arm64", }) - return os.path.join(self._source_subfolder, "PCbuild", build_subdir_lut[str(self.settings.arch)]) + return os.path.join(self.source_folder, "PCbuild", build_subdir_lut[str(self.settings.arch)]) @property def _msvc_install_subprefix(self): return "bin" def _copy_essential_dlls(self): - if self.settings.compiler == "Visual Studio": + if is_msvc(self): # Until MSVC builds support cross building, copy dll's of essential (shared) dependencies to python binary location. # These dll's are required when running the layout tool using the newly built python executable. dest_path = os.path.join(self.build_folder, self._msvc_artifacts_path) if self._with_libffi: - for bin_path in self.deps_cpp_info["libffi"].bin_paths: - self.copy("*.dll", src=bin_path, dst=dest_path) - for bin_path in self.deps_cpp_info["expat"].bin_paths: - self.copy("*.dll", src=bin_path, dst=dest_path) - for bin_path in self.deps_cpp_info["zlib"].bin_paths: - self.copy("*.dll", src=bin_path, dst=dest_path) + for bin_path in self.dependencies["libffi"].cpp_info.bindirs: + copy(self, "*.dll", src=bin_path, dst=dest_path) + for bin_path in self.dependencies["expat"].cpp_info.bindirs: + copy(self, "*.dll", src=bin_path, dst=dest_path) + for bin_path in self.dependencies["zlib"].cpp_info.bindirs: + copy(self, "*.dll", src=bin_path, dst=dest_path) def _msvc_package_layout(self): self._copy_essential_dlls() install_prefix = os.path.join(self.package_folder, self._msvc_install_subprefix) - tools.mkdir(install_prefix) + mkdir(self, install_prefix) build_path = self._msvc_artifacts_path infix = "_d" if self.settings.build_type == "Debug" else "" # FIXME: if cross building, use a build python executable here - python_built = os.path.join(build_path, "python{}.exe".format(infix)) + python_built = os.path.join(build_path, f"python{infix}.exe") layout_args = [ - os.path.join(self._source_subfolder, "PC", "layout", "main.py"), + os.path.join(self.source_folder, "PC", "layout", "main.py"), "-v", - "-s", self._source_subfolder, + "-s", self.source_folder, "-b", build_path, "--copy", install_prefix, "-p", @@ -481,10 +533,10 @@ def _msvc_package_layout(self): layout_args.append("--include-tcltk") if self.settings.build_type == "Debug": layout_args.append("-d") - python_args = " ".join("\"{}\"".format(a) for a in layout_args) - self.run("{} {}".format(python_built, python_args), run_environment=True) + python_args = " ".join(f'"{a}"' for a in layout_args) + self.run(f"{python_built} {python_args}") - tools.rmdir(os.path.join(self.package_folder, "bin", "tcl")) + rmdir(self, os.path.join(self.package_folder, "bin", "tcl")) for file in os.listdir(install_prefix): if re.match("vcruntime.*", file): @@ -498,45 +550,61 @@ def _msvc_package_layout(self): def _msvc_package_copy(self): build_path = self._msvc_artifacts_path infix = "_d" if self.settings.build_type == "Debug" else "" - self.copy("*.exe", src=build_path, dst=os.path.join(self.package_folder, self._msvc_install_subprefix)) - self.copy("*.dll", src=build_path, dst=os.path.join(self.package_folder, self._msvc_install_subprefix)) - self.copy("*.pyd", src=build_path, dst=os.path.join(self.package_folder, self._msvc_install_subprefix, "DLLs")) - self.copy("python{}{}.lib".format(self._version_suffix, infix), src=build_path, dst=os.path.join(self.package_folder, self._msvc_install_subprefix, "libs")) - self.copy("*", src=os.path.join(self._source_subfolder, "Include"), dst=os.path.join(self.package_folder, self._msvc_install_subprefix, "include")) - self.copy("pyconfig.h", src=os.path.join(self._source_subfolder, "PC"), dst=os.path.join(self.package_folder, self._msvc_install_subprefix, "include")) - self.copy("*.py", src=os.path.join(self._source_subfolder, "lib"), dst=os.path.join(self.package_folder, self._msvc_install_subprefix, "Lib")) - tools.rmdir(os.path.join(self.package_folder, self._msvc_install_subprefix, "Lib", "test")) + copy(self, "*.exe", + src=build_path, + dst=os.path.join(self.package_folder, self._msvc_install_subprefix)) + copy(self, "*.dll", + src=build_path, + dst=os.path.join(self.package_folder, self._msvc_install_subprefix)) + copy(self, "*.pyd", + src=build_path, + dst=os.path.join(self.package_folder, self._msvc_install_subprefix, "DLLs")) + copy(self, f"python{self._version_suffix}{infix}.lib", + src=build_path, + dst=os.path.join(self.package_folder, self._msvc_install_subprefix, "libs")) + copy(self, "*", + src=os.path.join(self.source_folder, "Include"), + dst=os.path.join(self.package_folder, self._msvc_install_subprefix, "include")) + copy(self, "pyconfig.h", + src=os.path.join(self.source_folder, "PC"), + dst=os.path.join(self.package_folder, self._msvc_install_subprefix, "include")) + copy(self, "*.py", + src=os.path.join(self.source_folder, "lib"), + dst=os.path.join(self.package_folder, self._msvc_install_subprefix, "Lib")) + rmdir(self, os.path.join(self.package_folder, self._msvc_install_subprefix, "Lib", "test")) packages = {} get_name_version = lambda fn: fn.split(".", 2)[:2] - whldir = os.path.join(self._source_subfolder, "Lib", "ensurepip", "_bundled") + whldir = os.path.join(self.source_folder, "Lib", "ensurepip", "_bundled") for fn in filter(lambda n: n.endswith(".whl"), os.listdir(whldir)): name, version = get_name_version(fn) add = True if name in packages: pname, pversion = get_name_version(packages[name]) - add = tools.Version(version) > tools.Version(pversion) + add = Version(version) > Version(pversion) if add: packages[name] = fn for fname in packages.values(): - tools.unzip(filename=os.path.join(whldir, fname), destination=os.path.join(self.package_folder, "bin", "Lib", "site-packages")) + unzip(self, filename=os.path.join(whldir, fname), + destination=os.path.join(self.package_folder, "bin", "Lib", "site-packages")) - self.run("{} -c \"import compileall; compileall.compile_dir('{}')\"".format(os.path.join(build_path, self._cpython_interpreter_name), os.path.join(self.package_folder, self._msvc_install_subprefix, "Lib").replace("\\", "/")), - run_environment=True) + interpreter_path = os.path.join(build_path, self._cpython_interpreter_name) + lib_dir_path = os.path.join(self.package_folder, self._msvc_install_subprefix, "Lib").replace("\\", "/") + self.run(f"{interpreter_path} -c \"import compileall; compileall.compile_dir('{lib_dir_path}')\"") def package(self): - self.copy("LICENSE", src=self._source_subfolder, dst="licenses") - if self.settings.compiler == "Visual Studio": + copy(self, "LICENSE", src=self.source_folder, dst=os.path.join(self.package_folder, "licenses")) + if is_msvc(self): if self._is_py2 or not self.options.shared: self._msvc_package_copy() else: self._msvc_package_layout() - tools.remove_files_by_mask(os.path.join(self.package_folder, "bin"), "vcruntime*") + rm(self, "vcruntime*", os.path.join(self.package_folder, "bin"), recursive=True) else: - autotools = self._configure_autotools() + autotools = Autotools(self) autotools.install() - tools.rmdir(os.path.join(self.package_folder, "lib", "pkgconfig")) - tools.rmdir(os.path.join(self.package_folder, "share")) + rmdir(self, os.path.join(self.package_folder, "lib", "pkgconfig")) + rmdir(self, os.path.join(self.package_folder, "share")) # Rewrite shebangs of python scripts for filename in os.listdir(os.path.join(self.package_folder, "bin")): @@ -550,22 +618,22 @@ def package(self): if not(firstline.startswith(b"#!") and b"/python" in firstline and b"/bin/sh" not in firstline): continue text = fn.read() - self.output.info("Rewriting shebang of {}".format(filename)) + self.output.info(f"Rewriting shebang of {filename}") with open(filepath, "wb") as fn: - fn.write(textwrap.dedent("""\ + fn.write(textwrap.dedent(f"""\ #!/bin/sh ''':' __file__="$0" while [ -L "$__file__" ]; do __file__="$(dirname "$__file__")/$(readlink "$__file__")" done - exec "$(dirname "$__file__")/python{}" "$0" "$@" + exec "$(dirname "$__file__")/python{self._version_suffix}" "$0" "$@" ''' - """.format(self._version_suffix)).encode()) + """).encode()) fn.write(text) if not os.path.exists(self._cpython_symlink): - os.symlink("python{}".format(self._version_suffix), self._cpython_symlink) + os.symlink(f"python{self._version_suffix}", self._cpython_symlink) self._fix_install_name() @property @@ -577,14 +645,12 @@ def _cpython_symlink(self): @property def _cpython_interpreter_name(self): - if self.settings.compiler == "Visual Studio": - suffix = "" - else: - suffix = self._version_suffix - python = "python{}".format(suffix) - if self.settings.compiler == "Visual Studio": + python = "python" + if is_msvc(self): if self.settings.build_type == "Debug": python += "_d" + else: + python += self._version_suffix if self.settings.os == "Windows": python += ".exe" return python @@ -599,34 +665,36 @@ def _abi_suffix(self): if self._is_py3: if self.settings.build_type == "Debug": res += "d" - if tools.Version(self._version_number_only) < "3.8": + if Version(self.version) < "3.8": if self.options.get_safe("pymalloc", False): res += "m" return res @property def _lib_name(self): - if self.settings.compiler == "Visual Studio": + if is_msvc(self): if self.settings.build_type == "Debug": lib_ext = "_d" else: lib_ext = "" else: - lib_ext = self._abi_suffix + (".dll.a" if self.options.shared and self.settings.os == "Windows" else "") - return "python{}{}".format(self._version_suffix, lib_ext) + lib_ext = self._abi_suffix + ( + ".dll.a" if self.options.shared and self.settings.os == "Windows" else "" + ) + return f"python{self._version_suffix}{lib_ext}" def _fix_install_name(self): - if tools.is_apple_os(self.settings.os) and self.options.shared: + if is_apple_os(self) and self.options.shared: buffer = StringIO() python = os.path.join(self.package_folder, "bin", "python") - self.run('otool -L "%s"' % python, output=buffer) - lines = buffer.getvalue().strip().split('\n')[1:] + self.run(f'otool -L "{python}"', buffer) + lines = buffer.getvalue().strip().split("\n")[1:] for line in lines: library = line.split()[0] if library.startswith(self.package_folder): new = library.replace(self.package_folder, "@executable_path/..") - self.output.info("patching {}, replace {} with {}".format(python, library, new)) - self.run("install_name_tool -change {} {} {}".format(library, new, python)) + self.output.info(f"patching {python}, replace {library} with {new}") + self.run(f"install_name_tool -change {library} {new} {python}") def package_info(self): # FIXME: conan components Python::Interpreter component, need a target type @@ -634,40 +702,50 @@ def package_info(self): # self.cpp_info.names["cmake_find_package_multi"] = "Python" # FIXME: conan components need to generate multiple .pc files (python2, python-27) - py_version = tools.Version(self._version_number_only) + py_version = Version(self.version) # python component: "Build a C extension for Python" - if self.settings.compiler == "Visual Studio": + if is_msvc(self): self.cpp_info.components["python"].includedirs = [os.path.join(self._msvc_install_subprefix, "include")] libdir = os.path.join(self._msvc_install_subprefix, "libs") else: - self.cpp_info.components["python"].includedirs.append(os.path.join("include", "python{}{}".format(self._version_suffix, self._abi_suffix))) + self.cpp_info.components["python"].includedirs.append( + os.path.join("include", f"python{self._version_suffix}{self._abi_suffix}") + ) libdir = "lib" if self.options.shared: self.cpp_info.components["python"].defines.append("Py_ENABLE_SHARED") else: self.cpp_info.components["python"].defines.append("Py_NO_ENABLE_SHARED") - if self.settings.os == "Linux": + if self.settings.os in ["Linux", "FreeBSD"]: self.cpp_info.components["python"].system_libs.extend(["dl", "m", "pthread", "util"]) elif self.settings.os == "Windows": - self.cpp_info.components["python"].system_libs.extend(["pathcch", "shlwapi", "version", "ws2_32"]) + self.cpp_info.components["python"].system_libs.extend( + ["pathcch", "shlwapi", "version", "ws2_32"] + ) self.cpp_info.components["python"].requires = ["zlib::zlib"] if self.settings.os != "Windows": self.cpp_info.components["python"].requires.append("libxcrypt::libxcrypt") - self.cpp_info.components["python"].names["pkg_config"] = "python-{}.{}".format(py_version.major, py_version.minor) + self.cpp_info.components["python"].set_property( + "pkg_config_name", f"python-{py_version.major}.{py_version.minor}" + ) self.cpp_info.components["python"].libdirs = [] - self.cpp_info.components["_python_copy"].names["pkg_config"] = "python{}".format(py_version.major) + self.cpp_info.components["_python_copy"].set_property("pkg_config_name", f"python{py_version.major}") self.cpp_info.components["_python_copy"].requires = ["python"] self.cpp_info.components["_python_copy"].libdirs = [] # embed component: "Embed Python into an application" self.cpp_info.components["embed"].libs = [self._lib_name] self.cpp_info.components["embed"].libdirs = [libdir] - self.cpp_info.components["embed"].names["pkg_config"] = "python-{}.{}-embed".format(py_version.major, py_version.minor) + self.cpp_info.components["embed"].set_property( + "pkg_config_name", f"python-{py_version.major}.{py_version.minor}-embed" + ) self.cpp_info.components["embed"].requires = ["python"] self.cpp_info.components["_embed_copy"].requires = ["embed"] - self.cpp_info.components["_embed_copy"].names["pkg_config"] = ["python{}-embed".format(py_version.major)] + self.cpp_info.components["_embed_copy"].set_property( + "pkg_config_name", f"python{py_version.major}-embed" + ) self.cpp_info.components["_embed_copy"].libdirs = [] if self._supports_modules: @@ -681,8 +759,8 @@ def package_info(self): if self._with_libffi: self.cpp_info.components["_hidden"].requires.append("libffi::libffi") if self.settings.os != "Windows": - if not tools.is_apple_os(self.settings.os): - self.cpp_info.components["_hidden"].requires.append("libuuid::libuuid") + if not is_apple_os(self): + self.cpp_info.components["_hidden"].requires.append("util-linux-libuuid::util-linux-libuuid") self.cpp_info.components["_hidden"].requires.append("libxcrypt::libxcrypt") if self.options.with_bz2: self.cpp_info.components["_hidden"].requires.append("bzip2::bzip2") @@ -702,30 +780,34 @@ def package_info(self): if self.options.env_vars: bindir = os.path.join(self.package_folder, "bin") - self.output.info("Appending PATH environment variable: {}".format(bindir)) + self.output.info(f"Appending PATH environment variable: {bindir}") self.env_info.PATH.append(bindir) python = self._cpython_interpreter_path + self.conf_info.define("user.cpython:python", python) self.user_info.python = python if self.options.env_vars: - self.output.info("Setting PYTHON environment variable: {}".format(python)) self.env_info.PYTHON = python - if self.settings.compiler == "Visual Studio": + if is_msvc(self): pythonhome = os.path.join(self.package_folder, "bin") - elif tools.is_apple_os(self.settings.os): + elif is_apple_os(self): pythonhome = self.package_folder else: - version = tools.Version(self._version_number_only) - pythonhome = os.path.join(self.package_folder, "lib", "python{}.{}".format(version.major, version.minor)) + version = Version(self.version) + pythonhome = os.path.join( + self.package_folder, "lib", f"python{version.major}.{version.minor}" + ) + self.conf_info.define("user.cpython:pythonhome", pythonhome) self.user_info.pythonhome = pythonhome - pythonhome_required = self.settings.compiler == "Visual Studio" or tools.is_apple_os(self.settings.os) + pythonhome_required = is_msvc(self) or is_apple_os(self) + self.conf_info.define("user.cpython:module_requires_pythonhome", pythonhome_required) self.user_info.module_requires_pythonhome = pythonhome_required - if self.settings.compiler == "Visual Studio": + if is_msvc(self): if self.options.env_vars: - self.output.info("Setting PYTHONHOME environment variable: {}".format(pythonhome)) + self.output.info(f"Setting PYTHONHOME environment variable: {pythonhome}") self.env_info.PYTHONHOME = pythonhome if self._is_py2: @@ -733,6 +815,7 @@ def package_info(self): else: python_root = self.package_folder if self.options.env_vars: - self.output.info("Setting PYTHON_ROOT environment variable: {}".format(python_root)) + self.output.info(f"Setting PYTHON_ROOT environment variable: {python_root}") self.env_info.PYTHON_ROOT = python_root + self.conf_info.define("user.cpython:python_root", python_root) self.user_info.python_root = python_root diff --git a/recipes/cpython/all/patches/2.7.18-0001-msvc.patch b/recipes/cpython/all/patches/2.7/2.7.18-0001-msvc.patch similarity index 100% rename from recipes/cpython/all/patches/2.7.18-0001-msvc.patch rename to recipes/cpython/all/patches/2.7/2.7.18-0001-msvc.patch diff --git a/recipes/cpython/all/patches/2.7.18-0002-add-support-msvc-14.patch b/recipes/cpython/all/patches/2.7/2.7.18-0002-add-support-msvc-14.patch similarity index 100% rename from recipes/cpython/all/patches/2.7.18-0002-add-support-msvc-14.patch rename to recipes/cpython/all/patches/2.7/2.7.18-0002-add-support-msvc-14.patch diff --git a/recipes/cpython/all/patches/2.7.18-0003-msvc-fix-static.patch b/recipes/cpython/all/patches/2.7/2.7.18-0003-msvc-fix-static.patch similarity index 100% rename from recipes/cpython/all/patches/2.7.18-0003-msvc-fix-static.patch rename to recipes/cpython/all/patches/2.7/2.7.18-0003-msvc-fix-static.patch diff --git a/recipes/cpython/all/patches/2.7.18-0004-disable-macos-tcltk.patch b/recipes/cpython/all/patches/2.7/2.7.18-0004-disable-macos-tcltk.patch similarity index 100% rename from recipes/cpython/all/patches/2.7.18-0004-disable-macos-tcltk.patch rename to recipes/cpython/all/patches/2.7/2.7.18-0004-disable-macos-tcltk.patch diff --git a/recipes/cpython/all/patches/3.10.0-0001-msvc.patch b/recipes/cpython/all/patches/3.10/3.10.0-0001-msvc.patch similarity index 100% rename from recipes/cpython/all/patches/3.10.0-0001-msvc.patch rename to recipes/cpython/all/patches/3.10/3.10.0-0001-msvc.patch diff --git a/recipes/cpython/all/patches/3.10.0-0003-_ctypes-ffi.patch b/recipes/cpython/all/patches/3.10/3.10.0-0003-_ctypes-ffi.patch similarity index 100% rename from recipes/cpython/all/patches/3.10.0-0003-_ctypes-ffi.patch rename to recipes/cpython/all/patches/3.10/3.10.0-0003-_ctypes-ffi.patch diff --git a/recipes/cpython/all/patches/3.10.0-0004-setup.py-pass-CFLAGS-CPPFLAGS.patch b/recipes/cpython/all/patches/3.10/3.10.0-0004-setup.py-pass-CFLAGS-CPPFLAGS.patch similarity index 100% rename from recipes/cpython/all/patches/3.10.0-0004-setup.py-pass-CFLAGS-CPPFLAGS.patch rename to recipes/cpython/all/patches/3.10/3.10.0-0004-setup.py-pass-CFLAGS-CPPFLAGS.patch diff --git a/recipes/cpython/all/patches/3.10.0-0005-disable-macos-tcltk.patch b/recipes/cpython/all/patches/3.10/3.10.0-0005-disable-macos-tcltk.patch similarity index 100% rename from recipes/cpython/all/patches/3.10.0-0005-disable-macos-tcltk.patch rename to recipes/cpython/all/patches/3.10/3.10.0-0005-disable-macos-tcltk.patch diff --git a/recipes/cpython/all/patches/3.7.9-0001-msvc.patch b/recipes/cpython/all/patches/3.7/3.7.9-0001-msvc.patch similarity index 100% rename from recipes/cpython/all/patches/3.7.9-0001-msvc.patch rename to recipes/cpython/all/patches/3.7/3.7.9-0001-msvc.patch diff --git a/recipes/cpython/all/patches/3.7.9-0002-setup.py-pass-CFLAGS-CPPFLAGS.patch b/recipes/cpython/all/patches/3.7/3.7.9-0002-setup.py-pass-CFLAGS-CPPFLAGS.patch similarity index 100% rename from recipes/cpython/all/patches/3.7.9-0002-setup.py-pass-CFLAGS-CPPFLAGS.patch rename to recipes/cpython/all/patches/3.7/3.7.9-0002-setup.py-pass-CFLAGS-CPPFLAGS.patch diff --git a/recipes/cpython/all/patches/3.7.9-0003-disable-macos-tcltk.patch b/recipes/cpython/all/patches/3.7/3.7.9-0003-disable-macos-tcltk.patch similarity index 100% rename from recipes/cpython/all/patches/3.7.9-0003-disable-macos-tcltk.patch rename to recipes/cpython/all/patches/3.7/3.7.9-0003-disable-macos-tcltk.patch diff --git a/recipes/cpython/all/patches/3.8.12-0001-msvc.patch b/recipes/cpython/all/patches/3.8/3.8.12-0001-msvc.patch similarity index 100% rename from recipes/cpython/all/patches/3.8.12-0001-msvc.patch rename to recipes/cpython/all/patches/3.8/3.8.12-0001-msvc.patch diff --git a/recipes/cpython/all/patches/3.8.12-0002-_ctypes-ffi.patch b/recipes/cpython/all/patches/3.8/3.8.12-0002-_ctypes-ffi.patch similarity index 100% rename from recipes/cpython/all/patches/3.8.12-0002-_ctypes-ffi.patch rename to recipes/cpython/all/patches/3.8/3.8.12-0002-_ctypes-ffi.patch diff --git a/recipes/cpython/all/patches/3.8.12-0003-setup.py-pass-CFLAGS-CPPFLAGS.patch b/recipes/cpython/all/patches/3.8/3.8.12-0003-setup.py-pass-CFLAGS-CPPFLAGS.patch similarity index 100% rename from recipes/cpython/all/patches/3.8.12-0003-setup.py-pass-CFLAGS-CPPFLAGS.patch rename to recipes/cpython/all/patches/3.8/3.8.12-0003-setup.py-pass-CFLAGS-CPPFLAGS.patch diff --git a/recipes/cpython/all/patches/3.8.12-0004-disable-macos-tcltk.patch b/recipes/cpython/all/patches/3.8/3.8.12-0004-disable-macos-tcltk.patch similarity index 100% rename from recipes/cpython/all/patches/3.8.12-0004-disable-macos-tcltk.patch rename to recipes/cpython/all/patches/3.8/3.8.12-0004-disable-macos-tcltk.patch diff --git a/recipes/cpython/all/patches/3.9.7-0001-msvc.patch b/recipes/cpython/all/patches/3.9/3.9.7-0001-msvc.patch similarity index 100% rename from recipes/cpython/all/patches/3.9.7-0001-msvc.patch rename to recipes/cpython/all/patches/3.9/3.9.7-0001-msvc.patch diff --git a/recipes/cpython/all/patches/3.9.7-0002-_msi-vcxproj.patch b/recipes/cpython/all/patches/3.9/3.9.7-0002-_msi-vcxproj.patch similarity index 100% rename from recipes/cpython/all/patches/3.9.7-0002-_msi-vcxproj.patch rename to recipes/cpython/all/patches/3.9/3.9.7-0002-_msi-vcxproj.patch diff --git a/recipes/cpython/all/patches/3.9.7-0003-_ctypes-ffi.patch b/recipes/cpython/all/patches/3.9/3.9.7-0003-_ctypes-ffi.patch similarity index 100% rename from recipes/cpython/all/patches/3.9.7-0003-_ctypes-ffi.patch rename to recipes/cpython/all/patches/3.9/3.9.7-0003-_ctypes-ffi.patch diff --git a/recipes/cpython/all/patches/3.9.7-0004-setup.py-pass-CFLAGS-CPPFLAGS.patch b/recipes/cpython/all/patches/3.9/3.9.7-0004-setup.py-pass-CFLAGS-CPPFLAGS.patch similarity index 100% rename from recipes/cpython/all/patches/3.9.7-0004-setup.py-pass-CFLAGS-CPPFLAGS.patch rename to recipes/cpython/all/patches/3.9/3.9.7-0004-setup.py-pass-CFLAGS-CPPFLAGS.patch diff --git a/recipes/cpython/all/patches/3.9.7-0005-disable-macos-tcltk.patch b/recipes/cpython/all/patches/3.9/3.9.7-0005-disable-macos-tcltk.patch similarity index 100% rename from recipes/cpython/all/patches/3.9.7-0005-disable-macos-tcltk.patch rename to recipes/cpython/all/patches/3.9/3.9.7-0005-disable-macos-tcltk.patch diff --git a/recipes/cpython/all/test_package/CMakeLists.txt b/recipes/cpython/all/test_package/CMakeLists.txt index 525329324757f..844120fd54e0c 100644 --- a/recipes/cpython/all/test_package/CMakeLists.txt +++ b/recipes/cpython/all/test_package/CMakeLists.txt @@ -1,13 +1,12 @@ -cmake_minimum_required(VERSION 3.1) +cmake_minimum_required(VERSION 3.15) project(test_package C) -include("${CMAKE_BINARY_DIR}/conanbuildinfo.cmake") -conan_basic_setup() +find_package(cpython REQUIRED CONFIG) -set(CACHE PY_VERSION_MAJOR "" CACHE STRING "MAJOR version of python") -set(CACHE PY_VERSION_MAJOR_MINOR "" CACHE STRING "MAJOR.MINOR version of python") -set(CACHE PY_VERSION "" CACHE STRING "Required version of python") -set(CACHE PY_VERSION_SUFFIX "" CACHE STRING "Suffix of python") +set(PY_VERSION_MAJOR "" CACHE STRING "MAJOR version of python") +set(PY_VERSION_MAJOR_MINOR "" CACHE STRING "MAJOR.MINOR version of python") +set(PY_VERSION "" CACHE STRING "Required version of python") +set(PY_VERSION_SUFFIX "" CACHE STRING "Suffix of python") set(Python_ADDITIONAL_VERSIONS ${PY_VERSION}${PY_VERSION_SUFFIX} ${PY_VERSION_MAJOR_MINOR}${PY_VERSION_SUFFIX} ${PY_VERSION_MAJOR}${PY_VERSION_SUFFIX} ${PY_VERSION} ${PY_VERSION_MAJOR_MINOR} ${PY_VERSION_MAJOR}) message("Using Python_ADDITIONAL_VERSIONS: ${Python_ADDITIONAL_VERSIONS}") @@ -73,7 +72,7 @@ if(BUILD_MODULE) endif() option(USE_FINDPYTHON_X "Use new-style FindPythonX module") - if(USE_FINDPYTHON_X AND NOT (CMAKE_VERSION VERSION_LESS "3.16")) + if(USE_FINDPYTHON_X AND NOT CMAKE_VERSION VERSION_LESS "3.16") # Require CMake 3.16 because this version introduces Python${PY_VERSION_MAJOR}_FIND_ABI find_package(Python${PY_VERSION_MAJOR} REQUIRED COMPONENTS Interpreter Development) message("Python${PY_VERSION_MAJOR}_EXECUTABLE: ${Python${PY_VERSION_MAJOR}_EXECUTABLE}") @@ -97,4 +96,4 @@ if(BUILD_MODULE) endif() add_executable(${PROJECT_NAME} "py${PY_VERSION_MAJOR}/test_package.c") -target_link_libraries(${PROJECT_NAME} ${CONAN_LIBS}) +target_link_libraries(${PROJECT_NAME} PRIVATE cpython::cpython) diff --git a/recipes/cpython/all/test_package/conanfile.py b/recipes/cpython/all/test_package/conanfile.py index 8abc18e4492c5..f48857c4fb12e 100644 --- a/recipes/cpython/all/test_package/conanfile.py +++ b/recipes/cpython/all/test_package/conanfile.py @@ -1,9 +1,17 @@ -from conans import AutoToolsBuildEnvironment, ConanFile, CMake, tools, RunEnvironment -from conans.errors import ConanException -from io import StringIO import os import re import shutil +from io import StringIO + +from conan import ConanFile +from conan.errors import ConanException +from conan.tools.apple import is_apple_os +from conan.tools.build import cross_building +from conan.tools.cmake import CMake, CMakeToolchain, cmake_layout +from conan.tools.env import VirtualRunEnv +from conan.tools.files import mkdir +from conan.tools.microsoft import is_msvc +from conan.tools.scm import Version class CmakePython3Abi(object): @@ -18,11 +26,14 @@ def __init__(self, debug, pymalloc, unicode): @property def suffix(self): - return "{}{}{}".format( - "d" if self.debug else "", - "m" if self.pymalloc else "", - "u" if self.unicode else "", - ) + suffix = "" + if self.debug: + suffix += "d" + if self.pymalloc: + suffix += "m" + if self.unicode: + suffix += "u" + return suffix @property def cmake_arg(self): @@ -30,163 +41,177 @@ def cmake_arg(self): class TestPackageConan(ConanFile): - settings = "os", "compiler", "build_type", "arch" - generators = "cmake" + settings = "os", "arch", "compiler", "build_type" + generators = "CMakeDeps" + test_type = "explicit" + + def requirements(self): + self.requires(self.tested_reference_str) + + def layout(self): + cmake_layout(self) + + @property + def _python(self): + return self.dependencies["cpython"].conf_info.get("user.cpython:python", check_type=str) + + @property + def _clean_py_version(self): + return re.match(r"^[0-9.]+", str(self.dependencies["cpython"].ref.version)).group(0) @property def _py_version(self): - return re.match(r"^([0-9.]+)", self.deps_cpp_info["cpython"].version).group(1) + return Version(self.dependencies["cpython"].ref.version) @property def _pymalloc(self): - return bool("pymalloc" in self.options["cpython"] and self.options["cpython"].pymalloc) + return bool(self.dependencies["cpython"].options.get_safe("pymalloc", False)) @property def _cmake_abi(self): - if self._py_version < tools.Version("3.8"): - return CmakePython3Abi( - debug=self.settings.build_type == "Debug", - pymalloc=self._pymalloc, - unicode=False, - ) + if self._py_version < "3.8": + return CmakePython3Abi(debug=self.settings.build_type == "Debug", pymalloc=self._pymalloc, unicode=False) else: - return CmakePython3Abi( - debug=self.settings.build_type == "Debug", - pymalloc=False, - unicode=False, - ) + return CmakePython3Abi(debug=self.settings.build_type == "Debug", pymalloc=False, unicode=False) @property def _cmake_try_FindPythonX(self): - if self.settings.compiler == "Visual Studio" and self.settings.build_type == "Debug": - return False - return True + # FIXME: re-enable + # return not is_msvc(self) or self.settings.build_type != "Debug" + return False @property def _supports_modules(self): - return self.settings.compiler != "Visual Studio" or self.options["cpython"].shared + return not is_msvc(self) or self.dependencies["cpython"].options.shared + + def generate(self): + tc = CMakeToolchain(self) + version = self._py_version + py_major = str(version.major) + tc.cache_variables["BUILD_MODULE"] = self._supports_modules + tc.cache_variables["PY_VERSION_MAJOR"] = py_major + tc.cache_variables["PY_VERSION_MAJOR_MINOR"] = f"{version.major}.{version.minor}" + tc.cache_variables["PY_FULL_VERSION"] = str(version) + tc.cache_variables["PY_VERSION"] = self._clean_py_version + tc.cache_variables["PY_VERSION_SUFFIX"] = self._cmake_abi.suffix + tc.cache_variables["PYTHON_EXECUTABLE"] = self._python + tc.cache_variables["USE_FINDPYTHON_X".format(py_major)] = self._cmake_try_FindPythonX + tc.cache_variables[f"Python{py_major}_EXECUTABLE"] = self._python + tc.cache_variables[f"Python{py_major}_ROOT_DIR"] = self.dependencies["cpython"].package_folder + tc.cache_variables[f"Python{py_major}_USE_STATIC_LIBS"] = not self.dependencies["cpython"].options.shared + tc.cache_variables[f"Python{py_major}_FIND_FRAMEWORK"] = "NEVER" + tc.cache_variables[f"Python{py_major}_FIND_REGISTRY"] = "NEVER" + tc.cache_variables[f"Python{py_major}_FIND_IMPLEMENTATIONS"] = "CPython" + tc.cache_variables[f"Python{py_major}_FIND_STRATEGY"] = "LOCATION" + if not is_msvc(self) and self._clean_py_version < "3.8": + tc.cache_variables[f"Python{py_major}_FIND_ABI"] = self._cmake_abi.cmake_arg + tc.generate() + + VirtualRunEnv(self).generate(scope="run") + VirtualRunEnv(self).generate(scope="build") def build(self): - if not tools.cross_building(self, skip_x64_x86=True): - command = "{} --version".format(self.deps_user_info["cpython"].python) + if not cross_building(self, skip_x64_x86=True): + command = f"{self._python} --version" buffer = StringIO() - self.run(command, output=buffer, ignore_errors=True, run_environment=True) - self.output.info("output: %s" % buffer.getvalue()) - self.run(command, run_environment=True) + self.run(command, stdout=buffer, ignore_errors=True) + self.output.info(f"output: {buffer.getvalue()}") + self.run(command) cmake = CMake(self) - py_major = self.deps_cpp_info["cpython"].version.split(".")[0] - cmake.definitions["BUILD_MODULE"] = self._supports_modules - cmake.definitions["PY_VERSION_MAJOR"] = py_major - cmake.definitions["PY_VERSION_MAJOR_MINOR"] = ".".join(self._py_version.split(".")[:2]) - cmake.definitions["PY_FULL_VERSION"] = self.deps_cpp_info["cpython"].version - cmake.definitions["PY_VERSION"] = self._py_version - cmake.definitions["PY_VERSION_SUFFIX"] = self._cmake_abi.suffix - cmake.definitions["PYTHON_EXECUTABLE"] = self.deps_user_info["cpython"].python - cmake.definitions["USE_FINDPYTHON_X".format(py_major)] = self._cmake_try_FindPythonX - cmake.definitions["Python{}_EXECUTABLE".format(py_major)] = self.deps_user_info["cpython"].python - cmake.definitions["Python{}_ROOT_DIR".format(py_major)] = self.deps_cpp_info["cpython"].rootpath - cmake.definitions["Python{}_USE_STATIC_LIBS".format(py_major)] = not self.options["cpython"].shared - cmake.definitions["Python{}_FIND_FRAMEWORK".format(py_major)] = "NEVER" - cmake.definitions["Python{}_FIND_REGISTRY".format(py_major)] = "NEVER" - cmake.definitions["Python{}_FIND_IMPLEMENTATIONS".format(py_major)] = "CPython" - cmake.definitions["Python{}_FIND_STRATEGY".format(py_major)] = "LOCATION" - - if self.settings.compiler != "Visual Studio": - if tools.Version(self._py_version) < tools.Version("3.8"): - cmake.definitions["Python{}_FIND_ABI".format(py_major)] = self._cmake_abi.cmake_arg - - with tools.environment_append(RunEnvironment(self).vars): - cmake.configure() + cmake.configure() cmake.build() - if not tools.cross_building(self, skip_x64_x86=True): + if not cross_building(self, skip_x64_x86=True): if self._supports_modules: - with tools.vcvars(self.settings) if self.settings.compiler == "Visual Studio" else tools.no_op(): - modsrcfolder = "py2" if tools.Version(self.deps_cpp_info["cpython"].version).major < "3" else "py3" - tools.mkdir(os.path.join(self.build_folder, modsrcfolder)) - for fn in os.listdir(os.path.join(self.source_folder, modsrcfolder)): - shutil.copy(os.path.join(self.source_folder, modsrcfolder, fn), os.path.join(self.build_folder, modsrcfolder, fn)) - shutil.copy(os.path.join(self.source_folder, "setup.py"), os.path.join(self.build_folder, "setup.py")) - env = { - "DISTUTILS_USE_SDK": "1", - "MSSdk": "1" - } - env.update(**AutoToolsBuildEnvironment(self).vars) - with tools.environment_append(env): - setup_args = [ - "{}/setup.py".format(self.source_folder), - # "conan", - # "--install-folder", self.build_folder, - "build", - "--build-base", self.build_folder, - "--build-platlib", os.path.join(self.build_folder, "lib_setuptools"), - ] - if self.settings.build_type == "Debug": - setup_args.append("--debug") - self.run("{} {}".format(self.deps_user_info["cpython"].python, " ".join("\"{}\"".format(a) for a in setup_args)), run_environment=True) + modsrcfolder = "py2" if self._py_version.major == 2 else "py3" + mkdir(self, os.path.join(self.build_folder, modsrcfolder)) + for fn in os.listdir(os.path.join(self.source_folder, modsrcfolder)): + shutil.copy( + os.path.join(self.source_folder, modsrcfolder, fn), + os.path.join(self.build_folder, modsrcfolder, fn), + ) + shutil.copy( + os.path.join(self.source_folder, "setup.py"), + os.path.join(self.build_folder, "setup.py"), + ) + os.environ["DISTUTILS_USE_SDK"] = "1" + os.environ["MSSdk"] = "1" + setup_args = [ + f"{self.source_folder}/setup.py", + # "conan", + # "--install-folder", self.build_folder, + "build", + "--build-base", self.build_folder, + "--build-platlib", os.path.join(self.build_folder, "lib_setuptools"), + ] + if self.settings.build_type == "Debug": + setup_args.append("--debug") + args = " ".join(f'"{a}"' for a in setup_args) + self.run(f"{self._python} {args}") def _test_module(self, module, should_work): try: - self.run("{} {}/test_package.py -b {} -t {} ".format( - self.deps_user_info["cpython"].python, self.source_folder, self.build_folder, module), run_environment=True) - works = True - except ConanException as e: - works = False - exception = e - if should_work == works: - self.output.info("Result of test was expected.") - else: - if works: - raise ConanException("Module '{}' works, but should not have worked".format(module)) - else: - self.output.warn("Module '{}' does not work, but should have worked".format(module)) - raise exception + self.run(f"{self._python} {self.source_folder}/test_package.py -b {self.build_folder} -t {module}") + except ConanException: + if should_work: + self.output.warning(f"Module '{module}' does not work, but should have worked") + raise + self.output.info("Module failed as expected") + return + if not should_work: + raise ConanException(f"Module '{module}' works, but should not have worked") + self.output.info("Module worked as expected") def _cpython_option(self, name): - try: - return getattr(self.options["cpython"], name, False) - except ConanException: - return False + return self.dependencies["cpython"].options.get_safe(name, False) def test(self): - if not tools.cross_building(self, skip_x64_x86=True): - self.run("{} -c \"print('hello world')\"".format(self.deps_user_info["cpython"].python), run_environment=True) + if not cross_building(self, skip_x64_x86=True): + self.run(f"{self._python} -c \"print('hello world')\"") buffer = StringIO() - self.run("{} -c \"import sys; print('.'.join(str(s) for s in sys.version_info[:3]))\"".format(self.deps_user_info["cpython"].python), run_environment=True, output=buffer) + self.run(f"{self._python} -c \"import sys; print('.'.join(str(s) for s in sys.version_info[:3]))\"", buffer) self.output.info(buffer.getvalue()) version_detected = buffer.getvalue().splitlines()[-1].strip() - if self._py_version != version_detected: - raise ConanException("python reported wrong version. Expected {exp}. Got {res}.".format(exp=self._py_version, res=version_detected)) + if self._clean_py_version != version_detected: + raise ConanException( + f"python reported wrong version. Expected {self._clean_py_version}. Got {version_detected}." + ) if self._supports_modules: self._test_module("gdbm", self._cpython_option("with_gdbm")) self._test_module("bz2", self._cpython_option("with_bz2")) - self._test_module("bsddb", self._cpython_option("with_bsddb")) + if self._py_version.major < 3: + self._test_module("bsddb", self._cpython_option("with_bsddb")) self._test_module("lzma", self._cpython_option("with_lzma")) self._test_module("tkinter", self._cpython_option("with_tkinter")) - with tools.environment_append({"TERM": "ansi"}): - self._test_module("curses", self._cpython_option("with_curses")) + os.environ["TERM"] = "ansi" + self._test_module("curses", self._cpython_option("with_curses")) self._test_module("expat", True) self._test_module("sqlite3", True) self._test_module("decimal", True) self._test_module("ctypes", True) + self._test_module("ssl", True) - if tools.is_apple_os(self.settings.os) and not self.options["cpython"].shared: - self.output.info("Not testing the module, because these seem not to work on apple when cpython is built as a static library") + if is_apple_os(self) and not self.dependencies["cpython"].options.shared: + self.output.info( + "Not testing the module, because these seem not to work on apple when cpython is built as" + " a static library" + ) # FIXME: find out why cpython on apple does not allow to use modules linked against a static python else: if self._supports_modules: - with tools.environment_append({"PYTHONPATH": [os.path.join(self.build_folder, "lib")]}): - self.output.info("Testing module (spam) using cmake built module") - self._test_module("spam", True) + os.environ["PYTHONPATH"] = os.path.join(self.build_folder, "lib") + self.output.info("Testing module (spam) using cmake built module") + self._test_module("spam", True) - with tools.environment_append({"PYTHONPATH": [os.path.join(self.build_folder, "lib_setuptools")]}): - self.output.info("Testing module (spam) using setup.py built module") - self._test_module("spam", True) + os.environ["PYTHONPATH"] = os.path.join(self.build_folder, "lib_setuptools") + self.output.info("Testing module (spam) using setup.py built module") + self._test_module("spam", True) # MSVC builds need PYTHONHOME set. - with tools.environment_append({"PYTHONHOME": self.deps_user_info["cpython"].pythonhome}) if self.deps_user_info["cpython"].module_requires_pythonhome == "True" else tools.no_op(): - self.run(os.path.join("bin", "test_package"), run_environment=True) + if self.dependencies["cpython"].conf_info.get("user.cpython:module_requires_pythonhome", check_type=bool): + os.environ["PYTHONHOME"] = self.dependencies["cpython"].conf_info.get("user.cpython:pythonhome", check_type=str) + self.run(os.path.join(self.cpp.build.bindir, "test_package"), env="conanrun") diff --git a/recipes/cpython/all/test_package/setup.py b/recipes/cpython/all/test_package/setup.py index cd1754cef8131..70f6e8f6961db 100644 --- a/recipes/cpython/all/test_package/setup.py +++ b/recipes/cpython/all/test_package/setup.py @@ -8,8 +8,8 @@ if os.path.isdir(directory): os.add_dll_directory(directory) -PY2 = (2, 0) <= sys.version_info < (3, 0) -PY3 = (3, 0) <= sys.version_info < (4, 0) +PY2 = sys.version_info[0] == 2 +PY3 = sys.version_info[0] == 3 if PY2: subdir = "py2" @@ -20,12 +20,13 @@ else: raise Exception +script_dir = os.path.dirname(os.path.realpath(__file__)) setup( name="test_package", version="1.0", use_2to3=True, ext_modules=[ - Extension("spam", [os.path.join(subdir, "test_module.c")]), + Extension("spam", [os.path.join(script_dir, subdir, "test_module.c")]), ], ) diff --git a/recipes/cpython/all/test_package/test_package.py b/recipes/cpython/all/test_package/test_package.py index f77d2837c6376..870e11b340eb1 100644 --- a/recipes/cpython/all/test_package/test_package.py +++ b/recipes/cpython/all/test_package/test_package.py @@ -14,12 +14,13 @@ def add_test(fn): global ALL_TESTS - name = fn.__name__[fn.__name__.find("_")+1:] + name = fn.__name__[fn.__name__.find("_") + 1 :] def inner_fn(): - print("testing {}".format(name)) + print(f"testing {name}") sys.stdout.flush() fn() + ALL_TESTS[name] = inner_fn return fn @@ -30,13 +31,13 @@ def test_expat(): # 3 handler functions def start_element(name, attrs): - print('Start element:', name, attrs) + print("Start element:", name, attrs) def end_element(name): - print('End element:', name) + print("End element:", name) def char_data(data): - print('Character data:', repr(data)) + print("Character data:", repr(data)) p = xml.parsers.expat.ParserCreate() @@ -44,10 +45,13 @@ def char_data(data): p.EndElementHandler = end_element p.CharacterDataHandler = char_data - p.Parse(""" + p.Parse( + """ Text goes here More text - """, 1) + """, + 1, + ) @add_test @@ -85,10 +89,8 @@ def test_spam(): if "This is an example spam doc." not in spam.__doc__: raise Exception("spam.__doc__ does not contain the expected text") - cmd = { - "Windows": "dir", - }.get(platform.system(), "ls") - print("About to run spam.system(\"{}\")".format(cmd)) + cmd = {"Windows": "dir"}.get(platform.system(), "ls") + print(f'About to run spam.system("{cmd}")') sys.stdout.flush() spam.system(cmd) @@ -116,9 +118,9 @@ def test_bsddb(): if len(db) != 2: raise Exception("Wrong length") if db["key1"] != "value1": - raise Exception("value1 incorrect {}".format(db["key1"])) + raise Exception(f"value1 incorrect {db['key1']}") if db["key2"] != "value2": - raise Exception("value2 incorrect {}".format(db["key2"])) + raise Exception(f"value2 incorrect {db['key2']}") @add_test @@ -133,6 +135,7 @@ def test_lzma(): @add_test def test_sqlite3(): import sqlite3 + conn = sqlite3.connect("sqlite3.db") c = conn.cursor() @@ -141,16 +144,16 @@ def test_sqlite3(): c.execute("INSERT INTO stocks VALUES ('2006-01-05','BUY','RHAT',100,35.14)") conn.commit() - t = ('RHAT',) - c.execute('SELECT * FROM stocks WHERE symbol=?', t) + t = ("RHAT",) + c.execute("SELECT * FROM stocks WHERE symbol=?", t) # Larger example that inserts many records at a time purchases = [ - ('2006-03-28', 'BUY', 'IBM', 1000, 45.00), - ('2006-04-05', 'BUY', 'MSFT', 1000, 72.00), - ('2006-04-06', 'SELL', 'IBM', 500, 53.00), + ("2006-03-28", "BUY", "IBM", 1000, 45.00), + ("2006-04-05", "BUY", "MSFT", 1000, 72.00), + ("2006-04-06", "SELL", "IBM", 500, 53.00), ] - c.executemany('INSERT INTO stocks VALUES (?,?,?,?,?)', purchases) + c.executemany("INSERT INTO stocks VALUES (?,?,?,?,?)", purchases) conn.commit() conn.close() conn = sqlite3.connect("sqlite3.db") @@ -165,7 +168,7 @@ def test_sqlite3(): @add_test def test_decimal(): - if sys.version_info >= (3, ): + if sys.version_info >= (3,): # Check whether the _decimal package was built successfully import _decimal as decimal else: @@ -180,7 +183,7 @@ def test_decimal(): def test_curses(): import _curses - print("Using _curses version {}".format(_curses.version)) + print(f"Using _curses version {_curses.version}") @add_test @@ -188,15 +191,23 @@ def test_ctypes(): import _ctypes errno = _ctypes.get_errno() - print("errno={}".format(errno)) + print(f"errno={errno}") @add_test def test_tkinter(): import _tkinter - print("tcl version: {}".format(_tkinter.TCL_VERSION)) - print("tk version: {}".format(_tkinter.TK_VERSION)) + print(f"tcl version: {_tkinter.TCL_VERSION}") + print(f"tk version: {_tkinter.TK_VERSION}") + + +@add_test +def test_ssl(): + import ssl + + default_context = ssl.create_default_context() + print(f"default_context.options={default_context.options}") def main(): diff --git a/recipes/cpython/all/test_v1_package/CMakeLists.txt b/recipes/cpython/all/test_v1_package/CMakeLists.txt new file mode 100644 index 0000000000000..91630d79f4abb --- /dev/null +++ b/recipes/cpython/all/test_v1_package/CMakeLists.txt @@ -0,0 +1,8 @@ +cmake_minimum_required(VERSION 3.15) +project(test_package) + +include(${CMAKE_BINARY_DIR}/conanbuildinfo.cmake) +conan_basic_setup(TARGETS) + +add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/../test_package/ + ${CMAKE_CURRENT_BINARY_DIR}/test_package/) diff --git a/recipes/cpython/all/test_v1_package/conanfile.py b/recipes/cpython/all/test_v1_package/conanfile.py new file mode 100644 index 0000000000000..e3dae899ab9ec --- /dev/null +++ b/recipes/cpython/all/test_v1_package/conanfile.py @@ -0,0 +1,192 @@ +from conans import AutoToolsBuildEnvironment, ConanFile, CMake, tools, RunEnvironment +from conans.errors import ConanException +from io import StringIO +import os +import re +import shutil + + +class CmakePython3Abi(object): + def __init__(self, debug, pymalloc, unicode): + self.debug, self.pymalloc, self.unicode = debug, pymalloc, unicode + + _cmake_lut = { + None: "ANY", + True: "ON", + False: "OFF", + } + + @property + def suffix(self): + return "{}{}{}".format( + "d" if self.debug else "", + "m" if self.pymalloc else "", + "u" if self.unicode else "", + ) + + @property + def cmake_arg(self): + return ";".join(self._cmake_lut[a] for a in (self.debug, self.pymalloc, self.unicode)) + + +class TestPackageConan(ConanFile): + settings = "os", "compiler", "build_type", "arch" + generators = "cmake", "cmake_find_package_multi" + + @property + def _py_version(self): + return re.match(r"^([0-9.]+)", self.deps_cpp_info["cpython"].version).group(1) + + @property + def _pymalloc(self): + return bool("pymalloc" in self.options["cpython"] and self.options["cpython"].pymalloc) + + @property + def _cmake_abi(self): + if self._py_version < tools.Version("3.8"): + return CmakePython3Abi( + debug=self.settings.build_type == "Debug", + pymalloc=self._pymalloc, + unicode=False, + ) + else: + return CmakePython3Abi( + debug=self.settings.build_type == "Debug", + pymalloc=False, + unicode=False, + ) + + @property + def _cmake_try_FindPythonX(self): + if self.settings.compiler == "Visual Studio" and self.settings.build_type == "Debug": + return False + return True + + @property + def _supports_modules(self): + return self.settings.compiler != "Visual Studio" or self.options["cpython"].shared + + def build(self): + if not tools.cross_building(self, skip_x64_x86=True): + command = "{} --version".format(self.deps_user_info["cpython"].python) + buffer = StringIO() + self.run(command, output=buffer, ignore_errors=True, run_environment=True) + self.output.info("output: %s" % buffer.getvalue()) + self.run(command, run_environment=True) + + cmake = CMake(self) + py_major = self.deps_cpp_info["cpython"].version.split(".")[0] + cmake.definitions["BUILD_MODULE"] = self._supports_modules + cmake.definitions["PY_VERSION_MAJOR"] = py_major + cmake.definitions["PY_VERSION_MAJOR_MINOR"] = ".".join(self._py_version.split(".")[:2]) + cmake.definitions["PY_FULL_VERSION"] = self.deps_cpp_info["cpython"].version + cmake.definitions["PY_VERSION"] = self._py_version + cmake.definitions["PY_VERSION_SUFFIX"] = self._cmake_abi.suffix + cmake.definitions["PYTHON_EXECUTABLE"] = self.deps_user_info["cpython"].python + cmake.definitions["USE_FINDPYTHON_X".format(py_major)] = self._cmake_try_FindPythonX + cmake.definitions["Python{}_EXECUTABLE".format(py_major)] = self.deps_user_info["cpython"].python + cmake.definitions["Python{}_ROOT_DIR".format(py_major)] = self.deps_cpp_info["cpython"].rootpath + cmake.definitions["Python{}_USE_STATIC_LIBS".format(py_major)] = not self.options["cpython"].shared + cmake.definitions["Python{}_FIND_FRAMEWORK".format(py_major)] = "NEVER" + cmake.definitions["Python{}_FIND_REGISTRY".format(py_major)] = "NEVER" + cmake.definitions["Python{}_FIND_IMPLEMENTATIONS".format(py_major)] = "CPython" + cmake.definitions["Python{}_FIND_STRATEGY".format(py_major)] = "LOCATION" + + if self.settings.compiler != "Visual Studio": + if tools.Version(self._py_version) < tools.Version("3.8"): + cmake.definitions["Python{}_FIND_ABI".format(py_major)] = self._cmake_abi.cmake_arg + + with tools.environment_append(RunEnvironment(self).vars): + cmake.configure() + cmake.build() + + if not tools.cross_building(self, skip_x64_x86=True): + if self._supports_modules: + with tools.vcvars(self.settings) if self.settings.compiler == "Visual Studio" else tools.no_op(): + modsrcfolder = "py2" if tools.Version(self.deps_cpp_info["cpython"].version).major < "3" else "py3" + tools.mkdir(os.path.join(self.build_folder, modsrcfolder)) + for fn in os.listdir(os.path.join(self.source_folder, modsrcfolder)): + shutil.copy(os.path.join(self.source_folder, modsrcfolder, fn), os.path.join(self.build_folder, modsrcfolder, fn)) + shutil.copy(os.path.join(self.source_folder, "setup.py"), os.path.join(self.build_folder, "setup.py")) + env = { + "DISTUTILS_USE_SDK": "1", + "MSSdk": "1" + } + env.update(**AutoToolsBuildEnvironment(self).vars) + with tools.environment_append(env): + setup_args = [ + "{}/setup.py".format(self.source_folder), + # "conan", + # "--install-folder", self.build_folder, + "build", + "--build-base", self.build_folder, + "--build-platlib", os.path.join(self.build_folder, "lib_setuptools"), + ] + if self.settings.build_type == "Debug": + setup_args.append("--debug") + self.run("{} {}".format(self.deps_user_info["cpython"].python, " ".join("\"{}\"".format(a) for a in setup_args)), run_environment=True) + + def _test_module(self, module, should_work): + try: + self.run("{} {}/test_package.py -b {} -t {} ".format( + self.deps_user_info["cpython"].python, self.source_folder, self.build_folder, module), run_environment=True) + works = True + except ConanException as e: + works = False + exception = e + if should_work == works: + self.output.info("Result of test was expected.") + else: + if works: + raise ConanException("Module '{}' works, but should not have worked".format(module)) + else: + self.output.warn("Module '{}' does not work, but should have worked".format(module)) + raise exception + + def _cpython_option(self, name): + try: + return getattr(self.options["cpython"], name, False) + except ConanException: + return False + + def test(self): + if not tools.cross_building(self, skip_x64_x86=True): + self.run("{} -c \"print('hello world')\"".format(self.deps_user_info["cpython"].python), run_environment=True) + + buffer = StringIO() + self.run("{} -c \"import sys; print('.'.join(str(s) for s in sys.version_info[:3]))\"".format(self.deps_user_info["cpython"].python), run_environment=True, output=buffer) + self.output.info(buffer.getvalue()) + version_detected = buffer.getvalue().splitlines()[-1].strip() + if self._py_version != version_detected: + raise ConanException("python reported wrong version. Expected {exp}. Got {res}.".format(exp=self._py_version, res=version_detected)) + + if self._supports_modules: + self._test_module("gdbm", self._cpython_option("with_gdbm")) + self._test_module("bz2", self._cpython_option("with_bz2")) + self._test_module("bsddb", self._cpython_option("with_bsddb")) + self._test_module("lzma", self._cpython_option("with_lzma")) + self._test_module("tkinter", self._cpython_option("with_tkinter")) + with tools.environment_append({"TERM": "ansi"}): + self._test_module("curses", self._cpython_option("with_curses")) + + self._test_module("expat", True) + self._test_module("sqlite3", True) + self._test_module("decimal", True) + self._test_module("ctypes", True) + + if tools.is_apple_os(self) and not self.options["cpython"].shared: + self.output.info("Not testing the module, because these seem not to work on apple when cpython is built as a static library") + # FIXME: find out why cpython on apple does not allow to use modules linked against a static python + else: + if self._supports_modules: + with tools.environment_append({"PYTHONPATH": [os.path.join(self.build_folder, "lib")]}): + self.output.info("Testing module (spam) using cmake built module") + self._test_module("spam", True) + + with tools.environment_append({"PYTHONPATH": [os.path.join(self.build_folder, "lib_setuptools")]}): + self.output.info("Testing module (spam) using setup.py built module") + self._test_module("spam", True) + + # MSVC builds need PYTHONHOME set. + with tools.environment_append({"PYTHONHOME": self.deps_user_info["cpython"].pythonhome}) if self.deps_user_info["cpython"].module_requires_pythonhome == "True" else tools.no_op(): + self.run(os.path.join("bin", "test_package"), run_environment=True) diff --git a/recipes/cpython/config.yml b/recipes/cpython/config.yml index e3abf3ec8acb2..9c1e3f1bc1142 100644 --- a/recipes/cpython/config.yml +++ b/recipes/cpython/config.yml @@ -1,11 +1,13 @@ versions: + "3.10.13": + folder: "all" "3.10.0": folder: "all" - "3.9.7": + "3.9.18": folder: "all" - "3.8.12": + "3.8.18": folder: "all" - "3.7.12": + "3.7.17": folder: "all" "2.7.18": folder: "all"