quickjs-tart

quickjs-based runtime for wallet-core logic
Log | Files | Refs | README | LICENSE

min_requirements.py (5002B)


      1 """Install all the required Python packages, with the minimum Python version.
      2 """
      3 
      4 # Copyright The Mbed TLS Contributors
      5 # SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
      6 
      7 import argparse
      8 import os
      9 import re
     10 import subprocess
     11 import sys
     12 import tempfile
     13 import typing
     14 
     15 from typing import List, Optional
     16 
     17 import framework_scripts_path # pylint: disable=unused-import
     18 from mbedtls_framework import typing_util
     19 
     20 def pylint_doesn_t_notice_that_certain_types_are_used_in_annotations(
     21         _list: List[typing.Any],
     22 ) -> None:
     23     pass
     24 
     25 
     26 class Requirements:
     27     """Collect and massage Python requirements."""
     28 
     29     def __init__(self) -> None:
     30         self.requirements = [] #type: List[str]
     31 
     32     def adjust_requirement(self, req: str) -> str:
     33         """Adjust a requirement to the minimum specified version."""
     34         # allow inheritance #pylint: disable=no-self-use
     35         # If a requirement specifies a minimum version, impose that version.
     36         split_req = req.split(';', 1)
     37         split_req[0] = re.sub(r'>=|~=', r'==', split_req[0])
     38         return ';'.join(split_req)
     39 
     40     def add_file(self, filename: str) -> None:
     41         """Add requirements from the specified file.
     42 
     43         This method supports a subset of pip's requirement file syntax:
     44         * One requirement specifier per line, which is passed to
     45           `adjust_requirement`.
     46         * Comments (``#`` at the beginning of the line or after whitespace).
     47         * ``-r FILENAME`` to include another file.
     48         """
     49         for line in open(filename):
     50             line = line.strip()
     51             line = re.sub(r'(\A|\s+)#.*', r'', line)
     52             if not line:
     53                 continue
     54             m = re.match(r'-r\s+', line)
     55             if m:
     56                 nested_file = os.path.join(os.path.dirname(filename),
     57                                            line[m.end(0):])
     58                 self.add_file(nested_file)
     59                 continue
     60             self.requirements.append(self.adjust_requirement(line))
     61 
     62     def write(self, out: typing_util.Writable) -> None:
     63         """List the gathered requirements."""
     64         for req in self.requirements:
     65             out.write(req + '\n')
     66 
     67     def install(
     68             self,
     69             pip_general_options: Optional[List[str]] = None,
     70             pip_install_options: Optional[List[str]] = None,
     71     ) -> None:
     72         """Call pip to install the requirements."""
     73         if pip_general_options is None:
     74             pip_general_options = []
     75         if pip_install_options is None:
     76             pip_install_options = []
     77         with tempfile.TemporaryDirectory() as temp_dir:
     78             # This is more complicated than it needs to be for the sake
     79             # of Windows. Use a temporary file rather than the command line
     80             # to avoid quoting issues. Use a temporary directory rather
     81             # than NamedTemporaryFile because with a NamedTemporaryFile on
     82             # Windows, the subprocess can't open the file because this process
     83             # has an exclusive lock on it.
     84             req_file_name = os.path.join(temp_dir, 'requirements.txt')
     85             with open(req_file_name, 'w') as req_file:
     86                 self.write(req_file)
     87             subprocess.check_call([sys.executable, '-m', 'pip'] +
     88                                   pip_general_options +
     89                                   ['install'] + pip_install_options +
     90                                   ['-r', req_file_name])
     91 
     92 def main(default_requirement_file: str) -> None:
     93     """Command line entry point."""
     94     parser = argparse.ArgumentParser(description=__doc__)
     95     parser.add_argument('--no-act', '-n',
     96                         action='store_true',
     97                         help="Don't act, just print what will be done")
     98     parser.add_argument('--pip-install-option',
     99                         action='append', dest='pip_install_options',
    100                         help="Pass this option to pip install")
    101     parser.add_argument('--pip-option',
    102                         action='append', dest='pip_general_options',
    103                         help="Pass this general option to pip")
    104     parser.add_argument('--user',
    105                         action='append_const', dest='pip_install_options',
    106                         const='--user',
    107                         help="Install to the Python user install directory"
    108                              " (short for --pip-install-option --user)")
    109     parser.add_argument('files', nargs='*', metavar='FILE',
    110                         help="Requirement files"
    111                              " (default: {})" \
    112                              .format(default_requirement_file))
    113     options = parser.parse_args()
    114     if not options.files:
    115         options.files = [default_requirement_file]
    116     reqs = Requirements()
    117     for filename in options.files:
    118         reqs.add_file(filename)
    119     reqs.write(sys.stdout)
    120     if not options.no_act:
    121         reqs.install(pip_general_options=options.pip_general_options,
    122                      pip_install_options=options.pip_install_options)