Skip to content

Instantly share code, notes, and snippets.

@nocarryr
Created February 26, 2020 14:40
Show Gist options
  • Save nocarryr/b75aada132bb589d805daad6a8e3a6c5 to your computer and use it in GitHub Desktop.
Save nocarryr/b75aada132bb589d805daad6a8e3a6c5 to your computer and use it in GitHub Desktop.
#! /usr/bin/env python3
from loguru import logger
import os
from pathlib import Path
import shlex
import subprocess
import tempfile
import shutil
import argparse
import requests
BASE_PATH = Path(__file__).resolve().parent
MXE_PATH = BASE_PATH / 'mxe'
ASIO_URL = 'https://www.steinberg.net/asiosdk'
ASIO_ARCHIVE = BASE_PATH / 'asiosdk_2.3.3_2019-06-14.zip'
def run_cmd(cmd_str: str, get_output=False, quiet=False):
if not quiet:
logger.success(f'{cmd_str}')
if get_output:
s = subprocess.check_output(shlex.split(cmd_str))
if isinstance(s, bytes):
s = s.decode('UTF-8')
return s
else:
subprocess.check_call(shlex.split(cmd_str))
class Cwd(object):
def __init__(self, path: Path):
self.path = path
self.prev_path = None
def __enter__(self):
logger.debug(f'Entering "{self}"')
assert self.prev_path is None
self.prev_path = Path(os.getcwd())
os.chdir(self.path)
return self.path
def __exit__(self, *args):
logger.debug(f'Leaving "{self}"')
os.chdir(self.prev_path)
self.prev_path = None
def __repr__(self):
return f'<{self.__class__.__name__}: "{self}">'
def __str__(self):
return str(self.path)
class TempDir(object):
def __init__(self, autoremove=True):
self.root = None
self.autoremove = autoremove
self._acquire_count = 0
def open(self):
assert self.root is None
p = tempfile.mkdtemp()
self.root = Path(p)
def close(self):
if self.autoremove:
shutil.rmtree(str(self.root))
self.root = None
def __enter__(self):
if self._acquire_count == 0:
self.open()
self._acquire_count += 1
return self.root
def __exit__(self, *args):
self._acquire_count -= 1
if self._acquire_count == 0:
self.close()
class Repo(object):
URI = None
REFSPEC = None
def __init__(self, path: Path, uri=None, refspec=None):
self.path = path
if uri is None:
uri = self.URI
self.uri = uri
if not self.path.exists():
self.clone()
if refspec is None:
if self.REFSPEC is not None:
refspec = self.REFSPEC
if refspec is None:
self.refspec = self.get_current_refspec()
else:
self.checkout(refspec)
@property
def name(self):
return self.path.name
def clone(self):
p = self.path.resolve()
run_cmd(f'git clone {self.uri} {p}')
def checkout(self, refspec: str):
cur_ref = self.get_current_refspec()
if cur_ref == refspec:
self.refspec = refspec
return
with Cwd(self.path):
run_cmd(f'git checkout {refspec}')
self.refspec = refspec
def get_current_refspec(self):
with Cwd(self.path):
r = run_cmd('git rev-parse --abbrev-ref HEAD', get_output=True, quiet=True)
return r.splitlines()[0]
def copy(self, new_path: Path):
logger.debug(f'Copy {self} to {new_path}')
shutil.copytree(str(self.path), str(new_path))
obj = Repo(new_path, self.uri, self.refspec)
return obj
def __repr__(self):
return f'<{self.__class__.__name__}: "{self}">'
def __str__(self):
return f'{self.path.name} [{self.refspec}]'
class MxeRepo(Repo):
URI = 'https://github.com/mxe/mxe.git'
# class PortAudioRepo(Repo):
# URI = 'https://github.com/nocarryr/portaudio.git'
# REFSPEC = 'pa_stable_v190600_20161030'
def get_asiosdk(root: Path):
r = requests.get(ASIO_URL, stream=True)
assert r.ok
filename = r.url.split('/')[-1]
filename = root / filename
assert filename.suffix.endswith('zip')
with filename.open('wb') as fd:
for chunk in r.iter_content(chunk_size=128):
fd.write(chunk)
return filename
class BuildEnv(object):
targets = ['x86_64-w64-mingw32', 'i686-w64-mingw32']
def __init__(self, autoremove=False, **kwargs):
# super().__init__(autoremove)
self.asio_archive = kwargs.get('asio_archive', ASIO_ARCHIVE)
self.tempdir = TempDir()
def run(self):
with self.tempdir as root:
self.root = root
self.setup()
self.pre_build()
self.build()
def setup(self):
# self.mxe = MxeRepo(self.root / 'mxe')
self.mxe = MxeRepo(MXE_PATH)
# self.portaudio = PortAudioRepo(self.root / 'portaudio')
if self.asio_archive is None or not self.asio_archive.exists():
self.asio_archive = get_asiosdk(BASE_PATH)
self.patch_makefile()
def patch_makefile(self):
logger.debug('Patching portaudio.mk')
mkfile = self.mxe.path / 'src' / 'portaudio.mk'
txt = mkfile.read_text()
if '__MAKEFILE_PATCHED__' in txt:
logger.debug('Already patched, skipping')
return
asio_dir = BASE_PATH / self.asio_archive.stem
output = []
test_suite_found = False
for line in mkfile.read_text().splitlines():
if test_suite_found and 'endef' not in line:
continue
elif line.lstrip(' ').startswith('--with-winapi='):
opts = line.split('=')[1].rstrip('\\').rstrip(' ')
opts = f'{opts},asio'
if line.endswith('\\'):
line_end = ' \\'
else:
line_end = ''
line = '='.join([line.split('=')[0], f'{opts}{line_end}'])
indent = ' '*8
output.append(f'{indent}--with-asiodir={asio_dir} \\')
elif line.lstrip(' ').startswith('$(MAKE)') and 'install' not in line:
line = line.replace("'$(JOBS)'", '1')
line = f'{line} EXAMPLES= SELFTESTS='
elif '$(TARGET)-gcc' in line:
test_suite_found = True
continue
output.append(line)
output.append('# __MAKEFILE_PATCHED__')
mkfile.write_text('\n'.join(output))
logger.info('portaudio.mk patched')
def pre_build(self):
pass
# logger.success('pre_build')
# with Cwd(self.mxe.path):
# for target in self.targets:
# os.putenv('TARGET', target)
# run_cmd('make cc')
def build(self):
for target in self.targets:
for lib_type in ['static', 'shared']:
_target = '.'.join([target, lib_type])
self.build_target(_target)
def build_target(self, target):
with TempDir() as root:
logger.success(f'Building target "{target}" in {root}')
asio_dir = BASE_PATH / self.asio_archive.stem
if asio_dir.exists():
shutil.rmtree(str(asio_dir))
shutil.unpack_archive(str(self.asio_archive), BASE_PATH)#str(root))
logger.info(f'asio_archive={self.asio_archive}, asio_dir={asio_dir}')
mxe = self.mxe#.copy(root / 'mxe')
# portaudio = self.portaudio.copy(root / 'portaudio')
os.putenv('TEMPASIODIR', str(asio_dir.resolve()))
os.putenv('TARGET', target)
# with Cwd(root / 'mxe'):
with Cwd(mxe.path):
run_cmd(f'make portaudio MXE_TARGETS={target}')
run_cmd(f'./usr/bin/{target}-gcc -O2 -shared -o libportaudio-{target}.dll -Wl,--whole-archive -lportaudio -Wl,--no-whole-archive -lstdc++ -lwinmm -lole32 -lsetupapi')
run_cmd(f'./usr/bin/{target}-strip libportaudio-{target}.dll')
# run_cmd('make -C mxe portaudio MXE_TARGETS=$TARGET')
# run_cmd('$TARGET-gcc -O2 -shared -o libportaudio-$TARGET.dll -Wl,--whole-archive -lportaudio -Wl,--no-whole-archive -lstdc++ -lwinmm -lole32 -lsetupapi')
# run_cmd('$TARGET-strip libportaudio-$TARGET.dll')
# run_cmd('chmod +x libportaudio-$TARGET.dll')
dll = Path('.') / f'libportaudio-{target}.dll'
# dll.rename(self.root / dll.name)
dll_dest = BASE_PATH / dll.name
shutil.copy2(str(dll), dll_dest)
def main(**kwargs):
env = BuildEnv(**kwargs)
env.run()
# print(env)
if __name__ == '__main__':
p = argparse.ArgumentParser()
p.add_argument('--asio-archive', dest='asio_archive', default=str(ASIO_ARCHIVE))
args = p.parse_args()
asio_archive = args.asio_archive
if asio_archive is not None:
asio_archive = Path(asio_archive).resolve()
main(asio_archive=asio_archive)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment