SCons:如何在构建一些目标后生成依赖项?

时间:2014-07-10 08:34:31

标签: python scons

我有一个SCons项目,它构建了一组Python模块(主要是作为从C ++编译的共享对象)。 这部分完美无瑕,所有依赖似乎都很好。

但是,现在我已经开始尝试为这些模块添加测试了。这些测试应该作为构建的一部分运行。测试是用Python编写的,运行时需要在已经构建了所有模块的环境下运行(以便import语句可以找到它们。)

我让那部分工作到了那些测试的依赖点。测试运行,但我似乎无法找到从import语句为它们生成依赖关系的方法。

我发现modulefinder完全符合我的要求。更重要的是,我能够在构建的环境中运行它并在构建我的项目之后获得预期的结果。 我想在 emitter 中使用modulefinder来扫描test / Python脚本所依赖的文件。

问题是依赖扫描/构建+运行发射器是在SCons构建所有模块之前发生的,之后才能为测试正确设置环境,因此modulefinder也无法工作。

我似乎无法弄清楚如何在已经构建其他目标后让SCons查找特定目标的依赖项。

修改

我在SCons文档中找到了ParseDepends,它似乎谈到了同一类型的问题(好吧,几乎完全相同,只有langauge)。

  

ParseDepends的这种限制导致不必要的重新编译。因此,只有当扫描仪不适用于所使用的语言或者对于特定任务不够强大时,才应使用ParseDepends。

尽管我的问题有一些干净的解决方案,但我仍然充满希望。

2 个答案:

答案 0 :(得分:1)

您无法在编译阶段更改SCons中的依赖项。 SCons创建其依赖树并在运行它之后。你无法改变它。 我建议你为你的建造者写一个扫描仪。在C ++中,SCons使用扫描程序查找包含依赖项。 [http://www.scons.org/doc/1.1.0/HTML/scons-user/c3742.html][1]

答案 1 :(得分:1)

经过大量的游戏后,我找到了一种不那么干净但不太可怕的方式 - 似乎是工作,我已经帮助了Scanner - 派生类:

from SCons.Node import Node
from SCons import Scanner
import logging

_LOGGER = logging.getLogger(__name__)

class DeferredScanner(Scanner.Current):
    """
    This is a helper class for implementing source scanners that need
    to wait for specific things to be built before source scanning can happen.

    One practical example of usage is when you are you generating Python
    modules (i.e. shared libraries) which you want to test.

    You have to wait for all your modules are ready before dependencies
    of your tests can be scanned.

    To do this approach with this scanner is to collect all generated modules
    and `wait_for` them before scanning dependncies of whatever this scanner
    is used for.

    Sample usage:

        py_scanner = DeferredScanner(
            wait_for = all_generated_modules,
            function = _python_source_scanner,
            recursive = True,
            skeys = ['.py'],
            path_function = FindENVPathDirs('PYTHONPATH'),
        )

    """
    def __init__(self, wait_for, **kw):
        Scanner.Current.__init__(
            self,
            node_factory = Node,
            **kw
        )
        self.wait_for = wait_for
        self.sources_to_rescan = []
        self.ready = False

        env = wait_for[0].get_build_env()
        env.AlwaysBuild(wait_for)

        self._hook_implicit_reset(
            wait_for[0],
            'built',
            self.sources_to_rescan,
            lambda: setattr(self, 'ready', True),
        )

    def _hook_implicit_reset(self, node, name, on, extra = None):
        # We can only modify dependencies in main thread
        # of Taskmaster processing.
        # However there seems to be no hook to "sign up" for
        # callback when post processing of built node is hapenning
        # (at least I couldn't find anything like this in SCons
        #  2.2.0).
        # AddPostAction executes actions in Executor thread, not
        # main one, se we can't used that.
        #
        # `built` is guaranteed to be executed in that thread,
        # so we hijack this one.
        #
        node.built = lambda old=node.built: (
            old(),
            self._reset_stored_dependencies(name, on),
            extra and extra(),
        )[0]

    def _reset_stored_dependencies(self, name, on):
        _LOGGER.debug('Resetting dependencies for {0}-{1} files: {2}'.format(
                        # oh, but it does have those
            self.skeys, # pylint: disable=no-member
            name,
            len(on),
        ))
        for s in on:
            # I can't find any official way to invalidate
            # FS.File (or Node in general) list of implicit dependencies
            # that were found.
            # Yet we want to force that Node to scan its implicit
            # dependencies again.
            # This seems to do the trick.
            s._memo.pop('get_found_includes', None) # pylint: disable=protected-access

    def __call__(self, node, env, path = ()):
        ready = self.ready
        _LOGGER.debug('Attempt to scan {0} {1}'.format(str(node), ready))

        deps = self.wait_for + [node]

        if ready:
            deps.extend(Scanner.Current.__call__(self, node, env, path))

        self.sources_to_rescan.append(node)

        # In case `wait_for` is not dependent on this node
        # we have to make sure we will rescan dependencies when
        # this node is built itself.
        # It boggles my mind that SCons scanns nodes before
        # they exist, and caches result even if there was no
        # list returned.
        self._hook_implicit_reset(
            node,
            'self',
            [node],
        )

        return deps

这似乎与我希望并完成工作完全一样。 它可能和你一样高效。

可能还应该注意,这适用于SCons 2.2.0,但我怀疑它对于新版本不应该有太大的不同。