text: #
  
  
  """ Core functionalities """
  
  
  import distutils
  import os
  import subprocess
  import tempfile
  import venv
  import zipapp
  
  import setuptools
  
  
  class ZappException(Exception):
      """Base exception class"""
  
  
  class BdistWheelMissing(ZappException):
      """The 'setuptools' command 'bdist_wheel' can not be found"""
  
  
  class _EnvBuilder(venv.EnvBuilder):
  
      def __init__(self, *args, **kwargs):
          self.context = None
          super().__init__(*args, **kwargs)
  
      def post_setup(self, context):
          """ Overrride """
          self.context = context
  
  
  def _venv_create(venv_dir):
      venv_builder = _EnvBuilder(with_pip=True)
      venv_builder.create(venv_dir)
      venv_context = venv_builder.context
      return venv_context
  
  
  def _pip_install(
          venv_context,
          requirements=None,
          requirements_txts=None,
          target_dir=None,
  ):
      command = [
          venv_context.env_exe,
          '-m', 'pip',
          'install',
      ]
      if target_dir:
          command.extend(['--target', target_dir])
      if requirements:
          command.extend(requirements)
      if requirements_txts:
          for requirements_txt in requirements_txts:
              command.extend(['--requirement', requirements_txt])
      subprocess.check_call(command)
  
  
  def _install_to_dir(
          target_dir,
          requirements=None,
          requirements_txts=None,
  ):
      """ Use pip to install the requirements into a specific directory
      """
      # pip is not usable as a library, so it is much simpler and safer to just
      # create a virtual environment and run a pip process from there
      with tempfile.TemporaryDirectory() as venv_dir:
          venv_context = _venv_create(venv_dir)
          _pip_install(venv_context, requirements=['wheel'])
          _pip_install(
              venv_context,
              requirements=requirements,
              requirements_txts=requirements_txts,
              target_dir=target_dir,
          )
  
  
  def _create_zipapp_archive(source_dir, entry_point, output_file):
      zipapp.create_archive(
          source_dir,
          interpreter='/usr/bin/env python3',
          main=entry_point,
          target=output_file,
      )
  
  
  def build_zapp(
          output_file,
          entry_point,
          requirements=None,
          requirements_txts=None,
  ):
      """ Build a zapp binary archive
      """
      with tempfile.TemporaryDirectory() as install_dir:
          if requirements or requirements_txts:
              _install_to_dir(
                  install_dir,
                  requirements=requirements,
                  requirements_txts=requirements_txts,
              )
          _create_zipapp_archive(install_dir, entry_point, output_file)
  
  
  class bdist_zapp(setuptools.Command):  # pylint: disable=invalid-name
      """ Custom 'setuptools' command to build a zipapp
      """
  
      description = "create a zipapp archive"
  
      user_options = [
          (
              'entry-point=',
              'e',
              "entry point for the generated 'zipapp'",
          ),
          (
              'bdist-dir=',
              'b',
              "temporary directory for creating the distribution",
          ),
          (
              'dist-dir=',
              'd',
              "directory to put final built distributions in",
          ),
          (
              'keep-temp',
              'k',
              "keep the pseudo-installation tree around after creating the "
              "distribution archive",
          ),
      ]
  
      boolean_options = [
          'keep-temp',
      ]
  
      def __init__(self, *args, **kwargs):
          self.bdist_dir = None
          self.dist_dir = None
          self.entry_point = None
          self.keep_temp = 0
          super().__init__(*args, **kwargs)
  
      def initialize_options(self):
          """ Override """
          self.bdist_dir = None
          self.dist_dir = None
          self.entry_point = None
  
      def finalize_options(self):
          """ Override """
          if self.bdist_dir is None:
              bdist_base = self.get_finalized_command('bdist').bdist_base
              self.bdist_dir = os.path.join(bdist_base, 'zapp')
  
          self.set_undefined_options(
              'bdist',
              (
                  'dist_dir',
                  'dist_dir',
              ),
          )
  
      def run(self):
          """ Override """
          if not self.entry_point:
              raise distutils.errors.DistutilsOptionError(
                  "no entry point selected",
              )
          output_file = os.path.join(
              self.dist_dir,
              '{}-{}.pyz'.format(
                  self.distribution.get_name(),
                  self.distribution.get_version(),
              ),
          )
          distutils.log.info(
              "Building zipapp '{}' with entry point '{}'".format(
                  output_file,
                  self.entry_point,
              ),
          )
  
          with tempfile.TemporaryDirectory() as temp_dir:
              dist_cmd = self.reinitialize_command('bdist_wheel')
              dist_cmd.bdist_dir = os.path.join(temp_dir, 'build')
              dist_cmd.dist_dir = os.path.join(temp_dir, 'dist')
              self.run_command('bdist_wheel')
              dist_file = None
              for dist in dist_cmd.distribution.dist_files:
                  if dist[0] == 'bdist_wheel':
                      dist_file = dist[2]
              if not dist_file:
                  raise BdistWheelMissing()
              _install_to_dir(self.bdist_dir, requirements=[dist_file])
              self.mkpath(self.dist_dir)
              _create_zipapp_archive(
                  self.bdist_dir,
                  self.entry_point,
                  output_file,
              )
              if not self.keep_temp:
                  distutils.dir_util.remove_tree(
                      self.bdist_dir,
                      dry_run=self.dry_run,
                  )
  
  
  # EOF
  