From 5d535b4a559ff114866368bfb3cde38b54f9462b Mon Sep 17 00:00:00 2001 From: pukkandan Date: Mon, 4 Oct 2021 02:25:13 +0530 Subject: [PATCH] [build] Allow building with py2exe (and misc fixes) py2exe config is copied from youtube-dl Closes #1160 --- .github/workflows/build.yml | 6 +-- pyinst.py | 13 +++-- setup.py | 96 ++++++++++++++++++++++++++----------- yt_dlp/update.py | 33 ++++++++----- yt_dlp/utils.py | 5 +- 5 files changed, 103 insertions(+), 50 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 515c50164..4f983f2c1 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -161,7 +161,7 @@ jobs: - name: Print version run: echo "${{ steps.bump_version.outputs.ytdlp_version }}" - name: Run PyInstaller Script - run: python pyinst.py 64 + run: python pyinst.py - name: Upload yt-dlp.exe Windows binary id: upload-release-windows uses: actions/upload-release-asset@v1 @@ -179,7 +179,7 @@ jobs: id: sha512_win run: echo "::set-output name=sha512_win::$((Get-FileHash dist\yt-dlp.exe -Algorithm SHA512).Hash.ToLower())" - name: Run PyInstaller Script with --onedir - run: python pyinst.py 64 --onedir + run: python pyinst.py --onedir - uses: papeloto/action-zip@v1 with: files: ./dist/yt-dlp @@ -227,7 +227,7 @@ jobs: - name: Print version run: echo "${{ steps.bump_version.outputs.ytdlp_version }}" - name: Run PyInstaller Script for 32 Bit - run: python pyinst.py 32 + run: python pyinst.py - name: Upload Executable yt-dlp_x86.exe id: upload-release-windows32 uses: actions/upload-release-asset@v1 diff --git a/pyinst.py b/pyinst.py index d65243f88..7e040647c 100644 --- a/pyinst.py +++ b/pyinst.py @@ -13,11 +13,18 @@ from PyInstaller.utils.win32.versioninfo import ( ) import PyInstaller.__main__ -arch = sys.argv[1] if len(sys.argv) > 1 else platform.architecture()[0][:2] +arch = platform.architecture()[0][:2] assert arch in ('32', '64') _x86 = '_x86' if arch == '32' else '' -opts = sys.argv[2:] or ['--onefile'] +# Compatability with older arguments +opts = sys.argv[1:] +if opts[0:1] in (['32'], ['64']): + if arch != opts[0]: + raise Exception(f'{opts[0]}bit executable cannot be built on a {arch}bit system') + opts = opts[1:] +opts = opts or ['--onefile'] + print(f'Building {arch}bit version with options {opts}') FILE_DESCRIPTION = 'yt-dlp%s' % (' (32 Bit)' if _x86 else '') @@ -82,4 +89,4 @@ PyInstaller.__main__.run([ *opts, 'yt_dlp/__main__.py', ]) -SetVersion('dist/yt-dlp%s.exe' % _x86, VERSION_FILE) +SetVersion('dist/%syt-dlp%s.exe' % ('yt-dlp/' if '--onedir' in opts else '', _x86), VERSION_FILE) diff --git a/setup.py b/setup.py index d54806f15..b5eb81c30 100644 --- a/setup.py +++ b/setup.py @@ -1,12 +1,16 @@ #!/usr/bin/env python3 # coding: utf-8 - -from setuptools import setup, Command, find_packages import os.path import warnings import sys -from distutils.spawn import spawn +try: + from setuptools import setup, Command, find_packages + setuptools_available = True +except ImportError: + from distutils.core import setup, Command + setuptools_available = False +from distutils.spawn import spawn # Get the version from yt_dlp/version.py without importing the package exec(compile(open('yt_dlp/version.py').read(), 'yt_dlp/version.py', 'exec')) @@ -21,32 +25,62 @@ LONG_DESCRIPTION = '\n\n'.join(( REQUIREMENTS = ['mutagen', 'pycryptodome', 'websockets'] + if sys.argv[1:2] == ['py2exe']: - raise NotImplementedError('py2exe is not currently supported; instead, use "pyinst.py" to build with pyinstaller') - - -files_spec = [ - ('share/bash-completion/completions', ['completions/bash/yt-dlp']), - ('share/zsh/site-functions', ['completions/zsh/_yt-dlp']), - ('share/fish/vendor_completions.d', ['completions/fish/yt-dlp.fish']), - ('share/doc/yt_dlp', ['README.txt']), - ('share/man/man1', ['yt-dlp.1']) -] -root = os.path.dirname(os.path.abspath(__file__)) -data_files = [] -for dirname, files in files_spec: - resfiles = [] - for fn in files: - if not os.path.exists(fn): - warnings.warn('Skipping file %s since it is not present. Try running `make pypi-files` first' % fn) - else: - resfiles.append(fn) - data_files.append((dirname, resfiles)) - -params = { - 'data_files': data_files, -} -params['entry_points'] = {'console_scripts': ['yt-dlp = yt_dlp:main']} + import py2exe + warnings.warn( + 'Building with py2exe is not officially supported. ' + 'The recommended way is to use "pyinst.py" to build using pyinstaller') + params = { + 'console': [{ + 'script': './yt_dlp/__main__.py', + 'dest_base': 'yt-dlp', + 'version': __version__, + 'description': DESCRIPTION, + 'comments': LONG_DESCRIPTION.split('\n')[0], + 'product_name': 'yt-dlp', + 'product_version': __version__, + }], + 'options': { + 'py2exe': { + 'bundle_files': 0, + 'compressed': 1, + 'optimize': 2, + 'dist_dir': './dist', + 'excludes': ['Crypto', 'Cryptodome'], # py2exe cannot import Crypto + 'dll_excludes': ['w9xpopen.exe', 'crypt32.dll'], + } + }, + 'zipfile': None + } + +else: + files_spec = [ + ('share/bash-completion/completions', ['completions/bash/yt-dlp']), + ('share/zsh/site-functions', ['completions/zsh/_yt-dlp']), + ('share/fish/vendor_completions.d', ['completions/fish/yt-dlp.fish']), + ('share/doc/yt_dlp', ['README.txt']), + ('share/man/man1', ['yt-dlp.1']) + ] + root = os.path.dirname(os.path.abspath(__file__)) + data_files = [] + for dirname, files in files_spec: + resfiles = [] + for fn in files: + if not os.path.exists(fn): + warnings.warn('Skipping file %s since it is not present. Try running `make pypi-files` first' % fn) + else: + resfiles.append(fn) + data_files.append((dirname, resfiles)) + + params = { + 'data_files': data_files, + } + + if setuptools_available: + params['entry_points'] = {'console_scripts': ['yt-dlp = yt_dlp:main']} + else: + params['scripts'] = ['yt-dlp'] class build_lazy_extractors(Command): @@ -64,7 +98,11 @@ class build_lazy_extractors(Command): dry_run=self.dry_run) -packages = find_packages(exclude=('youtube_dl', 'test', 'ytdlp_plugins')) +if setuptools_available: + packages = find_packages(exclude=('youtube_dl', 'youtube_dlc', 'test', 'ytdlp_plugins')) +else: + packages = ['yt_dlp', 'yt_dlp.downloader', 'yt_dlp.extractor', 'yt_dlp.postprocessor'] + setup( name='yt-dlp', diff --git a/yt_dlp/update.py b/yt_dlp/update.py index 8160dab37..4fbe7bd7e 100644 --- a/yt_dlp/update.py +++ b/yt_dlp/update.py @@ -32,10 +32,12 @@ def rsa_verify(message, signature, key): def detect_variant(): - if hasattr(sys, 'frozen') and getattr(sys, '_MEIPASS', None): - if sys._MEIPASS == os.path.dirname(sys.executable): - return 'dir' - return 'exe' + if hasattr(sys, 'frozen'): + if getattr(sys, '_MEIPASS', None): + if sys._MEIPASS == os.path.dirname(sys.executable): + return 'dir' + return 'exe' + return 'py2exe' elif isinstance(globals().get('__loader__'), zipimporter): return 'zip' elif os.path.basename(sys.argv[0]) == '__main__.py': @@ -43,6 +45,20 @@ def detect_variant(): return 'unknown' +_NON_UPDATEABLE_REASONS = { + 'exe': None, + 'zip': None, + 'dir': 'Auto-update is not supported for unpackaged windows executable. Re-download the latest release', + 'py2exe': 'There is no official release for py2exe executable. Build it again with the latest source code', + 'source': 'You cannot update when running from source code', + 'unknown': 'It looks like you installed yt-dlp with a package manager, pip, setup.py or a tarball. Use that to update', +} + + +def is_non_updateable(): + return _NON_UPDATEABLE_REASONS.get(detect_variant(), _NON_UPDATEABLE_REASONS['unknown']) + + def update_self(to_screen, verbose, opener): ''' Exists for backward compatibility. Use run_update(ydl) instead ''' @@ -114,14 +130,7 @@ def run_update(ydl): ydl.to_screen(f'yt-dlp is up to date ({__version__})') return - ERRORS = { - 'exe': None, - 'zip': None, - 'dir': 'Auto-update is not supported for unpackaged windows executable. Re-download the latest release', - 'source': 'You cannot update when running from source code', - 'unknown': 'It looks like you installed yt-dlp with a package manager, pip, setup.py or a tarball. Use that to update', - } - err = ERRORS.get(detect_variant(), ERRORS['unknown']) + err = is_non_updateable() if err: ydl.to_screen(f'Latest version: {version_id}, Current version: {__version__}') return report_error(err, expected=True) diff --git a/yt_dlp/utils.py b/yt_dlp/utils.py index 7a77edf4c..b79b79688 100644 --- a/yt_dlp/utils.py +++ b/yt_dlp/utils.py @@ -4521,11 +4521,10 @@ def is_outdated_version(version, limit, assume_new=True): def ytdl_is_updateable(): """ Returns if yt-dlp can be updated with -U """ - return False - from zipimport import zipimporter + from .update import is_non_updateable - return isinstance(globals().get('__loader__'), zipimporter) or hasattr(sys, 'frozen') + return not is_non_updateable() def args_to_str(args):