1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185
|
import hashlib
import os
import platform
import shutil
import subprocess
import sys
import tarfile
import urllib.request
from argparse import ArgumentParser
from dataclasses import dataclass
from pathlib import Path
taglib_version = "2.0"
taglib_url = f"https://github.com/taglib/taglib/archive/refs/tags/v{taglib_version}.tar.gz"
taglib_sha256sum = "e36ea877a6370810b97d84cf8f72b1e4ed205149ab3ac8232d44c850f38a2859"
utfcpp_version = "4.0.5"
utfcpp_url = f"https://github.com/nemtrif/utfcpp/archive/refs/tags/v{utfcpp_version}.tar.gz"
root = Path(__file__).resolve().parent
system = platform.system()
def run_script():
config = get_config()
_download(taglib_url, config.taglib_tarball, taglib_sha256sum)
_download(utfcpp_url, config.utfcpp_tarball)
if config.force or not config.taglib_extract_dir.exists():
_del_if_exists(config.taglib_extract_dir)
_del_if_exists(config.utfcpp_extract_dir)
_extract(config.taglib_tarball, config.taglib_extract_dir)
_extract(config.utfcpp_tarball, config.utfcpp_extract_dir)
shutil.copytree(config.utfcpp_extract_dir, config.taglib_extract_dir / "3rdparty" / "utfcpp",
dirs_exist_ok=True)
if config.force or not config.taglib_install_dir.exists():
_del_if_exists(config.taglib_install_dir)
cmake_config(config.taglib_extract_dir, config.taglib_install_dir)
cmake_build(config.taglib_extract_dir)
_del_if_exists(config.target_dir)
shutil.copytree(config.taglib_install_dir, config.target_dir)
@dataclass
class Configuration:
target_dir: Path
cache_dir: Path
force: bool
platform_id: str
@property
def taglib_tarball(self):
return self.cache_dir / f"taglib-{taglib_version}.tar.gz"
@property
def utfcpp_tarball(self):
return self.cache_dir / f"utfcpp-{utfcpp_version}.tar.gz"
@property
def platform_dir(self):
return self.cache_dir / self.platform_id
@property
def taglib_extract_dir(self):
return self.platform_dir / f"taglib-{taglib_version}"
@property
def utfcpp_extract_dir(self):
return self.platform_dir / f"utfcpp-{utfcpp_version}"
@property
def taglib_install_dir(self):
return self.platform_dir / "taglib-install"
def get_config() -> Configuration:
parser = ArgumentParser(description="helper to download and build C++ TagLib")
parser.add_argument(
"--target",
help="target directory for TagLib (binaries and headers)",
type=Path,
default=root / "lib" / "taglib-cpp",
)
parser.add_argument(
"--cache",
help="temporary directory for downloads and builds; suitable to be cached in CI",
type=Path,
default=root / "build" / "cache",
)
parser.add_argument("--force", action="store_true", help="fore clean build even if output already exists")
args = parser.parse_args()
return Configuration(target_dir=args.target.resolve(), cache_dir=args.cache.resolve(), force=args.force,
platform_id=get_platform_id())
def _download(url: str, target: Path, sha256sum: str = None):
print(f"downloading {url} ...")
if target.exists():
print("skipping download, file exists")
return
response = urllib.request.urlopen(url)
data = response.read()
target.parent.mkdir(exist_ok=True, parents=True)
target.write_bytes(data)
print("download complete")
if sha256sum is None:
return
the_hash = hashlib.sha256(target.read_bytes()).hexdigest()
if the_hash != taglib_sha256sum:
error = f'checksum of downloaded file ({the_hash}) does not match expected hash ({taglib_sha256sum})'
raise RuntimeError(error)
def get_platform_id():
"""Tries to generate a string that is unique for the C compiler configuration used to build
Python extensions.
Compiler potentially depends on:
- OS
- architecture
- Python implementation (CPython, PyPy)
- Python version (major/minor)
- C library type (uclib for musllinux)
In cibuildwheel, the AUDITWHEEL_PLAT environment variable is used for all of these except
Python version and implementation.
- """
platform_identifier = os.environ.get('AUDITWHEEL_PLAT', f"{system}-{platform.machine()}")
python_identifier = f"{sys.implementation.name}-{sys.version_info[0]}.{sys.version_info[1]}"
return f"{platform_identifier}-{python_identifier}"
def _extract(archive: Path, target: Path):
"""Extracts `archive` into `target`.
"""
print(f"extracting {archive} to {target} ...")
tar = tarfile.open(archive)
tar.extractall(target.parent)
def _del_if_exists(dir: Path):
if dir.exists():
shutil.rmtree(dir)
def _cmake(cwd: Path, *args):
print(f"running cmake {' '.join(args)}")
return subprocess.run(["cmake", *args], cwd=cwd, check=True)
def cmake_config(source_dir: Path, install_dir: Path):
print("running cmake ...")
args = ["-DWITH_ZLIB=OFF"] # todo fix building wheels with zlib support
if system == "Windows":
args += ["-A", "x64"]
elif system == "Linux":
args.append("-DCMAKE_POSITION_INDEPENDENT_CODE=ON")
args.append("-DBUILD_TESTING=OFF")
args.append(f"-DCMAKE_INSTALL_PREFIX={install_dir}")
args.append(f"-DCMAKE_CXX_FLAGS=-I{source_dir / '3rdparty' / 'utfcpp' / 'source'}")
args.append(".")
install_dir.mkdir(exist_ok=True, parents=True)
_cmake(source_dir, *args)
def cmake_build(source_dir: Path):
print("building taglib ...")
build_configuration = "Release"
_cmake(
source_dir,
"--build",
".",
"--config",
build_configuration
)
print("installing cmake ...")
_cmake(source_dir, "--install", ".", "--config", build_configuration)
if __name__ == "__main__":
run_script()
|