138 lines
7.8 KiB
Python
138 lines
7.8 KiB
Python
"""Build and package DSView host protocol 1 using an existing MSYS2 UCRT64 SDK."""
|
|
import argparse
|
|
import hashlib
|
|
import json
|
|
import os
|
|
from pathlib import Path
|
|
import re
|
|
import shutil
|
|
import subprocess
|
|
|
|
REVISION = '2e9e2c8e'
|
|
|
|
|
|
def recipe_digest():
|
|
"""Identify the patch, packaging recipe, adapters and their shared cores."""
|
|
recipe = Path(__file__).resolve().parent
|
|
core = recipe.parents[1] / 'python/logic_analyzer/decoders'
|
|
inputs = [recipe / 'build.py', recipe / 'setgui-host.patch', recipe / 'gate-pair-checkbox.patch', recipe / 'decoder-panel.patch', recipe / 'driver-summary.patch', recipe / 'windows-usb-events.patch', recipe / 'native/gate_summary.h']
|
|
inputs += sorted((recipe / 'decoders').rglob('*.py'))
|
|
inputs += [core / (name + '.py') for name in ('gate_timing', 'transistor_pair', 'set_uart', 'set_can', 'pm35_uart')]
|
|
digest = hashlib.sha256()
|
|
for item in inputs:
|
|
digest.update(item.relative_to(recipe.parents[1]).as_posix().encode('utf-8') + b'\0')
|
|
digest.update(hashlib.sha256(item.read_bytes()).digest())
|
|
return digest.hexdigest()
|
|
|
|
|
|
def runtime_is_current(output):
|
|
output = Path(output)
|
|
try:
|
|
stamp = json.loads((output / 'host-build.json').read_text(encoding='utf-8'))
|
|
return (stamp.get('recipe') == recipe_digest()
|
|
and stamp.get('executable') == hashlib.sha256((output / 'DSView.exe').read_bytes()).hexdigest()
|
|
and (output / 'host-protocol-1').read_text(encoding='utf-8').strip() == 'DSView host protocol 1')
|
|
except (OSError, ValueError, AttributeError):
|
|
return False
|
|
|
|
|
|
def package_decoders(output):
|
|
"""Stage sigrok adapters and the canonical parsing cores without forks."""
|
|
recipe = Path(__file__).resolve().parent
|
|
decoders = Path(output) / 'decoders'
|
|
shutil.copytree(recipe / 'decoders', decoders, dirs_exist_ok=True,
|
|
ignore=shutil.ignore_patterns('__pycache__'))
|
|
common = decoders / 'common/setgui_decoders'
|
|
common.mkdir(parents=True, exist_ok=True)
|
|
(common / '__init__.py').write_text('', encoding='utf-8')
|
|
core = recipe.parents[1] / 'python/logic_analyzer/decoders'
|
|
for name in ('gate_timing', 'transistor_pair', 'set_uart', 'set_can', 'pm35_uart'):
|
|
shutil.copy2(core / (name + '.py'), common / (name + '.py'))
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument('--source', type=Path, required=True)
|
|
parser.add_argument('--output', type=Path, required=True)
|
|
parser.add_argument('--msys-root', type=Path, default=Path(r'C:\setcorp\tools\msys64'))
|
|
args = parser.parse_args()
|
|
source, output = args.source.resolve(), args.output.resolve()
|
|
prefix = args.msys_root.resolve() / 'ucrt64'
|
|
if not (prefix / 'bin/cmake.exe').is_file():
|
|
raise SystemExit('Install the MSYS2 UCRT64 dependencies listed in README.md first.')
|
|
revision = subprocess.check_output(['git', 'rev-parse', 'HEAD'], cwd=source, text=True).strip()
|
|
if not revision.startswith(REVISION):
|
|
raise SystemExit('Expected DSView revision ' + REVISION + ', got ' + revision)
|
|
for retired_name in ('driver-summary.patch', 'gate-pair-checkbox.patch'):
|
|
retired = Path(__file__).with_name(retired_name)
|
|
if subprocess.run(['git', 'apply', '--ignore-space-change', '--reverse', '--check', str(retired)],
|
|
cwd=source, capture_output=True).returncode == 0:
|
|
subprocess.run(['git', 'apply', '--ignore-space-change', '--reverse', str(retired)], cwd=source, check=True)
|
|
for patch_name in ('setgui-host.patch', 'decoder-panel.patch', 'windows-usb-events.patch'):
|
|
patch = Path(__file__).with_name(patch_name)
|
|
applied = subprocess.run(['git', 'apply', '--ignore-space-change', '--reverse', '--check', str(patch)], cwd=source, capture_output=True)
|
|
if applied.returncode:
|
|
subprocess.run(['git', 'apply', '--ignore-space-change', '--check', str(patch)], cwd=source, check=True)
|
|
subprocess.run(['git', 'apply', '--ignore-space-change', str(patch)], cwd=source, check=True)
|
|
env = dict(os.environ)
|
|
env['PATH'] = os.pathsep.join([str(prefix/'bin'), str(args.msys_root/'usr/bin'), env.get('PATH', '')])
|
|
env['PKG_CONFIG_PATH'] = str(prefix/'lib/pkgconfig')
|
|
build = source/'build-win64'
|
|
subprocess.run([str(prefix/'bin/cmake.exe'), '-S', str(source), '-B', str(build), '-G', 'Ninja',
|
|
'-DCMAKE_POLICY_VERSION_MINIMUM=3.5', '-DCMAKE_CXX_STANDARD=11',
|
|
'-DCMAKE_POLICY_DEFAULT_CMP0167=OLD', '-DCMAKE_PREFIX_PATH='+prefix.as_posix()],
|
|
env=env, check=True)
|
|
subprocess.run([str(prefix/'bin/cmake.exe'), '--build', str(build), '--parallel', '6'], env=env, check=True)
|
|
output.mkdir(parents=True, exist_ok=True)
|
|
shutil.copy2(source/'build.dir/DSView.exe', output/'DSView.exe')
|
|
for relative in ('DSView/res', 'DSView/demo', 'libsigrokdecode4DSL/decoders', 'lang'):
|
|
item = source/relative
|
|
shutil.copytree(item, output/item.name, dirs_exist_ok=True, ignore=shutil.ignore_patterns('__pycache__'))
|
|
package_decoders(output)
|
|
for name in ('COPYING', 'NEWS25', 'NEWS31', 'ug25.pdf', 'ug31.pdf'):
|
|
shutil.copy2(source/name, output/name)
|
|
stdlib = sorted((prefix/'lib').glob('python3.*'))
|
|
stdlib = [item for item in stdlib if (item/'encodings').is_dir()]
|
|
if len(stdlib) != 1:
|
|
raise RuntimeError('Expected exactly one Python standard library in UCRT64')
|
|
shutil.copytree(stdlib[0], output/'lib'/stdlib[0].name, dirs_exist_ok=True,
|
|
ignore=shutil.ignore_patterns('__pycache__', 'site-packages', 'test', 'tests', 'idlelib', 'tkinter', 'ensurepip'))
|
|
for name in ('platforms', 'imageformats', 'iconengines', 'styles'):
|
|
shutil.copytree(prefix/'share/qt5/plugins'/name, output/name, dirs_exist_ok=True)
|
|
shutil.copytree(prefix/'share/licenses', output/'licenses', dirs_exist_ok=True)
|
|
(output/'qt.conf').write_text('[Paths]\nPrefix=.\nPlugins=.\n', encoding='utf-8')
|
|
queue = [p for p in output.rglob('*') if p.suffix.lower() in ('.exe', '.dll', '.pyd')]
|
|
seen = set()
|
|
while queue:
|
|
binary = queue.pop()
|
|
if binary in seen:
|
|
continue
|
|
seen.add(binary)
|
|
info = subprocess.check_output([str(prefix/'bin/objdump.exe'), '-p', str(binary)], env=env, text=True, errors='replace')
|
|
for dll in re.findall(r'DLL Name:\s*(\S+)', info):
|
|
dependency, target = prefix/'bin'/dll, output/dll
|
|
if dependency.is_file():
|
|
if target not in seen:
|
|
shutil.copy2(dependency, target)
|
|
queue.append(target)
|
|
elif not (Path(os.environ['SystemRoot'])/'System32'/dll).is_file() and not dll.lower().startswith(('api-ms-', 'ext-ms-')):
|
|
raise RuntimeError('Missing dependency: ' + dll)
|
|
check_env = dict(os.environ)
|
|
check_env['PATH'] = str(Path(os.environ['SystemRoot'])/'System32')
|
|
version = subprocess.check_output([str(output/'DSView.exe'), '--host-version'], env=check_env, text=True).strip()
|
|
if version != 'DSView host protocol 1':
|
|
raise RuntimeError('Host protocol self-check failed: ' + version)
|
|
(output/'host-protocol-1').write_text(version+'\n', encoding='utf-8')
|
|
for patch_name in ('setgui-host.patch', 'decoder-panel.patch', 'windows-usb-events.patch'):
|
|
shutil.copy2(Path(__file__).with_name(patch_name), output/patch_name)
|
|
(output/'SOURCE.txt').write_text('https://github.com/DreamSourceLab/DSView\nCommit: '+revision+'\nPatches: setgui-host.patch, decoder-panel.patch, windows-usb-events.patch\nLicense: GPL-3.0-or-later (COPYING)\n', encoding='utf-8')
|
|
(output/'host-build.json').write_text(json.dumps({
|
|
'recipe': recipe_digest(),
|
|
'executable': hashlib.sha256((output/'DSView.exe').read_bytes()).hexdigest(),
|
|
}, indent=2) + '\n', encoding='utf-8')
|
|
print('DSView runtime:', output)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|