Created
February 26, 2020 14:40
-
-
Save nocarryr/b75aada132bb589d805daad6a8e3a6c5 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#! /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