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

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)