<commit_before>"""
Functions for configuring/compiling/installing/packaging source distributions.
"""

import posixpath

from fabric.api import task, run, cd, settings, abort

from ..files import directory, exists
from ..environment import has_binary
from . import package


def _run_step(name, sentinels, forcing):
    sentinel = sentinels.get(name, None)
    sentinel_present = exists(sentinel) if sentinel else False
    return forcing[name] or not sentinel_present


# TODO: maybe break this up into fewer, richer args? e.g. objects or dicts.
# Pro: smaller API
# Con: requires boilerplate for simple stuff
# TODO: great candidate for "collection of tasks" class based approach.
# TODO: also re: each step now looking very similar, at LEAST loop.
@task
def build(name, version, iteration, workdir, uri, enable=(), with_=(),
    flagstr="", dependencies=(), sentinels=None, force="", runner=run):
    """
    Build a package from source, using fpm.

    Keyword arguments:

    * ``name``: Shorthand project/package name. Used both for determining what
      file to download, and what the unpacked directory is expected to be.
      Example: ``"php"``, as found in ``"php-5.4.0.tar.gz"``.
    * ``version``: Upstream release version. Example: ``"5.4.0"``.
    * ``iteration``: Your own version for this build. Usually you'll want to
      increment this every time you're ready to actually distribute the package
      for real. Example: ``3``.
    * ``workdir``: Base working directory. Sources will be unpacked inside of
      it in their own directories, and the staging directory is created as a
      child of this directory as well. Example: ``"/opt/build"``.
    * ``uri``: Download URI. May (and probably should) contain interpolation
      syntax using the following keys:
      * ``name``: same as kwarg
      * ``version``: same as kwarg
      * ``package_name``: same as ``"%(name)s-%(version)s"``

      E.g. ``"http://php.net/get/%(package_name)s.tar.bz2/from/us.php.net/mirror"``
      or ``"http://my.mirror/packages/%(name)s/%(name)_%(version)s.tgz"``

    * ``enable`` and ``with_``: shorthand for adding configure flags. E.g.
      ``build(..., enable=['foo', 'bar'], with_=['biz'])`` results in adding
      the flags ``--enable-foo``, ``--enable-bar`` and ``--with-biz``.
    * ``flagstr``: Arbitrary additional configure flags to add, e.g.
      ``"--no-bad-stuff --with-blah=/usr/blah"``.
    * ``dependencies``: Package names to ensure are installed prior to
      building, using ``package()``.
    * ``sentinels``: Keys are stages, values should be paths to files whose
      existence indicates a successful run of that stage. For each given stage,
      if ``force`` is not set (see below) and its ``sentinel`` value is
      non-empty and the file exists, that stage will automatically be
      skipped.

      Per-stage details:

      * ``"configure"``: Default value for this key is ``"Makefile"``. The
        other stages have no default values.
      * ``"stage"``: The path given here is considered relative to the staging
        directory, not the unpacked source directory.

    May force a reset to one of the following stages by specifying ``force``:

    * ``"get"``: downloading the sources
    * ``"configure"``: ./configure --flags
    * ``"build"``: make
    * ``"stage"``: make install into staging directory
    * ``"package"``: staging directory => package file

    Stages imply later ones, so force=stage will re-stage *and* re-package.
    Forcing will override any sentinel checks.
    """
    package_name = "%s-%s" % (name, version)
    context = {'name': name, 'version': version, 'package_name': package_name}
    uri = uri % context
    source = posixpath.join(workdir, package_name)
    stage = posixpath.join(workdir, 'stage')

    # Make sure we have fpm or the ability to install it
    if not has_binary("fpm"):
        gems = " install Rubygems and then" if not has_binary("gem") else ""
        abort("No fpm found! Please%s 'gem install fpm'." % gems)

    # Default to empty dict (can't use in sig, dicts are mutable)
    sentinels = sentinels or {}
    # Fill in empty configure value
    if 'configure' not in sentinels:
        sentinels['configure'] = "Makefile"

    # Dependencies
    package(*dependencies)

    # Handle forcing
    forcing = {}
    force = force.split(',')
    reset = False
    for key in ('get', 'configure', 'build', 'stage', 'package'):
        if key in force:
            reset = True
        forcing[key] = reset

    # Directory
    # TODO: make the chmod an option if users want a "wide open" build dir for
    # manual login user poking around; default to not bothering.
    directory(workdir, mode="777", runner=runner)
    # Download+unpack
    if forcing['get'] or not exists(source):
        with cd(workdir):
            # TODO: make obtainment process overrideable for users who prefer
            # wget, scp, etc
            flag = ""
            if uri.endswith((".tar.gz", ".tgz")):
                flag = "z"
            elif uri.endswith((".tar.bz2",)):
                flag = "j"
            runner("curl -L \"%s\" | tar x%sf -" % (uri, flag))

    # Configure
    with_flags = map(lambda x: "--with-%s" % x, with_)
    enable_flags = map(lambda x: "--enable-%s" % x, enable)
    all_flags = " ".join([flagstr] + with_flags + enable_flags)
    with cd(source):
        if _run_step('configure', sentinels, forcing):
            print "++ No Makefile found, running ./configure..."
            # If forcing, clean up!
            if forcing['configure']:
                # TODO: make this configurable, e.g. php 'really' wants
                # distclean for max cleaning, others may too
                # TODO: ties in w/ same call down in build
                runner("make clean")
            runner("./configure %s" % all_flags)
        else:
            print "!! Skipping configure step: %r exists." % sentinels['configure']

        # Build
        if _run_step('build', sentinels, forcing):
            if forcing['build']:
                runner("make clean")
            print "++ No build sentinel or build sentinel not found, running make..."
            runner("make")
        else:
            print "!! Skipping build step: %r exists" % sentinels['build']

        # Stage
        if sentinels.get('stage', None):
            sentinels['stage'] = posixpath.join("..", stage, sentinels['stage'])
        if _run_step('stage', sentinels, forcing):
            print "++ No stage sentinel or stage sentinel not found, running make install..."
            with settings(warn_only=True):
                # Nuke if forcing -- e.g. if --prefix changed, etc.
                # (Otherwise a prefix change would leave both prefixes in the
                # stage, causing problems on actual package install.)
                if forcing['stage']:
                    runner("rm -rf %s" % stage)<commit_msg>Rejigger make-clean execution<commit_after>"""
Functions for configuring/compiling/installing/packaging source distributions.
"""

