python.py 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  1. # Copyright (c) 2014-present PlatformIO <contact@platformio.org>
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License");
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain a copy of the License at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License.
  14. import glob
  15. import logging
  16. import os
  17. import platform
  18. import subprocess
  19. import sys
  20. import tempfile
  21. import click
  22. from pioinstaller import exception, util
  23. log = logging.getLogger(__name__)
  24. PORTABLE_PYTHONS = {
  25. "windows_x86": (
  26. "https://dl.bintray.com/platformio/dl-misc/"
  27. "python-portable-windows_x86-3.7.7.tar.gz"
  28. ),
  29. "windows_amd64": (
  30. "https://dl.bintray.com/platformio/dl-misc/"
  31. "python-portable-windows_amd64-3.7.7.tar.gz"
  32. ),
  33. "darwin_x86_64": (
  34. "https://dl.bintray.com/platformio/dl-misc/"
  35. "python-portable-darwin_x86_64-3.8.4.tar.gz"
  36. ),
  37. }
  38. def is_conda():
  39. return any(
  40. [
  41. os.path.exists(os.path.join(sys.prefix, "conda-meta")),
  42. # (os.getenv("CONDA_PREFIX") or os.getenv("CONDA_DEFAULT_ENV")),
  43. "anaconda" in sys.executable.lower(),
  44. "miniconda" in sys.executable.lower(),
  45. "continuum analytics" in sys.version.lower(),
  46. "conda" in sys.version.lower(),
  47. ]
  48. )
  49. def is_portable():
  50. try:
  51. __import__("winpython")
  52. return True
  53. except: # pylint:disable=bare-except
  54. return False
  55. def fetch_portable_python(dst):
  56. url = PORTABLE_PYTHONS.get(util.get_systype())
  57. if not url:
  58. log.debug("There is no portable Python for %s", util.get_systype())
  59. return None
  60. try:
  61. log.debug("Downloading portable python...")
  62. archive_path = util.download_file(
  63. url, os.path.join(os.path.join(dst, ".cache", "tmp"), os.path.basename(url))
  64. )
  65. python_dir = os.path.join(dst, "python3")
  66. util.safe_remove_dir(python_dir)
  67. util.safe_create_dir(python_dir, raise_exception=True)
  68. log.debug("Unpacking portable python...")
  69. util.unpack_archive(archive_path, python_dir)
  70. if util.IS_WINDOWS:
  71. return os.path.join(python_dir, "python.exe")
  72. return os.path.join(python_dir, "bin", "python3")
  73. except: # pylint:disable=bare-except
  74. log.debug("Could not download portable python")
  75. def check():
  76. # platform check
  77. if sys.platform == "cygwin":
  78. raise exception.IncompatiblePythonError("Unsupported cygwin platform")
  79. # version check
  80. if not (
  81. sys.version_info >= (2, 7, 9) and sys.version_info < (3,)
  82. ) and not sys.version_info >= (3, 5):
  83. raise exception.IncompatiblePythonError(
  84. "Unsupported python version: %s. "
  85. "Supported version: >= 2.7.9 and < 3, or >= 3.5"
  86. % platform.python_version(),
  87. )
  88. # conda check
  89. if is_conda():
  90. raise exception.IncompatiblePythonError("Conda is not supported")
  91. try:
  92. __import__("distutils.command")
  93. except ImportError:
  94. raise exception.DistutilsNotFound()
  95. # portable Python 3 for macOS is not compatible with macOS < 10.13
  96. # https://github.com/platformio/platformio-core-installer/issues/70
  97. if util.IS_MACOS and sys.version_info >= (3, 5):
  98. with tempfile.NamedTemporaryFile() as tmpfile:
  99. os.utime(tmpfile.name)
  100. if not util.IS_WINDOWS:
  101. return True
  102. # windows check
  103. if any(s in util.get_pythonexe_path().lower() for s in ("msys", "mingw", "emacs")):
  104. raise exception.IncompatiblePythonError(
  105. "Unsupported environments: msys, mingw, emacs >> %s"
  106. % util.get_pythonexe_path(),
  107. )
  108. try:
  109. assert os.path.isdir(os.path.join(sys.prefix, "Scripts")) or (
  110. sys.version_info >= (3, 5) and __import__("venv")
  111. )
  112. except (AssertionError, ImportError):
  113. raise exception.IncompatiblePythonError(
  114. "Unsupported python without 'Scripts' folder and 'venv' module"
  115. )
  116. return True
  117. def find_compatible_pythons(ignore_pythons=None): # pylint: disable=too-many-branches
  118. ignore_list = []
  119. for p in ignore_pythons or []:
  120. ignore_list.extend(glob.glob(p))
  121. exenames = ["python3", "python", "python2"]
  122. if util.IS_WINDOWS:
  123. exenames = ["%s.exe" % item for item in exenames]
  124. log.debug("Current environment PATH %s", os.getenv("PATH"))
  125. candidates = []
  126. for exe in exenames:
  127. for path in os.getenv("PATH").split(os.pathsep):
  128. if not os.path.isfile(os.path.join(path, exe)):
  129. continue
  130. candidates.append(os.path.join(path, exe))
  131. if sys.executable not in candidates:
  132. if sys.version_info >= (3,):
  133. candidates.insert(0, sys.executable)
  134. else:
  135. candidates.append(sys.executable)
  136. result = []
  137. for item in candidates:
  138. if item in ignore_list:
  139. continue
  140. log.debug("Checking a Python candidate %s", item)
  141. try:
  142. output = subprocess.check_output(
  143. [
  144. item,
  145. util.get_installer_script(),
  146. "--no-shutdown-piohome",
  147. "check",
  148. "python",
  149. ],
  150. stderr=subprocess.STDOUT,
  151. )
  152. result.append(item)
  153. try:
  154. log.debug(output.decode().strip())
  155. except UnicodeDecodeError:
  156. pass
  157. except subprocess.CalledProcessError as e: # pylint:disable=bare-except
  158. try:
  159. error = e.output.decode()
  160. log.debug(error)
  161. except UnicodeDecodeError:
  162. pass
  163. error = error or ""
  164. if "Could not find distutils module" in error:
  165. # pylint:disable=line-too-long
  166. raise click.ClickException(
  167. """Can not install PlatformIO Core due to a missed `distutils` package in your Python installation.
  168. Please install this package manually using the OS package manager. For example:
  169. $ apt-get install python3-venv
  170. (MAY require administrator access `sudo`)""",
  171. )
  172. return result