将 Waf 目标链接到外部构建系统 (CMake) 生成的库

Iva*_*aev 6 c c++ python cmake waf

我的 waf 项目有两个依赖项,使用 CMake 构建。

我想做的,是按照waf git repo 中找到的dynamic_build3示例,创建一个生成 CMake 的工具,并在成功构建后执行安装到 waf 的输出子目录中:

@extension('.txt')
def spawn_cmake(self, node):
    if node.name == 'CMakeLists.txt':
        self.cmake_task = self.create_task('CMake', node)
        self.cmake_task.name = self.target


@feature('cmake')
@after_method('process_source')
def update_outputs(self):
    self.cmake_task.add_target()


class CMake(Task.Task):
    color = 'PINK'

    def keyword(self):
        return 'CMake'

    def run(self):
        lists_file = self.generator.source[0]
        bld_dir = self.generator.bld.bldnode.make_node(self.name)
        bld_dir.mkdir()

        # process args and append install prefix
        try:
            cmake_args = self.generator.cmake_args
        except AttributeError:
            cmake_args = []
        cmake_args.append(
            '-DCMAKE_INSTALL_PREFIX={}'.format(bld_dir.abspath()))

        # execute CMake
        cmd = '{cmake} {args} {project_dir}'.format(
            cmake=self.env.get_flat('CMAKE'),
            args=' '.join(cmake_args),
            project_dir=lists_file.parent.abspath())
        try:
            self.generator.bld.cmd_and_log(
                cmd, cwd=bld_dir.abspath(), quiet=Context.BOTH)
        except WafError as err:
            return err.stderr

        # execute make install
        try:
            self.generator.bld.cmd_and_log(
                'make install', cwd=bld_dir.abspath(), quiet=Context.BOTH)
        except WafError as err:
            return err.stderr

        try:
            os.stat(self.outputs[0].abspath())
        except:
            return 'library {} does not exist'.format(self.outputs[0])

        # store the signature of the generated library to avoid re-running the
        # task without need
        self.generator.bld.raw_deps[self.uid()] = [self.signature()] + self.outputs

    def add_target(self):
        # override the outputs with the library file name
        name = self.name
        bld_dir = self.generator.bld.bldnode.make_node(name)
        lib_file = bld_dir.find_or_declare('lib/{}'.format(
            (
                self.env.cshlib_PATTERN
                if self.generator.lib_type == 'shared' else self.env.cstlib_PATTERN
            ) % name))
        self.set_outputs(lib_file)

    def runnable_status(self):
        ret = super(CMake, self).runnable_status()
        try:
            lst = self.generator.bld.raw_deps[self.uid()]
            if lst[0] != self.signature():
                raise Exception
            os.stat(lst[1].abspath())
            return Task.SKIP_ME
        except:
            return Task.RUN_ME
        return ret
Run Code Online (Sandbox Code Playgroud)

我想生成该工具,然后将 waf 目标链接到已安装的库,我通过调用以下命令使用“假库”机制执行此操作bld.read_shlib()

def build(bld):
    bld.post_mode = Build.POST_LAZY
    # build 3rd-party CMake dependencies first
    for lists_file in bld.env.CMAKE_LISTS:
        if 'Chipmunk2D' in lists_file:
            bld(
                source=lists_file,
                features='cmake',
                target='chipmunk',
                lib_type='shared',
                cmake_args=[
                    '-DBUILD_DEMOS=OFF',
                    '-DINSTALL_DEMOS=OFF',
                    '-DBUILD_SHARED=ON',
                    '-DBUILD_STATIC=OFF',
                    '-DINSTALL_STATIC=OFF',
                    '-Wno-dev',
                ])
    bld.add_group()

    # after this, specifying `use=['chipmunk']` in the target does the job
    out_dir = bld.bldnode.make_node('chipmunk')
    bld.read_shlib(
        'chipmunk',
        paths=[out_dir.make_node('lib')],
        export_includes=[out_dir.make_node('include')])
Run Code Online (Sandbox Code Playgroud)

我觉得这非常丑陋,因为:

  1. 在最终目标的链接阶段才需要花栗鼠库,没有理由阻止整个构建(通过使用Build.POST_LAZY模式和bld.add_group()),尽管解除阻止会read_shlib()失败。想象一下,如果在此之前还有某种git clone任务......
  2. read_shlib()在命令中调用build()意味着调用者知道该工具如何以及在何处安装文件。我希望该工具本身能够执行调用read_shlib()(如果有必要的话)。但我在run()和 中未能做到这一点runnable_status(),正如 Waf Book 部分关于自定义任务的第 11.4.2 段所建议的那样,似乎我必须以某种方式封装对read_shlib()另一个任务的调用并将其放入未记录的 more_tasks属性中。

还有以下问题:

  1. 如何将调用read_shlib()封装在由 CMake 任务生成的任务中?
  2. 是否可以让这些任务以非阻塞的方式并行执行其他任务(假设一个项目有 2 个或 3 个这些 CMake 依赖项,这些依赖项将从git远程存储库中获取)?

neu*_*uro 1

事实上你已经完成了大部分工作:)

read_shlib只创建一个假任务假装构建一个已经存在的库。就您而言,您确实构建了库,因此您实际上不需要read_shlib. use只要您设置了正确的参数,您就可以将cmake 任务生成器放在某处。

该关键字use识别所使用的任务生成器中的一些参数:

  • 导出包含
  • 导出定义

如果使用的任务生成器具有link_task.

因此,您只需在 cmake 任务生成器中正确设置export_includes和,再加上设置引用您的属性的属性。您还必须正确设置 cmake_task 输出才能使其正常工作,即列表的第一个输出必须是 lib 节点(您在 add_target 中所做的操作似乎没问题)。就像是:export_defineslink_taskcmake_task

@feature('cmake')
@after_method('update_outputs')
def export_for_use(self):
    self.link_task = self.cmake_task
    out_dir = self.bld.bldnode.make_node(self.target)
    self.export_includes = out_dir.make_node('include')
Run Code Online (Sandbox Code Playgroud)

完成后,您只需在主脚本中编写:

def build(bld):
    for lists_file in bld.env.CMAKE_LISTS:
        if 'Chipmunk2D' in lists_file:
            bld(
                source=lists_file,
                features='cmake',
                target='chipmunk',
                lib_type='shared',
                cmake_args=[
                    '-DBUILD_DEMOS=OFF',
                    '-DINSTALL_DEMOS=OFF',
                    '-DBUILD_SHARED=ON',
                    '-DBUILD_STATIC=OFF',
                    '-DINSTALL_STATIC=OFF',
                    '-Wno-dev',
                ])

    bld.program(source="main.cpp", use="chipmunk")
Run Code Online (Sandbox Code Playgroud)

您当然可以简化/分解代码。我认为add_target不应该在任务中,它主要管理任务生成器属性。