From 0e5927eebfcd02a4815fcb29319a1dd3f05fd1b3 Mon Sep 17 00:00:00 2001 From: Ricardo <10128951+smplayer-dev@users.noreply.github.com> Date: Thu, 21 Oct 2021 12:48:46 +0200 Subject: [PATCH] [build] Build standalone MacOS packages (#1221) Closes #1075 Authored by: smplayer-dev --- .github/workflows/build.yml | 108 ++++++++++++++++++++++++++++++------ README.md | 15 +++++ pyinst.py | 89 ++++++++++++++++------------- yt_dlp/update.py | 34 +++++++----- 4 files changed, 175 insertions(+), 71 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5717ce8ee..296380596 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -133,6 +133,70 @@ jobs: asset_name: yt-dlp.tar.gz asset_content_type: application/gzip + build_macos: + runs-on: macos-11 + needs: build_unix + + outputs: + sha256_macos: ${{ steps.sha256_macos.outputs.sha256_macos }} + sha512_macos: ${{ steps.sha512_macos.outputs.sha512_macos }} + sha256_macos_zip: ${{ steps.sha256_macos_zip.outputs.sha256_macos_zip }} + sha512_macos_zip: ${{ steps.sha512_macos_zip.outputs.sha512_macos_zip }} + + steps: + - uses: actions/checkout@v2 + # In order to create a universal2 application, the version of python3 in /usr/bin has to be used + - name: Install Requirements + run: | + brew install coreutils + /usr/bin/pip3 install --user Pyinstaller mutagen pycryptodomex websockets + - name: Bump version + id: bump_version + run: python devscripts/update-version.py + - name: Print version + run: echo "${{ steps.bump_version.outputs.ytdlp_version }}" + - name: Run PyInstaller Script + run: /usr/bin/python3 ./pyinst.py --target-architecture universal2 --onefile + - name: Upload yt-dlp MacOS binary + id: upload-release-macos + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ needs.build_unix.outputs.upload_url }} + asset_path: ./dist/yt-dlp_macos + asset_name: yt-dlp_macos + asset_content_type: application/octet-stream + - name: Get SHA2-256SUMS for yt-dlp_macos + id: sha256_macos + run: echo "::set-output name=sha256_macos::$(sha256sum dist/yt-dlp_macos | awk '{print $1}')" + - name: Get SHA2-512SUMS for yt-dlp_macos + id: sha512_macos + run: echo "::set-output name=sha512_macos::$(sha512sum dist/yt-dlp_macos | awk '{print $1}')" + + - name: Run PyInstaller Script with --onedir + run: /usr/bin/python3 ./pyinst.py --target-architecture universal2 --onedir + - uses: papeloto/action-zip@v1 + with: + files: ./dist/yt-dlp_macos + dest: ./dist/yt-dlp_macos.zip + - name: Upload yt-dlp MacOS onedir + id: upload-release-macos-zip + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ needs.build_unix.outputs.upload_url }} + asset_path: ./dist/yt-dlp_macos.zip + asset_name: yt-dlp_macos.zip + asset_content_type: application/zip + - name: Get SHA2-256SUMS for yt-dlp_macos.zip + id: sha256_macos_zip + run: echo "::set-output name=sha256_macos_zip::$(sha256sum dist/yt-dlp_macos.zip | awk '{print $1}')" + - name: Get SHA2-512SUMS for yt-dlp_macos + id: sha512_macos_zip + run: echo "::set-output name=sha512_macos_zip::$(sha512sum dist/yt-dlp_macos.zip | awk '{print $1}')" + build_windows: runs-on: windows-latest needs: build_unix @@ -150,11 +214,11 @@ jobs: uses: actions/setup-python@v2 with: python-version: '3.8' - - name: Upgrade pip and enable wheel support - run: python -m pip install --upgrade pip setuptools wheel - name: Install Requirements # Custom pyinstaller built with https://github.com/yt-dlp/pyinstaller-builds - run: pip install "https://yt-dlp.github.io/Pyinstaller-Builds/x86_64/pyinstaller-4.5.1-py3-none-any.whl" mutagen pycryptodomex websockets + run: | + python -m pip install --upgrade pip setuptools wheel + pip install "https://yt-dlp.github.io/Pyinstaller-Builds/x86_64/pyinstaller-4.5.1-py3-none-any.whl" mutagen pycryptodomex websockets - name: Bump version id: bump_version run: python devscripts/update-version.py @@ -183,27 +247,27 @@ jobs: - uses: papeloto/action-zip@v1 with: files: ./dist/yt-dlp - dest: ./dist/yt-dlp.zip - - name: Upload yt-dlp.zip Windows onedir + dest: ./dist/yt-dlp_win.zip + - name: Upload yt-dlp Windows onedir id: upload-release-windows-zip uses: actions/upload-release-asset@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: upload_url: ${{ needs.build_unix.outputs.upload_url }} - asset_path: ./dist/yt-dlp.zip - asset_name: yt-dlp.zip + asset_path: ./dist/yt-dlp_win.zip + asset_name: yt-dlp_win.zip asset_content_type: application/zip - - name: Get SHA2-256SUMS for yt-dlp.zip + - name: Get SHA2-256SUMS for yt-dlp_win.zip id: sha256_win_zip - run: echo "::set-output name=sha256_win_zip::$((Get-FileHash dist\yt-dlp.zip -Algorithm SHA256).Hash.ToLower())" - - name: Get SHA2-512SUMS for yt-dlp.zip + run: echo "::set-output name=sha256_win_zip::$((Get-FileHash dist\yt-dlp_win.zip -Algorithm SHA256).Hash.ToLower())" + - name: Get SHA2-512SUMS for yt-dlp_win.zip id: sha512_win_zip - run: echo "::set-output name=sha512_win_zip::$((Get-FileHash dist\yt-dlp.zip -Algorithm SHA512).Hash.ToLower())" + run: echo "::set-output name=sha512_win_zip::$((Get-FileHash dist\yt-dlp_win.zip -Algorithm SHA512).Hash.ToLower())" build_windows32: runs-on: windows-latest - needs: [build_unix, build_windows] + needs: build_unix outputs: sha256_win32: ${{ steps.sha256_win32.outputs.sha256_win32 }} @@ -217,10 +281,10 @@ jobs: with: python-version: '3.7' architecture: 'x86' - - name: Upgrade pip and enable wheel support - run: python -m pip install --upgrade pip setuptools wheel - name: Install Requirements - run: pip install "https://yt-dlp.github.io/Pyinstaller-Builds/i686/pyinstaller-4.5.1-py3-none-any.whl" mutagen pycryptodomex websockets + run: | + python -m pip install --upgrade pip setuptools wheel + pip install "https://yt-dlp.github.io/Pyinstaller-Builds/i686/pyinstaller-4.5.1-py3-none-any.whl" mutagen pycryptodomex websockets - name: Bump version id: bump_version run: python devscripts/update-version.py @@ -247,7 +311,7 @@ jobs: finish: runs-on: ubuntu-latest - needs: [build_unix, build_windows, build_windows32] + needs: [build_unix, build_windows, build_windows32, build_macos] steps: - name: Make SHA2-256SUMS file @@ -255,14 +319,18 @@ jobs: SHA256_WIN: ${{ needs.build_windows.outputs.sha256_win }} SHA256_WIN_ZIP: ${{ needs.build_windows.outputs.sha256_win_zip }} SHA256_WIN32: ${{ needs.build_windows32.outputs.sha256_win32 }} + SHA256_MACOS: ${{ needs.build_macos.outputs.sha256_macos }} + SHA256_MACOS_ZIP: ${{ needs.build_macos.outputs.sha256_macos_zip }} SHA256_BIN: ${{ needs.build_unix.outputs.sha256_bin }} SHA256_TAR: ${{ needs.build_unix.outputs.sha256_tar }} run: | echo "${{ env.SHA256_WIN }} yt-dlp.exe" >> SHA2-256SUMS echo "${{ env.SHA256_WIN32 }} yt-dlp_x86.exe" >> SHA2-256SUMS + echo "${{ env.SHA256_MACOS }} yt-dlp_macos" >> SHA2-256SUMS + echo "${{ env.SHA256_MACOS_ZIP }} yt-dlp_macos.zip" >> SHA2-256SUMS echo "${{ env.SHA256_BIN }} yt-dlp" >> SHA2-256SUMS echo "${{ env.SHA256_TAR }} yt-dlp.tar.gz" >> SHA2-256SUMS - echo "${{ env.SHA256_WIN_ZIP }} yt-dlp.zip" >> SHA2-256SUMS + echo "${{ env.SHA256_WIN_ZIP }} yt-dlp_win.zip" >> SHA2-256SUMS - name: Upload 256SUMS file id: upload-sums uses: actions/upload-release-asset@v1 @@ -278,14 +346,18 @@ jobs: SHA512_WIN: ${{ needs.build_windows.outputs.sha512_win }} SHA512_WIN_ZIP: ${{ needs.build_windows.outputs.sha512_win_zip }} SHA512_WIN32: ${{ needs.build_windows32.outputs.sha512_win32 }} + SHA512_MACOS: ${{ needs.build_macos.outputs.sha512_macos }} + SHA512_MACOS_ZIP: ${{ needs.build_macos.outputs.sha512_macos_zip }} SHA512_BIN: ${{ needs.build_unix.outputs.sha512_bin }} SHA512_TAR: ${{ needs.build_unix.outputs.sha512_tar }} run: | echo "${{ env.SHA512_WIN }} yt-dlp.exe" >> SHA2-512SUMS echo "${{ env.SHA512_WIN32 }} yt-dlp_x86.exe" >> SHA2-512SUMS + echo "${{ env.SHA512_MACOS }} yt-dlp_macos" >> SHA2-512SUMS + echo "${{ env.SHA512_MACOS_ZIP }} yt-dlp_macos.zip" >> SHA2-512SUMS echo "${{ env.SHA512_BIN }} yt-dlp" >> SHA2-512SUMS echo "${{ env.SHA512_TAR }} yt-dlp.tar.gz" >> SHA2-512SUMS - echo "${{ env.SHA512_WIN_ZIP }} yt-dlp.zip" >> SHA2-512SUMS + echo "${{ env.SHA512_WIN_ZIP }} yt-dlp_win.zip" >> SHA2-512SUMS - name: Upload 512SUMS file id: upload-512sums uses: actions/upload-release-asset@v1 diff --git a/README.md b/README.md index d410d04d1..edd7d298a 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,7 @@ yt-dlp is a [youtube-dl](https://github.com/ytdl-org/youtube-dl) fork based on t * [Differences in default behavior](#differences-in-default-behavior) * [INSTALLATION](#installation) * [Update](#update) + * [Release Files](#release-files) * [Dependencies](#dependencies) * [Compile](#compile) * [USAGE AND OPTIONS](#usage-and-options) @@ -190,6 +191,20 @@ You can use `yt-dlp -U` to update if you are using the provided release. If you are using `pip`, simply re-run the same command that was used to install the program. If you have installed using Homebrew, run `brew upgrade yt-dlp/taps/yt-dlp` +### RELEASE FILES + +File|Description +:---|:--- +[yt-dlp](https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp)|Platform independant binary. Needs Python (Recommended for UNIX like OSes) +[yt-dlp.exe](https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp.exe)|Windows standalone x64 binary (Recommended for Windows) +[yt-dlp_x86.exe](https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp_x86.exe)|Windows standalone x86 (32bit) binary +[yt-dlp_win.zip](https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp_win.zip)|Unpackaged windows executable +[yt-dlp_macos](https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp_macos)|MacOS standalone executable +[yt-dlp_macos.zip](https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp_macos.zip)|Unpackaged MacOS executable +[yt-dlp.tar.gz](https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp.tar.gz)|Source tarball. Also contains manpages, completions, etc +[SHA2-512SUMS](https://github.com/yt-dlp/yt-dlp/releases/latest/download/SHA2-512SUMS)|GNU-style SHA512 sums +[SHA2-256SUMS](https://github.com/yt-dlp/yt-dlp/releases/latest/download/SHA2-256SUMS)|GNU-style SHA256 sums + ### DEPENDENCIES Python versions 3.6+ (CPython and PyPy) are supported. Other versions and implementations may or may not work correctly. diff --git a/pyinst.py b/pyinst.py index ed410e0f2..5aa83f9da 100644 --- a/pyinst.py +++ b/pyinst.py @@ -6,16 +6,24 @@ import sys import platform from PyInstaller.utils.hooks import collect_submodules -from PyInstaller.utils.win32.versioninfo import ( - VarStruct, VarFileInfo, StringStruct, StringTable, - StringFileInfo, FixedFileInfo, VSVersionInfo, SetVersion, -) + +if platform.system() == 'Windows': + from PyInstaller.utils.win32.versioninfo import ( + VarStruct, VarFileInfo, StringStruct, StringTable, + StringFileInfo, FixedFileInfo, VSVersionInfo, SetVersion, + ) import PyInstaller.__main__ +suffix = '' arch = platform.architecture()[0][:2] assert arch in ('32', '64') _x86 = '_x86' if arch == '32' else '' +if platform.system() == 'Windows': + suffix = _x86 +if platform.system() == 'Darwin': + suffix = '_macos' + # Compatability with older arguments opts = sys.argv[1:] if opts[0:1] in (['32'], ['64']): @@ -37,39 +45,40 @@ VERSION_LIST = list(map(int, VERSION_LIST)) + [0] * (4 - len(VERSION_LIST)) print('Version: %s%s' % (VERSION, _x86)) print('Remember to update the version using devscipts\\update-version.py') -VERSION_FILE = VSVersionInfo( - ffi=FixedFileInfo( - filevers=VERSION_LIST, - prodvers=VERSION_LIST, - mask=0x3F, - flags=0x0, - OS=0x4, - fileType=0x1, - subtype=0x0, - date=(0, 0), - ), - kids=[ - StringFileInfo([ - StringTable( - '040904B0', [ - StringStruct('Comments', 'yt-dlp%s Command Line Interface.' % _x86), - StringStruct('CompanyName', 'https://github.com/yt-dlp'), - StringStruct('FileDescription', FILE_DESCRIPTION), - StringStruct('FileVersion', VERSION), - StringStruct('InternalName', 'yt-dlp%s' % _x86), - StringStruct( - 'LegalCopyright', - 'pukkandan.ytdlp@gmail.com | UNLICENSE', - ), - StringStruct('OriginalFilename', 'yt-dlp%s.exe' % _x86), - StringStruct('ProductName', 'yt-dlp%s' % _x86), - StringStruct( - 'ProductVersion', - '%s%s on Python %s' % (VERSION, _x86, platform.python_version())), - ])]), - VarFileInfo([VarStruct('Translation', [0, 1200])]) - ] -) +if platform.system() == 'Windows': + VERSION_FILE = VSVersionInfo( + ffi=FixedFileInfo( + filevers=VERSION_LIST, + prodvers=VERSION_LIST, + mask=0x3F, + flags=0x0, + OS=0x4, + fileType=0x1, + subtype=0x0, + date=(0, 0), + ), + kids=[ + StringFileInfo([ + StringTable( + '040904B0', [ + StringStruct('Comments', 'yt-dlp%s Command Line Interface.' % _x86), + StringStruct('CompanyName', 'https://github.com/yt-dlp'), + StringStruct('FileDescription', FILE_DESCRIPTION), + StringStruct('FileVersion', VERSION), + StringStruct('InternalName', 'yt-dlp%s' % _x86), + StringStruct( + 'LegalCopyright', + 'pukkandan.ytdlp@gmail.com | UNLICENSE', + ), + StringStruct('OriginalFilename', 'yt-dlp%s.exe' % _x86), + StringStruct('ProductName', 'yt-dlp%s' % _x86), + StringStruct( + 'ProductVersion', + '%s%s on Python %s' % (VERSION, _x86, platform.python_version())), + ])]), + VarFileInfo([VarStruct('Translation', [0, 1200])]) + ] + ) def pycryptodome_module(): @@ -90,7 +99,7 @@ dependancies = [pycryptodome_module(), 'mutagen'] + collect_submodules('websocke excluded_modules = ['test', 'ytdlp_plugins', 'youtube-dl', 'youtube-dlc'] PyInstaller.__main__.run([ - '--name=yt-dlp%s' % _x86, + '--name=yt-dlp%s' % suffix, '--icon=devscripts/logo.ico', *[f'--exclude-module={module}' for module in excluded_modules], *[f'--hidden-import={module}' for module in dependancies], @@ -99,4 +108,6 @@ PyInstaller.__main__.run([ *opts, 'yt_dlp/__main__.py', ]) -SetVersion('dist/%syt-dlp%s.exe' % ('yt-dlp/' if '--onedir' in opts else '', _x86), VERSION_FILE) + +if platform.system() == 'Windows': + SetVersion('dist/%syt-dlp%s.exe' % ('yt-dlp/' if '--onedir' in opts else '', _x86), VERSION_FILE) diff --git a/yt_dlp/update.py b/yt_dlp/update.py index e4b1280be..127b2cbc8 100644 --- a/yt_dlp/update.py +++ b/yt_dlp/update.py @@ -33,10 +33,11 @@ def rsa_verify(message, signature, key): def detect_variant(): if hasattr(sys, 'frozen'): + prefix = 'mac' if sys.platform == 'darwin' else 'win' if getattr(sys, '_MEIPASS', None): if sys._MEIPASS == os.path.dirname(sys.executable): - return 'dir' - return 'exe' + return f'{prefix}_dir' + return f'{prefix}_exe' return 'py2exe' elif isinstance(globals().get('__loader__'), zipimporter): return 'zip' @@ -46,9 +47,11 @@ def detect_variant(): _NON_UPDATEABLE_REASONS = { - 'exe': None, + 'win_exe': None, 'zip': None, - 'dir': 'Auto-update is not supported for unpackaged windows executable; Re-download the latest release', + 'mac_exe': None, + 'win_dir': 'Auto-update is not supported for unpackaged windows executable; Re-download the latest release', + 'mac_dir': 'Auto-update is not supported for unpackaged MacOS 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; Use git to pull the latest changes', 'unknown': 'It looks like you installed yt-dlp with a package manager, pip, setup.py or a tarball; Use that to update', @@ -119,6 +122,7 @@ def run_update(ydl): 'zip_3': '', 'exe_64': '.exe', 'exe_32': '_x86.exe', + 'mac_64': '_macos', } def get_bin_info(bin_or_exe, version): @@ -139,7 +143,8 @@ def run_update(ydl): return report_permission_error(filename) # PyInstaller - if hasattr(sys, 'frozen'): + variant = detect_variant() + if variant == 'win_exe': exe = filename directory = os.path.dirname(exe) if not os.access(directory, os.W_OK): @@ -161,13 +166,11 @@ def run_update(ydl): except (IOError, OSError): return report_network_error('download latest version') - if not os.access(exe + '.new', os.W_OK): - return report_permission_error(f'{exe}.new') try: with open(exe + '.new', 'wb') as outf: outf.write(newcontent) except (IOError, OSError): - return report_unable('write the new version') + return report_permission_error(f'{exe}.new') expected_sum = get_sha256sum('exe', arch) if not expected_sum: @@ -199,10 +202,10 @@ def run_update(ydl): except OSError: report_unable('delete the old version') - # Zip unix package - elif isinstance(globals().get('__loader__'), zipimporter): + elif variant in ('zip', 'mac_exe'): + pack_type = ('mac', '64') if variant == 'mac_exe' else ('zip', '3') try: - url = get_bin_info('zip', '3').get('browser_download_url') + url = get_bin_info(*pack_type).get('browser_download_url') if not url: return report_network_error('fetch updates') urlh = ydl._opener.open(url) @@ -211,11 +214,11 @@ def run_update(ydl): except (IOError, OSError): return report_network_error('download the latest version') - expected_sum = get_sha256sum('zip', '3') + expected_sum = get_sha256sum(*pack_type) if not expected_sum: ydl.report_warning('no hash information found for the release') elif hashlib.sha256(newcontent).hexdigest() != expected_sum: - return report_network_error('verify the new zip') + return report_network_error('verify the new package') try: with open(filename, 'wb') as outf: @@ -223,7 +226,10 @@ def run_update(ydl): except (IOError, OSError): return report_unable('overwrite current version') - ydl.to_screen('Updated yt-dlp to version %s; Restart yt-dlp to use the new version' % version_id) + ydl.to_screen('Updated yt-dlp to version %s; Restart yt-dlp to use the new version' % version_id) + return + + assert False, f'Unhandled variant: {variant}' ''' # UNUSED