Files
templates/tools/dsview/build.py

142 lines
8.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Сборка и упаковка модифицированного DSView на существующем MSYS2 UCRT64. Хеш рецепта
# помогает определить актуальность runtime; декодеры включаются в пакет вместе с поддержкой
# host protocol 1.
"""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()