3

私の質問は基本的に、「凍結され、デプロイされた Python ベースの Windows アプリケーションのファイルとフォルダーをどのように構成すればよいか」です。私の状況を理解するために、ここにいくつかの背景があります:

職場で Python 2.7 を使用してデスクトップ アプリケーションを構築しています。PyQt 上に構築された GUI ベースのアプリケーションです。クロスプラットフォームの凍結および更新フレームワークであるEskyを使用してアプリケーションを構築しています。Esky は基本的に、py2exe、py2app、bb_freeze、または現在のプラットフォームに適したインストール済みのツールをラップ/呼び出します。Esky は、次のような圧縮パッケージを作成します。

prog.exe                     - esky bootstrapping executable
appdata/                     - container for all the esky magic
  appname-X.Y.platform/      - specific version of the application
    prog.exe                 - executable(s) as produced by freezer module
    library.zip              - pure-python frozen modules
    pythonXY.dll             - python DLL
    esky-files/              - esky control files
      bootstrap/             - files not yet moved into bootstrapping env
      bootstrap-manifest.txt - list of files expected in bootstrap env
      lockfile.txt           - lockfile to block removal of in-use versions
      ...other deps...
  updates/                   - work area for fetching/unpacking updates

これらの圧縮されたパッケージは、Esky が更新のために参照するファイル サーバーに配置できます。非常に単純な auto_update() を含む、更新を管理するための多数のメソッドが提供されています。更新が発生すると、appname-XYplatform フォルダーは基本的に次のバージョンのフォルダーに置き換えられます... したがって、myApp.0.1.win32 フォルダーは myApp.0.2.win32 フォルダーに置き換えられます。

知っておくべき背景のもう 1 つの側面は、Python をインストールしていない同僚にアプリケーションを配布していることです。私は Python のパッケージやライブラリを配布しているのではなく、デスクトップ アプリケーションをデプロイしています (私の同僚は、それが何に書かれているかは特に気にしません。ただ、それが機能することだけです)。アプリケーションをインストールし、アンインストーラーとさまざまなショートカットを提供する Inno インストーラーを作成しました。チームの全員が基本的に同じ Windows 7 64 ビット環境を使用しているため、そのプラットフォームだけでビルドすることはかなり安全です。

というわけで、構造の問題に戻ります。Learn Python the Hard Way、Exercise 46Hitchhiker's Guide to Packagingなど、プロジェクトのスケルトンに特定の形式を推奨するガイドを読みました。ただし、これらのガイドは、コンパイル済みアプリケーションの開発者ではなく、Python パッケージの開発者を対象としています。

Esky の appname-XYplatform フォルダーでも問題が発生しました。これは、プログラムが更新されるたびに (バージョン番号を反映するために) 名前が変わるためです。スタート メニューのいくつかのショートカットが常にドキュメントや変更ログなどを参照するようにしたいので、インストーラーにそれらのファイルのいくつかを appdata フォルダーの下に配置させます。プログラムが更新されると、外部から「表示」したいファイルの新しいバージョンをチェックし、appname-XYplatform フォルダーから新しいバージョンをコピーして、appdata フォルダー内のコピーを上書きするコードがあります。次に、永続的なユーザー設定を保存する手段も必要だったので、プログラムは appdata\settings フォルダーを生成して使用します (そうしないと、更新のたびに設定が消去されます)。

更新後にアプリケーションが新しいファイルを appdata フォルダーにプッシュするプロセスを続行する必要がありますか? Docs、Examples、Settings などの独自の構造を構築し、必要に応じてプログラムがそれらのフォルダーに新しいファイルを入力するようにする必要がありますか? Esky の動作を変更したり、よりうまく利用したりして、自分の使用法により適したものにする必要がありますか? おそらく、Python パッケージとエンドユーザー アプリケーションの両方として配布できるようにアプリケーションを作り直す必要がありますか?

この質問は、 Eskyを使用した静的ファイルに関するthis one 、Python デプロイされたアプリケーション構造に関する this one、および Esky を使用して特に対処しない Python プロジェクト構造に関する多数の一般的な質問に関連しています。Esky について説明しているいくつかのビデオもここここで入手できます。

これらの課題に対処するための「ベスト プラクティス」手法の推奨事項を探しています。これが StackOverflow の質問形式に合わない場合は、喜んで質問の焦点を言い換えるか、絞り込みます。

4

1 に答える 1

2

Esky の自動更新によりアプリケーション フォルダの名前が更新ごとに変更されるという事実にもかかわらず、ファイルを静的な場所のショートカットで使用できるようにすることについて言及した問題に対する私の解決策を次に示します。以下の関数は、QMainWindow のクラス定義内にあります。

アプリケーションで logging モジュールを使用しない場合は、ロギング ステートメントを print ステートメントに置き換えることができますが、特にこのようなスタンドアロン アプリケーションをデプロイする場合は、ロギングを強くお勧めします。

import os
import shutil
import logging

def push_updated_files(self):
    """
    Manually push auto-updated files from the application folder up to the appdata folder
    This enables shortcuts and other features on the computer to point to these files since the application
      directory name changes with each update.
    """
    logger = logging.getLogger(__name__)

    #Verify whether running script or frozen/deployed application
    if getattr(sys, 'frozen', False):
        logger.info("Verifying Application Integrity...")

        #Files which should by copied to appdata directory to be easily referenced by shortcuts, etc.
        data_files = ['main.ico',
                      'uninstall.ico',
                      'ReadMe.txt',
                      'changelog.txt',
                      'WhatsNew.txt',
                      'copyright.txt',
                      'Documentation.pdf']

        logger.debug("  App Path: {0}".format(self._app_path))

        #Get application top directory
        logger.debug("  AppData Directory: {0}".format(self._appdata_path))

        #Get application internal file path
        for f in data_files:
            a_file = f
            int_path = os.path.join(self._app_path, a_file)
            logger.debug("  Internal File Path: {0}".format(int_path))

            #Get file's creation time
            mtime_int = os.stat(int_path).st_mtime
            logger.debug("  Internal File Modified Time: {0}".format(time.ctime(mtime_int)))

            #Get external file path
            ext_path = os.path.join(self._appdata_path, a_file)
            if os.path.exists(ext_path):
                mtime_ext = os.stat(ext_path).st_mtime
                logger.debug("  External File Modified Time: {0}".format(time.ctime(mtime_ext)))

                if mtime_int > mtime_ext:
                    logger.debug("  Replacing external file with new file...")
                    try:
                        os.remove(ext_path)
                        shutil.copy(int_path, ext_path)
                    except Exception, e:
                        logger.error("  Failed to replace the external file...", exc_info=True)
                else:
                    logger.debug("  External file is newer than internal file - all is well.")
            else:
                logger.debug("  Copying file to appdata to be externally accessible")
                shutil.copy(int_path, ext_path)

また、これに関連して、ユーザー設定 (現在、最近のファイル リストを作成するために使用される history.txt ファイルのみ) を処理するときに、設定が更新ごとに失われないように、appdata の下にアプリケーション フォルダーの外に設定フォルダーがあります。ドキュメントとアイコン用に同様のフォルダーを作成する場合があります。

于 2013-12-20T21:31:26.093 に答える