SpaceInvaders

conan 2.0 conanfile.py for cpython (WorldForge)

Apr 6th, 2023
95
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 36.49 KB | None | 0 0
  1. # Warning!!!!
  2. # Before using this file or any part of this file please review:
  3. # https://github.com/worldforge/conan-packages/issues/4
  4. # or search https://github.com/worldforge/conan-packages/issues
  5. # for details or a more current version
  6.  
  7. from conan import ConanFile
  8. from conan.errors import ConanInvalidConfiguration
  9. from conan.tools.build import check_min_cppstd
  10. from conan.tools.cmake import CMake, CMakeToolchain, cmake_layout
  11. from conan.tools.scm import Version
  12. from conan.tools.apple import is_apple_os
  13. from conan.tools.files import (
  14. apply_conandata_patches, collect_libs, copy, export_conandata_patches, get,
  15. rename, replace_in_file, rmdir, save
  16. )
  17. import glob
  18. import os
  19. import re
  20. import textwrap
  21.  
  22. required_conan_version = ">=1.53.0"
  23.  
  24. class CPythonConan(ConanFile):
  25. name = "cpython"
  26. url = "https://github.com/conan-io/conan-center-index"
  27. homepage = "https://www.python.org"
  28. description = "Python is a programming language that lets you work quickly and integrate systems more effectively."
  29. topics = ("python", "cpython", "language", "script")
  30. license = ("Python-2.0",)
  31. exports_sources = "patches/**"
  32. settings = "os", "arch", "compiler", "build_type"
  33. options = {
  34. "shared": [True, False],
  35. "fPIC": [True, False],
  36. "optimizations": [True, False],
  37. "lto": [True, False],
  38. "docstrings": [True, False],
  39. "pymalloc": [True, False],
  40. "with_bz2": [True, False],
  41. "with_gdbm": [True, False],
  42. "with_nis": [True, False],
  43. "with_sqlite3": [True, False],
  44. "with_tkinter": [True, False],
  45. "with_curses": [True, False],
  46.  
  47. # Python 2 options
  48. "unicode": ["ucs2", "ucs4"],
  49. "with_bsddb": [True, False],
  50. # Python 3 options
  51. "with_lzma": [True, False],
  52.  
  53. # options that don't change package id
  54. "env_vars": [True, False], # set environment variables
  55. }
  56. default_options = {
  57. "shared": False,
  58. "fPIC": True,
  59. "optimizations": False,
  60. "lto": False,
  61. "docstrings": True,
  62. "pymalloc": True,
  63. "with_bz2": True,
  64. "with_gdbm": True,
  65. "with_nis": False,
  66. "with_sqlite3": True,
  67. "with_tkinter": True,
  68. "with_curses": True,
  69.  
  70. # Python 2 options
  71. "unicode": "ucs2",
  72. "with_bsddb": False, # True, # FIXME: libdb package missing (#5309/#5392)
  73. # Python 3 options
  74. "with_lzma": True,
  75.  
  76. # options that don't change package id
  77. "env_vars": True,
  78. }
  79.  
  80. _autotools = None
  81.  
  82. @property
  83. def _source_subfolder(self):
  84. return "source_subfolder"
  85.  
  86. @property
  87. def _version_number_only(self):
  88. return re.match(r"^([0-9.]+)", self.version).group(1)
  89.  
  90. @property
  91. def _version_tuple(self):
  92. return tuple(self._version_number_only.split("."))
  93.  
  94. @property
  95. def _supports_modules(self):
  96. return self.settings.compiler != "msvc" or self.options.shared
  97.  
  98. @property
  99. def _version_suffix(self):
  100. if self.settings.compiler == "msvc":
  101. joiner = ""
  102. else:
  103. joiner = "."
  104. return joiner.join(self._version_tuple[:2])
  105.  
  106. @property
  107. def _is_py3(self):
  108. return Version(self._version_number_only).major == "3"
  109.  
  110. @property
  111. def _is_py2(self):
  112. return Version(self._version_number_only).major == "2"
  113.  
  114. def config_options(self):
  115. if self.settings.os == "Windows":
  116. del self.options.fPIC
  117. if self.settings.compiler == "msvc":
  118. del self.options.lto
  119. del self.options.docstrings
  120. del self.options.pymalloc
  121. del self.options.with_curses
  122. del self.options.with_gdbm
  123. del self.options.with_nis
  124. if self._is_py2:
  125. # Python 2.xx does not support following options
  126. del self.options.with_lzma
  127. elif self._is_py3:
  128. # Python 3.xx does not support following options
  129. del self.options.with_bsddb
  130. del self.options.unicode
  131.  
  132. del self.settings.compiler.libcxx
  133. del self.settings.compiler.cppstd
  134.  
  135. def configure(self):
  136. if self.options.shared:
  137. del self.options.fPIC
  138. if not self._supports_modules:
  139. del self.options.with_bz2
  140. del self.options.with_sqlite3
  141. del self.options.with_tkinter
  142.  
  143. del self.options.with_bsddb
  144. del self.options.with_lzma
  145. if self.settings.compiler == "msvc":
  146. # The msbuild generator only works with Visual Studio (msvc)
  147. self.generators.append("MSBuildDeps")
  148.  
  149. def validate(self):
  150. if self.options.shared:
  151. if self.settings.compiler == "msvc" and "MT" in self.settings.compiler.runtime:
  152. raise ConanInvalidConfiguration("cpython does not support MT(d) runtime when building a shared cpython library")
  153. if self.settings.compiler == "msvc":
  154. if self.options.optimizations:
  155. raise ConanInvalidConfiguration("This recipe does not support optimized MSVC cpython builds (yet)")
  156. # FIXME: should probably throw when cross building
  157. # FIXME: optimizations for Visual Studio (msvc), before building the final `build_type`:
  158. # 1. build the MSVC PGInstrument build_type,
  159. # 2. run the instrumented binaries, (PGInstrument should have created a `python.bat` file in the PCbuild folder)
  160. # 3. build the MSVC PGUpdate build_type
  161. if self.settings.build_type == "Debug" and "d" not in self.settings.compiler.runtime:
  162. raise ConanInvalidConfiguration("Building debug cpython requires a debug runtime (Debug cpython requires _CrtReportMode symbol, which only debug runtimes define)")
  163. if self._is_py2:
  164. if self.settings.compiler.version >= Version("14"):
  165. self.output.warn("Visual Studio (msvc) versions 14 and higher were never officially supported by the CPython developers")
  166. if str(self.settings.arch) not in self._msvc_archs:
  167. raise ConanInvalidConfiguration("Visual Studio (msvc) does not support this architecture")
  168.  
  169. if not self.options.shared and Version(self._version_number_only) >= "3.10":
  170. raise ConanInvalidConfiguration("Static msvc build disabled (>=3.10) due to \"AttributeError: module 'sys' has no attribute 'winver'\"")
  171.  
  172. if self.options.get_safe("with_curses", False) and not self.options["ncurses"].with_widec:
  173. raise ConanInvalidConfiguration("cpython requires ncurses with wide character support")
  174.  
  175. def package_id(self):
  176. del self.info.options.env_vars
  177.  
  178. def source(self):
  179. get(**self.conan_data["sources"][self.version],
  180. destination=self._source_subfolder, strip_root=True)
  181.  
  182. @property
  183. def _with_libffi(self):
  184. # 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+)
  185. return self._supports_modules \
  186. and (self.settings.compiler != "msvc" or Version(self._version_number_only) >= "3.8")
  187.  
  188. def requirements(self):
  189. self.requires("zlib/1.2.13")
  190. if self._supports_modules:
  191. self.requires("openssl/1.1.1l")
  192. self.requires("expat/2.4.1")
  193. if self._with_libffi:
  194. self.requires("libffi/3.2.1")
  195. if Version(self._version_number_only) < "3.8":
  196. self.requires("mpdecimal/2.4.2")
  197. elif Version(self._version_number_only) < "3.10":
  198. self.requires("mpdecimal/2.5.0")
  199. else:
  200. self.requires("mpdecimal/2.5.0") # FIXME: no 2.5.1 to troubleshoot apple
  201. if self.settings.os != "Windows":
  202. if not is_apple_os(self):
  203. self.requires("libuuid/1.0.3")
  204. self.requires("libxcrypt/4.4.25")
  205. if self.options.get_safe("with_bz2"):
  206. self.requires("bzip2/1.0.8")
  207. if self.options.get_safe("with_gdbm", False):
  208. self.requires("gdbm/1.19")
  209. if self.options.get_safe("with_nis", False):
  210. # TODO: Add nis when available.
  211. raise ConanInvalidConfiguration("nis is not available on CCI (yet)")
  212. if self.options.get_safe("with_sqlite3"):
  213. self.requires("sqlite3/3.36.0")
  214. if self.options.get_safe("with_tkinter"):
  215. self.requires("tk/8.6.10")
  216. if self.options.get_safe("with_curses", False):
  217. self.requires("ncurses/6.2")
  218. if self.options.get_safe("with_bsddb", False):
  219. self.requires("libdb/5.3.28")
  220. if self.options.get_safe("with_lzma", False):
  221. self.requires("xz_utils/5.2.5")
  222.  
  223. def _configure_autotools(self):
  224. if self._autotools:
  225. return self._autotools
  226. self._autotools = AutoToolsBuildEnvironment(self, win_bash=os_info.is_windows)
  227. self._autotools.libs = []
  228. yes_no = lambda v: "yes" if v else "no"
  229. conf_args = [
  230. "--enable-shared={}".format(yes_no(self.options.shared)),
  231. "--with-doc-strings={}".format(yes_no(self.options.docstrings)),
  232. "--with-pymalloc={}".format(yes_no(self.options.pymalloc)),
  233. "--with-system-expat",
  234. "--with-system-ffi",
  235. "--enable-optimizations={}".format(yes_no(self.options.optimizations)),
  236. "--with-lto={}".format(yes_no(self.options.lto)),
  237. "--with-pydebug={}".format(yes_no(self.settings.build_type == "Debug")),
  238. ]
  239. if self._is_py2:
  240. conf_args.extend([
  241. "--enable-unicode={}".format(yes_no(self.options.unicode)),
  242. ])
  243. if self._is_py3:
  244. conf_args.extend([
  245. "--with-system-libmpdec",
  246. "--with-openssl={}".format(self.deps_cpp_info["openssl"].rootpath),
  247. "--enable-loadable-sqlite-extensions={}".format(yes_no(not self.options["sqlite3"].omit_load_extension)),
  248. ])
  249. if self.settings.compiler == "intel":
  250. conf_args.extend(["--with-icc"])
  251. if get_env("CC") or self.settings.compiler != "gcc":
  252. conf_args.append("--without-gcc")
  253. if self.options.with_tkinter:
  254. tcltk_includes = []
  255. tcltk_libs = []
  256. # FIXME: collect using some conan util (https://github.com/conan-io/conan/issues/7656)
  257. for dep in ("tcl", "tk", "zlib"):
  258. tcltk_includes += ["-I{}".format(d) for d in self.deps_cpp_info[dep].include_paths]
  259. tcltk_libs += ["-l{}".format(lib) for lib in self.deps_cpp_info[dep].libs]
  260. if self.settings.os == "Linux" and not self.options["tk"].shared:
  261. # FIXME: use info from xorg.components (x11, xscrnsaver)
  262. tcltk_libs.extend(["-l{}".format(lib) for lib in ("X11", "Xss")])
  263. conf_args.extend([
  264. "--with-tcltk-includes={}".format(" ".join(tcltk_includes)),
  265. "--with-tcltk-libs={}".format(" ".join(tcltk_libs)),
  266. ])
  267. if self.settings.os in ("Linux", "FreeBSD"):
  268. # Building _testembed fails due to missing pthread/rt symbols
  269. self._autotools.link_flags.append("-lpthread")
  270.  
  271. build = None
  272. if cross_building(self) and not cross_building(self, skip_x64_x86=True):
  273. # Building from x86_64 to x86 is not a "real" cross build, so set build == host
  274. build = get_gnu_triplet(str(self.settings.os), str(self.settings.arch), str(self.settings.compiler))
  275. self._autotools.configure(args=conf_args, configure_dir=self._source_subfolder, build=build)
  276. return self._autotools
  277.  
  278. def _patch_sources(self):
  279. for patch in self.conan_data.get("patches",{}).get(self.version, []):
  280. patch(**patch)
  281. if self._is_py3 and Version(self._version_number_only) < "3.10":
  282. replace_in_file(os.path.join(self._source_subfolder, "setup.py"),
  283. ":libmpdec.so.2", "mpdec")
  284. if self.settings.compiler == "msvc":
  285. runtime_library = {
  286. "MT": "MultiThreaded",
  287. "MTd": "MultiThreadedDebug",
  288. "MD": "MultiThreadedDLL",
  289. "MDd": "MultiThreadedDebugDLL",
  290. }[str(self.settings.compiler.runtime)]
  291. self.output.info("Patching runtime")
  292. replace_in_file(os.path.join(self._source_subfolder, "PCbuild", "pyproject.props"),
  293. "MultiThreadedDLL", runtime_library)
  294. replace_in_file(os.path.join(self._source_subfolder, "PCbuild", "pyproject.props"),
  295. "MultiThreadedDebugDLL", runtime_library)
  296.  
  297. # Remove vendored packages
  298. rmdir(os.path.join(self._source_subfolder, "Modules", "_decimal", "libmpdec"))
  299. rmdir(os.path.join(self._source_subfolder, "Modules", "expat"))
  300.  
  301. if self.options.get_safe("with_curses", False):
  302. # FIXME: this will link to ALL libraries of ncurses. Only need to link to ncurses(w) (+ eventually tinfo)
  303. replace_in_file(os.path.join(self._source_subfolder, "setup.py"),
  304. "curses_libs = ",
  305. "curses_libs = {} #".format(repr(self.deps_cpp_info["ncurses"].libs + self.deps_cpp_info["ncurses"].system_libs)))
  306.  
  307. # Enable static MSVC cpython
  308. if not self.options.shared:
  309. replace_in_file(os.path.join(self._source_subfolder, "PCbuild", "pythoncore.vcxproj"),
  310. "<PreprocessorDefinitions>","<PreprocessorDefinitions>Py_NO_BUILD_SHARED;")
  311. replace_in_file(os.path.join(self._source_subfolder, "PCbuild", "pythoncore.vcxproj"),
  312. "Py_ENABLE_SHARED", "Py_NO_ENABLE_SHARED")
  313. replace_in_file(os.path.join(self._source_subfolder, "PCbuild", "pythoncore.vcxproj"),
  314. "DynamicLibrary", "StaticLibrary")
  315.  
  316. replace_in_file(os.path.join(self._source_subfolder, "PCbuild", "python.vcxproj"),
  317. "<Link>", "<Link><AdditionalDependencies>shlwapi.lib;ws2_32.lib;pathcch.lib;version.lib;%(AdditionalDependencies)</AdditionalDependencies>")
  318. replace_in_file(os.path.join(self._source_subfolder, "PCbuild", "python.vcxproj"),
  319. "<PreprocessorDefinitions>", "<PreprocessorDefinitions>Py_NO_ENABLE_SHARED;")
  320.  
  321. replace_in_file(os.path.join(self._source_subfolder, "PCbuild", "pythonw.vcxproj"),
  322. "<Link>", "<Link><AdditionalDependencies>shlwapi.lib;ws2_32.lib;pathcch.lib;version.lib;%(AdditionalDependencies)</AdditionalDependencies>")
  323. replace_in_file(os.path.join(self._source_subfolder, "PCbuild", "pythonw.vcxproj"),
  324. "<ItemDefinitionGroup>", "<ItemDefinitionGroup><ClCompile><PreprocessorDefinitions>Py_NO_ENABLE_SHARED;%(PreprocessorDefinitions)</PreprocessorDefinitions></ClCompile>")
  325.  
  326. def _upgrade_single_project_file(self, project_file):
  327. """
  328. `devenv /upgrade <project.vcxproj>` will upgrade *ALL* projects referenced by the project.
  329. By temporarily moving the solution project, only one project is upgraded
  330. This is needed for static cpython or for disabled optional dependencies (e.g. tkinter=False)
  331. Restore it afterwards because it is needed to build some targets.
  332. """
  333. rename(os.path.join(self._source_subfolder, "PCbuild", "pcbuild.sln"),
  334. os.path.join(self._source_subfolder, "PCbuild", "pcbuild.sln.bak"))
  335. rename(os.path.join(self._source_subfolder, "PCbuild", "pcbuild.proj"),
  336. os.path.join(self._source_subfolder, "PCbuild", "pcbuild.proj.bak"))
  337. with vcvars(self.settings):
  338. self.run("devenv \"{}\" /upgrade".format(project_file), run_environment=True)
  339. rename(os.path.join(self._source_subfolder, "PCbuild", "pcbuild.sln.bak"),
  340. os.path.join(self._source_subfolder, "PCbuild", "pcbuild.sln"))
  341. rename(os.path.join(self._source_subfolder, "PCbuild", "pcbuild.proj.bak"),
  342. os.path.join(self._source_subfolder, "PCbuild", "pcbuild.proj"))
  343.  
  344. @property
  345. def _solution_projects(self):
  346. if self.options.shared:
  347. solution_path = os.path.join(self._source_subfolder, "PCbuild", "pcbuild.sln")
  348. projects = set(m.group(1) for m in re.finditer("\"([^\"]+)\\.vcxproj\"", open(solution_path).read()))
  349.  
  350. def project_build(name):
  351. if os.path.basename(name) in self._msvc_discarded_projects:
  352. return False
  353. if "test" in name:
  354. return False
  355. return True
  356.  
  357. def sort_importance(key):
  358. importance = (
  359. "pythoncore", # The python library MUST be built first. All modules and executables depend on it
  360. "python", # Build the python executable next (for convenience, when debugging)
  361. )
  362. try:
  363. return importance.index(key)
  364. except ValueError:
  365. return len(importance)
  366.  
  367. projects = sorted((p for p in projects if project_build(p)), key=sort_importance)
  368. return projects
  369. else:
  370. return "pythoncore", "python", "pythonw"
  371.  
  372. @property
  373. def _msvc_discarded_projects(self):
  374. discarded = {"python_uwp", "pythonw_uwp"}
  375. if not self.options.with_bz2:
  376. discarded.add("bz2")
  377. if not self.options.with_sqlite3:
  378. discarded.add("_sqlite3")
  379. if not self.options.with_tkinter:
  380. discarded.add("_tkinter")
  381. if self._is_py2:
  382. # Python 2 Visual Studio (msvc) projects NOT to build
  383. discarded = discarded.union({"bdist_wininst", "libeay", "ssleay", "sqlite3", "tcl", "tk", "tix"})
  384. if not self.options.with_bsddb:
  385. discarded.add("_bsddb")
  386. elif self._is_py3:
  387. discarded = discarded.union({"bdist_wininst", "liblzma", "openssl", "sqlite3", "xxlimited"})
  388. if not self.options.with_lzma:
  389. discarded.add("_lzma")
  390. return discarded
  391.  
  392. @property
  393. def _msvc_archs(self):
  394. archs = {
  395. "x86": "Win32",
  396. "x86_64": "x64",
  397. }
  398. if Version(self._version_number_only) >= "3.8":
  399. archs.update({
  400. "armv7": "ARM",
  401. "armv8_32": "ARM",
  402. "armv8": "ARM64",
  403. })
  404. return archs
  405.  
  406. def _msvc_build(self):
  407. msbuild = MSBuild(self)
  408. msbuild_properties = {
  409. "IncludeExternals": "true",
  410. }
  411. projects = self._solution_projects
  412. self.output.info("Building {} Visual Studio (msvc) projects: {}".format(len(projects), projects))
  413.  
  414. with no_op():
  415. for project_i, project in enumerate(projects, 1):
  416. self.output.info("[{}/{}] Building project '{}'...".format(project_i, len(projects), project))
  417. project_file = os.path.join(self._source_subfolder, "PCbuild", project + ".vcxproj")
  418. self._upgrade_single_project_file(project_file)
  419. msbuild.build(project_file, upgrade_project=False, build_type="Debug" if self.settings.build_type == "Debug" else "Release",
  420. platforms=self._msvc_archs, properties=msbuild_properties)
  421.  
  422. def build(self):
  423. # FIXME: these checks belong in validate, but the versions of dependencies are not available there yet
  424. if self._supports_modules:
  425. if Version(self._version_number_only) < "3.8.0":
  426. if Version(self.deps_cpp_info["mpdecimal"].version) >= "2.5.0":
  427. raise ConanInvalidConfiguration("cpython versions lesser then 3.8.0 require a mpdecimal lesser then 2.5.0")
  428. elif Version(self._version_number_only) >= "3.9.0":
  429. if Version(self.deps_cpp_info["mpdecimal"].version) < "2.5.0":
  430. raise ConanInvalidConfiguration("cpython 3.9.0 (and newer) requires (at least) mpdecimal 2.5.0")
  431.  
  432. if self._with_libffi:
  433. if Version(self.deps_cpp_info["libffi"].version) >= "3.3" and self.settings.compiler == "msvc" and "d" in str(self.settings.compiler.runtime):
  434. raise ConanInvalidConfiguration("libffi versions >= 3.3 cause 'read access violations' when using a debug runtime (MTd/MDd)")
  435.  
  436. self._patch_sources()
  437. if self.settings.compiler == "msvc":
  438. self._msvc_build()
  439. else:
  440. autotools = self._configure_autotools()
  441. autotools.make()
  442.  
  443. @property
  444. def _msvc_artifacts_path(self):
  445. build_subdir_lut = {
  446. "x86_64": "amd64",
  447. "x86": "win32",
  448. }
  449. if Version(self._version_number_only) >= "3.8":
  450. build_subdir_lut.update({
  451. "armv7": "arm32",
  452. "armv8_32": "arm32",
  453. "armv8": "arm64",
  454. })
  455. return os.path.join(self._source_subfolder, "PCbuild", build_subdir_lut[str(self.settings.arch)])
  456.  
  457. @property
  458. def _msvc_install_subprefix(self):
  459. return "bin"
  460.  
  461. def _copy_essential_dlls(self):
  462. if self.settings.compiler == "msvc":
  463. # Until MSVC builds support cross building, copy dll's of essential (shared) dependencies to python binary location.
  464. # These dll's are required when running the layout tool using the newly built python executable.
  465. dest_path = os.path.join(self.build_folder, self._msvc_artifacts_path)
  466. if self._with_libffi:
  467. for bin_path in self.deps_cpp_info["libffi"].bin_paths:
  468. self.copy("*.dll", src=bin_path, dst=dest_path)
  469. for bin_path in self.deps_cpp_info["expat"].bin_paths:
  470. self.copy("*.dll", src=bin_path, dst=dest_path)
  471. for bin_path in self.deps_cpp_info["zlib"].bin_paths:
  472. self.copy("*.dll", src=bin_path, dst=dest_path)
  473.  
  474. def _msvc_package_layout(self):
  475. self._copy_essential_dlls()
  476. install_prefix = os.path.join(self.package_folder, self._msvc_install_subprefix)
  477. mkdir(install_prefix)
  478. build_path = self._msvc_artifacts_path
  479. infix = "_d" if self.settings.build_type == "Debug" else ""
  480. # FIXME: if cross building, use a build python executable here
  481. python_built = os.path.join(build_path, "python{}.exe".format(infix))
  482. layout_args = [
  483. os.path.join(self._source_subfolder, "PC", "layout", "main.py"),
  484. "-v",
  485. "-s", self._source_subfolder,
  486. "-b", build_path,
  487. "--copy", install_prefix,
  488. "-p",
  489. "--include-pip",
  490. "--include-venv",
  491. "--include-dev",
  492. ]
  493. if self.options.with_tkinter:
  494. layout_args.append("--include-tcltk")
  495. if self.settings.build_type == "Debug":
  496. layout_args.append("-d")
  497. python_args = " ".join("\"{}\"".format(a) for a in layout_args)
  498. self.run("{} {}".format(python_built, python_args), run_environment=True)
  499.  
  500. rmdir(os.path.join(self.package_folder, "bin", "tcl"))
  501.  
  502. for file in os.listdir(install_prefix):
  503. if re.match("vcruntime.*", file):
  504. os.unlink(os.path.join(install_prefix, file))
  505. continue
  506. os.unlink(os.path.join(install_prefix, "LICENSE.txt"))
  507. for file in os.listdir(os.path.join(install_prefix, "libs")):
  508. if not re.match("python.*", file):
  509. os.unlink(os.path.join(install_prefix, "libs", file))
  510.  
  511. def _msvc_package_copy(self):
  512. build_path = self._msvc_artifacts_path
  513. infix = "_d" if self.settings.build_type == "Debug" else ""
  514. self.copy("*.exe", src=build_path, dst=os.path.join(self.package_folder, self._msvc_install_subprefix))
  515. self.copy("*.dll", src=build_path, dst=os.path.join(self.package_folder, self._msvc_install_subprefix))
  516. self.copy("*.pyd", src=build_path, dst=os.path.join(self.package_folder, self._msvc_install_subprefix, "DLLs"))
  517. self.copy("python{}{}.lib".format(self._version_suffix, infix), src=build_path, dst=os.path.join(self.package_folder, self._msvc_install_subprefix, "libs"))
  518. self.copy("*", src=os.path.join(self._source_subfolder, "Include"), dst=os.path.join(self.package_folder, self._msvc_install_subprefix, "include"))
  519. self.copy("pyconfig.h", src=os.path.join(self._source_subfolder, "PC"), dst=os.path.join(self.package_folder, self._msvc_install_subprefix, "include"))
  520. self.copy("*.py", src=os.path.join(self._source_subfolder, "lib"), dst=os.path.join(self.package_folder, self._msvc_install_subprefix, "Lib"))
  521. rmdir(os.path.join(self.package_folder, self._msvc_install_subprefix, "Lib", "test"))
  522.  
  523. packages = {}
  524. get_name_version = lambda fn: fn.split(".", 2)[:2]
  525. whldir = os.path.join(self._source_subfolder, "Lib", "ensurepip", "_bundled")
  526. for fn in filter(lambda n: n.endswith(".whl"), os.listdir(whldir)):
  527. name, version = get_name_version(fn)
  528. add = True
  529. if name in packages:
  530. pname, pversion = get_name_version(packages[name])
  531. add = Version(version) > Version(pversion)
  532. if add:
  533. packages[name] = fn
  534. for fname in packages.values():
  535. unzip(filename=os.path.join(whldir, fname), destination=os.path.join(self.package_folder, "bin", "Lib", "site-packages"))
  536.  
  537. 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("\\", "/")),
  538. run_environment=True)
  539.  
  540. def package(self):
  541. self.copy("LICENSE", src=self._source_subfolder, dst="licenses")
  542. if self.settings.compiler == "msvc":
  543. if self._is_py2 or not self.options.shared:
  544. self._msvc_package_copy()
  545. else:
  546. self._msvc_package_layout()
  547. remove_files_by_mask(os.path.join(self.package_folder, "bin"), "vcruntime*")
  548. else:
  549. autotools = self._configure_autotools()
  550. autotools.install()
  551. rmdir(os.path.join(self.package_folder, "lib", "pkgconfig"))
  552. rmdir(os.path.join(self.package_folder, "share"))
  553.  
  554. # Rewrite shebangs of python scripts
  555. for filename in os.listdir(os.path.join(self.package_folder, "bin")):
  556. filepath = os.path.join(self.package_folder, "bin", filename)
  557. if not os.path.isfile(filepath):
  558. continue
  559. if os.path.islink(filepath):
  560. continue
  561. with open(filepath, "rb") as fn:
  562. firstline = fn.readline(1024)
  563. if not(firstline.startswith(b"#!") and b"/python" in firstline and b"/bin/sh" not in firstline):
  564. continue
  565. text = fn.read()
  566. self.output.info("Rewriting shebang of {}".format(filename))
  567. with open(filepath, "wb") as fn:
  568. fn.write(textwrap.dedent("""\
  569. #!/bin/sh
  570. ''':'
  571. __file__="$0"
  572. while [ -L "$__file__" ]; do
  573. __file__="$(dirname "$__file__")/$(readlink "$__file__")"
  574. done
  575. exec "$(dirname "$__file__")/python{}" "$0" "$@"
  576. '''
  577. """.format(self._version_suffix)).encode())
  578. fn.write(text)
  579.  
  580. if not os.path.exists(self._cpython_symlink):
  581. os.symlink("python{}".format(self._version_suffix), self._cpython_symlink)
  582. self._fix_install_name()
  583.  
  584. @property
  585. def _cpython_symlink(self):
  586. symlink = os.path.join(self.package_folder, "bin", "python")
  587. if self.settings.os == "Windows":
  588. symlink += ".exe"
  589. return symlink
  590.  
  591. @property
  592. def _cpython_interpreter_name(self):
  593. if self.settings.compiler == "msvc":
  594. suffix = ""
  595. else:
  596. suffix = self._version_suffix
  597. python = "python{}".format(suffix)
  598. if self.settings.compiler == "msvc":
  599. if self.settings.build_type == "Debug":
  600. python += "_d"
  601. if self.settings.os == "Windows":
  602. python += ".exe"
  603. return python
  604.  
  605. @property
  606. def _cpython_interpreter_path(self):
  607. return os.path.join(self.package_folder, "bin", self._cpython_interpreter_name)
  608.  
  609. @property
  610. def _abi_suffix(self):
  611. res = ""
  612. if self._is_py3:
  613. if self.settings.build_type == "Debug":
  614. res += "d"
  615. if Version(self._version_number_only) < "3.8":
  616. if self.options.get_safe("pymalloc", False):
  617. res += "m"
  618. return res
  619.  
  620. @property
  621. def _lib_name(self):
  622. if self.settings.compiler == "msvc":
  623. if self.settings.build_type == "Debug":
  624. lib_ext = "_d"
  625. else:
  626. lib_ext = ""
  627. else:
  628. lib_ext = self._abi_suffix + (".dll.a" if self.options.shared and self.settings.os == "Windows" else "")
  629. return "python{}{}".format(self._version_suffix, lib_ext)
  630.  
  631. def _fix_install_name(self):
  632. if is_apple_os(self) and self.options.shared:
  633. buffer = StringIO()
  634. python = os.path.join(self.package_folder, "bin", "python")
  635. self.run('otool -L "%s"' % python, output=buffer)
  636. lines = buffer.getvalue().strip().split('\n')[1:]
  637. for line in lines:
  638. library = line.split()[0]
  639. if library.startswith(self.package_folder):
  640. new = library.replace(self.package_folder, "@executable_path/..")
  641. self.output.info("patching {}, replace {} with {}".format(python, library, new))
  642. self.run("install_name_tool -change {} {} {}".format(library, new, python))
  643.  
  644. def package_info(self):
  645. # FIXME: conan components Python::Interpreter component, need a target type
  646. # self.cpp_info.names["cmake_find_package"] = "Python"
  647. # self.cpp_info.names["cmake_find_package_multi"] = "Python"
  648. # FIXME: conan components need to generate multiple .pc files (python2, python-27)
  649.  
  650. py_version = Version(self._version_number_only)
  651. # python component: "Build a C extension for Python"
  652. if self.settings.compiler == "msvc":
  653. self.cpp_info.components["python"].includedirs = [os.path.join(self._msvc_install_subprefix, "include")]
  654. libdir = os.path.join(self._msvc_install_subprefix, "libs")
  655. else:
  656. self.cpp_info.components["python"].includedirs.append(os.path.join("include", "python{}{}".format(self._version_suffix, self._abi_suffix)))
  657. libdir = "lib"
  658. if self.options.shared:
  659. self.cpp_info.components["python"].defines.append("Py_ENABLE_SHARED")
  660. else:
  661. self.cpp_info.components["python"].defines.append("Py_NO_ENABLE_SHARED")
  662. if self.settings.os == "Linux":
  663. self.cpp_info.components["python"].system_libs.extend(["dl", "m", "pthread", "util"])
  664. elif self.settings.os == "Windows":
  665. self.cpp_info.components["python"].system_libs.extend(["pathcch", "shlwapi", "version", "ws2_32"])
  666. self.cpp_info.components["python"].requires = ["zlib::zlib"]
  667. if self.settings.os != "Windows":
  668. self.cpp_info.components["python"].requires.append("libxcrypt::libxcrypt")
  669. self.cpp_info.components["python"].names["pkg_config"] = "python-{}.{}".format(py_version.major, py_version.minor)
  670. self.cpp_info.components["python"].libdirs = []
  671.  
  672. self.cpp_info.components["_python_copy"].names["pkg_config"] = "python{}".format(py_version.major)
  673. self.cpp_info.components["_python_copy"].requires = ["python"]
  674. self.cpp_info.components["_python_copy"].libdirs = []
  675.  
  676. # embed component: "Embed Python into an application"
  677. self.cpp_info.components["embed"].libs = [self._lib_name]
  678. self.cpp_info.components["embed"].libdirs = [libdir]
  679. self.cpp_info.components["embed"].names["pkg_config"] = "python-{}.{}-embed".format(py_version.major, py_version.minor)
  680. self.cpp_info.components["embed"].requires = ["python"]
  681.  
  682. self.cpp_info.components["_embed_copy"].requires = ["embed"]
  683. self.cpp_info.components["_embed_copy"].names["pkg_config"] = ["python{}-embed".format(py_version.major)]
  684. self.cpp_info.components["_embed_copy"].libdirs = []
  685.  
  686. if self._supports_modules:
  687. # hidden components: the C extensions of python are built as dynamically loaded shared libraries.
  688. # C extensions or applications with an embedded Python should not need to link to them..
  689. self.cpp_info.components["_hidden"].requires = [
  690. "openssl::openssl",
  691. "expat::expat",
  692. "mpdecimal::mpdecimal",
  693. ]
  694. if self._with_libffi:
  695. self.cpp_info.components["_hidden"].requires.append("libffi::libffi")
  696. if self.settings.os != "Windows":
  697. if not is_apple_os(self):
  698. self.cpp_info.components["_hidden"].requires.append("libuuid::libuuid")
  699. self.cpp_info.components["_hidden"].requires.append("libxcrypt::libxcrypt")
  700. if self.options.with_bz2:
  701. self.cpp_info.components["_hidden"].requires.append("bzip2::bzip2")
  702. if self.options.get_safe("with_gdbm", False):
  703. self.cpp_info.components["_hidden"].requires.append("gdbm::gdbm")
  704. if self.options.with_sqlite3:
  705. self.cpp_info.components["_hidden"].requires.append("sqlite3::sqlite3")
  706. if self.options.get_safe("with_curses", False):
  707. self.cpp_info.components["_hidden"].requires.append("ncurses::ncurses")
  708. if self.options.get_safe("with_bsddb"):
  709. self.cpp_info.components["_hidden"].requires.append("libdb::libdb")
  710. if self.options.get_safe("with_lzma"):
  711. self.cpp_info.components["_hidden"].requires.append("xz_utils::xz_utils")
  712. if self.options.get_safe("with_tkinter"):
  713. self.cpp_info.components["_hidden"].requires.append("tk::tk")
  714. self.cpp_info.components["_hidden"].libdirs = []
  715.  
  716. if self.options.env_vars:
  717. bindir = os.path.join(self.package_folder, "bin")
  718. self.output.info("Appending PATH environment variable: {}".format(bindir))
  719. self.env_info.PATH.append(bindir)
  720.  
  721. python = self._cpython_interpreter_path
  722. self.user_info.python = python
  723. if self.options.env_vars:
  724. self.output.info("Setting PYTHON environment variable: {}".format(python))
  725. self.env_info.PYTHON = python
  726.  
  727. if self.settings.compiler == "msvc":
  728. pythonhome = os.path.join(self.package_folder, "bin")
  729. elif is_apple_os(self):
  730. pythonhome = self.package_folder
  731. else:
  732. version = Version(self._version_number_only)
  733. pythonhome = os.path.join(self.package_folder, "lib", "python{}.{}".format(version.major, version.minor))
  734. self.user_info.pythonhome = pythonhome
  735.  
  736. pythonhome_required = self.settings.compiler == "msvc" or is_apple_os(self)
  737. self.user_info.module_requires_pythonhome = pythonhome_required
  738.  
  739. if self.settings.compiler == "msvc":
  740. if self.options.env_vars:
  741. self.output.info("Setting PYTHONHOME environment variable: {}".format(pythonhome))
  742. self.env_info.PYTHONHOME = pythonhome
  743.  
  744. if self._is_py2:
  745. python_root = ""
  746. else:
  747. python_root = self.package_folder
  748. if self.options.env_vars:
  749. self.output.info("Setting PYTHON_ROOT environment variable: {}".format(python_root))
  750. self.env_info.PYTHON_ROOT = python_root
  751. self.user_info.python_root = python_root
  752.  
Advertisement
Add Comment
Please, Sign In to add comment