Skip to main content
 首页 » 编程设计

python 源代码分发 (sdist)之生成的数据文件

2024年09月07日32tintown

在构建我的包期间,我正在生成数据文件。

我想创建源代码分发 (setup.py sdist),比如如果它们最初在源代码树中,但是,我不想在源代码树中生成它们但是在其他地方(最好是构建/生成)以免弄乱我的源代码(并意外提交)。

例如,最后我想在 dist_root/generated/data.txt 下有 data.txt (“dist_root”是 setup.py 所在的位置)。

我使用了data_files setuptools(不是 package_data,因为这个数据不是包的)并且遇到了以下问题:

  1. 如果我在 build 下生成 data.txt,它会被删除,因为该过程正在过滤 build_base 下的任何文件。
  2. 如果我在某个临时文件夹下生成它,例如 dist_root/temp/data.txt,则此“临时”文件夹被链接。

所以如果我输入 data_files = [('generated, temp/data.txt)],我将在分发中获得一条链路径 dist_root/generated/temp/data.txt

似乎我唯一的选择是在 dist_root/generated/data.txt 下生成它 但是,又一次,我弄乱了我的源代码树,并且不知道如何清理它,因为这个“生成的”文件夹名称是动态的。

有什么解决方法吗?

请您参考如下方法:

首选解决方案:将文件写入源目录,在sdist完成后删除它们

您可以重写 sdist 命令以将文件写入源目录并在命令完成后清理它们:

import os 
from distutils import dir_util 
 
from setuptools import setup 
from setuptools.command.sdist import sdist as sdist_orig 
 
 
class sdist(sdist_orig): 
 
    def run(self): 
        # generate data files 
        genbase = os.path.join(os.path.dirname(__file__), 'temp') 
        self.mkpath(genbase) 
        with open(os.path.join(genbase, 'data.txt'), 'w') as fp: 
            fp.write('hello distutils world') 
        # run original sdist 
        super().run() 
        # clean up generated data files 
        dir_util.remove_tree(genbase, dry_run=self.dry_run) 
 
 
setup( 
    ... 
    data_files=[ 
        ('generated', ['temp/data.txt']), 
    ], 
    cmdclass={'sdist': sdist}, 
) 

生成数据文件而不将它们写入源目录

在 sdist temp 中调整源元数据

虽然很脏,但最简单的方法是直接在 sdist 目录中更新源元数据。这样,您仍将拥有有效的 egg 元数据,并且不必在整个 sdist 方式中处理丢失的源文件。

genfiles = ['temp/data.txt'] 
 
 
class sdist(sdist_orig): 
 
    def make_release_tree(self, base_dir, files): 
        super().make_release_tree(base_dir, files) 
        for path in genfiles: 
            fullpath = os.path.join(base_dir, path) 
            self.mkpath(os.path.dirname(fullpath)) 
            if not self.dry_run: 
                with open(fullpath, 'w') as fp: 
                fp.write('hello distutils world') 
            # also adapt source metadata file 
            cmd_egg_info = self.get_finalized_command('egg_info') 
            sourcemeta = os.path.join(base_dir,  
                                      cmd_egg_info.egg_name + '.egg-info',  
                                      'SOURCES.txt') 
            with open(sourcemeta, 'a') as fp: 
                fp.write('\n') 
                fp.write(path) 
 
 
setup( 
    ..., 
    data_files=[ 
        ('generated', genfiles), 
    ], 
    cmdclass={'sdist': sdist}, 
) 

基本上,在实际复制源文件之前,生成的数据文件会被忽略。然后,生成文件(作为复制现有文件的替代),并且由于源元数据不完整,因此使用生成的文件对其进行更新。

在元数据中写入不存在的文件

我强烈建议不要这样做。

所有其他方法会更脏,因为它们会将不存在的文件写入元数据并使 distutils/setuptools 忽略不存在的文件生成源分发的整个方式。但如果你坚持,这里有一个尽可能少的猴子修补的解决方案:

genfiles = ['temp/data.txt'] 
 
 
class FileList(setuptools.command.egg_info.FileList): 
 
    def __init__(self, *args, **kwargs): 
        super().__init__(*args, **kwargs) 
        self.files += genfiles 
 
    def _safe_path(self, path): 
        return path in genfiles or super()._safe_path(path) 
 
 
class sdist(sdist_orig): 
 
    def run(self): 
        # monkeypatch begin 
        FileListOrig = setuptools.command.egg_info.FileList 
        setuptools.command.egg_info.FileList = FileList 
        # monkeypatch end 
        super().run() 
        # restore the original class 
        setuptools.command.egg_info.FileList = FileListOrig 
 
    def make_release_tree(self, base_dir, files): 
        super().make_release_tree(base_dir, files) 
        for path in genfiles: 
            fullpath = os.path.join(base_dir, path) 
            self.mkpath(os.path.dirname(fullpath)) 
            if not self.dry_run: 
                with open(fullpath, 'w') as fp: 
                    fp.write('hello distutils world') 
 
 
setup( 
    ..., 
    data_files=[ 
        ('generated', genfiles), 
    ], 
    cmdclass={'sdist': sdist}, 
)