aboutsummaryrefslogtreecommitdiff
path: root/contrib
diff options
context:
space:
mode:
Diffstat (limited to 'contrib')
-rw-r--r--contrib/debian/copyright2
-rwxr-xr-xcontrib/devtools/circular-dependencies.py9
-rwxr-xr-xcontrib/devtools/gen-manpages.sh18
-rw-r--r--contrib/devtools/pixie.py323
-rwxr-xr-xcontrib/devtools/security-check.py173
-rwxr-xr-xcontrib/devtools/symbol-check.py98
-rwxr-xr-xcontrib/devtools/test-symbol-check.py162
-rwxr-xr-xcontrib/filter-lcov.py2
-rwxr-xr-xcontrib/gitian-build.py2
-rw-r--r--contrib/gitian-descriptors/gitian-osx-signer.yml5
-rw-r--r--contrib/gitian-descriptors/gitian-osx.yml11
-rw-r--r--contrib/gitian-keys/keys.txt4
-rw-r--r--contrib/macdeploy/README.md22
-rwxr-xr-xcontrib/macdeploy/custom_dsstore.py58
-rwxr-xr-xcontrib/macdeploy/extract-osx-sdk.sh34
-rw-r--r--contrib/macdeploy/fancy.plist32
-rwxr-xr-xcontrib/macdeploy/macdeployqtplus562
-rw-r--r--contrib/testgen/base58.py2
-rwxr-xr-xcontrib/testgen/gen_key_io_test_vectors.py2
-rwxr-xr-xcontrib/zmq/zmq_sub.py2
20 files changed, 805 insertions, 718 deletions
diff --git a/contrib/debian/copyright b/contrib/debian/copyright
index 581fe712e9..a18c5bccc5 100644
--- a/contrib/debian/copyright
+++ b/contrib/debian/copyright
@@ -5,7 +5,7 @@ Upstream-Contact: Satoshi Nakamoto <satoshin@gmx.com>
Source: https://github.com/bitcoin/bitcoin
Files: *
-Copyright: 2009-2020, Bitcoin Core Developers
+Copyright: 2009-2021, Bitcoin Core Developers
License: Expat
Comment: The Bitcoin Core Developers encompasses the current developers listed on bitcoin.org,
as well as the numerous contributors to the project.
diff --git a/contrib/devtools/circular-dependencies.py b/contrib/devtools/circular-dependencies.py
index bc5f09a3e2..b1d9f2b7db 100755
--- a/contrib/devtools/circular-dependencies.py
+++ b/contrib/devtools/circular-dependencies.py
@@ -1,10 +1,11 @@
#!/usr/bin/env python3
-# Copyright (c) 2018-2019 The Bitcoin Core developers
+# Copyright (c) 2018-2020 The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
import sys
import re
+from typing import Dict, List, Set
MAPPING = {
'core_read.cpp': 'core_io.cpp',
@@ -32,7 +33,7 @@ def module_name(path):
return None
files = dict()
-deps = dict()
+deps: Dict[str, Set[str]] = dict()
RE = re.compile("^#include <(.*)>")
@@ -59,12 +60,12 @@ for arg in sorted(files.keys()):
deps[module].add(included_module)
# Loop to find the shortest (remaining) circular dependency
-have_cycle = False
+have_cycle: bool = False
while True:
shortest_cycle = None
for module in sorted(deps.keys()):
# Build the transitive closure of dependencies of module
- closure = dict()
+ closure: Dict[str, List[str]] = dict()
for dep in deps[module]:
closure[dep] = []
while True:
diff --git a/contrib/devtools/gen-manpages.sh b/contrib/devtools/gen-manpages.sh
index aa65953d83..b933b264e7 100755
--- a/contrib/devtools/gen-manpages.sh
+++ b/contrib/devtools/gen-manpages.sh
@@ -1,5 +1,5 @@
#!/usr/bin/env bash
-# Copyright (c) 2016-2019 The Bitcoin Core developers
+# Copyright (c) 2016-2020 The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
@@ -18,6 +18,22 @@ BITCOINQT=${BITCOINQT:-$BINDIR/qt/bitcoin-qt}
[ ! -x $BITCOIND ] && echo "$BITCOIND not found or not executable." && exit 1
+# Don't allow man pages to be generated for binaries built from a dirty tree
+DIRTY=""
+for cmd in $BITCOIND $BITCOINCLI $BITCOINTX $WALLET_TOOL $BITCOINQT; do
+ VERSION_OUTPUT=$($cmd --version)
+ if [[ $VERSION_OUTPUT == *"dirty"* ]]; then
+ DIRTY="${DIRTY}${cmd}\n"
+ fi
+done
+if [ -n "$DIRTY" ]
+then
+ echo -e "WARNING: the following binaries were built from a dirty tree:\n"
+ echo -e $DIRTY
+ echo "man pages generated from dirty binaries should NOT be committed."
+ echo "To properly generate man pages, please commit your changes to the above binaries, rebuild them, then run this script again."
+fi
+
# The autodetected version git tag can screw up manpage output a little bit
read -r -a BTCVER <<< "$($BITCOINCLI --version | head -n1 | awk -F'[ -]' '{ print $6, $7 }')"
diff --git a/contrib/devtools/pixie.py b/contrib/devtools/pixie.py
new file mode 100644
index 0000000000..8cf06a799a
--- /dev/null
+++ b/contrib/devtools/pixie.py
@@ -0,0 +1,323 @@
+#!/usr/bin/env python3
+# Copyright (c) 2020 Wladimir J. van der Laan
+# Distributed under the MIT software license, see the accompanying
+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+'''
+Compact, self-contained ELF implementation for bitcoin-core security checks.
+'''
+import struct
+import types
+from typing import Dict, List, Optional, Union, Tuple
+
+# you can find all these values in elf.h
+EI_NIDENT = 16
+
+# Byte indices in e_ident
+EI_CLASS = 4 # ELFCLASSxx
+EI_DATA = 5 # ELFDATAxxxx
+
+ELFCLASS32 = 1 # 32-bit
+ELFCLASS64 = 2 # 64-bit
+
+ELFDATA2LSB = 1 # little endian
+ELFDATA2MSB = 2 # big endian
+
+# relevant values for e_machine
+EM_386 = 3
+EM_PPC64 = 21
+EM_ARM = 40
+EM_AARCH64 = 183
+EM_X86_64 = 62
+EM_RISCV = 243
+
+# relevant values for e_type
+ET_DYN = 3
+
+# relevant values for sh_type
+SHT_PROGBITS = 1
+SHT_STRTAB = 3
+SHT_DYNAMIC = 6
+SHT_DYNSYM = 11
+SHT_GNU_verneed = 0x6ffffffe
+SHT_GNU_versym = 0x6fffffff
+
+# relevant values for p_type
+PT_LOAD = 1
+PT_GNU_STACK = 0x6474e551
+PT_GNU_RELRO = 0x6474e552
+
+# relevant values for p_flags
+PF_X = (1 << 0)
+PF_W = (1 << 1)
+PF_R = (1 << 2)
+
+# relevant values for d_tag
+DT_NEEDED = 1
+DT_FLAGS = 30
+
+# relevant values of `d_un.d_val' in the DT_FLAGS entry
+DF_BIND_NOW = 0x00000008
+
+# relevant d_tags with string payload
+STRING_TAGS = {DT_NEEDED}
+
+# rrlevant values for ST_BIND subfield of st_info (symbol binding)
+STB_LOCAL = 0
+
+class ELFRecord(types.SimpleNamespace):
+ '''Unified parsing for ELF records.'''
+ def __init__(self, data: bytes, offset: int, eh: 'ELFHeader', total_size: Optional[int]) -> None:
+ hdr_struct = self.STRUCT[eh.ei_class][0][eh.ei_data]
+ if total_size is not None and hdr_struct.size > total_size:
+ raise ValueError(f'{self.__class__.__name__} header size too small ({total_size} < {hdr_struct.size})')
+ for field, value in zip(self.STRUCT[eh.ei_class][1], hdr_struct.unpack(data[offset:offset + hdr_struct.size])):
+ setattr(self, field, value)
+
+def BiStruct(chars: str) -> Dict[int, struct.Struct]:
+ '''Compile a struct parser for both endians.'''
+ return {
+ ELFDATA2LSB: struct.Struct('<' + chars),
+ ELFDATA2MSB: struct.Struct('>' + chars),
+ }
+
+class ELFHeader(ELFRecord):
+ FIELDS = ['e_type', 'e_machine', 'e_version', 'e_entry', 'e_phoff', 'e_shoff', 'e_flags', 'e_ehsize', 'e_phentsize', 'e_phnum', 'e_shentsize', 'e_shnum', 'e_shstrndx']
+ STRUCT = {
+ ELFCLASS32: (BiStruct('HHIIIIIHHHHHH'), FIELDS),
+ ELFCLASS64: (BiStruct('HHIQQQIHHHHHH'), FIELDS),
+ }
+
+ def __init__(self, data: bytes, offset: int) -> None:
+ self.e_ident = data[offset:offset + EI_NIDENT]
+ if self.e_ident[0:4] != b'\x7fELF':
+ raise ValueError('invalid ELF magic')
+ self.ei_class = self.e_ident[EI_CLASS]
+ self.ei_data = self.e_ident[EI_DATA]
+
+ super().__init__(data, offset + EI_NIDENT, self, None)
+
+ def __repr__(self) -> str:
+ return f'Header(e_ident={self.e_ident!r}, e_type={self.e_type}, e_machine={self.e_machine}, e_version={self.e_version}, e_entry={self.e_entry}, e_phoff={self.e_phoff}, e_shoff={self.e_shoff}, e_flags={self.e_flags}, e_ehsize={self.e_ehsize}, e_phentsize={self.e_phentsize}, e_phnum={self.e_phnum}, e_shentsize={self.e_shentsize}, e_shnum={self.e_shnum}, e_shstrndx={self.e_shstrndx})'
+
+class Section(ELFRecord):
+ name: Optional[bytes] = None
+ FIELDS = ['sh_name', 'sh_type', 'sh_flags', 'sh_addr', 'sh_offset', 'sh_size', 'sh_link', 'sh_info', 'sh_addralign', 'sh_entsize']
+ STRUCT = {
+ ELFCLASS32: (BiStruct('IIIIIIIIII'), FIELDS),
+ ELFCLASS64: (BiStruct('IIQQQQIIQQ'), FIELDS),
+ }
+
+ def __init__(self, data: bytes, offset: int, eh: ELFHeader) -> None:
+ super().__init__(data, offset, eh, eh.e_shentsize)
+ self._data = data
+
+ def __repr__(self) -> str:
+ return f'Section(sh_name={self.sh_name}({self.name!r}), sh_type=0x{self.sh_type:x}, sh_flags={self.sh_flags}, sh_addr=0x{self.sh_addr:x}, sh_offset=0x{self.sh_offset:x}, sh_size={self.sh_size}, sh_link={self.sh_link}, sh_info={self.sh_info}, sh_addralign={self.sh_addralign}, sh_entsize={self.sh_entsize})'
+
+ def contents(self) -> bytes:
+ '''Return section contents.'''
+ return self._data[self.sh_offset:self.sh_offset + self.sh_size]
+
+class ProgramHeader(ELFRecord):
+ STRUCT = {
+ # different ELF classes have the same fields, but in a different order to optimize space versus alignment
+ ELFCLASS32: (BiStruct('IIIIIIII'), ['p_type', 'p_offset', 'p_vaddr', 'p_paddr', 'p_filesz', 'p_memsz', 'p_flags', 'p_align']),
+ ELFCLASS64: (BiStruct('IIQQQQQQ'), ['p_type', 'p_flags', 'p_offset', 'p_vaddr', 'p_paddr', 'p_filesz', 'p_memsz', 'p_align']),
+ }
+
+ def __init__(self, data: bytes, offset: int, eh: ELFHeader) -> None:
+ super().__init__(data, offset, eh, eh.e_phentsize)
+
+ def __repr__(self) -> str:
+ return f'ProgramHeader(p_type={self.p_type}, p_offset={self.p_offset}, p_vaddr={self.p_vaddr}, p_paddr={self.p_paddr}, p_filesz={self.p_filesz}, p_memsz={self.p_memsz}, p_flags={self.p_flags}, p_align={self.p_align})'
+
+class Symbol(ELFRecord):
+ STRUCT = {
+ # different ELF classes have the same fields, but in a different order to optimize space versus alignment
+ ELFCLASS32: (BiStruct('IIIBBH'), ['st_name', 'st_value', 'st_size', 'st_info', 'st_other', 'st_shndx']),
+ ELFCLASS64: (BiStruct('IBBHQQ'), ['st_name', 'st_info', 'st_other', 'st_shndx', 'st_value', 'st_size']),
+ }
+
+ def __init__(self, data: bytes, offset: int, eh: ELFHeader, symtab: Section, strings: bytes, version: Optional[bytes]) -> None:
+ super().__init__(data, offset, eh, symtab.sh_entsize)
+ self.name = _lookup_string(strings, self.st_name)
+ self.version = version
+
+ def __repr__(self) -> str:
+ return f'Symbol(st_name={self.st_name}({self.name!r}), st_value={self.st_value}, st_size={self.st_size}, st_info={self.st_info}, st_other={self.st_other}, st_shndx={self.st_shndx}, version={self.version!r})'
+
+ @property
+ def is_import(self) -> bool:
+ '''Returns whether the symbol is an imported symbol.'''
+ return self.st_bind != STB_LOCAL and self.st_shndx == 0
+
+ @property
+ def is_export(self) -> bool:
+ '''Returns whether the symbol is an exported symbol.'''
+ return self.st_bind != STB_LOCAL and self.st_shndx != 0
+
+ @property
+ def st_bind(self) -> int:
+ '''Returns STB_*.'''
+ return self.st_info >> 4
+
+class Verneed(ELFRecord):
+ DEF = (BiStruct('HHIII'), ['vn_version', 'vn_cnt', 'vn_file', 'vn_aux', 'vn_next'])
+ STRUCT = { ELFCLASS32: DEF, ELFCLASS64: DEF }
+
+ def __init__(self, data: bytes, offset: int, eh: ELFHeader) -> None:
+ super().__init__(data, offset, eh, None)
+
+ def __repr__(self) -> str:
+ return f'Verneed(vn_version={self.vn_version}, vn_cnt={self.vn_cnt}, vn_file={self.vn_file}, vn_aux={self.vn_aux}, vn_next={self.vn_next})'
+
+class Vernaux(ELFRecord):
+ DEF = (BiStruct('IHHII'), ['vna_hash', 'vna_flags', 'vna_other', 'vna_name', 'vna_next'])
+ STRUCT = { ELFCLASS32: DEF, ELFCLASS64: DEF }
+
+ def __init__(self, data: bytes, offset: int, eh: ELFHeader, strings: bytes) -> None:
+ super().__init__(data, offset, eh, None)
+ self.name = _lookup_string(strings, self.vna_name)
+
+ def __repr__(self) -> str:
+ return f'Veraux(vna_hash={self.vna_hash}, vna_flags={self.vna_flags}, vna_other={self.vna_other}, vna_name={self.vna_name}({self.name!r}), vna_next={self.vna_next})'
+
+class DynTag(ELFRecord):
+ STRUCT = {
+ ELFCLASS32: (BiStruct('II'), ['d_tag', 'd_val']),
+ ELFCLASS64: (BiStruct('QQ'), ['d_tag', 'd_val']),
+ }
+
+ def __init__(self, data: bytes, offset: int, eh: ELFHeader, section: Section) -> None:
+ super().__init__(data, offset, eh, section.sh_entsize)
+
+ def __repr__(self) -> str:
+ return f'DynTag(d_tag={self.d_tag}, d_val={self.d_val})'
+
+def _lookup_string(data: bytes, index: int) -> bytes:
+ '''Look up string by offset in ELF string table.'''
+ endx = data.find(b'\x00', index)
+ assert endx != -1
+ return data[index:endx]
+
+VERSYM_S = BiStruct('H') # .gnu_version section has a single 16-bit integer per symbol in the linked section
+def _parse_symbol_table(section: Section, strings: bytes, eh: ELFHeader, versym: bytes, verneed: Dict[int, bytes]) -> List[Symbol]:
+ '''Parse symbol table, return a list of symbols.'''
+ data = section.contents()
+ symbols = []
+ versym_iter = (verneed.get(v[0]) for v in VERSYM_S[eh.ei_data].iter_unpack(versym))
+ for ofs, version in zip(range(0, len(data), section.sh_entsize), versym_iter):
+ symbols.append(Symbol(data, ofs, eh, section, strings, version))
+ return symbols
+
+def _parse_verneed(section: Section, strings: bytes, eh: ELFHeader) -> Dict[int, bytes]:
+ '''Parse .gnu.version_r section, return a dictionary of {versym: 'GLIBC_...'}.'''
+ data = section.contents()
+ ofs = 0
+ result = {}
+ while True:
+ verneed = Verneed(data, ofs, eh)
+ aofs = verneed.vn_aux
+ while True:
+ vernaux = Vernaux(data, aofs, eh, strings)
+ result[vernaux.vna_other] = vernaux.name
+ if not vernaux.vna_next:
+ break
+ aofs += vernaux.vna_next
+
+ if not verneed.vn_next:
+ break
+ ofs += verneed.vn_next
+
+ return result
+
+def _parse_dyn_tags(section: Section, strings: bytes, eh: ELFHeader) -> List[Tuple[int, Union[bytes, int]]]:
+ '''Parse dynamic tags. Return array of tuples.'''
+ data = section.contents()
+ ofs = 0
+ result = []
+ for ofs in range(0, len(data), section.sh_entsize):
+ tag = DynTag(data, ofs, eh, section)
+ val = _lookup_string(strings, tag.d_val) if tag.d_tag in STRING_TAGS else tag.d_val
+ result.append((tag.d_tag, val))
+
+ return result
+
+class ELFFile:
+ sections: List[Section]
+ program_headers: List[ProgramHeader]
+ dyn_symbols: List[Symbol]
+ dyn_tags: List[Tuple[int, Union[bytes, int]]]
+
+ def __init__(self, data: bytes) -> None:
+ self.data = data
+ self.hdr = ELFHeader(self.data, 0)
+ self._load_sections()
+ self._load_program_headers()
+ self._load_dyn_symbols()
+ self._load_dyn_tags()
+ self._section_to_segment_mapping()
+
+ def _load_sections(self) -> None:
+ self.sections = []
+ for idx in range(self.hdr.e_shnum):
+ offset = self.hdr.e_shoff + idx * self.hdr.e_shentsize
+ self.sections.append(Section(self.data, offset, self.hdr))
+
+ shstr = self.sections[self.hdr.e_shstrndx].contents()
+ for section in self.sections:
+ section.name = _lookup_string(shstr, section.sh_name)
+
+ def _load_program_headers(self) -> None:
+ self.program_headers = []
+ for idx in range(self.hdr.e_phnum):
+ offset = self.hdr.e_phoff + idx * self.hdr.e_phentsize
+ self.program_headers.append(ProgramHeader(self.data, offset, self.hdr))
+
+ def _load_dyn_symbols(self) -> None:
+ # first, load 'verneed' section
+ verneed = None
+ for section in self.sections:
+ if section.sh_type == SHT_GNU_verneed:
+ strtab = self.sections[section.sh_link].contents() # associated string table
+ assert verneed is None # only one section of this kind please
+ verneed = _parse_verneed(section, strtab, self.hdr)
+ assert verneed is not None
+
+ # then, correlate GNU versym sections with dynamic symbol sections
+ versym = {}
+ for section in self.sections:
+ if section.sh_type == SHT_GNU_versym:
+ versym[section.sh_link] = section
+
+ # finally, load dynsym sections
+ self.dyn_symbols = []
+ for idx, section in enumerate(self.sections):
+ if section.sh_type == SHT_DYNSYM: # find dynamic symbol tables
+ strtab_data = self.sections[section.sh_link].contents() # associated string table
+ versym_data = versym[idx].contents() # associated symbol version table
+ self.dyn_symbols += _parse_symbol_table(section, strtab_data, self.hdr, versym_data, verneed)
+
+ def _load_dyn_tags(self) -> None:
+ self.dyn_tags = []
+ for idx, section in enumerate(self.sections):
+ if section.sh_type == SHT_DYNAMIC: # find dynamic tag tables
+ strtab = self.sections[section.sh_link].contents() # associated string table
+ self.dyn_tags += _parse_dyn_tags(section, strtab, self.hdr)
+
+ def _section_to_segment_mapping(self) -> None:
+ for ph in self.program_headers:
+ ph.sections = []
+ for section in self.sections:
+ if ph.p_vaddr <= section.sh_addr < (ph.p_vaddr + ph.p_memsz):
+ ph.sections.append(section)
+
+ def query_dyn_tags(self, tag_in: int) -> List[Union[int, bytes]]:
+ '''Return the values of all dyn tags with the specified tag.'''
+ return [val for (tag, val) in self.dyn_tags if tag == tag_in]
+
+
+def load(filename: str) -> ELFFile:
+ with open(filename, 'rb') as f:
+ data = f.read()
+ return ELFFile(data)
diff --git a/contrib/devtools/security-check.py b/contrib/devtools/security-check.py
index 02615edb54..7b09c42fde 100755
--- a/contrib/devtools/security-check.py
+++ b/contrib/devtools/security-check.py
@@ -6,15 +6,15 @@
Perform basic security checks on a series of executables.
Exit status will be 0 if successful, and the program will be silent.
Otherwise the exit status will be 1 and it will log which executables failed which checks.
-Needs `readelf` (for ELF), `objdump` (for PE) and `otool` (for MACHO).
+Needs `objdump` (for PE) and `otool` (for MACHO).
'''
import subprocess
import sys
import os
-
from typing import List, Optional
-READELF_CMD = os.getenv('READELF', '/usr/bin/readelf')
+import pixie
+
OBJDUMP_CMD = os.getenv('OBJDUMP', '/usr/bin/objdump')
OTOOL_CMD = os.getenv('OTOOL', '/usr/bin/otool')
@@ -26,75 +26,20 @@ def check_ELF_PIE(executable) -> bool:
'''
Check for position independent executable (PIE), allowing for address space randomization.
'''
- stdout = run_command([READELF_CMD, '-h', '-W', executable])
-
- ok = False
- for line in stdout.splitlines():
- tokens = line.split()
- if len(line)>=2 and tokens[0] == 'Type:' and tokens[1] == 'DYN':
- ok = True
- return ok
-
-def get_ELF_program_headers(executable):
- '''Return type and flags for ELF program headers'''
- stdout = run_command([READELF_CMD, '-l', '-W', executable])
-
- in_headers = False
- headers = []
- for line in stdout.splitlines():
- if line.startswith('Program Headers:'):
- in_headers = True
- count = 0
- if line == '':
- in_headers = False
- if in_headers:
- if count == 1: # header line
- header = [x.strip() for x in line.split()]
- ofs_typ = header.index('Type')
- ofs_flags = header.index('Flg')
- # assert readelf output is what we expect
- if ofs_typ == -1 or ofs_flags == -1:
- raise ValueError('Cannot parse elfread -lW output')
- elif count > 1:
- splitline = [x.strip() for x in line.split()]
- typ = splitline[ofs_typ]
- if not typ.startswith('[R'): # skip [Requesting ...]
- splitline = [x.strip() for x in line.split()]
- flags = splitline[ofs_flags]
- # check for 'R', ' E'
- if splitline[ofs_flags + 1] == 'E':
- flags += ' E'
- headers.append((typ, flags, []))
- count += 1
-
- if line.startswith(' Section to Segment mapping:'):
- in_mapping = True
- count = 0
- if line == '':
- in_mapping = False
- if in_mapping:
- if count == 1: # header line
- ofs_segment = line.find('Segment')
- ofs_sections = line.find('Sections...')
- if ofs_segment == -1 or ofs_sections == -1:
- raise ValueError('Cannot parse elfread -lW output')
- elif count > 1:
- segment = int(line[ofs_segment:ofs_sections].strip())
- sections = line[ofs_sections:].strip().split()
- headers[segment][2].extend(sections)
- count += 1
- return headers
+ elf = pixie.load(executable)
+ return elf.hdr.e_type == pixie.ET_DYN
def check_ELF_NX(executable) -> bool:
'''
Check that no sections are writable and executable (including the stack)
'''
+ elf = pixie.load(executable)
have_wx = False
have_gnu_stack = False
- for (typ, flags, _) in get_ELF_program_headers(executable):
- if typ == 'GNU_STACK':
+ for ph in elf.program_headers:
+ if ph.p_type == pixie.PT_GNU_STACK:
have_gnu_stack = True
- if 'W' in flags and 'E' in flags: # section is both writable and executable
+ if (ph.p_flags & pixie.PF_W) != 0 and (ph.p_flags & pixie.PF_X) != 0: # section is both writable and executable
have_wx = True
return have_gnu_stack and not have_wx
@@ -104,35 +49,34 @@ def check_ELF_RELRO(executable) -> bool:
GNU_RELRO program header must exist
Dynamic section must have BIND_NOW flag
'''
+ elf = pixie.load(executable)
have_gnu_relro = False
- for (typ, flags, _) in get_ELF_program_headers(executable):
- # Note: not checking flags == 'R': here as linkers set the permission differently
+ for ph in elf.program_headers:
+ # Note: not checking p_flags == PF_R: here as linkers set the permission differently
# This does not affect security: the permission flags of the GNU_RELRO program
# header are ignored, the PT_LOAD header determines the effective permissions.
# However, the dynamic linker need to write to this area so these are RW.
# Glibc itself takes care of mprotecting this area R after relocations are finished.
# See also https://marc.info/?l=binutils&m=1498883354122353
- if typ == 'GNU_RELRO':
+ if ph.p_type == pixie.PT_GNU_RELRO:
have_gnu_relro = True
have_bindnow = False
- stdout = run_command([READELF_CMD, '-d', '-W', executable])
-
- for line in stdout.splitlines():
- tokens = line.split()
- if len(tokens)>1 and tokens[1] == '(BIND_NOW)' or (len(tokens)>2 and tokens[1] == '(FLAGS)' and 'BIND_NOW' in tokens[2:]):
+ for flags in elf.query_dyn_tags(pixie.DT_FLAGS):
+ assert isinstance(flags, int)
+ if flags & pixie.DF_BIND_NOW:
have_bindnow = True
+
return have_gnu_relro and have_bindnow
def check_ELF_Canary(executable) -> bool:
'''
Check for use of stack canary
'''
- stdout = run_command([READELF_CMD, '--dyn-syms', '-W', executable])
-
+ elf = pixie.load(executable)
ok = False
- for line in stdout.splitlines():
- if '__stack_chk_fail' in line:
+ for symbol in elf.dyn_symbols:
+ if symbol.name == b'__stack_chk_fail':
ok = True
return ok
@@ -142,48 +86,55 @@ def check_ELF_separate_code(executable):
based on their permissions. This checks for missing -Wl,-z,separate-code
and potentially other problems.
'''
+ elf = pixie.load(executable)
+ R = pixie.PF_R
+ W = pixie.PF_W
+ E = pixie.PF_X
EXPECTED_FLAGS = {
# Read + execute
- '.init': 'R E',
- '.plt': 'R E',
- '.plt.got': 'R E',
- '.plt.sec': 'R E',
- '.text': 'R E',
- '.fini': 'R E',
+ b'.init': R | E,
+ b'.plt': R | E,
+ b'.plt.got': R | E,
+ b'.plt.sec': R | E,
+ b'.text': R | E,
+ b'.fini': R | E,
# Read-only data
- '.interp': 'R',
- '.note.gnu.property': 'R',
- '.note.gnu.build-id': 'R',
- '.note.ABI-tag': 'R',
- '.gnu.hash': 'R',
- '.dynsym': 'R',
- '.dynstr': 'R',
- '.gnu.version': 'R',
- '.gnu.version_r': 'R',
- '.rela.dyn': 'R',
- '.rela.plt': 'R',
- '.rodata': 'R',
- '.eh_frame_hdr': 'R',
- '.eh_frame': 'R',
- '.qtmetadata': 'R',
- '.gcc_except_table': 'R',
- '.stapsdt.base': 'R',
+ b'.interp': R,
+ b'.note.gnu.property': R,
+ b'.note.gnu.build-id': R,
+ b'.note.ABI-tag': R,
+ b'.gnu.hash': R,
+ b'.dynsym': R,
+ b'.dynstr': R,
+ b'.gnu.version': R,
+ b'.gnu.version_r': R,
+ b'.rela.dyn': R,
+ b'.rela.plt': R,
+ b'.rodata': R,
+ b'.eh_frame_hdr': R,
+ b'.eh_frame': R,
+ b'.qtmetadata': R,
+ b'.gcc_except_table': R,
+ b'.stapsdt.base': R,
# Writable data
- '.init_array': 'RW',
- '.fini_array': 'RW',
- '.dynamic': 'RW',
- '.got': 'RW',
- '.data': 'RW',
- '.bss': 'RW',
+ b'.init_array': R | W,
+ b'.fini_array': R | W,
+ b'.dynamic': R | W,
+ b'.got': R | W,
+ b'.data': R | W,
+ b'.bss': R | W,
}
+ if elf.hdr.e_machine == pixie.EM_PPC64:
+ # .plt is RW on ppc64 even with separate-code
+ EXPECTED_FLAGS[b'.plt'] = R | W
# For all LOAD program headers get mapping to the list of sections,
# and for each section, remember the flags of the associated program header.
flags_per_section = {}
- for (typ, flags, sections) in get_ELF_program_headers(executable):
- if typ == 'LOAD':
- for section in sections:
- assert(section not in flags_per_section)
- flags_per_section[section] = flags
+ for ph in elf.program_headers:
+ if ph.p_type == pixie.PT_LOAD:
+ for section in ph.sections:
+ assert(section.name not in flags_per_section)
+ flags_per_section[section.name] = ph.p_flags
# Spot-check ELF LOAD program header flags per section
# If these sections exist, check them against the expected R/W/E flags
for (section, flags) in flags_per_section.items():
@@ -236,7 +187,7 @@ def check_PE_NX(executable) -> bool:
def get_MACHO_executable_flags(executable) -> List[str]:
stdout = run_command([OTOOL_CMD, '-vh', executable])
- flags = []
+ flags: List[str] = []
for line in stdout.splitlines():
tokens = line.split()
# filter first two header lines
diff --git a/contrib/devtools/symbol-check.py b/contrib/devtools/symbol-check.py
index 6949cb7ced..b30ed62521 100755
--- a/contrib/devtools/symbol-check.py
+++ b/contrib/devtools/symbol-check.py
@@ -11,10 +11,11 @@ Example usage:
find ../gitian-builder/build -type f -executable | xargs python3 contrib/devtools/symbol-check.py
'''
import subprocess
-import re
import sys
import os
-from typing import List, Optional, Tuple
+from typing import List, Optional
+
+import pixie
# Debian 8 (Jessie) EOL: 2020. https://wiki.debian.org/DebianReleases#Production_Releases
#
@@ -50,7 +51,6 @@ IGNORE_EXPORTS = {
'_edata', '_end', '__end__', '_init', '__bss_start', '__bss_start__', '_bss_end__', '__bss_end__', '_fini', '_IO_stdin_used', 'stdin', 'stdout', 'stderr',
'environ', '_environ', '__environ',
}
-READELF_CMD = os.getenv('READELF', '/usr/bin/readelf')
CPPFILT_CMD = os.getenv('CPPFILT', '/usr/bin/c++filt')
OBJDUMP_CMD = os.getenv('OBJDUMP', '/usr/bin/objdump')
OTOOL_CMD = os.getenv('OTOOL', '/usr/bin/otool')
@@ -68,6 +68,8 @@ ELF_ALLOWED_LIBRARIES = {
'ld-linux.so.2', # 32-bit dynamic linker
'ld-linux-aarch64.so.1', # 64-bit ARM dynamic linker
'ld-linux-armhf.so.3', # 32-bit ARM dynamic linker
+'ld64.so.1', # POWER64 ABIv1 dynamic linker
+'ld64.so.2', # POWER64 ABIv2 dynamic linker
'ld-linux-riscv64-lp64d.so.1', # 64-bit RISC-V dynamic linker
# bitcoin-qt only
'libxcb.so.1', # part of X11
@@ -76,11 +78,12 @@ ELF_ALLOWED_LIBRARIES = {
'libdl.so.2' # programming interface to dynamic linker
}
ARCH_MIN_GLIBC_VER = {
-'80386': (2,1),
-'X86-64': (2,2,5),
-'ARM': (2,4),
-'AArch64':(2,17),
-'RISC-V': (2,27)
+pixie.EM_386: (2,1),
+pixie.EM_X86_64: (2,2,5),
+pixie.EM_ARM: (2,4),
+pixie.EM_AARCH64:(2,17),
+pixie.EM_PPC64: (2,17),
+pixie.EM_RISCV: (2,27)
}
MACHO_ALLOWED_LIBRARIES = {
@@ -140,29 +143,6 @@ class CPPFilt(object):
self.proc.stdout.close()
self.proc.wait()
-def read_symbols(executable, imports=True) -> List[Tuple[str, str, str]]:
- '''
- Parse an ELF executable and return a list of (symbol,version, arch) tuples
- for dynamic, imported symbols.
- '''
- p = subprocess.Popen([READELF_CMD, '--dyn-syms', '-W', '-h', executable], stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE, universal_newlines=True)
- (stdout, stderr) = p.communicate()
- if p.returncode:
- raise IOError('Could not read symbols for {}: {}'.format(executable, stderr.strip()))
- syms = []
- for line in stdout.splitlines():
- line = line.split()
- if 'Machine:' in line:
- arch = line[-1]
- if len(line)>7 and re.match('[0-9]+:$', line[0]):
- (sym, _, version) = line[7].partition('@')
- is_import = line[6] == 'UND'
- if version.startswith('@'):
- version = version[1:]
- if is_import == imports:
- syms.append((sym, version, arch))
- return syms
-
def check_version(max_versions, version, arch) -> bool:
if '_' in version:
(lib, _, ver) = version.rpartition('_')
@@ -174,46 +154,42 @@ def check_version(max_versions, version, arch) -> bool:
return False
return ver <= max_versions[lib] or lib == 'GLIBC' and ver <= ARCH_MIN_GLIBC_VER[arch]
-def elf_read_libraries(filename) -> List[str]:
- p = subprocess.Popen([READELF_CMD, '-d', '-W', filename], stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE, universal_newlines=True)
- (stdout, stderr) = p.communicate()
- if p.returncode:
- raise IOError('Error opening file')
- libraries = []
- for line in stdout.splitlines():
- tokens = line.split()
- if len(tokens)>2 and tokens[1] == '(NEEDED)':
- match = re.match(r'^Shared library: \[(.*)\]$', ' '.join(tokens[2:]))
- if match:
- libraries.append(match.group(1))
- else:
- raise ValueError('Unparseable (NEEDED) specification')
- return libraries
-
def check_imported_symbols(filename) -> bool:
+ elf = pixie.load(filename)
cppfilt = CPPFilt()
- ok = True
- for sym, version, arch in read_symbols(filename, True):
- if version and not check_version(MAX_VERSIONS, version, arch):
+ ok: bool = True
+
+ for symbol in elf.dyn_symbols:
+ if not symbol.is_import:
+ continue
+ sym = symbol.name.decode()
+ version = symbol.version.decode() if symbol.version is not None else None
+ if version and not check_version(MAX_VERSIONS, version, elf.hdr.e_machine):
print('{}: symbol {} from unsupported version {}'.format(filename, cppfilt(sym), version))
ok = False
return ok
def check_exported_symbols(filename) -> bool:
+ elf = pixie.load(filename)
cppfilt = CPPFilt()
- ok = True
- for sym,version,arch in read_symbols(filename, False):
- if arch == 'RISC-V' or sym in IGNORE_EXPORTS:
+ ok: bool = True
+ for symbol in elf.dyn_symbols:
+ if not symbol.is_export:
+ continue
+ sym = symbol.name.decode()
+ if elf.hdr.e_machine == pixie.EM_RISCV or sym in IGNORE_EXPORTS:
continue
print('{}: export of symbol {} not allowed'.format(filename, cppfilt(sym)))
ok = False
return ok
def check_ELF_libraries(filename) -> bool:
- ok = True
- for library_name in elf_read_libraries(filename):
- if library_name not in ELF_ALLOWED_LIBRARIES:
- print('{}: NEEDED library {} is not allowed'.format(filename, library_name))
+ ok: bool = True
+ elf = pixie.load(filename)
+ for library_name in elf.query_dyn_tags(pixie.DT_NEEDED):
+ assert(isinstance(library_name, bytes))
+ if library_name.decode() not in ELF_ALLOWED_LIBRARIES:
+ print('{}: NEEDED library {} is not allowed'.format(filename, library_name.decode()))
ok = False
return ok
@@ -231,7 +207,7 @@ def macho_read_libraries(filename) -> List[str]:
return libraries
def check_MACHO_libraries(filename) -> bool:
- ok = True
+ ok: bool = True
for dylib in macho_read_libraries(filename):
if dylib not in MACHO_ALLOWED_LIBRARIES:
print('{} is not in ALLOWED_LIBRARIES!'.format(dylib))
@@ -251,7 +227,7 @@ def pe_read_libraries(filename) -> List[str]:
return libraries
def check_PE_libraries(filename) -> bool:
- ok = True
+ ok: bool = True
for dylib in pe_read_libraries(filename):
if dylib not in PE_ALLOWED_LIBRARIES:
print('{} is not in ALLOWED_LIBRARIES!'.format(dylib))
@@ -284,7 +260,7 @@ def identify_executable(executable) -> Optional[str]:
return None
if __name__ == '__main__':
- retval = 0
+ retval: int = 0
for filename in sys.argv[1:]:
try:
etype = identify_executable(filename)
@@ -293,7 +269,7 @@ if __name__ == '__main__':
retval = 1
continue
- failed = []
+ failed: List[str] = []
for (name, func) in CHECKS[etype]:
if not func(filename):
failed.append(name)
diff --git a/contrib/devtools/test-symbol-check.py b/contrib/devtools/test-symbol-check.py
new file mode 100755
index 0000000000..18ed7d61e0
--- /dev/null
+++ b/contrib/devtools/test-symbol-check.py
@@ -0,0 +1,162 @@
+#!/usr/bin/env python3
+# Copyright (c) 2020 The Bitcoin Core developers
+# Distributed under the MIT software license, see the accompanying
+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+'''
+Test script for symbol-check.py
+'''
+import subprocess
+import unittest
+
+def call_symbol_check(cc, source, executable, options):
+ subprocess.run([cc,source,'-o',executable] + options, check=True)
+ p = subprocess.run(['./contrib/devtools/symbol-check.py',executable], stdout=subprocess.PIPE, universal_newlines=True)
+ return (p.returncode, p.stdout.rstrip())
+
+def get_machine(cc):
+ p = subprocess.run([cc,'-dumpmachine'], stdout=subprocess.PIPE, universal_newlines=True)
+ return p.stdout.rstrip()
+
+class TestSymbolChecks(unittest.TestCase):
+ def test_ELF(self):
+ source = 'test1.c'
+ executable = 'test1'
+ cc = 'gcc'
+
+ # there's no way to do this test for RISC-V at the moment; bionic's libc is 2.27
+ # and we allow all symbols from 2.27.
+ if 'riscv' in get_machine(cc):
+ self.skipTest("test not available for RISC-V")
+
+ # memfd_create was introduced in GLIBC 2.27, so is newer than the upper limit of
+ # all but RISC-V but still available on bionic
+ with open(source, 'w', encoding="utf8") as f:
+ f.write('''
+ #define _GNU_SOURCE
+ #include <sys/mman.h>
+
+ int memfd_create(const char *name, unsigned int flags);
+
+ int main()
+ {
+ memfd_create("test", 0);
+ return 0;
+ }
+ ''')
+
+ self.assertEqual(call_symbol_check(cc, source, executable, []),
+ (1, executable + ': symbol memfd_create from unsupported version GLIBC_2.27\n' +
+ executable + ': failed IMPORTED_SYMBOLS'))
+
+ # -lutil is part of the libc6 package so a safe bet that it's installed
+ # it's also out of context enough that it's unlikely to ever become a real dependency
+ source = 'test2.c'
+ executable = 'test2'
+ with open(source, 'w', encoding="utf8") as f:
+ f.write('''
+ #include <utmp.h>
+
+ int main()
+ {
+ login(0);
+ return 0;
+ }
+ ''')
+
+ self.assertEqual(call_symbol_check(cc, source, executable, ['-lutil']),
+ (1, executable + ': NEEDED library libutil.so.1 is not allowed\n' +
+ executable + ': failed LIBRARY_DEPENDENCIES'))
+
+ # finally, check a conforming file that simply uses a math function
+ source = 'test3.c'
+ executable = 'test3'
+ with open(source, 'w', encoding="utf8") as f:
+ f.write('''
+ #include <math.h>
+
+ int main()
+ {
+ return (int)pow(2.0, 4.0);
+ }
+ ''')
+
+ self.assertEqual(call_symbol_check(cc, source, executable, ['-lm']),
+ (0, ''))
+
+ def test_MACHO(self):
+ source = 'test1.c'
+ executable = 'test1'
+ cc = 'clang'
+
+ with open(source, 'w', encoding="utf8") as f:
+ f.write('''
+ #include <expat.h>
+
+ int main()
+ {
+ XML_ExpatVersion();
+ return 0;
+ }
+
+ ''')
+
+ self.assertEqual(call_symbol_check(cc, source, executable, ['-lexpat']),
+ (1, 'libexpat.1.dylib is not in ALLOWED_LIBRARIES!\n' +
+ executable + ': failed DYNAMIC_LIBRARIES'))
+
+ source = 'test2.c'
+ executable = 'test2'
+ with open(source, 'w', encoding="utf8") as f:
+ f.write('''
+ #include <CoreGraphics/CoreGraphics.h>
+
+ int main()
+ {
+ CGMainDisplayID();
+ return 0;
+ }
+ ''')
+
+ self.assertEqual(call_symbol_check(cc, source, executable, ['-framework', 'CoreGraphics']),
+ (0, ''))
+
+ def test_PE(self):
+ source = 'test1.c'
+ executable = 'test1.exe'
+ cc = 'x86_64-w64-mingw32-gcc'
+
+ with open(source, 'w', encoding="utf8") as f:
+ f.write('''
+ #include <pdh.h>
+
+ int main()
+ {
+ PdhConnectMachineA(NULL);
+ return 0;
+ }
+ ''')
+
+ self.assertEqual(call_symbol_check(cc, source, executable, ['-lpdh']),
+ (1, 'pdh.dll is not in ALLOWED_LIBRARIES!\n' +
+ executable + ': failed DYNAMIC_LIBRARIES'))
+
+ source = 'test2.c'
+ executable = 'test2.exe'
+ with open(source, 'w', encoding="utf8") as f:
+ f.write('''
+ #include <windows.h>
+
+ int main()
+ {
+ CoFreeUnusedLibrariesEx(0,0);
+ return 0;
+ }
+ ''')
+
+ self.assertEqual(call_symbol_check(cc, source, executable, ['-lole32']),
+ (0, ''))
+
+
+if __name__ == '__main__':
+ unittest.main()
+
diff --git a/contrib/filter-lcov.py b/contrib/filter-lcov.py
index e005cb96da..db780ad53b 100755
--- a/contrib/filter-lcov.py
+++ b/contrib/filter-lcov.py
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
-# Copyright (c) 2017-2019 The Bitcoin Core developers
+# Copyright (c) 2017-2020 The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
diff --git a/contrib/gitian-build.py b/contrib/gitian-build.py
index d498c9e2c8..f105968515 100755
--- a/contrib/gitian-build.py
+++ b/contrib/gitian-build.py
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
-# Copyright (c) 2018-2019 The Bitcoin Core developers
+# Copyright (c) 2018-2020 The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
diff --git a/contrib/gitian-descriptors/gitian-osx-signer.yml b/contrib/gitian-descriptors/gitian-osx-signer.yml
index a4f3219c22..2330ff7736 100644
--- a/contrib/gitian-descriptors/gitian-osx-signer.yml
+++ b/contrib/gitian-descriptors/gitian-osx-signer.yml
@@ -7,6 +7,7 @@ architectures:
- "amd64"
packages:
- "faketime"
+- "xorriso"
remotes:
- "url": "https://github.com/bitcoin-core/bitcoin-detached-sigs.git"
"dir": "signature"
@@ -18,7 +19,7 @@ script: |
WRAP_DIR=$HOME/wrapped
mkdir -p ${WRAP_DIR}
export PATH="$PWD":$PATH
- FAKETIME_PROGS="dmg genisoimage"
+ FAKETIME_PROGS="dmg xorrisofs"
# Create global faketime wrappers
for prog in ${FAKETIME_PROGS}; do
@@ -36,5 +37,5 @@ script: |
tar -xf ${UNSIGNED}
OSX_VOLNAME="$(cat osx_volname)"
./detached-sig-apply.sh ${UNSIGNED} signature/osx
- ${WRAP_DIR}/genisoimage -no-cache-inodes -D -l -probe -V "${OSX_VOLNAME}" -no-pad -r -dir-mode 0755 -apple -o uncompressed.dmg signed-app
+ ${WRAP_DIR}/xorrisofs -D -l -V "${OSX_VOLNAME}" -no-pad -r -dir-mode 0755 -o uncompressed.dmg signed-app
${WRAP_DIR}/dmg dmg uncompressed.dmg ${OUTDIR}/${SIGNED}
diff --git a/contrib/gitian-descriptors/gitian-osx.yml b/contrib/gitian-descriptors/gitian-osx.yml
index 4119e88003..9a7dd13c9c 100644
--- a/contrib/gitian-descriptors/gitian-osx.yml
+++ b/contrib/gitian-descriptors/gitian-osx.yml
@@ -28,6 +28,7 @@ packages:
- "python3-dev"
- "python3-setuptools"
- "fonts-tuffy"
+- "xorriso"
remotes:
- "url": "https://github.com/bitcoin/bitcoin.git"
"dir": "bitcoin"
@@ -38,9 +39,9 @@ script: |
WRAP_DIR=$HOME/wrapped
HOSTS="x86_64-apple-darwin18"
- CONFIGFLAGS="--enable-reduce-exports --disable-bench --disable-gui-tests GENISOIMAGE=$WRAP_DIR/genisoimage"
+ CONFIGFLAGS="--enable-reduce-exports --disable-bench --disable-gui-tests XORRISOFS=${WRAP_DIR}/xorrisofs DMG=${WRAP_DIR}/dmg"
FAKETIME_HOST_PROGS=""
- FAKETIME_PROGS="ar ranlib date dmg genisoimage"
+ FAKETIME_PROGS="ar ranlib date dmg xorrisofs"
export QT_RCC_TEST=1
export QT_RCC_SOURCE_DATE_OVERRIDE=1
@@ -132,12 +133,11 @@ script: |
make osx_volname
make deploydir
- OSX_VOLNAME="$(cat osx_volname)"
mkdir -p unsigned-app-${i}
cp osx_volname unsigned-app-${i}/
cp contrib/macdeploy/detached-sig-apply.sh unsigned-app-${i}
cp contrib/macdeploy/detached-sig-create.sh unsigned-app-${i}
- cp ${BASEPREFIX}/${i}/native/bin/dmg ${BASEPREFIX}/${i}/native/bin/genisoimage unsigned-app-${i}
+ cp ${BASEPREFIX}/${i}/native/bin/dmg unsigned-app-${i}
cp ${BASEPREFIX}/${i}/native/bin/${i}-codesign_allocate unsigned-app-${i}/codesign_allocate
cp ${BASEPREFIX}/${i}/native/bin/${i}-pagestuff unsigned-app-${i}/pagestuff
mv dist unsigned-app-${i}
@@ -145,8 +145,7 @@ script: |
find . | sort | tar --mtime="$REFERENCE_DATETIME" --no-recursion --mode='u+rw,go+r-w,a+X' --owner=0 --group=0 -c -T - | gzip -9n > ${OUTDIR}/${DISTNAME}-osx-unsigned.tar.gz
popd
- make deploy
- ${WRAP_DIR}/dmg dmg "${OSX_VOLNAME}.dmg" ${OUTDIR}/${DISTNAME}-osx-unsigned.dmg
+ make deploy OSX_DMG="${OUTDIR}/${DISTNAME}-osx-unsigned.dmg"
cd installed
find . -name "lib*.la" -delete
diff --git a/contrib/gitian-keys/keys.txt b/contrib/gitian-keys/keys.txt
index 0a2c1302c8..93831284c5 100644
--- a/contrib/gitian-keys/keys.txt
+++ b/contrib/gitian-keys/keys.txt
@@ -2,6 +2,8 @@
617C90010B3BD370B0AC7D424BB42E31C79111B8 Akira Takizawa
E944AE667CF960B1004BC32FCA662BE18B877A60 Andreas Schildbach
152812300785C96444D3334D17565732E08E5E41 Andrew Chow
+590B7292695AFFA5B672CBB2E13FC145CD3F4304 Antoine Poinsot (darosior)
+0AD83877C1F0CD1EE9BD660AD7CC770B81FD22A8 Ben Carman (benthecarman)
912FD3228387123DC97E0E57D5566241A0295FA9 BtcDrak
C519EBCF3B926298946783EFF6430754120EC2F4 Christian Decker (cdecker)
F20F56EF6A067F70E8A5C99FFF95FAA971697405 centaur
@@ -24,10 +26,12 @@ CA03882CB1FC067B5D3ACFE4D300116E1C875A3D MeshCollider
E777299FC265DD04793070EB944D35F9AC3DB76A Michael Ford
9692B91BBF0E8D34DFD33B1882C5C009628ECF0C Michagogo
77E72E69DA7EE0A148C06B21B34821D4944DE5F7 Nils Schneider
+F4FC70F07310028424EFC20A8E4256593F177720 Oliver Gugger
D62A803E27E7F43486035ADBBCD04D8E9CCCAC2A Paul Rabahy
37EC7D7B0A217CDB4B4E007E7FAB114267E4FA04 Peter Todd
D762373D24904A3E42F33B08B9A408E71DAAC974 Pieter Wuille (Location: Leuven, Belgium)
133EAC179436F14A5CF1B794860FEB804E669320 Pieter Wuille
+6A8F9C266528E25AEB1D7731C2371D91CB716EA7 Sebastian Falbesoner (theStack)
A8FC55F3B04BA3146F3492E79303B33A305224CB Sebastian Kung (TheCharlatan)
ED9BDF7AD6A55E232E84524257FF9BDBCC301009 Sjors Provoost
9EDAFF80E080659604F4A76B2EBB056FD847F8A7 Stephan Oeste (Emzy)
diff --git a/contrib/macdeploy/README.md b/contrib/macdeploy/README.md
index fe677e3a1f..2d9a4a2153 100644
--- a/contrib/macdeploy/README.md
+++ b/contrib/macdeploy/README.md
@@ -6,11 +6,7 @@ The `macdeployqtplus` script should not be run manually. Instead, after building
make deploy
```
-During the deployment process, the disk image window will pop up briefly
-when the fancy settings are applied. This is normal, please do not interfere,
-the process will unmount the DMG and cleanup before finishing.
-
-When complete, it will have produced `Bitcoin-Qt.dmg`.
+When complete, it will have produced `Bitcoin-Core.dmg`.
## SDK Extraction
@@ -96,22 +92,18 @@ created using these tools. The build process has been designed to avoid includin
SDK's files in Gitian's outputs. All interim tarballs are fully deterministic and may be freely
redistributed.
-`genisoimage` is used to create the initial DMG. It is not deterministic as-is, so it has been
-patched. A system `genisoimage` will work fine, but it will not be deterministic because
-the file-order will change between invocations. The patch can be seen here: [cdrkit-deterministic.patch](https://github.com/bitcoin/bitcoin/blob/master/depends/patches/native_cdrkit/cdrkit-deterministic.patch).
-No effort was made to fix this cleanly, so it likely leaks memory badly, however it's only used for
-a single invocation, so that's no real concern.
+[`xorrisofs`](https://www.gnu.org/software/xorriso/) is used to create the DMG.
-`genisoimage` cannot compress DMGs, so afterwards, the DMG tool from the
-`libdmg-hfsplus` project is used to compress it. There are several bugs in this tool and its
-maintainer has seemingly abandoned the project.
+`xorrisofs` cannot compress DMGs, so afterwards, the DMG tool from the
+`libdmg-hfsplus` project is used to compress it. There are several bugs in this
+tool and its maintainer has seemingly abandoned the project.
The DMG tool has the ability to create DMGs from scratch as well, but this functionality is
broken. Only the compression feature is currently used. Ideally, the creation could be fixed
-and `genisoimage` would no longer be necessary.
+and `xorrisofs` would no longer be necessary.
Background images and other features can be added to DMG files by inserting a
-`.DS_Store` before creation. This is generated by the script `contrib/macdeploy/custom_dsstore.py`.
+`.DS_Store` during creation.
As of OS X 10.9 Mavericks, using an Apple-blessed key to sign binaries is a requirement in
order to satisfy the new Gatekeeper requirements. Because this private key cannot be
diff --git a/contrib/macdeploy/custom_dsstore.py b/contrib/macdeploy/custom_dsstore.py
deleted file mode 100755
index 7ab42ea5d4..0000000000
--- a/contrib/macdeploy/custom_dsstore.py
+++ /dev/null
@@ -1,58 +0,0 @@
-#!/usr/bin/env python3
-# Copyright (c) 2013-2018 The Bitcoin Core developers
-# Distributed under the MIT software license, see the accompanying
-# file COPYING or http://www.opensource.org/licenses/mit-license.php.
-from ds_store import DSStore
-from mac_alias import Alias
-import sys
-
-output_file = sys.argv[1]
-package_name_ns = sys.argv[2]
-
-ds = DSStore.open(output_file, 'w+')
-ds['.']['bwsp'] = {
- 'ShowStatusBar': False,
- 'WindowBounds': '{{300, 280}, {500, 343}}',
- 'ContainerShowSidebar': False,
- 'SidebarWidth': 0,
- 'ShowTabView': False,
- 'PreviewPaneVisibility': False,
- 'ShowToolbar': False,
- 'ShowSidebar': False,
- 'ShowPathbar': True
-}
-
-icvp = {
- 'gridOffsetX': 0.0,
- 'textSize': 12.0,
- 'viewOptionsVersion': 1,
- 'backgroundImageAlias': b'\x00\x00\x00\x00\x02\x1e\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xd1\x94\\\xb0H+\x00\x05\x00\x00\x00\x98\x0fbackground.tiff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x99\xd19\xb0\xf8\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\x00\x00\r\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0b.background\x00\x00\x10\x00\x08\x00\x00\xd1\x94\\\xb0\x00\x00\x00\x11\x00\x08\x00\x00\xd19\xb0\xf8\x00\x00\x00\x01\x00\x04\x00\x00\x00\x98\x00\x0e\x00 \x00\x0f\x00b\x00a\x00c\x00k\x00g\x00r\x00o\x00u\x00n\x00d\x00.\x00t\x00i\x00f\x00f\x00\x0f\x00\x02\x00\x00\x00\x12\x00\x1c/.background/background.tiff\x00\x14\x01\x06\x00\x00\x00\x00\x01\x06\x00\x02\x00\x00\x0cMacintosh HD\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xce\x97\xab\xc3H+\x00\x00\x01\x88[\x88\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02u\xab\x8d\xd1\x94\\\xb0devrddsk\xff\xff\xff\xff\x00\x00\t \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x07bitcoin\x00\x00\x10\x00\x08\x00\x00\xce\x97\xab\xc3\x00\x00\x00\x11\x00\x08\x00\x00\xd1\x94\\\xb0\x00\x00\x00\x01\x00\x14\x01\x88[\x88\x00\x16\xa9\t\x00\x08\xfaR\x00\x08\xfaQ\x00\x02d\x8e\x00\x0e\x00\x02\x00\x00\x00\x0f\x00\x1a\x00\x0c\x00M\x00a\x00c\x00i\x00n\x00t\x00o\x00s\x00h\x00 \x00H\x00D\x00\x13\x00\x01/\x00\x00\x15\x00\x02\x00\x14\xff\xff\x00\x00\xff\xff\x00\x00',
- 'backgroundColorBlue': 1.0,
- 'iconSize': 96.0,
- 'backgroundColorGreen': 1.0,
- 'arrangeBy': 'none',
- 'showIconPreview': True,
- 'gridSpacing': 100.0,
- 'gridOffsetY': 0.0,
- 'showItemInfo': False,
- 'labelOnBottom': True,
- 'backgroundType': 2,
- 'backgroundColorRed': 1.0
-}
-alias = Alias.from_bytes(icvp['backgroundImageAlias'])
-alias.volume.name = package_name_ns
-alias.volume.posix_path = '/Volumes/' + package_name_ns
-alias.volume.disk_image_alias.target.filename = package_name_ns + '.temp.dmg'
-alias.volume.disk_image_alias.target.carbon_path = 'Macintosh HD:Users:\x00bitcoinuser:\x00Documents:\x00bitcoin:\x00bitcoin:\x00' + package_name_ns + '.temp.dmg'
-alias.volume.disk_image_alias.target.posix_path = 'Users/bitcoinuser/Documents/bitcoin/bitcoin/' + package_name_ns + '.temp.dmg'
-alias.target.carbon_path = package_name_ns + ':.background:\x00background.tiff'
-icvp['backgroundImageAlias'] = alias.to_bytes()
-ds['.']['icvp'] = icvp
-
-ds['.']['vSrn'] = ('long', 1)
-
-ds['Applications']['Iloc'] = (370, 156)
-ds['Bitcoin-Qt.app']['Iloc'] = (128, 156)
-
-ds.flush()
-ds.close()
diff --git a/contrib/macdeploy/extract-osx-sdk.sh b/contrib/macdeploy/extract-osx-sdk.sh
deleted file mode 100755
index 3c7bdf4217..0000000000
--- a/contrib/macdeploy/extract-osx-sdk.sh
+++ /dev/null
@@ -1,34 +0,0 @@
-#!/usr/bin/env bash
-# Copyright (c) 2016-2020 The Bitcoin Core developers
-# Distributed under the MIT software license, see the accompanying
-# file COPYING or http://www.opensource.org/licenses/mit-license.php.
-
-export LC_ALL=C
-set -e
-
-INPUTFILE="Xcode_7.3.1.dmg"
-HFSFILENAME="5.hfs"
-SDKDIR="Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.11.sdk"
-
-7z x "${INPUTFILE}" "${HFSFILENAME}"
-SDKNAME="$(basename "${SDKDIR}")"
-SDKDIRINODE=$(ifind -n "${SDKDIR}" "${HFSFILENAME}")
-fls "${HFSFILENAME}" -rpF ${SDKDIRINODE} |
- while read type inode filename; do
- inode="${inode::-1}"
- if [ "${filename:0:14}" = "usr/share/man/" ]; then
- continue
- fi
- filename="${SDKNAME}/$filename"
- echo "Extracting $filename ..."
- mkdir -p "$(dirname "$filename")"
- if [ "$type" = "l/l" ]; then
- ln -s "$(icat "${HFSFILENAME}" $inode)" "$filename"
- else
- icat "${HFSFILENAME}" $inode >"$filename"
- fi
-done
-echo "Building ${SDKNAME}.tar.gz ..."
-MTIME="$(istat "${HFSFILENAME}" "${SDKDIRINODE}" | perl -nle 'm/Content Modified:\s+(.*?)\s\(/ && print $1')"
-find "${SDKNAME}" | sort | tar --no-recursion --mtime="${MTIME}" --mode='u+rw,go+r-w,a+X' --owner=0 --group=0 -c -T - | gzip -9n > "${SDKNAME}.tar.gz"
-echo 'All done!'
diff --git a/contrib/macdeploy/fancy.plist b/contrib/macdeploy/fancy.plist
deleted file mode 100644
index ef277a7f14..0000000000
--- a/contrib/macdeploy/fancy.plist
+++ /dev/null
@@ -1,32 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
-<plist version="1.0">
-<dict>
- <key>window_bounds</key>
- <array>
- <integer>300</integer>
- <integer>300</integer>
- <integer>800</integer>
- <integer>620</integer>
- </array>
- <key>background_picture</key>
- <string>background.tiff</string>
- <key>icon_size</key>
- <integer>96</integer>
- <key>applications_symlink</key>
- <true/>
- <key>items_position</key>
- <dict>
- <key>Applications</key>
- <array>
- <integer>370</integer>
- <integer>156</integer>
- </array>
- <key>Bitcoin-Qt.app</key>
- <array>
- <integer>128</integer>
- <integer>156</integer>
- </array>
- </dict>
-</dict>
-</plist>
diff --git a/contrib/macdeploy/macdeployqtplus b/contrib/macdeploy/macdeployqtplus
index 524104398b..9bf3305288 100755
--- a/contrib/macdeploy/macdeployqtplus
+++ b/contrib/macdeploy/macdeployqtplus
@@ -16,9 +16,13 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
-import subprocess, sys, re, os, shutil, stat, os.path, time
-from string import Template
+import plistlib
+import sys, re, os, shutil, stat, os.path
from argparse import ArgumentParser
+from ds_store import DSStore
+from mac_alias import Alias
+from pathlib import Path
+from subprocess import PIPE, run
from typing import List, Optional
# This is ported from the original macdeployqt with modifications
@@ -49,28 +53,18 @@ class FrameworkInfo(object):
return False
def __str__(self):
- return """ Framework name: {}
- Framework directory: {}
- Framework path: {}
- Binary name: {}
- Binary directory: {}
- Binary path: {}
- Version: {}
- Install name: {}
- Deployed install name: {}
- Source file Path: {}
- Deployed Directory (relative to bundle): {}
-""".format(self.frameworkName,
- self.frameworkDirectory,
- self.frameworkPath,
- self.binaryName,
- self.binaryDirectory,
- self.binaryPath,
- self.version,
- self.installName,
- self.deployedInstallName,
- self.sourceFilePath,
- self.destinationDirectory)
+ return f""" Framework name: {frameworkName}
+ Framework directory: {self.frameworkDirectory}
+ Framework path: {self.frameworkPath}
+ Binary name: {self.binaryName}
+ Binary directory: {self.binaryDirectory}
+ Binary path: {self.binaryPath}
+ Version: {self.version}
+ Install name: {self.installName}
+ Deployed install name: {self.deployedInstallName}
+ Source file Path: {self.sourceFilePath}
+ Deployed Directory (relative to bundle): {self.destinationDirectory}
+"""
def isDylib(self):
return self.frameworkName.endswith(".dylib")
@@ -97,7 +91,7 @@ class FrameworkInfo(object):
m = cls.reOLine.match(line)
if m is None:
- raise RuntimeError("otool line could not be parsed: " + line)
+ raise RuntimeError(f"otool line could not be parsed: {line}")
path = m.group(1)
@@ -117,7 +111,7 @@ class FrameworkInfo(object):
info.version = "-"
info.installName = path
- info.deployedInstallName = "@executable_path/../Frameworks/" + info.binaryName
+ info.deployedInstallName = f"@executable_path/../Frameworks/{info.binaryName}"
info.sourceFilePath = path
info.destinationDirectory = cls.bundleFrameworkDirectory
else:
@@ -129,7 +123,7 @@ class FrameworkInfo(object):
break
i += 1
if i == len(parts):
- raise RuntimeError("Could not find .framework or .dylib in otool line: " + line)
+ raise RuntimeError(f"Could not find .framework or .dylib in otool line: {line}")
info.frameworkName = parts[i]
info.frameworkDirectory = "/".join(parts[:i])
@@ -140,7 +134,7 @@ class FrameworkInfo(object):
info.binaryPath = os.path.join(info.binaryDirectory, info.binaryName)
info.version = parts[i+2]
- info.deployedInstallName = "@executable_path/../Frameworks/" + os.path.join(info.frameworkName, info.binaryPath)
+ info.deployedInstallName = f"@executable_path/../Frameworks/{os.path.join(info.frameworkName, info.binaryPath)}"
info.destinationDirectory = os.path.join(cls.bundleFrameworkDirectory, info.frameworkName, info.binaryDirectory)
info.sourceResourcesDirectory = os.path.join(info.frameworkPath, "Resources")
@@ -154,10 +148,10 @@ class FrameworkInfo(object):
class ApplicationBundleInfo(object):
def __init__(self, path: str):
self.path = path
- appName = "Bitcoin-Qt"
- self.binaryPath = os.path.join(path, "Contents", "MacOS", appName)
+ # for backwards compatibility reasons, this must remain as Bitcoin-Qt
+ self.binaryPath = os.path.join(path, "Contents", "MacOS", "Bitcoin-Qt")
if not os.path.exists(self.binaryPath):
- raise RuntimeError("Could not find bundle binary for " + path)
+ raise RuntimeError(f"Could not find bundle binary for {path}")
self.resourcesPath = os.path.join(path, "Contents", "Resources")
self.pluginPath = os.path.join(path, "Contents", "PlugIns")
@@ -181,30 +175,26 @@ class DeploymentInfo(object):
self.pluginPath = pluginPath
def usesFramework(self, name: str) -> bool:
- nameDot = "{}.".format(name)
- libNameDot = "lib{}.".format(name)
for framework in self.deployedFrameworks:
if framework.endswith(".framework"):
- if framework.startswith(nameDot):
+ if framework.startswith(f"{name}."):
return True
elif framework.endswith(".dylib"):
- if framework.startswith(libNameDot):
+ if framework.startswith(f"lib{name}."):
return True
return False
def getFrameworks(binaryPath: str, verbose: int) -> List[FrameworkInfo]:
- if verbose >= 3:
- print("Inspecting with otool: " + binaryPath)
+ if verbose:
+ print(f"Inspecting with otool: {binaryPath}")
otoolbin=os.getenv("OTOOL", "otool")
- otool = subprocess.Popen([otoolbin, "-L", binaryPath], stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True)
- o_stdout, o_stderr = otool.communicate()
+ otool = run([otoolbin, "-L", binaryPath], stdout=PIPE, stderr=PIPE, universal_newlines=True)
if otool.returncode != 0:
- if verbose >= 1:
- sys.stderr.write(o_stderr)
- sys.stderr.flush()
- raise RuntimeError("otool failed with return code {}".format(otool.returncode))
+ sys.stderr.write(otool.stderr)
+ sys.stderr.flush()
+ raise RuntimeError(f"otool failed with return code {otool.returncode}")
- otoolLines = o_stdout.split("\n")
+ otoolLines = otool.stdout.split("\n")
otoolLines.pop(0) # First line is the inspected binary
if ".framework" in binaryPath or binaryPath.endswith(".dylib"):
otoolLines.pop(0) # Frameworks and dylibs list themselves as a dependency.
@@ -214,7 +204,7 @@ def getFrameworks(binaryPath: str, verbose: int) -> List[FrameworkInfo]:
line = line.replace("@loader_path", os.path.dirname(binaryPath))
info = FrameworkInfo.fromOtoolLibraryLine(line.strip())
if info is not None:
- if verbose >= 3:
+ if verbose:
print("Found framework:")
print(info)
libraries.append(info)
@@ -223,10 +213,10 @@ def getFrameworks(binaryPath: str, verbose: int) -> List[FrameworkInfo]:
def runInstallNameTool(action: str, *args):
installnametoolbin=os.getenv("INSTALLNAMETOOL", "install_name_tool")
- subprocess.check_call([installnametoolbin, "-"+action] + list(args))
+ run([installnametoolbin, "-"+action] + list(args), check=True)
def changeInstallName(oldName: str, newName: str, binaryPath: str, verbose: int):
- if verbose >= 3:
+ if verbose:
print("Using install_name_tool:")
print(" in", binaryPath)
print(" change reference", oldName)
@@ -234,7 +224,7 @@ def changeInstallName(oldName: str, newName: str, binaryPath: str, verbose: int)
runInstallNameTool("change", oldName, newName, binaryPath)
def changeIdentification(id: str, binaryPath: str, verbose: int):
- if verbose >= 3:
+ if verbose:
print("Using install_name_tool:")
print(" change identification in", binaryPath)
print(" to", id)
@@ -242,22 +232,22 @@ def changeIdentification(id: str, binaryPath: str, verbose: int):
def runStrip(binaryPath: str, verbose: int):
stripbin=os.getenv("STRIP", "strip")
- if verbose >= 3:
+ if verbose:
print("Using strip:")
print(" stripped", binaryPath)
- subprocess.check_call([stripbin, "-x", binaryPath])
+ run([stripbin, "-x", binaryPath], check=True)
def copyFramework(framework: FrameworkInfo, path: str, verbose: int) -> Optional[str]:
if framework.sourceFilePath.startswith("Qt"):
#standard place for Nokia Qt installer's frameworks
- fromPath = "/Library/Frameworks/" + framework.sourceFilePath
+ fromPath = f"/Library/Frameworks/{framework.sourceFilePath}"
else:
fromPath = framework.sourceFilePath
toDir = os.path.join(path, framework.destinationDirectory)
toPath = os.path.join(toDir, framework.binaryName)
if not os.path.exists(fromPath):
- raise RuntimeError("No file at " + fromPath)
+ raise RuntimeError(f"No file at {fromPath}")
if os.path.exists(toPath):
return None # Already there
@@ -266,7 +256,7 @@ def copyFramework(framework: FrameworkInfo, path: str, verbose: int) -> Optional
os.makedirs(toDir)
shutil.copy2(fromPath, toPath)
- if verbose >= 3:
+ if verbose:
print("Copied:", fromPath)
print(" to:", toPath)
@@ -280,13 +270,12 @@ def copyFramework(framework: FrameworkInfo, path: str, verbose: int) -> Optional
linkto = framework.version
if not os.path.exists(linkfrom):
os.symlink(linkto, linkfrom)
- if verbose >= 2:
- print("Linked:", linkfrom, "->", linkto)
+ print("Linked:", linkfrom, "->", linkto)
fromResourcesDir = framework.sourceResourcesDirectory
if os.path.exists(fromResourcesDir):
toResourcesDir = os.path.join(path, framework.destinationResourcesDirectory)
shutil.copytree(fromResourcesDir, toResourcesDir, symlinks=True)
- if verbose >= 3:
+ if verbose:
print("Copied resources:", fromResourcesDir)
print(" to:", toResourcesDir)
fromContentsDir = framework.sourceVersionContentsDirectory
@@ -295,7 +284,7 @@ def copyFramework(framework: FrameworkInfo, path: str, verbose: int) -> Optional
if os.path.exists(fromContentsDir):
toContentsDir = os.path.join(path, framework.destinationVersionContentsDirectory)
shutil.copytree(fromContentsDir, toContentsDir, symlinks=True)
- if verbose >= 3:
+ if verbose:
print("Copied Contents:", fromContentsDir)
print(" to:", toContentsDir)
elif framework.frameworkName.startswith("libQtGui"): # Copy qt_menu.nib (applies to non-framework layout)
@@ -303,7 +292,7 @@ def copyFramework(framework: FrameworkInfo, path: str, verbose: int) -> Optional
qtMenuNibDestinationPath = os.path.join(path, "Contents", "Resources", "qt_menu.nib")
if os.path.exists(qtMenuNibSourcePath) and not os.path.exists(qtMenuNibDestinationPath):
shutil.copytree(qtMenuNibSourcePath, qtMenuNibDestinationPath, symlinks=True)
- if verbose >= 3:
+ if verbose:
print("Copied for libQtGui:", qtMenuNibSourcePath)
print(" to:", qtMenuNibDestinationPath)
@@ -317,16 +306,14 @@ def deployFrameworks(frameworks: List[FrameworkInfo], bundlePath: str, binaryPat
framework = frameworks.pop(0)
deploymentInfo.deployedFrameworks.append(framework.frameworkName)
- if verbose >= 2:
- print("Processing", framework.frameworkName, "...")
+ print("Processing", framework.frameworkName, "...")
# Get the Qt path from one of the Qt frameworks
if deploymentInfo.qtPath is None and framework.isQtFramework():
deploymentInfo.detectQtPath(framework.frameworkDirectory)
if framework.installName.startswith("@executable_path") or framework.installName.startswith(bundlePath):
- if verbose >= 2:
- print(framework.frameworkName, "already deployed, skipping.")
+ print(framework.frameworkName, "already deployed, skipping.")
continue
# install_name_tool the new id into the binary
@@ -357,8 +344,8 @@ def deployFrameworks(frameworks: List[FrameworkInfo], bundlePath: str, binaryPat
def deployFrameworksForAppBundle(applicationBundle: ApplicationBundleInfo, strip: bool, verbose: int) -> DeploymentInfo:
frameworks = getFrameworks(applicationBundle.binaryPath, verbose)
- if len(frameworks) == 0 and verbose >= 1:
- print("Warning: Could not find any external frameworks to deploy in {}.".format(applicationBundle.path))
+ if len(frameworks) == 0:
+ print(f"Warning: Could not find any external frameworks to deploy in {applicationBundle.path}.")
return DeploymentInfo()
else:
return deployFrameworks(frameworks, applicationBundle.path, applicationBundle.binaryPath, strip, verbose)
@@ -477,8 +464,7 @@ def deployPlugins(appBundleInfo: ApplicationBundleInfo, deploymentInfo: Deployme
plugins.append((pluginDirectory, pluginName))
for pluginDirectory, pluginName in plugins:
- if verbose >= 2:
- print("Processing plugin", os.path.join(pluginDirectory, pluginName), "...")
+ print("Processing plugin", os.path.join(pluginDirectory, pluginName), "...")
sourcePath = os.path.join(deploymentInfo.pluginPath, pluginDirectory, pluginName)
destinationDirectory = os.path.join(appBundleInfo.pluginPath, pluginDirectory)
@@ -487,7 +473,7 @@ def deployPlugins(appBundleInfo: ApplicationBundleInfo, deploymentInfo: Deployme
destinationPath = os.path.join(destinationDirectory, pluginName)
shutil.copy2(sourcePath, destinationPath)
- if verbose >= 3:
+ if verbose:
print("Copied:", sourcePath)
print(" to:", destinationPath)
@@ -503,147 +489,50 @@ def deployPlugins(appBundleInfo: ApplicationBundleInfo, deploymentInfo: Deployme
if dependency.frameworkName not in deploymentInfo.deployedFrameworks:
deployFrameworks([dependency], appBundleInfo.path, destinationPath, strip, verbose, deploymentInfo)
-qt_conf="""[Paths]
-Translations=Resources
-Plugins=PlugIns
-"""
-
ap = ArgumentParser(description="""Improved version of macdeployqt.
Outputs a ready-to-deploy app in a folder "dist" and optionally wraps it in a .dmg file.
Note, that the "dist" folder will be deleted before deploying on each run.
-Optionally, Qt translation files (.qm) and additional resources can be added to the bundle.
-
-Also optionally signs the .app bundle; set the CODESIGNARGS environment variable to pass arguments
-to the codesign tool.
-E.g. CODESIGNARGS='--sign "Developer ID Application: ..." --keychain /encrypted/foo.keychain'""")
+Optionally, Qt translation files (.qm) can be added to the bundle.""")
ap.add_argument("app_bundle", nargs=1, metavar="app-bundle", help="application bundle to be deployed")
-ap.add_argument("-verbose", type=int, nargs=1, default=[1], metavar="<0-3>", help="0 = no output, 1 = error/warning (default), 2 = normal, 3 = debug")
+ap.add_argument("appname", nargs=1, metavar="appname", help="name of the app being deployed")
+ap.add_argument("-verbose", nargs="?", const=True, help="Output additional debugging information")
ap.add_argument("-no-plugins", dest="plugins", action="store_false", default=True, help="skip plugin deployment")
ap.add_argument("-no-strip", dest="strip", action="store_false", default=True, help="don't run 'strip' on the binaries")
-ap.add_argument("-sign", dest="sign", action="store_true", default=False, help="sign .app bundle with codesign tool")
-ap.add_argument("-dmg", nargs="?", const="", metavar="basename", help="create a .dmg disk image; if basename is not specified, a camel-cased version of the app name is used")
-ap.add_argument("-fancy", nargs=1, metavar="plist", default=[], help="make a fancy looking disk image using the given plist file with instructions; requires -dmg to work")
-ap.add_argument("-add-qt-tr", nargs=1, metavar="languages", default=[], help="add Qt translation files to the bundle's resources; the language list must be separated with commas, not with whitespace")
-ap.add_argument("-translations-dir", nargs=1, metavar="path", default=None, help="Path to Qt's translation files")
-ap.add_argument("-add-resources", nargs="+", metavar="path", default=[], help="list of additional files or folders to be copied into the bundle's resources; must be the last argument")
-ap.add_argument("-volname", nargs=1, metavar="volname", default=[], help="custom volume name for dmg")
+ap.add_argument("-dmg", nargs="?", const="", metavar="basename", help="create a .dmg disk image")
+ap.add_argument("-translations-dir", nargs=1, metavar="path", default=None, help="Path to Qt's translations. Base translations will automatically be added to the bundle's resources.")
config = ap.parse_args()
-verbose = config.verbose[0]
+verbose = config.verbose
# ------------------------------------------------
app_bundle = config.app_bundle[0]
+appname = config.appname[0]
if not os.path.exists(app_bundle):
- if verbose >= 1:
- sys.stderr.write("Error: Could not find app bundle \"{}\"\n".format(app_bundle))
+ sys.stderr.write(f"Error: Could not find app bundle \"{app_bundle}\"\n")
sys.exit(1)
-app_bundle_name = os.path.splitext(os.path.basename(app_bundle))[0]
-
-# ------------------------------------------------
-translations_dir = None
-if config.translations_dir and config.translations_dir[0]:
- if os.path.exists(config.translations_dir[0]):
- translations_dir = config.translations_dir[0]
- else:
- if verbose >= 1:
- sys.stderr.write("Error: Could not find translation dir \"{}\"\n".format(translations_dir))
- sys.exit(1)
-# ------------------------------------------------
-
-for p in config.add_resources:
- if verbose >= 3:
- print("Checking for \"%s\"..." % p)
- if not os.path.exists(p):
- if verbose >= 1:
- sys.stderr.write("Error: Could not find additional resource file \"{}\"\n".format(p))
- sys.exit(1)
-
-# ------------------------------------------------
-
-if len(config.fancy) == 1:
- if verbose >= 3:
- print("Fancy: Importing plistlib...")
- try:
- import plistlib
- except ImportError:
- if verbose >= 1:
- sys.stderr.write("Error: Could not import plistlib which is required for fancy disk images.\n")
- sys.exit(1)
-
- p = config.fancy[0]
- if verbose >= 3:
- print("Fancy: Loading \"{}\"...".format(p))
- if not os.path.exists(p):
- if verbose >= 1:
- sys.stderr.write("Error: Could not find fancy disk image plist at \"{}\"\n".format(p))
- sys.exit(1)
-
- try:
- with open(p, 'rb') as fp:
- fancy = plistlib.load(fp, fmt=plistlib.FMT_XML)
- except:
- if verbose >= 1:
- sys.stderr.write("Error: Could not parse fancy disk image plist at \"{}\"\n".format(p))
- sys.exit(1)
-
- try:
- assert "window_bounds" not in fancy or (isinstance(fancy["window_bounds"], list) and len(fancy["window_bounds"]) == 4)
- assert "background_picture" not in fancy or isinstance(fancy["background_picture"], str)
- assert "icon_size" not in fancy or isinstance(fancy["icon_size"], int)
- assert "applications_symlink" not in fancy or isinstance(fancy["applications_symlink"], bool)
- if "items_position" in fancy:
- assert isinstance(fancy["items_position"], dict)
- for key, value in fancy["items_position"].items():
- assert isinstance(value, list) and len(value) == 2 and isinstance(value[0], int) and isinstance(value[1], int)
- except:
- if verbose >= 1:
- sys.stderr.write("Error: Bad format of fancy disk image plist at \"{}\"\n".format(p))
- sys.exit(1)
-
- if "background_picture" in fancy:
- bp = fancy["background_picture"]
- if verbose >= 3:
- print("Fancy: Resolving background picture \"{}\"...".format(bp))
- if not os.path.exists(bp):
- bp = os.path.join(os.path.dirname(p), bp)
- if not os.path.exists(bp):
- if verbose >= 1:
- sys.stderr.write("Error: Could not find background picture at \"{}\" or \"{}\"\n".format(fancy["background_picture"], bp))
- sys.exit(1)
- else:
- fancy["background_picture"] = bp
-else:
- fancy = None
-
# ------------------------------------------------
if os.path.exists("dist"):
- if verbose >= 2:
- print("+ Removing old dist folder +")
-
+ print("+ Removing existing dist folder +")
shutil.rmtree("dist")
-# ------------------------------------------------
-
-if len(config.volname) == 1:
- volname = config.volname[0]
-else:
- volname = app_bundle_name
+if os.path.exists(appname + ".dmg"):
+ print("+ Removing existing DMG +")
+ os.unlink(appname + ".dmg")
# ------------------------------------------------
target = os.path.join("dist", "Bitcoin-Qt.app")
-if verbose >= 2:
- print("+ Copying source bundle +")
-if verbose >= 3:
+print("+ Copying source bundle +")
+if verbose:
print(app_bundle, "->", target)
os.mkdir("dist")
@@ -653,257 +542,154 @@ applicationBundle = ApplicationBundleInfo(target)
# ------------------------------------------------
-if verbose >= 2:
- print("+ Deploying frameworks +")
+print("+ Deploying frameworks +")
try:
deploymentInfo = deployFrameworksForAppBundle(applicationBundle, config.strip, verbose)
if deploymentInfo.qtPath is None:
deploymentInfo.qtPath = os.getenv("QTDIR", None)
if deploymentInfo.qtPath is None:
- if verbose >= 1:
- sys.stderr.write("Warning: Could not detect Qt's path, skipping plugin deployment!\n")
+ sys.stderr.write("Warning: Could not detect Qt's path, skipping plugin deployment!\n")
config.plugins = False
except RuntimeError as e:
- if verbose >= 1:
- sys.stderr.write("Error: {}\n".format(str(e)))
+ sys.stderr.write(f"Error: {str(e)}\n")
sys.exit(1)
# ------------------------------------------------
if config.plugins:
- if verbose >= 2:
- print("+ Deploying plugins +")
+ print("+ Deploying plugins +")
try:
deployPlugins(applicationBundle, deploymentInfo, config.strip, verbose)
except RuntimeError as e:
- if verbose >= 1:
- sys.stderr.write("Error: {}\n".format(str(e)))
+ sys.stderr.write(f"Error: {str(e)}\n")
sys.exit(1)
# ------------------------------------------------
-if len(config.add_qt_tr) == 0:
- add_qt_tr = []
-else:
- if translations_dir is not None:
- qt_tr_dir = translations_dir
- else:
- if deploymentInfo.qtPath is not None:
- qt_tr_dir = os.path.join(deploymentInfo.qtPath, "translations")
- else:
- sys.stderr.write("Error: Could not find Qt translation path\n")
- sys.exit(1)
- add_qt_tr = ["qt_{}.qm".format(lng) for lng in config.add_qt_tr[0].split(",")]
- for lng_file in add_qt_tr:
- p = os.path.join(qt_tr_dir, lng_file)
- if verbose >= 3:
- print("Checking for \"{}\"...".format(p))
- if not os.path.exists(p):
- if verbose >= 1:
- sys.stderr.write("Error: Could not find Qt translation file \"{}\"\n".format(lng_file))
- sys.exit(1)
+if config.translations_dir:
+ if not Path(config.translations_dir[0]).exists():
+ sys.stderr.write(f"Error: Could not find translation dir \"{config.translations_dir[0]}\"\n")
+ sys.exit(1)
+
+print("+ Adding Qt translations +")
+
+translations = Path(config.translations_dir[0])
+
+regex = re.compile('qt_[a-z]*(.qm|_[A-Z]*.qm)')
+
+lang_files = [x for x in translations.iterdir() if regex.match(x.name)]
+
+for file in lang_files:
+ if verbose:
+ print(file.as_posix(), "->", os.path.join(applicationBundle.resourcesPath, file.name))
+ shutil.copy2(file.as_posix(), os.path.join(applicationBundle.resourcesPath, file.name))
# ------------------------------------------------
-if verbose >= 2:
- print("+ Installing qt.conf +")
+print("+ Installing qt.conf +")
+
+qt_conf="""[Paths]
+Translations=Resources
+Plugins=PlugIns
+"""
with open(os.path.join(applicationBundle.resourcesPath, "qt.conf"), "wb") as f:
f.write(qt_conf.encode())
# ------------------------------------------------
-if len(add_qt_tr) > 0 and verbose >= 2:
- print("+ Adding Qt translations +")
-
-for lng_file in add_qt_tr:
- if verbose >= 3:
- print(os.path.join(qt_tr_dir, lng_file), "->", os.path.join(applicationBundle.resourcesPath, lng_file))
- shutil.copy2(os.path.join(qt_tr_dir, lng_file), os.path.join(applicationBundle.resourcesPath, lng_file))
+print("+ Generating .DS_Store +")
+
+output_file = os.path.join("dist", ".DS_Store")
+
+ds = DSStore.open(output_file, 'w+')
+
+ds['.']['bwsp'] = {
+ 'WindowBounds': '{{300, 280}, {500, 343}}',
+ 'PreviewPaneVisibility': False,
+}
+
+icvp = {
+ 'gridOffsetX': 0.0,
+ 'textSize': 12.0,
+ 'viewOptionsVersion': 1,
+ 'backgroundImageAlias': b'\x00\x00\x00\x00\x02\x1e\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xd1\x94\\\xb0H+\x00\x05\x00\x00\x00\x98\x0fbackground.tiff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x99\xd19\xb0\xf8\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\x00\x00\r\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0b.background\x00\x00\x10\x00\x08\x00\x00\xd1\x94\\\xb0\x00\x00\x00\x11\x00\x08\x00\x00\xd19\xb0\xf8\x00\x00\x00\x01\x00\x04\x00\x00\x00\x98\x00\x0e\x00 \x00\x0f\x00b\x00a\x00c\x00k\x00g\x00r\x00o\x00u\x00n\x00d\x00.\x00t\x00i\x00f\x00f\x00\x0f\x00\x02\x00\x00\x00\x12\x00\x1c/.background/background.tiff\x00\x14\x01\x06\x00\x00\x00\x00\x01\x06\x00\x02\x00\x00\x0cMacintosh HD\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xce\x97\xab\xc3H+\x00\x00\x01\x88[\x88\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02u\xab\x8d\xd1\x94\\\xb0devrddsk\xff\xff\xff\xff\x00\x00\t \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x07bitcoin\x00\x00\x10\x00\x08\x00\x00\xce\x97\xab\xc3\x00\x00\x00\x11\x00\x08\x00\x00\xd1\x94\\\xb0\x00\x00\x00\x01\x00\x14\x01\x88[\x88\x00\x16\xa9\t\x00\x08\xfaR\x00\x08\xfaQ\x00\x02d\x8e\x00\x0e\x00\x02\x00\x00\x00\x0f\x00\x1a\x00\x0c\x00M\x00a\x00c\x00i\x00n\x00t\x00o\x00s\x00h\x00 \x00H\x00D\x00\x13\x00\x01/\x00\x00\x15\x00\x02\x00\x14\xff\xff\x00\x00\xff\xff\x00\x00',
+ 'backgroundColorBlue': 1.0,
+ 'iconSize': 96.0,
+ 'backgroundColorGreen': 1.0,
+ 'arrangeBy': 'none',
+ 'showIconPreview': True,
+ 'gridSpacing': 100.0,
+ 'gridOffsetY': 0.0,
+ 'showItemInfo': False,
+ 'labelOnBottom': True,
+ 'backgroundType': 2,
+ 'backgroundColorRed': 1.0
+}
+alias = Alias().from_bytes(icvp['backgroundImageAlias'])
+alias.volume.name = appname
+alias.volume.posix_path = '/Volumes/' + appname
+icvp['backgroundImageAlias'] = alias.to_bytes()
+ds['.']['icvp'] = icvp
+
+ds['.']['vSrn'] = ('long', 1)
+
+ds['Applications']['Iloc'] = (370, 156)
+ds['Bitcoin-Qt.app']['Iloc'] = (128, 156)
+
+ds.flush()
+ds.close()
# ------------------------------------------------
-if len(config.add_resources) > 0 and verbose >= 2:
- print("+ Adding additional resources +")
+if config.dmg is not None:
-for p in config.add_resources:
- t = os.path.join(applicationBundle.resourcesPath, os.path.basename(p))
- if verbose >= 3:
- print(p, "->", t)
- if os.path.isdir(p):
- shutil.copytree(p, t, symlinks=True)
- else:
- shutil.copy2(p, t)
+ print("+ Preparing .dmg disk image +")
-# ------------------------------------------------
+ if verbose:
+ print("Determining size of \"dist\"...")
+ size = 0
+ for path, dirs, files in os.walk("dist"):
+ for file in files:
+ size += os.path.getsize(os.path.join(path, file))
+ size += int(size * 0.15)
-if config.sign and 'CODESIGNARGS' not in os.environ:
- print("You must set the CODESIGNARGS environment variable. Skipping signing.")
-elif config.sign:
- if verbose >= 1:
- print("Code-signing app bundle {}".format(target))
- subprocess.check_call("codesign --force {} {}".format(os.environ['CODESIGNARGS'], target), shell=True)
+ if verbose:
+ print("Creating temp image for modification...")
-# ------------------------------------------------
+ tempname: str = appname + ".temp.dmg"
-if config.dmg is not None:
+ run(["hdiutil", "create", tempname, "-srcfolder", "dist", "-format", "UDRW", "-size", str(size), "-volname", appname], check=True, universal_newlines=True)
- def runHDIUtil(verb: str, image_basename: str, **kwargs) -> int:
- hdiutil_args = ["hdiutil", verb, image_basename + ".dmg"]
- if "capture_stdout" in kwargs:
- del kwargs["capture_stdout"]
- run = subprocess.check_output
- else:
- if verbose < 2:
- hdiutil_args.append("-quiet")
- elif verbose >= 3:
- hdiutil_args.append("-verbose")
- run = subprocess.check_call
-
- for key, value in kwargs.items():
- hdiutil_args.append("-" + key)
- if value is not True:
- hdiutil_args.append(str(value))
-
- return run(hdiutil_args, universal_newlines=True)
-
- if verbose >= 2:
- if fancy is None:
- print("+ Creating .dmg disk image +")
- else:
- print("+ Preparing .dmg disk image +")
-
- if config.dmg != "":
- dmg_name = config.dmg
- else:
- spl = app_bundle_name.split(" ")
- dmg_name = spl[0] + "".join(p.capitalize() for p in spl[1:])
-
- if fancy is None:
- try:
- runHDIUtil("create", dmg_name, srcfolder="dist", format="UDBZ", volname=volname, ov=True)
- except subprocess.CalledProcessError as e:
- sys.exit(e.returncode)
- else:
- if verbose >= 3:
- print("Determining size of \"dist\"...")
- size = 0
- for path, dirs, files in os.walk("dist"):
- for file in files:
- size += os.path.getsize(os.path.join(path, file))
- size += int(size * 0.15)
-
- if verbose >= 3:
- print("Creating temp image for modification...")
- try:
- runHDIUtil("create", dmg_name + ".temp", srcfolder="dist", format="UDRW", size=size, volname=volname, ov=True)
- except subprocess.CalledProcessError as e:
- sys.exit(e.returncode)
-
- if verbose >= 3:
- print("Attaching temp image...")
- try:
- output = runHDIUtil("attach", dmg_name + ".temp", readwrite=True, noverify=True, noautoopen=True, capture_stdout=True)
- except subprocess.CalledProcessError as e:
- sys.exit(e.returncode)
-
- m = re.search(r"/Volumes/(.+$)", output)
- disk_root = m.group(0)
- disk_name = m.group(1)
-
- if verbose >= 2:
- print("+ Applying fancy settings +")
-
- if "background_picture" in fancy:
- bg_path = os.path.join(disk_root, ".background", os.path.basename(fancy["background_picture"]))
- os.mkdir(os.path.dirname(bg_path))
- if verbose >= 3:
- print(fancy["background_picture"], "->", bg_path)
- shutil.copy2(fancy["background_picture"], bg_path)
- else:
- bg_path = None
-
- if fancy.get("applications_symlink", False):
- os.symlink("/Applications", os.path.join(disk_root, "Applications"))
-
- # The Python appscript package broke with OSX 10.8 and isn't being fixed.
- # So we now build up an AppleScript string and use the osascript command
- # to make the .dmg file pretty:
- appscript = Template( """
- on run argv
- tell application "Finder"
- tell disk "$disk"
- open
- set current view of container window to icon view
- set toolbar visible of container window to false
- set statusbar visible of container window to false
- set the bounds of container window to {$window_bounds}
- set theViewOptions to the icon view options of container window
- set arrangement of theViewOptions to not arranged
- set icon size of theViewOptions to $icon_size
- $background_commands
- $items_positions
- close -- close/reopen works around a bug...
- open
- update without registering applications
- delay 5
- eject
- end tell
- end tell
- end run
- """)
-
- itemscript = Template('set position of item "${item}" of container window to {${position}}')
- items_positions = []
- if "items_position" in fancy:
- for name, position in fancy["items_position"].items():
- params = { "item" : name, "position" : ",".join([str(p) for p in position]) }
- items_positions.append(itemscript.substitute(params))
-
- params = {
- "disk" : volname,
- "window_bounds" : "300,300,800,620",
- "icon_size" : "96",
- "background_commands" : "",
- "items_positions" : "\n ".join(items_positions)
- }
- if "window_bounds" in fancy:
- params["window_bounds"] = ",".join([str(p) for p in fancy["window_bounds"]])
- if "icon_size" in fancy:
- params["icon_size"] = str(fancy["icon_size"])
- if bg_path is not None:
- # Set background file, then call SetFile to make it invisible.
- # (note: making it invisible first makes set background picture fail)
- bgscript = Template("""set background picture of theViewOptions to file ".background:$bgpic"
- do shell script "SetFile -a V /Volumes/$disk/.background/$bgpic" """)
- params["background_commands"] = bgscript.substitute({"bgpic" : os.path.basename(bg_path), "disk" : params["disk"]})
-
- s = appscript.substitute(params)
- if verbose >= 2:
- print("Running AppleScript:")
- print(s)
-
- p = subprocess.Popen(['osascript', '-'], stdin=subprocess.PIPE)
- p.communicate(input=s.encode('utf-8'))
- if p.returncode:
- print("Error running osascript.")
-
- if verbose >= 2:
- print("+ Finalizing .dmg disk image +")
- time.sleep(5)
-
- try:
- runHDIUtil("convert", dmg_name + ".temp", format="UDBZ", o=dmg_name + ".dmg", ov=True)
- except subprocess.CalledProcessError as e:
- sys.exit(e.returncode)
-
- os.unlink(dmg_name + ".temp.dmg")
+ if verbose:
+ print("Attaching temp image...")
+ output = run(["hdiutil", "attach", tempname, "-readwrite"], check=True, universal_newlines=True, stdout=PIPE).stdout
+
+ m = re.search(r"/Volumes/(.+$)", output)
+ disk_root = m.group(0)
+
+ print("+ Applying fancy settings +")
+
+ bg_path = os.path.join(disk_root, ".background", os.path.basename('background.tiff'))
+ os.mkdir(os.path.dirname(bg_path))
+ if verbose:
+ print('background.tiff', "->", bg_path)
+ shutil.copy2('background.tiff', bg_path)
+
+ os.symlink("/Applications", os.path.join(disk_root, "Applications"))
+
+ print("+ Finalizing .dmg disk image +")
+
+ run(["hdiutil", "detach", f"/Volumes/{appname}"], universal_newlines=True)
+
+ run(["hdiutil", "convert", tempname, "-format", "UDZO", "-o", appname, "-imagekey", "zlib-level=9"], check=True, universal_newlines=True)
+
+ os.unlink(tempname)
# ------------------------------------------------
-if verbose >= 2:
- print("+ Done +")
+print("+ Done +")
sys.exit(0)
diff --git a/contrib/testgen/base58.py b/contrib/testgen/base58.py
index c7ebac50d4..87341ccf96 100644
--- a/contrib/testgen/base58.py
+++ b/contrib/testgen/base58.py
@@ -1,4 +1,4 @@
-# Copyright (c) 2012-2018 The Bitcoin Core developers
+# Copyright (c) 2012-2020 The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
'''
diff --git a/contrib/testgen/gen_key_io_test_vectors.py b/contrib/testgen/gen_key_io_test_vectors.py
index 49320d92e6..8a3918da6b 100755
--- a/contrib/testgen/gen_key_io_test_vectors.py
+++ b/contrib/testgen/gen_key_io_test_vectors.py
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
-# Copyright (c) 2012-2018 The Bitcoin Core developers
+# Copyright (c) 2012-2020 The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
'''
diff --git a/contrib/zmq/zmq_sub.py b/contrib/zmq/zmq_sub.py
index 8b8503331d..9cb887e2dc 100755
--- a/contrib/zmq/zmq_sub.py
+++ b/contrib/zmq/zmq_sub.py
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
-# Copyright (c) 2014-2018 The Bitcoin Core developers
+# Copyright (c) 2014-2020 The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.