import posixpath

from fabric.api import task, run, cd, settings, abort

from ..files import directory, exists
from ..environment import has_binary
from . import package


def _run_step(name, sentinels, forcing):
    sentinel = sentinels.get(name, None)
    sentinel_present = exists(sentinel) if sentinel else False
    return forcing[name] or not sentinel_present


# TODO: maybe break this up into fewer, richer args? e.g. objects or dicts.
# Pro: smaller API
# Con: requires boilerplate for simple stuff
# TODO: great candidate for "collection of tasks" class based approach.
# TODO: also re: each step now looking very similar, at LEAST loop.
@task
def build(name, version, iteration, workdir, uri, enable=(), with_=(),
    flagstr="", dependencies=(), sentinels=None, force="", runner=run):
    """
    Build a package from source, using fpm.

    Keyword arguments:

    * ``name``: Shorthand project/package name. Used both for determining what
      file to download, and what the unpacked directory is expected to be.
      Example: ``"php"``, as found in ``"php-5.4.0.tar.gz"``.
    * ``version``: Upstream release version. Example: ``"5.4.0"``.
    * ``iteration``: Your own version for this build. Usually you'll want to
      increment this every time you're ready to actually distribute the package
      for real. Example: ``3``.
    * ``workdir``: Base working directory. Sources will be unpacked inside of
      it in their own directories, and the staging directory is created as a
      child of this directory as well. Example: ``"/opt/build"``.
    * ``uri``: Download URI. May (and probably should) contain interpolation
      syntax using the following keys:
      * ``name``: same as kwarg
      * ``version``: same as kwarg
      * ``package_name``: same as ``"%(name)s-%(version)s"``

      E.g. ``"http://php.net/get/%(package_name)s.tar.bz2/from/us.php.net/mirror"``
      or ``"http://my.mirror/packages/%(name)s/%(name)_%(version)s.tgz"``

    * ``enable`` and ``with_``: shorthand for adding configure flags. E.g.
      ``build(..., enable=['foo', 'bar'], with_=['biz'])`` results in adding
      the flags ``--enable-foo``, ``--enable-bar`` and ``--with-biz``.
    * ``flagstr``: Arbitrary additional configure flags to add, e.g.
      ``"--no-bad-stuff --with-blah=/usr/blah"``.
    * ``dependencies``: Package names to ensure are installed prior to
      building, using ``package()``.
    * ``sentinels``: Keys are stages, values should be paths to files whose
      existence indicates a successful run of that stage. For each given stage,
      if ``force`` is not set (see below) and its ``sentinel`` value is
      non-empty and the file exists, that stage will automatically be
      skipped.

      Per-stage details:

      * ``"configure"``: Default value for this key is ``"Makefile"``. The
        other stages have no default values.
      * ``"stage"``: The path given here is considered relative to the staging
        directory, not the unpacked source directory.

    May force a reset to one of the following stages by specifying ``force``:

    * ``"get"``: downloading the sources
    * ``"configure"``: ./configure --flags
    * ``"build"``: make
    * ``"stage"``: make install into staging directory
    * ``"package"``: staging directory => package file

    Stages imply later ones, so force=stage will re-stage *and* re-package.
    Forcing will override any sentinel checks.
    """
    package_name = "%s-%s" % (name, version)
    context = {'name': name, 'version': version, 'package_name': package_name}
    uri = uri % context
    source = posixpath.join(workdir, package_name)
    stage = posixpath.join(workdir, 'stage')

    # Make sure we have fpm or the ability to install it
    if not has_binary("fpm"):
        gems = " install Rubygems and then" if not has_binary("gem") else ""
        abort("No fpm found! Please%s 'gem install fpm'." % gems)

    # Default to empty dict (can't use in sig, dicts are mutable)
    sentinels = sentinels or {}
    # Fill in empty configure value
    if 'configure' not in sentinels:
        sentinels['configure'] = "Makefile"

    # Dependencies
    package(*dependencies)

    # Handle forcing
    forcing = {}
    force = force.split(',')
    reset = False
    for key in ('get', 'configure', 'build', 'stage', 'package'):
        if key in force:
            reset = True
        forcing[key] = reset

    # Directory
    # TODO: make the chmod an option if users want a "wide open" build dir for
    # manual login user poking around; default to not bothering.
    directory(workdir, mode="777", runner=runner)
    # Download+unpack
    if forcing['get'] or not exists(source):
        with cd(workdir):
            # TODO: make obtainment process overrideable for users who prefer
            # wget, scp, etc
            flag = ""
            if uri.endswith((".tar.gz", ".tgz")):
                flag = "z"
            elif uri.endswith((".tar.bz2",)):
                flag = "j"
            runner("curl -L \"%s\" | tar x%sf -" % (uri, flag))

    # Configure
    with_flags = map(lambda x: "--with-%s" % x, with_)
    enable_flags = map(lambda x: "--enable-%s" % x, enable)
    all_flags = " ".join([flagstr] + with_flags + enable_flags)
    with cd(source):
        # If forcing configure or build, clean up first. Leftover artifacts
        # from bad builds can be seriously annoying, especially if they don't
        # cause outright problems.
        if forcing['configure'] and exists('Makefile'):
            # TODO: make this configurable, e.g. php 'really' wants
            # distclean for max cleaning, others may too
            runner("make clean")
        if _run_step('configure', sentinels, forcing):
            print "++ No Makefile found, running ./configure..."
            runner("./configure %s" % all_flags)
        else:
            print "!! Skipping configure step: %r exists." % sentinels['configure']

        # Build
        if _run_step('build', sentinels, forcing):
            print "++ No build sentinel or build sentinel not found, running make..."
            runner("make")
        else:
            print "!! Skipping build step: %r exists" % sentinels['build']

        # Stage
        if sentinels.get('stage', None):
            sentinels['stage'] = posixpath.join("..", stage, sentinels['stage'])
        if _run_step('stage', sentinels, forcing):
            print "++ No stage sentinel or stage sentinel not found, running make install..."
            with settings(warn_only=True):
                # Nuke if forcing -- e.g. if --prefix changed, etc.
                # (Otherwise a prefix change would leave both prefixes in the
                # stage, causing problems on actual package install.)
                if forcing['stage']:
                    runner("rm -rf %s" % stage)
{'fraction_non_alphanumeric': 0.12889304466661872, 'fraction_numerical': 0.0017262461339279292, 'mean_word_length': 2.657984740857669, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 8, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1049587', 'n_tokens_mistral': 4051, 'n_tokens_neox': 3893, 'n_words': 1472}