core.py 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277
  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 json
  15. import logging
  16. import os
  17. import platform
  18. import subprocess
  19. import time
  20. import click
  21. import semantic_version
  22. from pioinstaller import __version__, exception, home, util
  23. log = logging.getLogger(__name__)
  24. PIO_CORE_DEVELOP_URL = "https://github.com/platformio/platformio/archive/develop.zip"
  25. UPDATE_INTERVAL = 60 * 60 * 24 * 3 # 3 days
  26. def get_core_dir():
  27. if os.getenv("PLATFORMIO_CORE_DIR"):
  28. return os.getenv("PLATFORMIO_CORE_DIR")
  29. core_dir = os.path.join(util.expanduser("~"), ".platformio")
  30. if not util.IS_WINDOWS:
  31. return core_dir
  32. win_core_dir = os.path.splitdrive(core_dir)[0] + "\\.platformio"
  33. if os.path.isdir(win_core_dir):
  34. return win_core_dir
  35. try:
  36. if util.has_non_ascii_char(core_dir):
  37. os.makedirs(win_core_dir)
  38. with open(os.path.join(win_core_dir, "file.tmp"), "w") as fp:
  39. fp.write("test")
  40. os.remove(os.path.join(win_core_dir, "file.tmp"))
  41. return win_core_dir
  42. except: # pylint:disable=bare-except
  43. pass
  44. return core_dir
  45. def get_cache_dir(path=None):
  46. core_dir = path or get_core_dir()
  47. path = os.path.join(core_dir, ".cache")
  48. if not os.path.isdir(path):
  49. os.makedirs(path)
  50. return path
  51. def install_platformio_core(shutdown_piohome=True, develop=False, ignore_pythons=None):
  52. # pylint: disable=bad-option-value, import-outside-toplevel, unused-import, import-error, unused-variable, cyclic-import
  53. from pioinstaller import penv
  54. if shutdown_piohome:
  55. home.shutdown_pio_home_servers()
  56. penv_dir = penv.create_core_penv(ignore_pythons=ignore_pythons)
  57. python_exe = os.path.join(
  58. penv.get_penv_bin_dir(penv_dir), "python.exe" if util.IS_WINDOWS else "python"
  59. )
  60. command = [python_exe, "-m", "pip", "install", "-U"]
  61. if develop:
  62. click.echo("Installing a development version of PlatformIO Core")
  63. command.append(PIO_CORE_DEVELOP_URL)
  64. else:
  65. click.echo("Installing PlatformIO Core")
  66. command.append("platformio")
  67. try:
  68. subprocess.check_call(command)
  69. except Exception as e: # pylint:disable=broad-except
  70. error = str(e)
  71. if util.IS_WINDOWS:
  72. error = (
  73. "If you have antivirus/firewall/defender software in a system,"
  74. " try to disable it for a while.\n %s" % error
  75. )
  76. raise exception.PIOInstallerException(
  77. "Could not install PlatformIO Core: %s" % error
  78. )
  79. platformio_exe = os.path.join(
  80. penv.get_penv_bin_dir(penv_dir),
  81. "platformio.exe" if util.IS_WINDOWS else "platformio",
  82. )
  83. try:
  84. home.install_pio_home(platformio_exe)
  85. except Exception as e: # pylint:disable=broad-except
  86. log.debug(e)
  87. click.secho(
  88. "\nPlatformIO Core has been successfully installed into an isolated environment `%s`!\n"
  89. % penv_dir,
  90. fg="green",
  91. )
  92. click.secho("The full path to `platformio.exe` is `%s`" % platformio_exe, fg="cyan")
  93. # pylint:disable=line-too-long
  94. click.secho(
  95. """
  96. If you need an access to `platformio.exe` from other applications, please install Shell Commands
  97. (add PlatformIO Core binary directory `%s` to the system environment PATH variable):
  98. See https://docs.platformio.org/page/installation.html#install-shell-commands
  99. """
  100. % penv.get_penv_bin_dir(penv_dir),
  101. fg="cyan",
  102. )
  103. return True
  104. def check(dev=False, auto_upgrade=False, version_spec=None):
  105. # pylint: disable=bad-option-value, import-outside-toplevel, unused-import, import-error, unused-variable, cyclic-import
  106. from pioinstaller import penv
  107. platformio_exe = os.path.join(
  108. penv.get_penv_bin_dir(), "platformio.exe" if util.IS_WINDOWS else "platformio",
  109. )
  110. python_exe = os.path.join(
  111. penv.get_penv_bin_dir(), "python.exe" if util.IS_WINDOWS else "python"
  112. )
  113. result = {}
  114. if not os.path.isfile(platformio_exe):
  115. raise exception.InvalidPlatformIOCore(
  116. "PlatformIO executable not found in `%s`" % penv.get_penv_bin_dir()
  117. )
  118. if not os.path.isfile(os.path.join(penv.get_penv_dir(), "state.json")):
  119. raise exception.InvalidPlatformIOCore(
  120. "Could not found state.json file in `%s`"
  121. % os.path.join(penv.get_penv_dir(), "state.json")
  122. )
  123. try:
  124. result.update(fetch_python_state(python_exe))
  125. except subprocess.CalledProcessError as e:
  126. error = e.output.decode()
  127. raise exception.InvalidPlatformIOCore(
  128. "Could not import PlatformIO module. Error: %s" % error
  129. )
  130. piocore_version = convert_version(result.get("core_version"))
  131. dev = dev or bool(piocore_version.prerelease if piocore_version else False)
  132. result.update(
  133. {
  134. "core_dir": get_core_dir(),
  135. "cache_dir": get_cache_dir(),
  136. "penv_dir": penv.get_penv_dir(),
  137. "penv_bin_dir": penv.get_penv_bin_dir(),
  138. "platformio_exe": platformio_exe,
  139. "installer_version": __version__,
  140. "python_exe": python_exe,
  141. "system": util.get_systype(),
  142. "is_develop_core": dev,
  143. }
  144. )
  145. if version_spec:
  146. try:
  147. if piocore_version not in semantic_version.Spec(version_spec):
  148. raise exception.InvalidPlatformIOCore(
  149. "PlatformIO Core version %s does not match version requirements %s."
  150. % (str(piocore_version), version_spec)
  151. )
  152. except ValueError:
  153. click.secho(
  154. "Invalid version requirements format: %s. "
  155. "More about Semantic Versioning: https://semver.org/" % version_spec
  156. )
  157. with open(os.path.join(penv.get_penv_dir(), "state.json")) as fp:
  158. penv_state = json.load(fp)
  159. if penv_state.get("platform") != platform.platform(terse=True):
  160. raise exception.InvalidPlatformIOCore(
  161. "PlatformIO installed using another platform `%s`. Your platform: %s"
  162. % (penv_state.get("platform"), platform.platform(terse=True))
  163. )
  164. try:
  165. subprocess.check_output([platformio_exe, "--version"], stderr=subprocess.STDOUT)
  166. except subprocess.CalledProcessError as e:
  167. error = e.output.decode()
  168. raise exception.InvalidPlatformIOCore(
  169. "Could not run `%s --version`.\nError: %s" % (platformio_exe, str(error))
  170. )
  171. if not auto_upgrade:
  172. return result
  173. time_now = int(round(time.time()))
  174. last_piocore_version_check = penv_state.get("last_piocore_version_check")
  175. if (
  176. last_piocore_version_check
  177. and (time_now - int(last_piocore_version_check)) < UPDATE_INTERVAL
  178. ):
  179. return result
  180. with open(os.path.join(penv.get_penv_dir(), "state.json"), "w") as fp:
  181. penv_state["last_piocore_version_check"] = time_now
  182. json.dump(penv_state, fp)
  183. if not last_piocore_version_check:
  184. return result
  185. upgrade_core(platformio_exe, dev)
  186. try:
  187. result.update(fetch_python_state(python_exe))
  188. except: # pylint:disable=bare-except
  189. raise exception.InvalidPlatformIOCore("Could not import PlatformIO module")
  190. return result
  191. def fetch_python_state(python_exe):
  192. code = """import platform
  193. import json
  194. import platformio
  195. state = {
  196. "core_version": platformio.__version__,
  197. "python_version": platform.python_version()
  198. }
  199. print(json.dumps(state))
  200. """
  201. state = subprocess.check_output(
  202. [python_exe, "-c", code,], stderr=subprocess.STDOUT,
  203. )
  204. return json.loads(state.decode())
  205. def convert_version(version):
  206. try:
  207. return semantic_version.Version(util.pepver_to_semver(version))
  208. except: # pylint:disable=bare-except
  209. return None
  210. def upgrade_core(platformio_exe, dev=False):
  211. command = [platformio_exe, "upgrade"]
  212. if dev:
  213. command.append("--dev")
  214. try:
  215. subprocess.check_output(
  216. command, stderr=subprocess.PIPE,
  217. )
  218. return True
  219. except Exception as e: # pylint:disable=broad-except
  220. raise exception.PIOInstallerException(
  221. "Could not upgrade PlatformIO Core: %s" % str(e)
  222. )
  223. def dump_state(target, state):
  224. assert isinstance(target, str)
  225. if os.path.isdir(target):
  226. target = os.path.join(target, "get-platformio-core-state.json")
  227. if not os.path.isdir(os.path.dirname(target)):
  228. os.makedirs(os.path.dirname(target))
  229. with open(target, "w") as fp:
  230. json.dump(state, fp)