2021-05-30 22:50:01 +00:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
"""
|
|
|
|
Downloads required libraries for xemu builds on macOS from MacPorts repositories
|
|
|
|
"""
|
|
|
|
# Based on https://github.com/tpoechtrager/osxcross/blob/master/tools/osxcross-macports
|
|
|
|
# which is based on https://github.com/maci0/pmmacports
|
|
|
|
from urllib.request import urlopen
|
|
|
|
import re
|
|
|
|
import os.path
|
|
|
|
from tarfile import TarFile
|
|
|
|
import subprocess
|
|
|
|
|
|
|
|
# MIRROR = 'http://packages.macports.org/macports/packages'
|
|
|
|
MIRROR = 'http://nue.de.packages.macports.org/macports/packages'
|
|
|
|
|
2021-10-13 20:52:49 +00:00
|
|
|
# FIXME: Inline macports key
|
|
|
|
# FIXME: Move packages to archive directory to track used vs unused
|
|
|
|
# FIXME: Support multiple mirrors
|
|
|
|
|
2021-05-30 22:50:01 +00:00
|
|
|
class LibInstaller:
|
|
|
|
DARWIN_TARGET_X64="darwin_17" # macOS 10.13
|
2024-03-10 01:52:37 +00:00
|
|
|
DARWIN_TARGET_ARM64="darwin_21" # macOS 12.x
|
2021-05-30 22:50:01 +00:00
|
|
|
|
|
|
|
def __init__(self, arch):
|
|
|
|
self._queue = []
|
|
|
|
self._installed = []
|
|
|
|
if arch == 'x86_64':
|
|
|
|
self._darwin_target = self.DARWIN_TARGET_X64
|
|
|
|
elif arch == 'arm64':
|
|
|
|
self._darwin_target = self.DARWIN_TARGET_ARM64
|
|
|
|
else:
|
|
|
|
assert False, "Add arch"
|
|
|
|
self._arch = arch
|
|
|
|
|
|
|
|
self._extract_path = os.path.realpath(f'./macos-libs/{self._arch}')
|
|
|
|
if not os.path.exists(self._extract_path):
|
|
|
|
os.makedirs(self._extract_path)
|
|
|
|
self._installed_path = os.path.join(self._extract_path, 'INSTALLED')
|
|
|
|
self._pkgs_path = os.path.realpath(os.path.join(f'./macos-pkgs'))
|
|
|
|
if not os.path.exists(self._pkgs_path):
|
|
|
|
os.makedirs(self._pkgs_path)
|
|
|
|
|
|
|
|
def get_latest_pkg_filename_url(self, pkg_name):
|
2021-12-03 00:31:17 +00:00
|
|
|
pkg_base_url = f'{MIRROR}/{pkg_name}'
|
2021-05-30 22:50:01 +00:00
|
|
|
pkg_list = urlopen(pkg_base_url).read().decode('utf-8')
|
2024-07-15 08:30:57 +00:00
|
|
|
pkgs = re.findall(pkg_name + r'[\w\.\-\_\+]*?\.(?:any_any|darwin_any|' + self._darwin_target + r')\.(?:noarch|' + self._arch + r')\.tbz2', pkg_list)
|
2024-03-10 01:52:37 +00:00
|
|
|
if len(pkgs) < 1:
|
|
|
|
print(f' [*] [ERROR] Unable to find version of {pkg_name} compatible with {self._darwin_target}.{self._arch}')
|
|
|
|
exit(1)
|
|
|
|
|
2021-05-30 22:50:01 +00:00
|
|
|
pkg_filename = pkgs[-1]
|
|
|
|
return pkg_filename, f'{pkg_base_url}/{pkg_filename}'
|
|
|
|
|
|
|
|
def is_pkg_installed(self, pkg_name):
|
|
|
|
if not os.path.exists(self._installed_path):
|
|
|
|
return False
|
|
|
|
with open(self._installed_path) as f:
|
|
|
|
installed = [l.strip().split('=')[0] for l in f.readlines()]
|
|
|
|
return pkg_name in installed
|
|
|
|
|
|
|
|
def mark_pkg_installed(self, pkg_name, pkg_version):
|
|
|
|
if self.is_pkg_installed(pkg_name):
|
|
|
|
return
|
|
|
|
with open(os.path.join(self._extract_path, 'INSTALLED'), 'a+') as f:
|
|
|
|
f.write(f'{pkg_name}={pkg_version}\n')
|
|
|
|
|
|
|
|
def download_file(self, desc, url, dst):
|
|
|
|
if os.path.exists(dst):
|
|
|
|
print(f' [+] Already have {desc}')
|
|
|
|
else:
|
|
|
|
print(f' [+] Downloading {desc}')
|
|
|
|
with open(dst, 'wb') as f:
|
|
|
|
f.write(urlopen(url).read())
|
|
|
|
|
|
|
|
def verify_pkg(self, pkg_path, sig_path):
|
|
|
|
PUBKEYURL="https://svn.macports.org/repository/macports/trunk/base/macports-pubkey.pem"
|
|
|
|
PUBKEYRMD160="d3a22f5be7184d6575afcc1be6fdb82fd25562e8"
|
|
|
|
PUBKEYSHA1="214baa965af76ff71187e6c1ac91c559547f48ab"
|
|
|
|
key_filename = 'macports-pubkey.pem'
|
|
|
|
dst_key_filename = os.path.join(self._pkgs_path, key_filename)
|
|
|
|
self.download_file('MacPorts key', PUBKEYURL, dst_key_filename)
|
|
|
|
rmd160 = subprocess.run('openssl rmd160 "' + dst_key_filename + "\" | awk '{print $2}'",
|
|
|
|
capture_output=True, shell=True,
|
|
|
|
check=True).stdout.decode('utf-8').strip()
|
|
|
|
sha1 = subprocess.run('openssl sha1 "' + dst_key_filename + "\" | awk '{print $2}'",
|
|
|
|
capture_output=True, shell=True,
|
|
|
|
check=True).stdout.decode('utf-8').strip()
|
|
|
|
assert (rmd160 == PUBKEYRMD160 and sha1 == PUBKEYSHA1), 'Invalid MacPorts key'
|
|
|
|
sha1 = subprocess.run('openssl dgst -ripemd160 '
|
|
|
|
f'-verify "{dst_key_filename}" '
|
|
|
|
f'-signature "{sig_path}" "{pkg_path}"',
|
|
|
|
shell=True, check=True)
|
|
|
|
|
2021-09-04 23:41:13 +00:00
|
|
|
def is_pkg_skipped(self, pkg_name):
|
2021-10-13 20:52:49 +00:00
|
|
|
return any(pkg_name.startswith(n) for n in ('python', 'ncurses'))
|
2021-09-04 23:41:13 +00:00
|
|
|
|
2021-05-30 22:50:01 +00:00
|
|
|
def install_pkg(self, pkg_name):
|
|
|
|
if self.is_pkg_installed(pkg_name):
|
2021-12-03 00:31:17 +00:00
|
|
|
print(f'[*] Package {pkg_name} already installed')
|
2021-05-30 22:50:01 +00:00
|
|
|
return
|
|
|
|
|
2021-09-04 23:41:13 +00:00
|
|
|
if self.is_pkg_skipped(pkg_name):
|
|
|
|
print(f'[*] Skipping package {pkg_name}')
|
|
|
|
return
|
|
|
|
|
2021-05-30 22:50:01 +00:00
|
|
|
print(f'[*] Fetching {pkg_name}')
|
|
|
|
pkg_filename, pkg_url = self.get_latest_pkg_filename_url(pkg_name)
|
2021-12-03 00:31:17 +00:00
|
|
|
pkg_version = pkg_filename[re.search(r'-\d', pkg_filename).span()[0]+1:]
|
|
|
|
pkg_version = pkg_version[:pkg_version.find('.'+self._darwin_target)]
|
2021-05-30 22:50:01 +00:00
|
|
|
dst_pkg_filename = os.path.join(self._pkgs_path, pkg_filename)
|
|
|
|
print(f' [*] Found package {pkg_filename}')
|
|
|
|
self.download_file(pkg_filename, pkg_url, dst_pkg_filename)
|
|
|
|
|
|
|
|
dst_pkg_sig_filename = dst_pkg_filename + '.rmd160'
|
|
|
|
pkg_sig_url = pkg_url + '.rmd160'
|
|
|
|
self.download_file('package signature', pkg_sig_url, dst_pkg_sig_filename)
|
|
|
|
|
|
|
|
print(f' [+] Verifying package')
|
|
|
|
self.verify_pkg(dst_pkg_filename, dst_pkg_sig_filename)
|
|
|
|
|
|
|
|
print(f' [+] Looking for dependencies')
|
|
|
|
tb = TarFile.open(dst_pkg_filename)
|
|
|
|
pkg_contents_file = tb.extractfile('./+CONTENTS').read().decode('utf-8')
|
|
|
|
for dep in re.findall(r'@pkgdep (.+)', pkg_contents_file):
|
|
|
|
print(f' [>] {dep}')
|
2021-12-03 00:31:17 +00:00
|
|
|
s = re.search(r'-\d', dep)
|
|
|
|
if s:
|
|
|
|
dep = dep[0:s.span()[0]]
|
2021-05-30 22:50:01 +00:00
|
|
|
self._queue.append(dep)
|
|
|
|
|
|
|
|
print(f' [*] Checking tarball...')
|
|
|
|
|
|
|
|
for fpath in tb.getnames():
|
|
|
|
extracted_path = os.path.realpath(os.path.join(self._extract_path, fpath))
|
|
|
|
assert extracted_path.startswith(self._extract_path), f'tarball has a global file: {fname}'
|
|
|
|
|
|
|
|
print(f' [*] Extracting to {self._extract_path}')
|
|
|
|
tb.extractall(self._extract_path, numeric_owner=True)
|
|
|
|
|
|
|
|
for fpath in tb.getnames():
|
2021-09-04 23:41:13 +00:00
|
|
|
# FIXME: Symlinks
|
2021-05-30 22:50:01 +00:00
|
|
|
extracted_path = os.path.realpath(os.path.join(self._extract_path, fpath))
|
|
|
|
if extracted_path.endswith('.pc'):
|
|
|
|
print(f' [*] Fixing {extracted_path}')
|
|
|
|
with open(extracted_path, 'r') as f:
|
|
|
|
lines = f.readlines()
|
|
|
|
for i, l in enumerate(lines):
|
|
|
|
if l.strip().startswith('prefix'):
|
2021-10-13 20:52:49 +00:00
|
|
|
new_prefix = f'prefix={self._extract_path}/opt/local\n'
|
|
|
|
if pkg_name.startswith('openssl'): # FIXME
|
|
|
|
new_prefix = f'prefix={self._extract_path}/opt/local/libexec/openssl11\n'
|
|
|
|
lines[i] = new_prefix
|
2021-05-30 22:50:01 +00:00
|
|
|
break
|
|
|
|
with open(extracted_path, 'w') as f:
|
|
|
|
f.write(''.join(lines))
|
|
|
|
|
|
|
|
if pkg_name == 'glib2':
|
|
|
|
fpath = './opt/local/include/glib-2.0/glib/gi18n.h'
|
|
|
|
extracted_path = os.path.realpath(os.path.join(self._extract_path, fpath))
|
|
|
|
print(f' [*] Fixing {extracted_path}')
|
|
|
|
with open(extracted_path, 'r') as f:
|
|
|
|
lines = f.read()
|
|
|
|
s = '/opt/local/include/libintl.h'
|
|
|
|
lines = lines.replace(s, self._extract_path + s)
|
|
|
|
with open(extracted_path, 'w') as f:
|
|
|
|
f.write(lines)
|
|
|
|
|
|
|
|
self.mark_pkg_installed(pkg_name, pkg_version)
|
|
|
|
|
|
|
|
def install_pkgs(self, requested):
|
|
|
|
self._queue.extend(requested)
|
|
|
|
while len(self._queue) > 0:
|
|
|
|
pkg_name = self._queue.pop(0)
|
|
|
|
self.install_pkg(pkg_name)
|
|
|
|
|
|
|
|
def main():
|
|
|
|
import argparse
|
|
|
|
ap = argparse.ArgumentParser()
|
|
|
|
ap.add_argument('arch', choices=('arm64', 'x86_64'))
|
|
|
|
args = ap.parse_args()
|
|
|
|
li = LibInstaller(args.arch)
|
|
|
|
li.install_pkgs([
|
|
|
|
'libsdl2',
|
|
|
|
'glib2',
|
|
|
|
'libsamplerate',
|
|
|
|
'libpixman',
|
|
|
|
'libepoxy',
|
2021-10-13 20:52:49 +00:00
|
|
|
'openssl11',
|
2023-01-23 09:03:56 +00:00
|
|
|
'libpcap',
|
|
|
|
'libslirp'])
|
2021-05-30 22:50:01 +00:00
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
main()
|