File: btInit.py

package info (click to toggle)
brewtarget 4.2.1-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 35,468 kB
  • sloc: cpp: 56,958; xml: 19,031; python: 1,266; sh: 183; makefile: 11
file content (168 lines) | stat: -rwxr-xr-x 9,055 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
#!/usr/bin/env python3
#-----------------------------------------------------------------------------------------------------------------------
# scripts/btInit.py is part of Brewtarget, and is copyright the following authors 2022-2024:
#   • Matt Young <mfsy@yahoo.com>
#
# Brewtarget is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later
# version.
#
# Brewtarget is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with this program.  If not, see
# <http://www.gnu.org/licenses/>.
#-----------------------------------------------------------------------------------------------------------------------

#-----------------------------------------------------------------------------------------------------------------------
# This Python script is intended to be invoked by the `bt` bash script in the parent directory.  See comments in that
# script for why.
#
# Here we do some general set-up, including ensuring that a suitable Python virtual environment (venv) exists, before
# returning to the `bt` script to switch into that environment and invoke the main Python build tool script
# (buildTool.py)
#-----------------------------------------------------------------------------------------------------------------------

#-----------------------------------------------------------------------------------------------------------------------
# Python built-in modules we use
#-----------------------------------------------------------------------------------------------------------------------
import os
import platform
import shutil
import subprocess
import sys

#-----------------------------------------------------------------------------------------------------------------------
# Our own modules
#-----------------------------------------------------------------------------------------------------------------------
import btUtils

log = btUtils.getLogger()


