Exemple #1
0
def _effective_debug_level(env, bundle, extra_filters=None, default=None):
    """This is a helper used both in the urls() and the build() recursions.

    It returns the debug level that this bundle, in a tree structure
    of bundles, should use. It looks at any bundle-specific ``debug``
    attribute, considers an automatic upgrade to "merge" due to filters that
    are present, and will finally use the value in the ``default`` argument,
    which in turn defaults to ``env.debug``.

    It also ensures our rule that in a bundle hierarchy, the debug level may
    only ever be lowered. Nested bundle may lower the level from ``True`` to
    ``"merge"`` to ``False``, but never in the other direction. Which makes
    sense: If a bundle is already being merged, we cannot start exposing the
    source urls a child bundle, not if the correct order should be maintained.

    And while in theory it would seem possible to switch between full-out
    production (debug=False) and ``"merge"``, the complexity there, in
    particular with view as to how certain filter types like input() and
    open() need to be applied to child bundles, is just not worth it.
    """
    if default is None:
        default = env.debug

    if bundle.debug is not None:
        level = bundle.debug
    else:
        # If bundle doesn't force a level, then the presence of filters which
        # declare they should always run puts the bundle automatically in
        # merge mode.
        filters = merge_filters(bundle.filters, extra_filters)
        level = 'merge' if select_filters(filters, True) else None

    if level is not None:
        return level
    return default
Exemple #2
0
def _effective_debug_level(env, bundle, extra_filters=None, default=None):
    """This is a helper used both in the urls() and the build() recursions.

    It returns the debug level that this bundle, in a tree structure
    of bundles, should use. It looks at any bundle-specific ``debug``
    attribute, considers an automatic upgrade to "merge" due to filters that
    are present, and will finally use the value in the ``default`` argument,
    which in turn defaults to ``env.debug``.

    It also ensures our rule that in a bundle hierarchy, the debug level may
    only ever be lowered. Nested bundle may lower the level from ``True`` to
    ``"merge"`` to ``False``, but never in the other direction. Which makes
    sense: If a bundle is already being merged, we cannot start exposing the
    source urls a child bundle, not if the correct order should be maintained.

    And while in theory it would seem possible to switch between full-out
    production (debug=False) and ``"merge"``, the complexity there, in
    particular with view as to how certain filter types like input() and
    open() need to be applied to child bundles, is just not worth it.
    """
    if default is None:
        default = env.debug

    if bundle.debug is not None:
        level = bundle.debug
    else:
        # If bundle doesn't force a level, then the presence of filters which
        # declare they should always run puts the bundle automatically in
        # merge mode.
        filters = merge_filters(bundle.filters, extra_filters)
        level = 'merge' if select_filters(filters, True) else None

    if level is not None:
        # The new level must be lower than the older one. We do not thrown an
        # error if this is NOT the case, but silently ignore it. This is so
        # that a debug=True can be used to overwrite auto_debug_upgrade.
        # Otherwise debug=True would always fail.
        if cmp_debug_levels(default, level) > 0:
            return level
    return default
