Python模块应如何使用代码生成?

问题描述

我有一个python模块,它是用C编写的本机扩展构建的。此扩展包括使用GNU Bison和(不是GNU)Flex工具生成代码。这意味着我的C扩展程序的构建过程包括调用这些工具,然后将其输出(C源文件包括在扩展程序源中。

为使此功能调用python setup.py install时起作用,我扩展了setuptools.command.build_ext类以同时调用Flex和Bison,然后在调用超类run方法之前将生成的源添加到Extension源。 / p>

这意味着我的setup.py如下:

import os
from setuptools import setup,Extension
from setuptools.command.build_ext import build_ext

c_extension = Extension('_mymod',include_dirs = ['inc'],sources = [
                          os.path.join('src','lib.c'),os.path.join('src','etc.c')
                         ])

class MyBuild(build_ext):
    def run(self):
        parser_dir = os.path.join(self.build_temp,'parser')
        # add the parser directory to include_dirs
        self.include_dirs.append(parser_dir)
        # add the source files to the sources
        self.extensions[0].sources.extend([os.path.join(parser_dir,'lex.yy.c'),os.path.join(parser_dir,'parse.tab.c')])
        
        # honor the --dry-run flag
        if not self.dry_run:
            self.mkpath(parser_dir)

            os.system('flex -o ' + os.path.join(parser_dir,'lex.yy.c') + ' ' + os.path.join('src','lex.l'))
            os.system('bison -d -o ' + os.path.join(parser_dir,'parse.tab.c') + ' ' + os.path.join('src','parse.y'))

        # call the super class method
        return build_ext.run(self)

setup (name = 'MyMod',version = '0.1',description = 'A module that uses external code generation tools',author = 'Sean Kauffman',packages = ['MyMod'],ext_modules = [c_extension],cmdclass={'build_ext': MyBuild},python_requires='>=3',zip_safe=False)

但是,现在,我正在尝试打包此模块以进行分发,但是我遇到了问题。想要安装我的软件包的用户需要安装Bison和Flex,或者在构建源发行版时需要运行这些工具。我看到两种可能的解决方案:

  1. 我确认flex和bison位于系统执行路径中。这将使定制构建器保持原样。我没有找到任何文件可以暗示我可以验证系统文件是否像bison和flex一样存在。最接近的是使用扩展程序的libraries字段,但是似乎我需要真正的黑客才能检查整个PATH以查找可执行文件。我还没有尝试过,因为我的第一选择是选项2。
  2. 我将代码生成移到创建源分发时发生。这意味着源代码发行版将包含bison和flex的输出文件,因此安装该软件包的人员不需要这些工具。这似乎是更清洁的选择。我已经尝试扩展sdist命令而不是像上面的build_ext扩展,但是目前尚不清楚如何将生成文件添加到清单中,以便将它们包括在内。此外,我想确保它仍然可以使用python setup.py install进行构建,但是我认为该命令在构建之前不会运行sdist。

任何解决方案都只能在Linux和OS X上运行是很好的。

解决方法

用于分发需要(f)lex和bison / yacc的代码的常用解决方案是将生成的扫描器和解析器捆绑在一起,但如果不存在,则准备生成它们。第二部分使开发更容易一些,如果人们有充分的理由这样做,还可以使他们选择使用自己的flex / bison版本。我想这个建议也将适用于Python模块。

(IANAL,但我的理解是,野牛生成的代码存在许可例外,这使得即使在非GPL项目中也可以分发。Flex不是GPL的开始,并且afaik没有分发限制。 )

要在源代码发布中有条件地构建扫描器和解析器,可以在验证生成的文件不存在之后使用已经提供的代码。 (理想情况下,您将检查生成的文件是否不存在或比相应的源文件新。这取决于文件日期在归档过程中未更改。在Linux和OS X上可以正常工作,但可以可能不是完全可移植的。)

假定该软件包是在执行sdist命令之前构建的。 sdist通常应排除源代码树中内置的目标文件,因此不需要手动清除源代码。但是,如果您想确保在执行sdist时生成的文件存在,则可以用与setup.py相同的方式在build_ext中覆盖它们,在此之前调用bison和flex调用基本的sdist命令。