Добавить конфигурационный файл вне Pyinstaller --onefile exe в каталог dist

ситуация

Я использую Pyinstaller на Windows, чтобы сделать.exe-файл моего проекта.

Я хотел бы использовать --onefile возможность получить чистый результат и легко распространять файл / программу.

Моя программа использует config.ini файл для хранения настроек конфигурации. Этот файл может быть настроен пользователями.

проблема

С помощью --onefile Опция Pyinstaller помещает все объявленные "данные-файлы" в единый .exe файл файл.

Я видел этот запрос, но он дает возможность добавить файл пакета внутри одного файла, а не снаружи, на том же уровне .exe и в том же dist каталог.

В какой-то момент я решил использовать команду shutil.copy внутри.spec-файла, чтобы скопировать этот файл... но я думаю, что это неправильно.

Кто-нибудь может мне помочь? Я буду признателен за это:-)

7 ответов

Решение

Репозиторий на Github помог мне найти решение моего вопроса.

Я использовал shutil модуль и .spec файл для добавления дополнительных файлов данных (в моем случае config-sample.ini файл) в папку dist с помощью Pyinstaller --onefile вариант.

Создайте файл.spec для pyinstaller

Прежде всего я создаю файл makepec с нужными мне опциями:

$ pyi-makespec --onefile --windowed --name exefilename scriptname.py

Эта команда создает exefilename.spec файл для использования с Pyinstaller

Измените exefilename.spec, добавив shutil.copyfile

Теперь я отредактировал exefilename.spec добавив в конец файла следующий код.

import shutil
shutil.copyfile('config-sample.ini', '{0}/config-sample.ini'.format(DISTPATH))
shutil.copyfile('whateveryouwant.ext', '{0}/whateveryouwant.ext'.format(DISTPATH))

Этот код копирует файлы данных, необходимые в конце процесса компиляции. Вы можете использовать все методы, доступные в shutil пакет.

Запустите PyInstaller

Последний шаг - запустить процесс компиляции.

pyinstaller --clean exefilename.spec

В результате в папке dist у вас должен быть скомпилированный файл.exe вместе с скопированными файлами данных.

рассмотрение

В официальной документации Pyinstaller я не нашел возможности получить этот результат. Я думаю, что это можно рассматривать как обходной путь... это работает.

Для тех, кто спотыкается об эту тему и ищет, как получить доступ к файлам, которые находятся на том же уровне, что и выходной файл. Хитрость в том, что sys.executable находится там, где находится однофайловый .exe. Так просто это делает трюк:

      import sys
import os.path
CWD = os.path.abspath(os.path.dirname(sys.executable))

использовать его, например, с

      with open(os.path.join(CWD, "config.ini")) as config_file:
    print(config_file.read())

Причина, по которой /относительные пути не работают

Исполняемый файл — это просто исполняемый архив, который при выполнении извлекается во временный каталог, где выполняются файлы .pyc. Поэтому, когда вы звонитевместо пути к исполняемому файлу вы получаете путь к временной папке.

Мое решение похоже на отличное решение @Stefano-Giraldi. Мне было отказано в разрешении при передаче каталогов вshutil.copyfile.

В итоге я использовал shutil.copytree:

import sys, os, shutil

site_packages = os.path.join(os.path.dirname(sys.executable), "Lib", "site-packages")
added_files = [
                (os.path.join(site_packages, 'dash_html_components'), 'dash_html_components'),
                (os.path.join(site_packages, 'dash_core_components'), 'dash_core_components'),
                (os.path.join(site_packages, 'plotly'), 'plotly'),
                (os.path.join(site_packages, 'scipy', '.libs', '*.dll'), '.')
                ]
working_dir_files = [
                ('assets', 'assets'),
                ('csv', 'csv'))
                ]

print('ADDED FILES: (will show up in sys._MEIPASS)')
print(added_files)
print('Copying files to the dist folder')

print(os.getcwd())
for tup in working_dir_files:
        print(tup)
        to_path = os.path.join(DISTPATH, tup[1])
        if os.path.exists(to_path):
                if os.path.isdir(to_path):
                        shutil.rmtree(to_path)
                else:
                        os.remove(to_path)
        if os.path.isdir(tup[0]):
                shutil.copytree(tup[0], to_path )
        else:
                shutil.copyfile(tup[0], to_path )

#### ... Rest of spec file
a = Analysis(['myapp.py'],
             pathex=['.', os.path.join(site_packages, 'scipy', '.libs')],
             binaries=[],
             datas=added_files,
             hiddenimports=[],
             hookspath=[],
             runtime_hooks=[],
             excludes=[],
             win_no_prefer_redirects=False,
             win_private_assemblies=False,
             cipher=block_cipher,
             noarchive=False)