Exemple #3
0
    def _merge_and_apply(self,
                         env,
                         output,
                         force,
                         parent_debug=None,
                         parent_filters=[],
                         extra_filters=[],
                         disable_cache=None):
        """Internal recursive build method.

        ``parent_debug`` is the debug setting used by the parent bundle. This
        is not necessarily ``bundle.debug``, but rather what the calling method
        in the recursion tree is actually using.

        ``parent_filters`` are what the parent passes along, for us to be
        applied as input filters. Like ``parent_debug``, it is a collection of
        the filters of all parents in the hierarchy.

        ``extra_filters`` may exist if the parent is a container bundle passing
        filters along to its children; these are applied as input and output
        filters (since there is no parent who could do the latter), and they
        are not passed further down the hierarchy (but instead they become part
        of ``parent_filters``.

        ``disable_cache`` is necessary because in some cases, when an external
        bundle dependency has changed, we must not rely on the cache, since the
        cache key is not taking into account changes in those dependencies
        (for now).
        """

        assert not path.isabs(output)

        # Determine the debug level to use. It determines if and which filters
        # should be applied.
        #
        # The debug level is inherited (if the parent bundle is merging, a
        # child bundle clearly cannot act in full debug=True mode). Bundles
        # may define a custom ``debug`` attributes, but child bundles may only
        # ever lower it, not increase it.
        #
        # If not parent_debug is given (top level), use the Environment value.
        parent_debug = parent_debug if parent_debug is not None else env.debug
        # Consider bundle's debug attribute and other things
        current_debug_level = _effective_debug_level(env,
                                                     self,
                                                     extra_filters,
                                                     default=parent_debug)
        # Special case: If we end up with ``True``, assume ``False`` instead.
        # The alternative would be for the build() method to refuse to work at
        # this point, which seems unnecessarily inconvenient (Instead how it
        # works is that urls() simply doesn't call build() when debugging).
        # Note: This can only happen if the Environment sets debug=True and
        # nothing else overrides it.
        if current_debug_level is True:
            current_debug_level = False

        # Put together a list of filters that we would want to run here.
        # These will be the bundle's filters, and any extra filters given
        # to use if the parent is a container bundle. Note we do not yet
        # include input/open filters pushed down by a parent build iteration.
        filters = merge_filters(self.filters, extra_filters)

        # Given the debug level, determine which of the filters want to run
        selected_filters = select_filters(filters, current_debug_level)

        # We construct two lists of filters. The ones we want to use in this
        # iteration, and the ones we want to pass down to child bundles.
        # Why? Say we are in merge mode. Assume an "input()" filter  which does
        # not run in merge mode, and a child bundle that switches to
        # debug=False. The child bundle then DOES want to run those input
        # filters, so we do need to pass them.
        filters_to_run = merge_filters(
            selected_filters,
            select_filters(parent_filters, current_debug_level))
        filters_to_pass_down = merge_filters(filters, parent_filters)

        # Initialize al the filters (those we use now, those we pass down).
        for filter in filters:
            filter.set_environment(env)
            # Since we call this now every single time before the filter
            # is used, we might pass the bundle instance it is going
            # to be used with. For backwards-compatibility reasons, this
            # is problematic. However, by inspecting the support arguments,
            # we can deal with it. We probably then want to deprecate
            # the old syntax before 1.0 (TODO).
            filter.setup()

        # Prepare contents
        resolved_contents = self.resolve_contents(env, force=True)
        if not resolved_contents:
            raise BuildError('empty bundle cannot be built')

        # Unless we have been told by our caller to use or not use the cache
        # for this, try to decide for ourselves. The issue here is that when a
        # bundle has dependencies, like a sass file with includes otherwise not
        # listed in the bundle sources, a change in such an external include
        # would not influence the cache key, those the use of the cache causing
        # such a change to be ignored. For now, we simply do not use the cache
        # for any bundle with dependencies.  Another option would be to read
        # the contents of all files declared via "depends", and use them as a
        # cache key modifier. For now I am worried about the performance impact.
        #
        # Note: This decision only affects the current bundle instance. Even if
        # dependencies cause us to ignore the cache for this bundle instance,
        # child bundles may still use it!
        if disable_cache is None:
            actually_skip_cache_here = bool(self.resolve_depends(env))
        else:
            actually_skip_cache_here = disable_cache

        filtertool = FilterTool(env.cache,
                                no_cache_read=actually_skip_cache_here,
                                kwargs={
                                    'output': output,
                                    'output_path': env.abspath(output)
                                })

        # Apply input()/open() filters to all the contents.
        hunks = []
        for rel_name, item in resolved_contents:
            if isinstance(item, Bundle):
                hunk = item._merge_and_apply(env,
                                             output,
                                             force,
                                             current_debug_level,
                                             filters_to_pass_down,
                                             disable_cache=disable_cache)
                hunks.append(hunk)
            else:
                # Give a filter the chance to open his file.
                try:
                    hunk = filtertool.apply_func(
                        filters_to_run,
                        'open',
                        [item],
                        # Also pass along the original relative path, as
                        # specified by the user, before resolving.
                        kwargs={'source': rel_name},
                        # We still need to open the file ourselves too and use
                        # it's content as part of the cache key, otherwise this
                        # filter application would only be cached by filename,
                        # and changes in the source not detected. The other
                        # option is to not use the cache at all here. Both have
                        # different performance implications, but I'm guessing
                        # that reading and hashing some files unnecessarily
                        # very often is better than running filters
                        # unnecessarily occasionally.
                        cache_key=[FileHunk(item)] if not is_url(item) else [])
                except MoreThanOneFilterError, e:
                    raise BuildError(e)

                if not hunk:
                    if is_url(item):
                        hunk = UrlHunk(item)
                    else:
                        hunk = FileHunk(item)

                hunks.append(
                    filtertool.apply(
                        hunk,
                        filters_to_run,
                        'input',
                        # Pass along both the original relative path, as
                        # specified by the user, and the one that has been
                        # resolved to a filesystem location.
                        kwargs={
                            'source': rel_name,
                            'source_path': item
                        }))
