diff options
Diffstat (limited to 'generate_release.py')
-rw-r--r-- | generate_release.py | 135 |
1 files changed, 93 insertions, 42 deletions
diff --git a/generate_release.py b/generate_release.py index 962fb00..8bec3a0 100644 --- a/generate_release.py +++ b/generate_release.py @@ -1,6 +1,8 @@ # Generate a windows release and a generated embedded distribution of python -# Latest python version is the argument of the script -# Only works on windows at the moment +# Latest python version is the argument of the script (or oldwin for +# vista, 7 and 32-bit versions) +# Requirements: 7z, git +# wine is required in order to build on Linux import sys import urllib @@ -8,18 +10,27 @@ import urllib.request import subprocess import shutil import os +import hashlib latest_version = sys.argv[1] -if sys.argv[2] == '-nd': - downloads_enabled = False -elif sys.argv[2] == '-d': - downloads_enabled = True +if len(sys.argv) > 2: + bitness = sys.argv[2] else: - raise Exception('No download switch specified') + bitness = '64' + +if latest_version == 'oldwin': + bitness = '32' + latest_version = '3.7.9' + suffix = 'windows-vista-7-only' +else: + suffix = 'windows' def check(code): if code != 0: raise Exception('Got nonzero exit code from command') +def check_subp(x): + if x.returncode != 0: + raise Exception('Got nonzero exit code from command') def log(line): print('[generate_release.py] ' + line) @@ -31,6 +42,35 @@ def remove_files_with_extensions(path, extensions): if os.path.splitext(file)[1] in extensions: os.remove(os.path.join(root, file)) +def download_if_not_exists(file_name, url, sha256=None): + if not os.path.exists('./' + file_name): + log('Downloading ' + file_name + '..') + data = urllib.request.urlopen(url).read() + log('Finished downloading ' + file_name) + with open('./' + file_name, 'wb') as f: + f.write(data) + if sha256: + digest = hashlib.sha256(data).hexdigest() + if digest != sha256: + log('Error: ' + file_name + ' has wrong hash: ' + digest) + sys.exit(1) + else: + log('Using existing ' + file_name) + +def wine_run_shell(command): + if os.name == 'posix': + check(os.system('wine ' + command.replace('\\', '/'))) + elif os.name == 'nt': + check(os.system(command)) + else: + raise Exception('Unsupported OS') + +def wine_run(command_parts): + if os.name == 'posix': + command_parts = ['wine',] + command_parts + if subprocess.run(command_parts).returncode != 0: + raise Exception('Got nonzero exit code from command') + # ---------- Get current release version, for later ---------- log('Getting current release version') describe_result = subprocess.run(['git', 'describe', '--tags'], stdout=subprocess.PIPE) @@ -40,43 +80,54 @@ if describe_result.returncode != 0: release_tag = describe_result.stdout.strip().decode('ascii') -# ----------- Make copy of youtube-local files using git ----------- +# ----------- Make copy of yt-local files using git ----------- -if os.path.exists('./youtube-local'): +if os.path.exists('./yt-local'): log('Removing old release') - shutil.rmtree('./youtube-local') + shutil.rmtree('./yt-local') # Export git repository - this will ensure .git and things in gitignore won't # be included. Git only supports exporting archive formats, not into -# directories, so pipe into 7z to put it into .\youtube-local (not to be +# directories, so pipe into 7z to put it into .\yt-local (not to be # confused with working directory. I'm calling it the same thing so it will # have that name when extracted from the final release zip archive) -log('Making copy of youtube-local files') -check(os.system('git archive --format tar master | 7z x -si -ttar -oyoutube-local')) +log('Making copy of yt-local files') +check(os.system('git archive --format tar master | 7z x -si -ttar -oyt-local')) -if len(os.listdir('./youtube-local')) == 0: - raise Exception('Failed to copy youtube-local files') +if len(os.listdir('./yt-local')) == 0: + raise Exception('Failed to copy yt-local files') # ----------- Generate embedded python distribution ----------- os.environ['PYTHONDONTWRITEBYTECODE'] = '1' # *.pyc files double the size of the distribution get_pip_url = 'https://bootstrap.pypa.io/get-pip.py' -latest_dist_url = 'https://www.python.org/ftp/python/' + latest_version + '/python-' + latest_version + '-embed-win32.zip' - -if downloads_enabled: - log('Downloading get-pip.py...') - get_pip = urllib.request.urlopen(get_pip_url).read() - log('Finished downloading get-pip.py') +latest_dist_url = 'https://www.python.org/ftp/python/' + latest_version + '/python-' + latest_version +if bitness == '32': + latest_dist_url += '-embed-win32.zip' +else: + latest_dist_url += '-embed-amd64.zip' + +# I've verified that all the dlls in the following are signed by Microsoft. +# Using this because Microsoft only provides installers whose files can't be +# extracted without a special tool. +if bitness == '32': + visual_c_runtime_url = 'https://github.com/yuempek/vc-archive/raw/master/archives/vc15_(14.10.25017.0)_2017_x86.7z' + visual_c_runtime_sha256 = '2549eb4d2ce4cf3a87425ea01940f74368bf1cda378ef8a8a1f1a12ed59f1547' + visual_c_name = 'vc15_(14.10.25017.0)_2017_x86.7z' + visual_c_path_to_dlls = 'runtime_minimum/System' +else: + visual_c_runtime_url = 'https://github.com/yuempek/vc-archive/raw/master/archives/vc15_(14.10.25017.0)_2017_x64.7z' + visual_c_runtime_sha256 = '4f00b824c37e1017a93fccbd5775e6ee54f824b6786f5730d257a87a3d9ce921' + visual_c_name = 'vc15_(14.10.25017.0)_2017_x64.7z' + visual_c_path_to_dlls = 'runtime_minimum/System64' - with open('./get-pip.py', 'wb') as f: - f.write(get_pip) +download_if_not_exists('get-pip.py', get_pip_url) - log('Downloading latest python distribution...') - latest_dist= urllib.request.urlopen(latest_dist_url).read() - log('Finished downloading python distribution') +python_dist_name = 'python-dist-' + latest_version + '-' + bitness + '.zip' - with open('./latest-dist.zip', 'wb') as f: - f.write(latest_dist) +download_if_not_exists(python_dist_name, latest_dist_url) +download_if_not_exists(visual_c_name, + visual_c_runtime_url, sha256=visual_c_runtime_sha256) if os.path.exists('./python'): log('Removing old python distribution') @@ -85,17 +136,17 @@ if os.path.exists('./python'): log('Extracting python distribution') -check(os.system(r'7z -y x -opython latest-dist.zip')) +check(os.system(r'7z -y x -opython ' + python_dist_name)) log('Executing get-pip.py') -os.system(r'.\python\python.exe -I get-pip.py') +wine_run(['./python/python.exe', '-I', 'get-pip.py']) ''' # Explanation of .pth, ._pth, and isolated mode ## Isolated mode We want to run in what is called isolated mode, given by the switch -I. -This mode prevents the embedded python distribution from searching in +This mode prevents the embedded python distribution from searching in global directories for imports For example, if a user has `C:\Python37` and the embedded distribution is @@ -133,35 +184,35 @@ and replaced with a .pth. Isolated mode will have to be specified manually. log('Removing ._pth') major_release = latest_version.split('.')[1] -os.remove(r'.\python\python3' + major_release + '._pth') +os.remove(r'./python/python3' + major_release + '._pth') log('Adding path_fixes.pth') -with open(r'.\python\path_fixes.pth', 'w', encoding='utf-8') as f: +with open(r'./python/path_fixes.pth', 'w', encoding='utf-8') as f: f.write("import sys; sys.path.insert(0, '')\n") '''# python3x._pth file tells the python executable where to look for files # Need to add the directory where packages are installed, -# and the parent directory (which is where the youtube-local files are) +# and the parent directory (which is where the yt-local files are) major_release = latest_version.split('.')[1] with open('./python/python3' + major_release + '._pth', 'a', encoding='utf-8') as f: f.write('.\\Lib\\site-packages\n') f.write('..\n')''' log('Inserting Microsoft C Runtime') -check(os.system(r'copy C:\Windows\SysWOW64\msvcp140.dll .\python\msvcp140.dll')) +check_subp(subprocess.run([r'7z', '-y', 'e', '-opython', visual_c_name, visual_c_path_to_dlls])) log('Installing dependencies') -check(os.system(r'.\python\python.exe -I -m pip install --no-compile -r .\requirements.txt')) +wine_run(['./python/python.exe', '-I', '-m', 'pip', 'install', '--no-compile', '-r', './requirements.txt']) log('Uninstalling unnecessary gevent stuff') -check(os.system(r'.\python\python.exe -I -m pip uninstall --yes cffi pycparser')) +wine_run(['./python/python.exe', '-I', '-m', 'pip', 'uninstall', '--yes', 'cffi', 'pycparser']) shutil.rmtree(r'./python/Lib/site-packages/gevent/tests') shutil.rmtree(r'./python/Lib/site-packages/gevent/testing') remove_files_with_extensions(r'./python/Lib/site-packages/gevent', ['.html']) # bloated html documentation log('Uninstalling pip and others') -check(os.system(r'.\python\python.exe -I -m pip uninstall --yes pip setuptools wheel')) +wine_run(['./python/python.exe', '-I', '-m', 'pip', 'uninstall', '--yes', 'pip', 'wheel']) log('Removing pyc files') # Have to do this because get-pip and some packages don't respect --no-compile remove_files_with_extensions(r'./python', ['.pyc']) @@ -182,15 +233,15 @@ log('Finished generating python distribution') # ----------- Copy generated distribution into release folder ----------- log('Copying python distribution into release folder') -shutil.copytree(r'.\python', r'.\youtube-local\python') +shutil.copytree(r'./python', r'./yt-local/python') # ----------- Create release zip ----------- -output_filename = 'youtube-local-' + release_tag + '-windows.zip' +output_filename = 'yt-local-' + release_tag + '-' + suffix + '.zip' if os.path.exists('./' + output_filename): log('Removing previous zipped release') - os.remove('.\\' + output_filename) + os.remove('./' + output_filename) log('Zipping release') -check(os.system(r'7z -mx=9 a ' + output_filename + ' .\youtube-local')) +check(os.system(r'7z -mx=9 a ' + output_filename + ' ./yt-local')) print('\n') log('Finished') |