docs: def run_pymodules_install(ctx, modules, project_dir, ignore_setup_py=False):
      """ This function will take care of all non-recipe things, by:
  
          1. Processing them from --requirements (the modules argument)
             and installing them
  
          2. Installing the user project/app itself via setup.py if
             ignore_setup_py=True
  
      """
  
      info('*** PYTHON PACKAGE / PROJECT INSTALL STAGE ***')
      modules = list(filter(ctx.not_has_package, modules))
  
      # Bail out if no python deps and no setup.py to process:
      if not modules and (
              ignore_setup_py or
              not project_has_setup_py(project_dir)
              ):
          info('No Python modules and no setup.py to process, skipping')
          return
  
      # Output messages about what we're going to do:
      if modules:
          info('The requirements ({}) don\'t have recipes, attempting to '
               'install them with pip'.format(', '.join(modules)))
          info('If this fails, it may mean that the module has compiled '
               'components and needs a recipe.')
      if project_has_setup_py(project_dir) and not ignore_setup_py:
          info('Will process project install, if it fails then the '
               'project may not be compatible for Android install.')
  
      venv = sh.Command(ctx.virtualenv)
      with current_directory(join(ctx.build_dir)):
          shprint(venv,
                  '--python=python{}'.format(
                      ctx.python_recipe.major_minor_version_string.
                      partition(".")[0]
                      ),
                  'venv'
                 )
  
          # Prepare base environment and upgrade pip:
          base_env = copy.copy(os.environ)
          base_env["PYTHONPATH"] = ctx.get_site_packages_dir()
          info('Upgrade pip to latest version')
          shprint(sh.bash, '-c', (
              "source venv/bin/activate && pip install -U pip"
          ), _env=copy.copy(base_env))
  
          # Install Cython in case modules need it to build:
          info('Install Cython in case one of the modules needs it to build')
          shprint(sh.bash, '-c', (
              "venv/bin/pip install Cython"
          ), _env=copy.copy(base_env))
  
          # Get environment variables for build (with CC/compiler set):
          standard_recipe = CythonRecipe()
          standard_recipe.ctx = ctx
          # (note: following line enables explicit -lpython... linker options)
          standard_recipe.call_hostpython_via_targetpython = False
          recipe_env = standard_recipe.get_recipe_env(ctx.archs[0])
          env = copy.copy(base_env)
          env.update(recipe_env)
  
          # Make sure our build package dir is available, and the virtualenv
          # site packages come FIRST (so the proper pip version is used):
          env["PYTHONPATH"] += ":" + ctx.get_site_packages_dir()
          env["PYTHONPATH"] = os.path.abspath(join(
              ctx.build_dir, "venv", "lib",
              "python" + ctx.python_recipe.major_minor_version_string,
              "site-packages")) + ":" + env["PYTHONPATH"]
  
          # Install the manually specified requirements first:
          if not modules:
              info('There are no Python modules to install, skipping')
          else:
              info('Creating a requirements.txt file for the Python modules')
              with open('requirements.txt', 'w') as fileh:
                  for module in modules:
                      key = 'VERSION_' + module
                      if key in environ:
                          line = '{}=={}\n'.format(module, environ[key])
                      else:
                          line = '{}\n'.format(module)
                      fileh.write(line)
  
              info('Installing Python modules with pip')
              info('IF THIS FAILS, THE MODULES MAY NEED A RECIPE. '
                   'A reason for this is often modules compiling '
                   'native code that is unaware of Android cross-compilation '
                   'and does not work without additional '
                   'changes / workarounds.')
  
              shprint(sh.bash, '-c', (
                  "venv/bin/pip " +
                  "install -v --target '{0}' --no-deps -r requirements.txt"
              ).format(ctx.get_site_packages_dir().replace("'", "'\"'\"'")),
                      _env=copy.copy(env))
  
          # Afterwards, run setup.py if present:
          if project_has_setup_py(project_dir) and not ignore_setup_py:
              with current_directory(project_dir):
                  info('got setup.py or similar, running project install. ' +
                       '(disable this behavior with --ignore-setup-py)')
  
                  # Compute & output the constraints we will use:
                  info('Contents that will be used for constraints.txt:')
                  constraints = subprocess.check_output([
                      join(
                          ctx.build_dir, "venv", "bin", "pip"
                      ),
                      "freeze"
                  ], env=copy.copy(env))
                  try:
                      constraints = constraints.decode("utf-8", "replace")
                  except AttributeError:
                      pass
                  info(constraints)
  
                  # Make sure all packages found are fixed in version
                  # by writing a constraint file, to avoid recipes being
                  # upgraded & reinstalled:
                  with open('constraints.txt', 'wb') as fileh:
                      fileh.write(constraints.encode("utf-8", "replace"))
  
                  info('Populating venv\'s site-packages with '
                       'ctx.get_site_packages_dir()...')
  
                  # Copy dist contents into site-packages for discovery.
                  # Why this is needed:
                  # --target is somewhat evil and messes with discovery of
                  # packages in PYTHONPATH if that also includes the target
                  # folder. So we need to use the regular virtualenv
                  # site-packages folder instead.
                  # Reference:
                  # https://github.com/pypa/pip/issues/6223
                  ctx_site_packages_dir = os.path.normpath(
                      os.path.abspath(ctx.get_site_packages_dir())
                  )
                  venv_site_packages_dir = os.path.normpath(os.path.join(
                      ctx.build_dir, "venv", "lib", [
                          f for f in os.listdir(os.path.join(
                              ctx.build_dir, "venv", "lib"
                          )) if f.startswith("python")
                      ][0], "site-packages"
                  ))
                  copied_over_contents = []
                  for f in os.listdir(ctx_site_packages_dir):
                      full_path = os.path.join(ctx_site_packages_dir, f)
                      if not os.path.exists(os.path.join(
                                  venv_site_packages_dir, f
                              )):
                          if os.path.isdir(full_path):
                              shutil.copytree(full_path, os.path.join(
                                  venv_site_packages_dir, f
                              ))
                          else:
                              shutil.copy2(full_path, os.path.join(
                                  venv_site_packages_dir, f
                              ))
                          copied_over_contents.append(f)
  
                  # Get listing of virtualenv's site-packages, to see the
                  # newly added things afterwards & copy them back into
                  # the distribution folder / build context site-packages:
                  previous_venv_contents = os.listdir(venv_site_packages_dir)
  
                  # Actually run setup.py:
                  info('Launching package install...')
                  shprint(sh.bash, '-c', (
                      "'" + join(
                          ctx.build_dir, "venv", "bin", "pip"
                      ).replace("'", "'\"'\"'") + "' " +
                      "install -c constraints.txt -v ."
                  ).format(ctx.get_site_packages_dir().replace("'", "'\"'\"'")),
                          _env=copy.copy(env))
  
                  # Go over all new additions and copy them back:
                  info('Copying additions resulting from setup.py back ' +
                       'into ctx.get_site_packages_dir()...')
                  new_venv_additions = []
                  for f in (set(os.listdir(venv_site_packages_dir)) -
                            set(previous_venv_contents)):
                      new_venv_additions.append(f)
                      full_path = os.path.join(venv_site_packages_dir, f)
                      if os.path.isdir(full_path):
                          shutil.copytree(full_path, os.path.join(
                              ctx_site_packages_dir, f
                          ))
                      else:
                          shutil.copy2(full_path, os.path.join(
                              ctx_site_packages_dir, f
                          ))
  
                  # Undo all the changes we did to the venv-site packages:
                  info('Reverting additions to virtualenv\'s site-packages...')
                  for f in set(copied_over_contents + new_venv_additions):
                      full_path = os.path.join(venv_site_packages_dir, f)
                      if os.path.isdir(full_path):
                          shutil.rmtree(full_path)
                      else:
                          os.remove(full_path)
          elif not ignore_setup_py:
              info("No setup.py found in project directory: " +
                   str(project_dir)
                  )
  
          # Strip object files after potential Cython or native code builds:
          standard_recipe.strip_object_files(ctx.archs[0], env,
                                             build_dir=ctx.build_dir)