(root)/
Python-3.11.7/
Tools/
freeze/
test/
freeze.py
       1  import os
       2  import os.path
       3  import shlex
       4  import shutil
       5  import subprocess
       6  import sysconfig
       7  from test import support
       8  
       9  
      10  def get_python_source_dir():
      11      src_dir = sysconfig.get_config_var('abs_srcdir')
      12      if not src_dir:
      13          src_dir = sysconfig.get_config_var('srcdir')
      14      return os.path.abspath(src_dir)
      15  
      16  
      17  TESTS_DIR = os.path.dirname(__file__)
      18  TOOL_ROOT = os.path.dirname(TESTS_DIR)
      19  SRCDIR = get_python_source_dir()
      20  
      21  MAKE = shutil.which('make')
      22  FREEZE = os.path.join(TOOL_ROOT, 'freeze.py')
      23  OUTDIR = os.path.join(TESTS_DIR, 'outdir')
      24  
      25  
      26  class ESC[4;38;5;81mUnsupportedError(ESC[4;38;5;149mException):
      27      """The operation isn't supported."""
      28  
      29  
      30  def _run_quiet(cmd, *, cwd=None):
      31      if cwd:
      32          print('+', 'cd', cwd, flush=True)
      33      print('+', shlex.join(cmd), flush=True)
      34      try:
      35          return subprocess.run(
      36              cmd,
      37              cwd=cwd,
      38              capture_output=True,
      39              text=True,
      40              check=True,
      41          )
      42      except subprocess.CalledProcessError as err:
      43          # Don't be quiet if things fail
      44          print(f"{err.__class__.__name__}: {err}")
      45          print("--- STDOUT ---")
      46          print(err.stdout)
      47          print("--- STDERR ---")
      48          print(err.stderr)
      49          print("---- END ----")
      50          raise
      51  
      52  
      53  def _run_stdout(cmd):
      54      proc = _run_quiet(cmd)
      55      return proc.stdout.strip()
      56  
      57  
      58  def find_opt(args, name):
      59      opt = f'--{name}'
      60      optstart = f'{opt}='
      61      for i, arg in enumerate(args):
      62          if arg == opt or arg.startswith(optstart):
      63              return i
      64      return -1
      65  
      66  
      67  def ensure_opt(args, name, value):
      68      opt = f'--{name}'
      69      pos = find_opt(args, name)
      70      if value is None:
      71          if pos < 0:
      72              args.append(opt)
      73          else:
      74              args[pos] = opt
      75      elif pos < 0:
      76          args.extend([opt, value])
      77      else:
      78          arg = args[pos]
      79          if arg == opt:
      80              if pos == len(args) - 1:
      81                  raise NotImplementedError((args, opt))
      82              args[pos + 1] = value
      83          else:
      84              args[pos] = f'{opt}={value}'
      85  
      86  
      87  def copy_source_tree(newroot, oldroot):
      88      print(f'copying the source tree from {oldroot} to {newroot}...')
      89      if os.path.exists(newroot):
      90          if newroot == SRCDIR:
      91              raise Exception('this probably isn\'t what you wanted')
      92          shutil.rmtree(newroot)
      93  
      94      shutil.copytree(oldroot, newroot, ignore=support.copy_python_src_ignore)
      95      if os.path.exists(os.path.join(newroot, 'Makefile')):
      96          # Out-of-tree builds require a clean srcdir. "make clean" keeps
      97          # the "python" program, so use "make distclean" instead.
      98          _run_quiet([MAKE, 'distclean'], cwd=newroot)
      99  
     100  
     101  ##################################
     102  # freezing
     103  
     104  def prepare(script=None, outdir=None):
     105      print()
     106      print("cwd:", os.getcwd())
     107  
     108      if not outdir:
     109          outdir = OUTDIR
     110      os.makedirs(outdir, exist_ok=True)
     111  
     112      # Write the script to disk.
     113      if script:
     114          scriptfile = os.path.join(outdir, 'app.py')
     115          print(f'creating the script to be frozen at {scriptfile}')
     116          with open(scriptfile, 'w', encoding='utf-8') as outfile:
     117              outfile.write(script)
     118  
     119      # Make a copy of the repo to avoid affecting the current build
     120      # (e.g. changing PREFIX).
     121      srcdir = os.path.join(outdir, 'cpython')
     122      copy_source_tree(srcdir, SRCDIR)
     123  
     124      # We use an out-of-tree build (instead of srcdir).
     125      builddir = os.path.join(outdir, 'python-build')
     126      os.makedirs(builddir, exist_ok=True)
     127  
     128      # Run configure.
     129      print(f'configuring python in {builddir}...')
     130      config_args = shlex.split(sysconfig.get_config_var('CONFIG_ARGS') or '')
     131      cmd = [os.path.join(srcdir, 'configure'), *config_args]
     132      ensure_opt(cmd, 'cache-file', os.path.join(outdir, 'python-config.cache'))
     133      prefix = os.path.join(outdir, 'python-installation')
     134      ensure_opt(cmd, 'prefix', prefix)
     135      _run_quiet(cmd, cwd=builddir)
     136  
     137      if not MAKE:
     138          raise UnsupportedError('make')
     139  
     140      cores = os.cpu_count()
     141      if cores and cores >= 3:
     142          # this test is most often run as part of the whole suite with a lot
     143          # of other tests running in parallel, from 1-2 vCPU systems up to
     144          # people's NNN core beasts. Don't attempt to use it all.
     145          jobs = cores * 2 // 3
     146          parallel = f'-j{jobs}'
     147      else:
     148          parallel = '-j2'
     149  
     150      # Build python.
     151      print(f'building python {parallel=} in {builddir}...')
     152      _run_quiet([MAKE, parallel], cwd=builddir)
     153  
     154      # Install the build.
     155      print(f'installing python into {prefix}...')
     156      _run_quiet([MAKE, 'install'], cwd=builddir)
     157      python = os.path.join(prefix, 'bin', 'python3')
     158  
     159      return outdir, scriptfile, python
     160  
     161  
     162  def freeze(python, scriptfile, outdir):
     163      if not MAKE:
     164          raise UnsupportedError('make')
     165  
     166      print(f'freezing {scriptfile}...')
     167      os.makedirs(outdir, exist_ok=True)
     168      # Use -E to ignore PYTHONSAFEPATH
     169      _run_quiet([python, '-E', FREEZE, '-o', outdir, scriptfile], cwd=outdir)
     170      _run_quiet([MAKE], cwd=os.path.dirname(scriptfile))
     171  
     172      name = os.path.basename(scriptfile).rpartition('.')[0]
     173      executable = os.path.join(outdir, name)
     174      return executable
     175  
     176  
     177  def run(executable):
     178      return _run_stdout([executable])