新增Win7离线构建并调整PDF采购章及供需方签章位置
This commit is contained in:
@@ -0,0 +1,122 @@
|
||||
"""Offline setup and EXE build, intended to run on the Windows 7 server."""
|
||||
import argparse
|
||||
from datetime import datetime
|
||||
import hashlib
|
||||
import importlib.metadata
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import platform
|
||||
import shutil
|
||||
import struct
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
ARCH = 'x64' if struct.calcsize('P') == 8 else 'x86'
|
||||
|
||||
|
||||
def run(args, **kwargs):
|
||||
print('>', subprocess.list2cmdline([str(x) for x in args]), flush=True)
|
||||
subprocess.run([str(x) for x in args], check=True, **kwargs)
|
||||
|
||||
|
||||
def verify():
|
||||
manifest = json.loads((ROOT/'SHA256SUMS.json').read_text(encoding='utf-8'))
|
||||
for name, digest in manifest.items():
|
||||
path = (ROOT/name).resolve()
|
||||
if ROOT.resolve() not in path.parents:
|
||||
raise RuntimeError('Invalid manifest path')
|
||||
if hashlib.sha256(path.read_bytes()).hexdigest() != digest:
|
||||
raise RuntimeError('File checksum mismatch: ' + name)
|
||||
print('Package checksums verified.', flush=True)
|
||||
|
||||
|
||||
def check_python():
|
||||
if os.name != 'nt' or sys.version_info[:3] != (3, 8, 10):
|
||||
raise RuntimeError('Use the bundled official Windows Python 3.8.10 installer first.')
|
||||
version = sys.getwindowsversion()
|
||||
if (version.major, version.minor) < (6, 1) or ((version.major, version.minor) == (6, 1) and version.service_pack_major < 1):
|
||||
raise RuntimeError('Windows 7 SP1 or later is required.')
|
||||
print('Python:', sys.version.split()[0], ARCH, '| OS:', platform.platform(), flush=True)
|
||||
if (version.major, version.minor) != (6, 1):
|
||||
print('NOTE: This is not Windows 7. The resulting EXE must still be built/tested on Windows 7.', flush=True)
|
||||
|
||||
|
||||
def setup():
|
||||
check_python()
|
||||
verify()
|
||||
env_dir = ROOT/'.buildenv'
|
||||
python = env_dir/'Scripts/python.exe'
|
||||
if not python.exists():
|
||||
run([sys.executable, '-m', 'venv', env_dir])
|
||||
wheels = ROOT/'wheels'/ARCH
|
||||
options = ['--no-index', '--find-links', wheels, '--disable-pip-version-check', '--no-cache-dir']
|
||||
run([python, '-m', 'pip', 'install'] + options + ['pip==25.0.1', 'setuptools==68.2.2', 'wheel==0.45.1'])
|
||||
run([python, '-m', 'pip', 'install'] + options + ['-r', ROOT/'win7/requirements-win7.txt'])
|
||||
run([python, '-m', 'pip', 'check'])
|
||||
run([python, ROOT/'win7/selftest.py', ROOT/'po_app'])
|
||||
print('SETUP COMPLETE. Run 02_build_exe.cmd next.', flush=True)
|
||||
|
||||
|
||||
def build():
|
||||
check_python()
|
||||
verify()
|
||||
# Running from the dedicated environment keeps modern packages out of the build.
|
||||
expected = (ROOT/'.buildenv/Scripts/python.exe').resolve()
|
||||
if Path(sys.executable).resolve() != expected:
|
||||
raise RuntimeError('Run 02_build_exe.cmd after 01_install_env.cmd.')
|
||||
for line in (ROOT/'win7/requirements-win7.txt').read_text().splitlines():
|
||||
if not line or line.startswith('#'):
|
||||
continue
|
||||
package, pinned = line.split('==')
|
||||
if importlib.metadata.version(package) != pinned:
|
||||
raise RuntimeError('Dependency changed; run 01_install_env.cmd again: ' + package)
|
||||
run([sys.executable, ROOT/'win7/selftest.py', ROOT/'po_app'])
|
||||
build_id = datetime.now().strftime('%Y%m%d-%H%M%S-%f') + '-' + ARCH
|
||||
work = ROOT/'build'/build_id
|
||||
output = ROOT/'dist'/build_id
|
||||
work.mkdir(parents=True)
|
||||
output.mkdir(parents=True)
|
||||
env = os.environ.copy()
|
||||
env['PYINSTALLER_CONFIG_DIR'] = str(work/'cache')
|
||||
command = [sys.executable, '-m', 'PyInstaller', '--noconfirm', '--onefile', '--console',
|
||||
'--noupx', '--name', 'PO_Server_Win7', '--distpath', output,
|
||||
'--workpath', work/'temp', '--specpath', work,
|
||||
'--paths', ROOT/'po_app',
|
||||
'--add-data', str(ROOT/'po_app/templates') + ';templates',
|
||||
'--add-data', str(ROOT/'po_app/static') + ';static',
|
||||
'--hidden-import', 'sqlalchemy.dialects.sqlite',
|
||||
'--hidden-import', 'openpyxl', '--hidden-import', 'xlrd',
|
||||
'--collect-all', 'reportlab', ROOT/'po_app/app.py']
|
||||
log_path = work/'build.log'
|
||||
print('Building EXE. Build log:', log_path, flush=True)
|
||||
with log_path.open('wb') as log:
|
||||
subprocess.run([str(x) for x in command], cwd=ROOT, env=env,
|
||||
stdout=log, stderr=subprocess.STDOUT, check=True)
|
||||
exe = output/'PO_Server_Win7.exe'
|
||||
run([sys.executable, ROOT/'win7/probe_exe.py', exe])
|
||||
info = {'built_on': platform.platform(), 'python': sys.version, 'architecture': ARCH,
|
||||
'source_commit': json.loads((ROOT/'PACKAGE_INFO.json').read_text(encoding='utf-8'))['source_commit'],
|
||||
'sha256': hashlib.sha256(exe.read_bytes()).hexdigest(),
|
||||
'source_selftest': 'passed', 'exe_startup_probe': 'passed',
|
||||
'built_on_windows7': sys.getwindowsversion()[:2] == (6, 1)}
|
||||
(output/'BUILD_INFO.json').write_text(json.dumps(info, ensure_ascii=False, indent=2), encoding='utf-8')
|
||||
shutil.copy2(ROOT/'README_WIN7.txt', output/'README_WIN7.txt')
|
||||
for pdf in (ROOT/'manual').glob('*.pdf'):
|
||||
shutil.copy2(pdf, output/pdf.name)
|
||||
print('BUILD COMPLETE:', exe, flush=True)
|
||||
print('Copy EXE to a fixed service folder. Keep the original instance/uploads/exports when upgrading.', flush=True)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('command', choices=['setup', 'build', 'verify'])
|
||||
args = parser.parse_args()
|
||||
try:
|
||||
{'setup': setup, 'build': build, 'verify': verify}[args.command]()
|
||||
except Exception:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
print('FAILED. Do not replace the running server. Check the message and build log above.', flush=True)
|
||||
sys.exit(1)
|
||||
Reference in New Issue
Block a user