Skip to content

Commit

Permalink
Overhauled compiling, updated to PyInstaller 6.2.0
Browse files Browse the repository at this point in the history
PyInstaller's 6.0.0 update brought a `--contents-directory` argument,
making most of my previous work moot. A hook is no longer needed, and
other breaking changes have been corrected.

Other `executable/build.py` changes:
- `compiled/release` is now automatically flattened to just `compiled`
- Everything in `compiled/bin` now gets flattened at once, altogether
- `executable/exclude.txt` no longer has to exist
- `executable/exclude.txt` now supports comments (//, #, or ;)
- In-progress compiles are temporarily saved to `executable/compiling`,
  with the old build not being deleted until the very end
- Any config, history, or undo files present in the old build are now
  reused. Likewise, such files in the `executable` folder will act as
  override-files and will always be used if present

The gitignore, requirements, and compiling readme have been updated too.
  • Loading branch information
thisismy-github committed Nov 12, 2023
1 parent cb17300 commit 3176ef2
Show file tree
Hide file tree
Showing 8 changed files with 83 additions and 81 deletions.
5 changes: 3 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -132,10 +132,11 @@ dmypy.json

# Instant Replay Suite
testing
executable/build
executable/compiled
executable/build*
executable/compil*
resources/*.ico
version_info*.txt
update*.txt
history*.txt
undo*.txt
*.ini
17 changes: 4 additions & 13 deletions executable/!readme.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,10 @@ Summary of contents:
build -- PyInstaller build files that are created during the first compilation.
These are not needed for anything, but speed up future compilations.

compiled -- PyInstaller's actual compilation folder. This is where the final products
are placed. Normally called "dist", but renamed to "compiled" for clarity.

compiled/release -- The compiled files for this program's main script(s). This is where compiled
files for the launcher and updater will be merged, the "include" files will
be added, and all other misc files will be placed. The launcher and updater
folders and include-files will all be merged and deleted automatically.
As the name implies, this is the folder that would be released on Github.
compiled -- PyInstaller's actual compilation folder, containing the compiled files and
executable for Instant Replay Suite's main script, irs.pyw. This is where
our resources, the updater's executable, and all of our other misc. files
will be placed. This is the folder that would be released on GitHub.

build.py -- A cross-platform Python script for compiling. This searches for a venv, uses
the .spec files to compile both our script and its updater, and then performs
Expand All @@ -31,11 +27,6 @@ Summary of contents:
automatically after compilation. Based on compilations done through a
virtualenv on Windows 10 using only the packages in requirements.txt.

hook.py -- A runtime hook added to main.spec which runs at startup. For this script,
its only purpose is adding our "bin" folder to sys.path, allowing the
executable to look for .dll and .pyd files within it, thus letting us
hide many files and cut down on clutter.

updater.py -- A cross-platform Python script for safely installing a downloaded update.
Waits for script to close, extracts a given .zip file, leaves a report
detailing success/failure as well as what files should be cleaned up
Expand Down
107 changes: 60 additions & 47 deletions executable/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@

CWD = os.path.dirname(os.path.realpath(__file__))
SCRIPT_DIR = os.path.dirname(CWD)
RELEASE_DIR = pjoin(CWD, 'compiled', 'release')
RELEASE_DIR = pjoin(CWD, 'compiling', 'release')
FINAL_RELEASE_DIR = pjoin(CWD, 'compiled')
BIN_DIR = pjoin(RELEASE_DIR, 'bin')


Expand All @@ -42,53 +43,63 @@


def get_default_resources():
''' Generates a new "!defaults.txt" file in the resource folder. '''
resource_dir = os.path.join(RELEASE_DIR, 'resources')
output = os.path.join(resource_dir, '!defaults.txt')
''' Moves the "resources" folder to the root directory
and generates a new "!defaults.txt" file. '''
resource_dir = pjoin(RELEASE_DIR, 'resources')
shutil.move(pjoin(BIN_DIR, 'resources'), resource_dir)
output = pjoin(resource_dir, '!defaults.txt')

comments = '''
// This lists the current default resources and their sizes. This
// is used to detect which resources should not be replaced while
// updating. Do not edit this file.
'''
'''

with open(output, 'w') as out:
out.write(comments.strip() + '\n\n')

for filename in os.listdir(resource_dir):
if filename[-4:] != '.txt':
path = os.path.join(resource_dir, filename)
path = pjoin(resource_dir, filename)
out.write(f'{filename}: {os.path.getsize(path)}\n')


def compile():
print(f'\nCompiling Instant Replay Suite (sys.executable="{sys.executable}")...\n')
pyinstaller = f'"{sys.executable}" -m PyInstaller'
args = f'--distpath "{pjoin(CWD, "compiled")}" --workpath "{pjoin(CWD, "build")}"'
subprocess.call(f'{pyinstaller} "{pjoin(CWD, "main.spec")}" --noconfirm {args}')
subprocess.call(f'{pyinstaller} "{pjoin(CWD, "updater.spec")}" --noconfirm {args}')

if not os.path.isdir(BIN_DIR): os.makedirs(BIN_DIR)
args = f'--distpath "{pjoin(CWD, "compiling")}" --workpath "{pjoin(CWD, "build")}"'
subprocess.run(f'{pyinstaller} "{pjoin(CWD, "main.spec")}" --noconfirm {args}')
subprocess.run(f'{pyinstaller} "{pjoin(CWD, "updater.spec")}" --noconfirm {args}')

print('Moving updater to bin folder...')
name = 'updater' + ('.exe' if PLATFORM == 'Windows' else '')
shutil.move(pjoin(CWD, 'compiled', name), pjoin(BIN_DIR, name))

print('Copying cacert.pem...')
certifi_dir = pjoin(RELEASE_DIR, 'certifi')
shutil.copy2(pjoin(certifi_dir, 'cacert.pem'), pjoin(BIN_DIR, 'cacert.pem'))
shutil.rmtree(certifi_dir)

print(f'Deleting files defined in {pjoin(CWD, "exclude.txt")}...')
with open(pjoin(CWD, 'exclude.txt')) as exclude:
for line in exclude:
line = line.strip()
if not line: continue
for path in glob.glob(pjoin(RELEASE_DIR, line.strip())):
print(f'exists={os.path.exists(path)} - {path}')
if os.path.exists(path):
if os.path.isdir(path): shutil.rmtree(path)
else: os.remove(path)
shutil.move(pjoin(os.path.dirname(RELEASE_DIR), name), pjoin(BIN_DIR, name))

for file in ('config.settings.ini', 'config.menu.ini', 'history.txt', 'undo.txt'):
print(f'Checking for pre-existing "{file}" file to reuse...')
override_path = pjoin(CWD, file)
existing_path = pjoin(FINAL_RELEASE_DIR, file)
if os.path.exists(override_path):
print(f'Copying override-file for "{file}" from current directory...')
shutil.copyfile(override_path, pjoin(RELEASE_DIR, file))
elif os.path.exists(existing_path):
print(f'Copying pre-existing "{file}" from previous build...')
shutil.copyfile(existing_path, pjoin(RELEASE_DIR, file))
else:
print(f'No pre-existing "{file}" file detected, skipping...')

exclude_path = pjoin(CWD, 'exclude.txt')
print(f'Deleting files defined in {exclude_path}...')
if os.path.exists(exclude_path):
with open(exclude_path) as exclude:
for line in exclude:
line = line.strip()
if line and line[0] not in '#;' and line[:2] != '//':
for path in glob.glob(pjoin(BIN_DIR, line)):
print(f'exists={os.path.exists(path)} - {path}')
if os.path.exists(path):
if os.path.isdir(path): shutil.rmtree(path)
else: os.remove(path)

print('Generating "!defaults.txt" file for resources...')
get_default_resources()
Expand All @@ -101,32 +112,34 @@ def compile_windows():
compile()
print(f'\nPerforming post-compilation tasks for {PLATFORM}...')

print('Moving .pyd and .dll files to bin folder...')
for pattern in ('*.pyd', '*.dll'):
for path in glob.glob(pjoin(RELEASE_DIR, pattern)):
print(f'{path} -> {pjoin(BIN_DIR, os.path.basename(path))}')
shutil.move(path, pjoin(BIN_DIR, os.path.basename(path)))

print('Moving MediaInfo.dll from pymediainfo folder to bin folder...')
old = pjoin(RELEASE_DIR, 'pymediainfo')
shutil.move(pjoin(old, 'MediaInfo.dll'), pjoin(BIN_DIR, 'MediaInfo.dll'))
os.rmdir(old)

print('Moving python3*.dll back to root folder...')
for path in glob.glob(pjoin(BIN_DIR, 'python*.dll')):
filename = os.path.basename(path)
print(path, filename, filename != 'python3.dll')
if filename != 'python3.dll' and 'com' not in filename:
shutil.move(path, pjoin(RELEASE_DIR, filename))
print('Flattening and removing all subdirectories in the "bin" folder...')
for filename in os.listdir(BIN_DIR):
folder = pjoin(BIN_DIR, filename)
if os.path.isdir(folder):
for path in glob.glob(pjoin(folder, '*.*')):
flattened_path = pjoin(BIN_DIR, os.path.basename(path))
print(f'{path} -> {flattened_path}')
shutil.move(path, flattened_path)
os.rmdir(folder)


#######################################
if __name__ == '__main__':
while True:
try:
compile_windows() if PLATFORM == 'Windows' else compile()
choice = input('\nDone! Type anything to exit, or press enter to recompile... ')
if choice != '': break

if os.path.exists(FINAL_RELEASE_DIR):
print('Deleting old build...')
shutil.rmtree(FINAL_RELEASE_DIR)
else:
print('No previous build detected, skipping...')

print('Compile finished, renaming "compiling" to "compiled"...')
os.renames(RELEASE_DIR, FINAL_RELEASE_DIR)

if input('\nDone! Type anything to exit, or press enter to recompile... '):
break
except:
import traceback
input(f'\n(!) Compile failed:\n\n{traceback.format_exc()}')
7 changes: 5 additions & 2 deletions executable/exclude.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ markupsafe
numpy
psutil
PyQt5
pywin32_system32
Pythonwin
win32com
api-ms-win*
d3dcompiler_47.dll
Expand All @@ -21,6 +21,7 @@ win32pdh.pyd
win32trace.pyd
win32ui.pyd
_asyncio.pyd
_brotli*
_bz2.pyd
_cffi_backend*
_decimal.pyd
Expand All @@ -30,4 +31,6 @@ _lzma.pyd
_multiprocessing.pyd
_overlapped.pyd
_uuid.pyd
_win32sysloader.pyd
win32/win32trace.pyd
win32/win32ui.pyd
win32/_win32sysloader.pyd
8 changes: 0 additions & 8 deletions executable/hook.py

This file was deleted.

5 changes: 3 additions & 2 deletions executable/main.spec
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import os
import sys
block_cipher = None

CWD = os.path.dirname(os.path.realpath(sys.argv[1]))
CWD = os.path.dirname(os.path.realpath(sys.argv[0]))
SCRIPT_DIR = os.path.dirname(CWD)
VERSION_FILE = os.path.join(CWD, 'version_info_main.txt')
ICON = os.path.join(CWD, 'icon_main.ico')
Expand All @@ -18,7 +18,7 @@ a = Analysis([os.path.join(SCRIPT_DIR, 'irs.pyw')],
hiddenimports=[],
hookspath=[],
hooksconfig={},
runtime_hooks=[os.path.join(CWD, 'hook.py')],
runtime_hooks=[],
excludes=[],
win_no_prefer_redirects=False,
win_private_assemblies=False,
Expand All @@ -33,6 +33,7 @@ exe = EXE(pyz,
[],
exclude_binaries=True,
name='InstantReplaySuite',
contents_directory='bin',
debug=False,
bootloader_ignore_signals=False,
strip=False,
Expand Down
2 changes: 1 addition & 1 deletion executable/updater.spec
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import os
import sys
block_cipher = None

CWD = os.path.dirname(os.path.realpath(sys.argv[1]))
CWD = os.path.dirname(os.path.realpath(sys.argv[0]))
VERSION_FILE = os.path.join(CWD, 'version_info_updater.txt')
ICON = os.path.join(CWD, 'icon_updater.ico')

Expand Down
13 changes: 7 additions & 6 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
keyboard
pymediainfo
pystray
requests
send2trash
win32-setctime
keyboard>=0.13.5
pymediainfo>=6.1.0
pyinstaller>=6.2.0
pystray>=0.19.5
requests>=2.31.0
send2trash>=1.8.2
win32-setctime>=1.1.0

0 comments on commit 3176ef2

Please sign in to comment.