zdoom-macos-deps/aedi/state.py
alexey.lysiuk 4999b2ee4a split build script onto several modules
put all code into new package, build script will use it as a black box
split build state from builder class, and pass state to target methods
place base, main, dependency, special targets into separate files, and put them in own package

This implements #19
2021-01-14 10:34:20 +02:00

177 lines
6.1 KiB
Python

#
# Helper module to build macOS version of various source ports
# Copyright (C) 2020-2021 Alexey Lysiuk
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
import hashlib
import os
import shutil
import subprocess
import urllib.request
class BuildState:
def __init__(self):
self_path = os.path.dirname(os.path.abspath(__file__))
self.root_path = os.path.abspath(self_path + os.sep + os.pardir) + os.sep
self.deps_path = self.root_path + 'deps' + os.sep
self.prefix_path = self.root_path + 'prefix' + os.sep
self.bin_path = self.prefix_path + 'bin' + os.sep
self.include_path = self.prefix_path + 'include' + os.sep
self.lib_path = self.prefix_path + 'lib' + os.sep
self.root_source_path = self.root_path + 'source' + os.sep
self.source_path = None
self.external_source = True
self.patch_path = None
self.build_path = None
self.native_build_path = None
self.output_path = None
self.install_path = None
self.platform = None
self.xcode = False
self.checkout_commit = None
self.verbose = False
self.jobs = 1
def architecture(self) -> str:
return self.platform.architecture if self.platform else ''
def host(self) -> str:
return self.platform.host if self.platform else ''
def os_version(self) -> str:
return self.platform.os_version if self.platform else ''
def sdk_path(self) -> str:
return self.platform.sdk_path if self.platform else ''
def c_compiler(self) -> str:
return self.platform.c_compiler if self.platform else ''
def cxx_compiler(self) -> str:
return self.platform.cxx_compiler if self.platform else ''
def checkout_git(self, url: str):
if not os.path.exists(self.source_path):
args = ('git', 'clone', '--recurse-submodules', url, self.source_path)
subprocess.check_call(args, cwd=self.root_path)
if self.checkout_commit:
args = ['git', 'checkout', self.checkout_commit]
subprocess.check_call(args, cwd=self.source_path)
def download_source(self, url: str, checksum: str):
if self.external_source:
return
os.makedirs(self.source_path, exist_ok=True)
data, filepath = self._read_source_package(url)
self._verify_checksum(checksum, data, filepath)
first_path_component, extract_path = self._unpack_source_package(filepath)
self._apply_source_patch(extract_path)
# Adjust source and build paths according to extracted source code
self.source_path = extract_path
self.build_path = self.build_path + first_path_component + os.sep
def _read_source_package(self, url: str) -> (bytes, str):
filename = url.rsplit(os.sep, 1)[1]
filepath = self.source_path + filename
if os.path.exists(filepath):
# Read existing source package
with open(filepath, 'rb') as f:
data = f.read()
else:
# Download package with source code
print(f'Downloading {filename}')
response = urllib.request.urlopen(url)
try:
with open(filepath, 'wb') as f:
data = response.read()
f.write(data)
except IOError:
os.unlink(filepath)
raise
return data, filepath
@staticmethod
def _verify_checksum(checksum: str, data: bytes, filepath: str) -> None:
file_hasher = hashlib.sha256()
file_hasher.update(data)
file_checksum = file_hasher.hexdigest()
if file_checksum != checksum:
os.unlink(filepath)
raise Exception(f'Checksum of {filepath} does not match, expected: {checksum}, actual: {file_checksum}')
def _unpack_source_package(self, filepath: str) -> (str, str):
filepaths = subprocess.check_output(['tar', '-tf', filepath]).decode("utf-8")
filepaths = filepaths.split('\n')
first_path_component = None
for path in filepaths:
if os.sep in path:
first_path_component = path[:path.find(os.sep)]
break
if not first_path_component:
raise Exception("Failed to figure out source code path for " + filepath)
extract_path = self.source_path + first_path_component + os.sep
if not os.path.exists(extract_path):
# Extract source code package
try:
subprocess.check_call(['tar', '-xf', filepath], cwd=self.source_path)
except (IOError, subprocess.CalledProcessError):
shutil.rmtree(extract_path, ignore_errors=True)
raise
return first_path_component, extract_path
def _apply_source_patch(self, extract_path: str):
if not self.patch_path:
return
assert os.path.exists(self.patch_path)
# Check if patch is already applied
test_arg = '--dry-run'
args = ['patch', test_arg, '--strip=1', '--input=' + self.patch_path]
if subprocess.call(args, cwd=extract_path) == 0:
# Patch wasn't applied yet, do it now
args.remove(test_arg)
subprocess.check_call(args, cwd=extract_path)
def run_pkg_config(self, *args) -> str:
os.makedirs(self.build_path, exist_ok=True)
args = (self.bin_path + 'pkg-config',) + args
result = subprocess.check_output(args, cwd=self.build_path)
return result.decode('utf-8').rstrip('\n')