[build] Allow building with py2exe (and misc fixes)

py2exe config is copied from youtube-dl
Closes #1160
This commit is contained in:
pukkandan 2021-10-04 02:25:13 +05:30
parent a1c3967307
commit 5d535b4a55
No known key found for this signature in database
GPG Key ID: 0F00D95A001F4698
5 changed files with 100 additions and 47 deletions

View File

@ -161,7 +161,7 @@ jobs:
- name: Print version - name: Print version
run: echo "${{ steps.bump_version.outputs.ytdlp_version }}" run: echo "${{ steps.bump_version.outputs.ytdlp_version }}"
- name: Run PyInstaller Script - name: Run PyInstaller Script
run: python pyinst.py 64 run: python pyinst.py
- name: Upload yt-dlp.exe Windows binary - name: Upload yt-dlp.exe Windows binary
id: upload-release-windows id: upload-release-windows
uses: actions/upload-release-asset@v1 uses: actions/upload-release-asset@v1
@ -179,7 +179,7 @@ jobs:
id: sha512_win id: sha512_win
run: echo "::set-output name=sha512_win::$((Get-FileHash dist\yt-dlp.exe -Algorithm SHA512).Hash.ToLower())" run: echo "::set-output name=sha512_win::$((Get-FileHash dist\yt-dlp.exe -Algorithm SHA512).Hash.ToLower())"
- name: Run PyInstaller Script with --onedir - name: Run PyInstaller Script with --onedir
run: python pyinst.py 64 --onedir run: python pyinst.py --onedir
- uses: papeloto/action-zip@v1 - uses: papeloto/action-zip@v1
with: with:
files: ./dist/yt-dlp files: ./dist/yt-dlp
@ -227,7 +227,7 @@ jobs:
- name: Print version - name: Print version
run: echo "${{ steps.bump_version.outputs.ytdlp_version }}" run: echo "${{ steps.bump_version.outputs.ytdlp_version }}"
- name: Run PyInstaller Script for 32 Bit - name: Run PyInstaller Script for 32 Bit
run: python pyinst.py 32 run: python pyinst.py
- name: Upload Executable yt-dlp_x86.exe - name: Upload Executable yt-dlp_x86.exe
id: upload-release-windows32 id: upload-release-windows32
uses: actions/upload-release-asset@v1 uses: actions/upload-release-asset@v1

View File

@ -13,11 +13,18 @@
) )
import PyInstaller.__main__ 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') assert arch in ('32', '64')
_x86 = '_x86' if arch == '32' else '' _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}') print(f'Building {arch}bit version with options {opts}')
FILE_DESCRIPTION = 'yt-dlp%s' % (' (32 Bit)' if _x86 else '') FILE_DESCRIPTION = 'yt-dlp%s' % (' (32 Bit)' if _x86 else '')
@ -82,4 +89,4 @@
*opts, *opts,
'yt_dlp/__main__.py', '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)

View File

@ -1,12 +1,16 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# coding: utf-8 # coding: utf-8
from setuptools import setup, Command, find_packages
import os.path import os.path
import warnings import warnings
import sys 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 # 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')) exec(compile(open('yt_dlp/version.py').read(), 'yt_dlp/version.py', 'exec'))
@ -21,32 +25,62 @@
REQUIREMENTS = ['mutagen', 'pycryptodome', 'websockets'] REQUIREMENTS = ['mutagen', 'pycryptodome', 'websockets']
if sys.argv[1:2] == ['py2exe']: if sys.argv[1:2] == ['py2exe']:
raise NotImplementedError('py2exe is not currently supported; instead, use "pyinst.py" to build with pyinstaller') 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))
files_spec = [ params = {
('share/bash-completion/completions', ['completions/bash/yt-dlp']), 'data_files': data_files,
('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 = { if setuptools_available:
'data_files': data_files, params['entry_points'] = {'console_scripts': ['yt-dlp = yt_dlp:main']}
} else:
params['entry_points'] = {'console_scripts': ['yt-dlp = yt_dlp:main']} params['scripts'] = ['yt-dlp']
class build_lazy_extractors(Command): class build_lazy_extractors(Command):
@ -64,7 +98,11 @@ def run(self):
dry_run=self.dry_run) 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( setup(
name='yt-dlp', name='yt-dlp',

View File

@ -32,10 +32,12 @@ def rsa_verify(message, signature, key):
def detect_variant(): def detect_variant():
if hasattr(sys, 'frozen') and getattr(sys, '_MEIPASS', None): if hasattr(sys, 'frozen'):
if sys._MEIPASS == os.path.dirname(sys.executable): if getattr(sys, '_MEIPASS', None):
return 'dir' if sys._MEIPASS == os.path.dirname(sys.executable):
return 'exe' return 'dir'
return 'exe'
return 'py2exe'
elif isinstance(globals().get('__loader__'), zipimporter): elif isinstance(globals().get('__loader__'), zipimporter):
return 'zip' return 'zip'
elif os.path.basename(sys.argv[0]) == '__main__.py': elif os.path.basename(sys.argv[0]) == '__main__.py':
@ -43,6 +45,20 @@ def detect_variant():
return 'unknown' 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): def update_self(to_screen, verbose, opener):
''' Exists for backward compatibility. Use run_update(ydl) instead ''' ''' Exists for backward compatibility. Use run_update(ydl) instead '''
@ -114,14 +130,7 @@ def version_tuple(version_str):
ydl.to_screen(f'yt-dlp is up to date ({__version__})') ydl.to_screen(f'yt-dlp is up to date ({__version__})')
return return
ERRORS = { err = is_non_updateable()
'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'])
if err: if err:
ydl.to_screen(f'Latest version: {version_id}, Current version: {__version__}') ydl.to_screen(f'Latest version: {version_id}, Current version: {__version__}')
return report_error(err, expected=True) return report_error(err, expected=True)

View File

@ -4521,11 +4521,10 @@ def is_outdated_version(version, limit, assume_new=True):
def ytdl_is_updateable(): def ytdl_is_updateable():
""" Returns if yt-dlp can be updated with -U """ """ 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): def args_to_str(args):