Exemple #4
0
    def _merge_and_apply(self, env, output, force, parent_debug=None,
                         parent_filters=[], extra_filters=[],
                         disable_cache=None):
        """Internal recursive build method.

        ``parent_debug`` is the debug setting used by the parent bundle. This
        is not necessarily ``bundle.debug``, but rather what the calling method
        in the recursion tree is actually using.

        ``parent_filters`` are what the parent passes along, for us to be
        applied as input filters. Like ``parent_debug``, it is a collection of
        the filters of all parents in the hierarchy.

        ``extra_filters`` may exist if the parent is a container bundle passing
        filters along to its children; these are applied as input and output
        filters (since there is no parent who could do the latter), and they
        are not passed further down the hierarchy (but instead they become part
        of ``parent_filters``.

        ``disable_cache`` is necessary because in some cases, when an external
        bundle dependency has changed, we must not rely on the cache, since the
        cache key is not taking into account changes in those dependencies
        (for now).
        """

        assert not path.isabs(output)

        # Determine the debug level to use. It determines if and which filters
        # should be applied.
        #
        # The debug level is inherited (if the parent bundle is merging, a
        # child bundle clearly cannot act in full debug=True mode). Bundles
        # may define a custom ``debug`` attributes, but child bundles may only
        # ever lower it, not increase it.
        #
        # If not parent_debug is given (top level), use the Environment value.
        parent_debug = parent_debug if parent_debug is not None else env.debug
        # Consider bundle's debug attribute and other things
        current_debug_level = _effective_debug_level(
            env, self, extra_filters, default=parent_debug)
        # Special case: If we end up with ``True``, assume ``False`` instead.
        # The alternative would be for the build() method to refuse to work at
        # this point, which seems unnecessarily inconvenient (Instead how it
        # works is that urls() simply doesn't call build() when debugging).
        # Note: This can only happen if the Environment sets debug=True and
        # nothing else overrides it.
        if current_debug_level is True:
            current_debug_level = False

        # Put together a list of filters that we would want to run here.
        # These will be the bundle's filters, and any extra filters given
        # to use if the parent is a container bundle. Note we do not yet
        # include input/open filters pushed down by a parent build iteration.
        filters = merge_filters(self.filters, extra_filters)

        # Given the debug level, determine which of the filters want to run
        selected_filters = select_filters(filters, current_debug_level)

        # We construct two lists of filters. The ones we want to use in this
        # iteration, and the ones we want to pass down to child bundles.
        # Why? Say we are in merge mode. Assume an "input()" filter  which does
        # not run in merge mode, and a child bundle that switches to
        # debug=False. The child bundle then DOES want to run those input
        # filters, so we do need to pass them.
        filters_to_run = merge_filters(
            selected_filters, select_filters(parent_filters, current_debug_level))
        filters_to_pass_down = merge_filters(filters, parent_filters)

        # Initialize al the filters (those we use now, those we pass down).
        for filter in filters:
            filter.set_environment(env)
            # Since we call this now every single time before the filter
            # is used, we might pass the bundle instance it is going
            # to be used with. For backwards-compatibility reasons, this
            # is problematic. However, by inspecting the support arguments,
            # we can deal with it. We probably then want to deprecate
            # the old syntax before 1.0 (TODO).
            filter.setup()

        # Prepare contents
        resolved_contents = self.resolve_contents(env, force=True)
        if not resolved_contents:
            raise BuildError('empty bundle cannot be built')

        # Unless we have been told by our caller to use or not use the cache
        # for this, try to decide for ourselves. The issue here is that when a
        # bundle has dependencies, like a sass file with includes otherwise not
        # listed in the bundle sources, a change in such an external include
        # would not influence the cache key, thus the use of the cache causing
        # such a change to be ignored. For now, we simply do not use the cache
        # for any bundle with dependencies. Another option would be to read
        # the contents of all files declared via "depends", and use them as a
        # cache key modifier. For now I am worried about the performance impact.
        #
        # Note: This decision only affects the current bundle instance. Even if
        # dependencies cause us to ignore the cache for this bundle instance,
        # child bundles may still use it!
        if disable_cache is None:
            actually_skip_cache_here = bool(self.resolve_depends(env))
        else:
            actually_skip_cache_here = disable_cache

        filtertool = FilterTool(
            env.cache, no_cache_read=actually_skip_cache_here,
            kwargs={'output': output,
                    'output_path': env.abspath(output)})

        # Apply input()/open() filters to all the contents.
        hunks = []
        for rel_name, item in resolved_contents:
            if isinstance(item, Bundle):
                hunk = item._merge_and_apply(
                    env, output, force, current_debug_level,
                    filters_to_pass_down, disable_cache=disable_cache)
                hunks.append(hunk)
            else:
                # Give a filter the chance to open his file.
                try:
                    hunk = filtertool.apply_func(
                        filters_to_run, 'open', [item],
                        # Also pass along the original relative path, as
                        # specified by the user, before resolving.
                        kwargs={'source': rel_name},
                        # We still need to open the file ourselves too and use
                        # it's content as part of the cache key, otherwise this
                        # filter application would only be cached by filename,
                        # and changes in the source not detected. The other
                        # option is to not use the cache at all here. Both have
                        # different performance implications, but I'm guessing
                        # that reading and hashing some files unnecessarily
                        # very often is better than running filters
                        # unnecessarily occasionally.
                        cache_key=[FileHunk(item)] if not is_url(item) else [])
                except MoreThanOneFilterError, e:
                    raise BuildError(e)

                if not hunk:
                    if is_url(item):
                        hunk = UrlHunk(item, env=env)
                    else:
                        hunk = FileHunk(item)

                hunks.append(filtertool.apply(
                    hunk, filters_to_run, 'input',
                    # Pass along both the original relative path, as
                    # specified by the user, and the one that has been
                    # resolved to a filesystem location.
                    kwargs={'source': rel_name, 'source_path': item}))