pyz = PYZ(a.pure, a.zipped_data,
             cipher=block_cipher)
exe = EXE(pyz,
          a.scripts,
          a.binaries,
          a.zipfiles,
          a.datas,
          [],
          name='myapp',
          debug=False,
          bootloader_ignore_signals=False,
          strip=False,
          upx=True,
          upx_exclude=[],
          runtime_tmpdir=None,
          console=True )

Это позволяет избежать папки _MEI и не позволяет ей копировать файлы конфигурации, которые вы хотите, в папку dist, а не во временную папку.

Надеюсь, это поможет.

Если какой-либо из ваших сценариев Python использует внешние файлы (JSON, текст или любые файлы конфигурации) и вы хотите включить эти файлы в исполняемый файл, выполните следующие действия в системе Windows .

Учитывая, что есть сценарий Python, и он читает файл JSON, наша цель — добавить файл в какой-нибудь подходящий каталог, где к нему может получить доступ приложение во время его работы (как.exe).

Этот ответ применим, даже если он не читается напрямую. может быть прочитан любым из используемых модулей, и ответ все равно поможет.

      app.py
config.json

Шаг 1. Разрешение пути к файлу JSON во время работы приложения

Когда приложение запущено, файлы копируются во временное место в вашей системе Windows.C:\Users\<You>\AppData\Local\Temp\MEIxxx. Итак, необходимо прочитать файл JSON из этого временного местоположения. Но как приложение узнает во время выполнения, в каком каталоге ему нужно искать файлы? Из этого отличного ответа в у нас будет функция,

      def resolve_path(path):
    if getattr(sys, "frozen", False):
        # If the 'frozen' flag is set, we are in bundled-app mode!
        resolved_path = os.path.abspath(os.path.join(sys._MEIPASS, path))
    else:
        # Normal development mode. Use os.getcwd() or __file__ as appropriate in your case...
        resolved_path = os.path.abspath(os.path.join(os.getcwd(), path))

    return resolved_path

# While reading the JSON file
with open(resolve_path("config.json"), "r") as jsonfile:
   # Access the contents here

Теперь знает, где искать файл. Перед этим нам нужно указать PyInstaller скопировать в этот каталог временных файлов.

Обратите внимание, вам необходимо использоватьresolve_pathвезде, где вы используете относительный путь в своих сценариях Python.

Шаг 2: Создайте и отредактируйте файл для копированияconfig.json

Поскольку мы хотим создать исполняемый файлapp.py, мы сначала создадим для него файл (ссылаясь на ответ @Stefano Giraldi )

      pyi-makespec --onefile --windowed --name appexe app.py

Откройте полученныйappexe.specфайл, и вы заметите это содержимое,

      # -*- mode: python ; coding: utf-8 -*-

block_cipher = None
a = Analysis(
    ...
    datas=[],
    ...
)
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)

exe = EXE(
    ...
)

Создать новый списокfilesи передать его вdatasаргумент,

      # -*- mode: python ; coding: utf-8 -*-

block_cipher = None

files = [
    ( 'config.json' , '.' ) 
]

a = Analysis(
    ...
    datas=files,
    ...
)
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)

exe = EXE(
    ...
)

Кортеж( 'config.json' , '.' )обозначает исходный и конечный пути файла. Путь назначения относится к каталогу временных файлов.

Шаг 3: Создайте исполняемый файл

Наконец, мы можем запустить.specфайл для сборки установщика,

      pyinstaller --clean appexe.spec

Полученный установщик теперь должен работать без каких-либоFileNotFoundErrorс.

Пробовал много подходов, этот работает для меня:

  1. Не используйте расширение .py для файла конфигурации. Вместо этого используйте json. Json не годится, потому что в нем нельзя писать комментарии, но если вы хотите сделать exe, вам придется его использовать, к сожалению.

  2. Внутри вашего скрипта загрузите этот файл настроек таким образом

      filename = "settings.json"
contents = open(filename).read()
config = eval(contents)
setting1 = config['setting1']
  1. Запустите pyinstaller или auto-py-to-exe (пробовал оба, все работает)

  2. Поместите файл settings.json в ту же папку, где находится ваш файл .exe.

  3. Запустите его, и он возьмет настройки из этого файла.

Решение есть, но оно не самое лучшее:

      config_str = """
some configuration code string
"""
with open('path_to_somewhere\\config.ini', 'w', encoding="gbk") as writer:
    writer.write(config_str)

Я решил эту проблему, удалив файл конфигурации (для меня config.py) из папки проекта перед запуском pyinstaller.

Другие вопросы по тегам