#
# First step is to ensure we have the minimum-required Python packages, including pip, are installed at system level.
#
# Prior to Python 3.12, setuptools is required to create and use virtual environments.
#
match platform.system():
   case 'Linux':
      # We don't want to run a sudo command every time the script is invoked, so check whether it's necessary
      exe_pip = shutil.which('pip3')
      ranUpdate = False
      if (exe_pip is None or exe_pip == ''):
         log.info('Need to install pip')
         btUtils.abortOnRunFail(subprocess.run(['sudo', 'apt', 'update']))
         ranUpdate = True
         btUtils.abortOnRunFail(subprocess.run(['sudo', 'apt', 'install', 'python3-pip']))
      # This is a bit clunky, but it's the simplest way to see if setuptools is already installed.  (Alternatively we
      # could run `pip3 list` and search for setuptools in the outpout.)
      foundSetupTools = False
      try:
         import setuptools
      except ImportError:
         log.info('Need to install setuptools')
      else:
         foundSetupTools = True
      if (not foundSetupTools):
         if (not ranUpdate):
            btUtils.abortOnRunFail(subprocess.run(['sudo', 'apt', 'update']))
            ranUpdate = True
         btUtils.abortOnRunFail(subprocess.run(['sudo', 'apt', 'install', 'python3-setuptools']))
      # It's a similar process for ensurepip, which is needed by venv below
      foundEnsurepip = False
      try:
         import ensurepip
      except ImportError:
         log.info('Need to install ensurepip')
      else:
         log.info('Found ensurepip')
         foundEnsurepip = True
      if (not foundEnsurepip):
         if (not ranUpdate):
            btUtils.abortOnRunFail(subprocess.run(['sudo', 'apt', 'update']))
            ranUpdate = True
         # Yes, I know it's confusing that we have to install a package called venv to ensure that the venv command
         # below doesn't complain about ensurepip not being present.  We're just doing what the error messages tell us
         # to.
         btUtils.abortOnRunFail(subprocess.run(['sudo', 'apt', 'install', 'python3-venv']))
   case 'Windows':
      #
      # In the past, we were able to install pip via Python, with the following code:
      #
      #    # https://docs.python.org/3/library/sys.html#sys.executable says sys.executable is '"the absolute path of the
      #    # executable binary for the Python interpreter, on systems where this makes sense".
      #    log.info(
      #       'Attempting to ensure latest version of pip is installed via  ' + sys.executable + ' -m ensurepip --upgrade'
      #    )
      #    btUtils.abortOnRunFail(subprocess.run([sys.executable, '-m', 'ensurepip', '--upgrade']))
      #
      # However, as of 2024-11, this gives "error: externally-managed-environment" and a direction "To install Python
      # packages system-wide, try 'pacman -S $MINGW_PACKAGE_PREFIX-python-xyz', where xyz is the package you are trying
      # to install."  So now we do that instead.  (Note that MINGW_PACKAGE_PREFIX will normally be set to
      # "mingw-w64-x86_64".)  As in buildTool.py, we need to specify '--overwrite' options otherwise we'll get "error:
      # failed to commit transaction (conflicting files)".
      #
      log.info('Install pip (' + os.environ['MINGW_PACKAGE_PREFIX'] + '-python-pip) via pacman')
      btUtils.abortOnRunFail(
         subprocess.run(['pacman', '-S',
                         '--noconfirm',
                         '--overwrite', '*python*',
                         '--overwrite', '*pip*',
                         os.environ['MINGW_PACKAGE_PREFIX'] + '-python-pip'])
      )
      #
      # Similarly, in the past, we were able to install setuptools as follows:
      #
      #    # See comment in scripts/buildTool.py about why we have to run pip via Python rather than just invoking pip
      #    # directly eg via `shutil.which('pip3')`.
      #    log.info('python -m pip install setuptools')
      #    btUtils.abortOnRunFail(subprocess.run([sys.executable, '-m', 'pip', 'install', 'setuptools']))
      #
      # But, as of 2024-11, this gives an error "No module named pip.__main__; 'pip' is a package and cannot be directly
      # executed".  So now we install via pacman instead.
      #
      log.info('Install setuptools (' + os.environ['MINGW_PACKAGE_PREFIX'] + '-python-setuptools) via pacman')
      btUtils.abortOnRunFail(
         subprocess.run(['pacman', '-S',
                         '--noconfirm',
#                         '--overwrite', '*python*',
#                         '--overwrite', '*pip*',
                         os.environ['MINGW_PACKAGE_PREFIX'] + '-python-setuptools'])
      )
   case 'Darwin':
      # Assuming it was Homebrew that installed Python, then, according to https://docs.brew.sh/Homebrew-and-Python,
      # it bundles various packages, including pip.  Since Python version 3.12, Homebrew marks itself as package manager
      # for the Python packages it bundles, so it's an error to try to install or update them via Python.
      log.info('Assuming pip is already up-to-date; installing python-setuptools')
      btUtils.abortOnRunFail(subprocess.run(['brew', 'install', 'python-setuptools']))

   case _:
      log.critical('Unrecognised platform: ' + platform.system())
      exit(1)

exe_python = shutil.which('python3')
log.info('sys.version: ' + sys.version + '; exe_python: ' + exe_python + '; ' + sys.executable)

#
# At this point we should have enough installed to set up a virtual environment.  In principle, it doesn't matter if the
# virtual environment already exists, as we are only using it to run the scripts/buildTool.py script.  In practice, life
# is a lot easier if we always start with a new virtual environment.  Partly this is because it makes debugging the
# scripts easier.  But more importantly, if there are old versions of Python sitting around in a previously-used venv,
# then some the paths won't get set up correctly, and we won't be able to find modules we install in the venv.  There
# will be multiple site-packages directories (one for each version of Python in the venv) but none of them will be in
# the search path for packages.
#
# Fortunately, venv can clear any existing environment for us with the '--clear' parameter
#
# Once running inside the virtual environment, any packages we need there can be installed directly in the venv with
# Python and Pip.
#
dir_venv = btUtils.getBaseDir().joinpath('.venv')
log.info('Create new Python virtual environment in ' + dir_venv.as_posix())
btUtils.abortOnRunFail(
   subprocess.run([sys.executable, '-m', 'venv', '--clear', dir_venv.as_posix()])
)

# Control now returns to the bt bash script in the parent directory