Skip to content
Bitcoin Roots

v30.3-roots.1 · Commit 66: [PATCH 66/68] fix(release): validate notices and reuse proven promotion CI

ci/release/runner-notices.py

Before: ci/release/runner-notices.py · After: ci/release/runner-notices.py

File metadata
index 4876f59c3fb520b360d662b0a1c5b7a3c2082e3f..c4d73139b6d2d8a2bcd8eabf7df70e5a6bf02c78 100755

Download original patch · Release notes

Diff rows 1–56 of 56. Line numbers refer to the original before/after files.

+ Added− RemovedUnmarked lines are context.

Scroll the code area horizontally to read long lines. Before is on the left; after is on the right. Unified view fits narrow screens more easily.

ci/release/runner-notices.py CHANGED
@@ -47,14 +47,17 @@ def source_notices(archive, expected_hash, destination):
47
  if not member.isfile():
48
  continue
49
  path = PurePosixPath(member.name)
50
- if path.is_absolute() or '..' in path.parts or '\\' in member.name or ':' in member.name:
51
- raise ValueError('Unsafe source archive member')
52
- name = path.name.lower()
53
  selected = bool(re.fullmatch(r'(?:copying|licen[cs]e|copyright|notice)(?:[._-].*)?', name))
54
- selected |= 'LICENSES' in path.parts or name in {'ftl.txt', 'gplv2.txt'}
55
- header = name == 'sqlite3.c' or path.as_posix().endswith('/keysyms/keysyms.c')
56
  if not selected and not header:
57
  continue
 
 
 
 
58
  data = package.extractfile(member).read()
59
  if header:
60
  data = data.split(b'*/', 1)[0] + b'*/\n'
@@ -80,9 +83,16 @@ def acquire(url, checksum, destination):
80
 
81
 
82
  def depends_notices(depends, host, destination):
83
- def variables(names):
 
 
 
 
 
 
 
84
  output = run('make', '--no-print-directory', '-C', str(depends), f'HOST={host}',
85
- *[f'print-{name}' for name in names])
86
  return dict(line.split('=', 1) for line in output.splitlines() if '=' in line)
87
  selected = variables(['packages', 'host_prefix', 'SOURCES_PATH'])
88
  if not (Path(selected['host_prefix']) / 'toolchain.cmake').is_file():
@@ -90,7 +100,7 @@ def depends_notices(depends, host, destination):
90
  packages = selected['packages'].split()
91
  names = [f'{p}_{suffix}' for p in packages for suffix in
92
  ('version', 'source', 'sha256_hash', 'download_path', 'download_file', 'extra_sources')]
93
- values = variables(names)
94
  result = {}
95
  for package in packages:
96
  source = Path(values[f'{package}_source'])
@@ -100,7 +110,7 @@ def depends_notices(depends, host, destination):
100
  # Qt translations/tools are separately fetched and bundled by the recipe.
101
  if package == 'qt':
102
  extras = variables(['qt_qttranslations_file_name', 'qt_qttranslations_sha256_hash',
103
- 'qt_qttools_file_name', 'qt_qttools_sha256_hash'])
104
  for part in ('qttranslations', 'qttools'):
105
  filename = extras[f'qt_{part}_file_name']
106
  extra_hash = extras[f'qt_{part}_sha256_hash']
 
47
  if not member.isfile():
48
  continue
49
  path = PurePosixPath(member.name)
50
+ scan_path = PurePosixPath(member.name.replace('\\', '/'))
51
+ name = scan_path.name.lower()
 
52
  selected = bool(re.fullmatch(r'(?:copying|licen[cs]e|copyright|notice)(?:[._-].*)?', name))
53
+ selected |= 'LICENSES' in scan_path.parts or name in {'ftl.txt', 'gplv2.txt'}
54
+ header = name == 'sqlite3.c' or scan_path.as_posix().endswith('/keysyms/keysyms.c')
55
  if not selected and not header:
56
  continue
57
+ # Source tarballs can contain irrelevant manpage names such as
58
+ # SystemTap's function::HZ.3stap. Never write those members.
59
+ if path.is_absolute() or '..' in path.parts or '\\' in member.name or ':' in member.name:
60
+ raise ValueError(f'Unsafe source archive notice: {archive.name}: {member.name}')
61
  data = package.extractfile(member).read()
62
  if header:
63
  data = data.split(b'*/', 1)[0] + b'*/\n'
 
83
 
84
 
85
  def depends_notices(depends, host, destination):
86
+ def variables(names, packages=()):
87
+ bindings = []
88
+ for name in names:
89
+ package = next((p for p in sorted(packages, key=len, reverse=True)
90
+ if name.startswith(p + '_')), None)
91
+ if package:
92
+ # Recipes defer $(package)-based expansion until a target runs.
93
+ bindings.append(f'--eval=print-{name}: package={package}')
94
  output = run('make', '--no-print-directory', '-C', str(depends), f'HOST={host}',
95
+ *bindings, *[f'print-{name}' for name in names])
96
  return dict(line.split('=', 1) for line in output.splitlines() if '=' in line)
97
  selected = variables(['packages', 'host_prefix', 'SOURCES_PATH'])
98
  if not (Path(selected['host_prefix']) / 'toolchain.cmake').is_file():
 
100
  packages = selected['packages'].split()
101
  names = [f'{p}_{suffix}' for p in packages for suffix in
102
  ('version', 'source', 'sha256_hash', 'download_path', 'download_file', 'extra_sources')]
103
+ values = variables(names, packages)
104
  result = {}
105
  for package in packages:
106
  source = Path(values[f'{package}_source'])
 
110
  # Qt translations/tools are separately fetched and bundled by the recipe.
111
  if package == 'qt':
112
  extras = variables(['qt_qttranslations_file_name', 'qt_qttranslations_sha256_hash',
113
+ 'qt_qttools_file_name', 'qt_qttools_sha256_hash'], ['qt'])
114
  for part in ('qttranslations', 'qttools'):
115
  filename = extras[f'qt_{part}_file_name']
116
  extra_hash = extras[f'qt_{part}_sha256_hash']