2021-01-14 08:34:20 +00:00
|
|
|
#
|
|
|
|
# Helper module to build macOS version of various source ports
|
2022-01-11 09:43:29 +00:00
|
|
|
# Copyright (C) 2020-2022 Alexey Lysiuk
|
2021-01-14 08:34:20 +00:00
|
|
|
#
|
|
|
|
# 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
|
2021-09-12 07:19:16 +00:00
|
|
|
import re
|
2021-01-14 08:34:20 +00:00
|
|
|
import shutil
|
|
|
|
import subprocess
|
2021-08-04 06:38:54 +00:00
|
|
|
import typing
|
2021-01-14 08:34:20 +00:00
|
|
|
import urllib.request
|
2021-08-05 07:12:20 +00:00
|
|
|
from distutils.version import StrictVersion
|
|
|
|
from pathlib import Path
|
2021-01-14 08:34:20 +00:00
|
|
|
|
2021-08-05 06:59:49 +00:00
|
|
|
from .utility import CommandLineOptions
|
|
|
|
|
2021-01-14 08:34:20 +00:00
|
|
|
|
|
|
|
class BuildState:
|
|
|
|
def __init__(self):
|
2021-07-22 06:51:13 +00:00
|
|
|
self_path = Path(__file__)
|
|
|
|
self.root_path = self_path.parent.parent
|
|
|
|
self.deps_path = self.root_path / 'deps'
|
|
|
|
self.prefix_path = self.root_path / 'prefix'
|
|
|
|
self.bin_path = self.prefix_path / 'bin'
|
|
|
|
self.include_path = self.prefix_path / 'include'
|
|
|
|
self.lib_path = self.prefix_path / 'lib'
|
|
|
|
self.patch_path = self.root_path / 'patch'
|
|
|
|
self.source_path = self.root_path / 'source'
|
|
|
|
|
|
|
|
self.source = Path()
|
2021-01-14 08:34:20 +00:00
|
|
|
self.external_source = True
|
|
|
|
|
|
|
|
self.build_path = None
|
|
|
|
self.native_build_path = None
|
|
|
|
|
|
|
|
self.output_path = None
|
|
|
|
self.install_path = None
|
|
|
|
|
|
|
|
self.platform = None
|
|
|
|
self.xcode = False
|
|
|
|
self.verbose = False
|
|
|
|
self.jobs = 1
|
|
|
|
|
2021-08-05 06:53:07 +00:00
|
|
|
self.environment = os.environ.copy()
|
2021-08-05 06:59:49 +00:00
|
|
|
self.options = CommandLineOptions()
|
2021-08-05 06:53:07 +00:00
|
|
|
|
2021-01-14 08:34:20 +00:00
|
|
|
def architecture(self) -> str:
|
|
|
|
return self.platform.architecture if self.platform else ''
|
|
|
|
|
|
|
|
def host(self) -> str:
|
|
|
|
return self.platform.host if self.platform else ''
|
|
|
|
|
2021-06-25 07:03:53 +00:00
|
|
|
def os_version(self) -> StrictVersion:
|
|
|
|
return self.platform.os_version if self.platform else None
|
2021-01-14 08:34:20 +00:00
|
|
|
|
2021-08-05 07:02:55 +00:00
|
|
|
def sdk_path(self) -> Path:
|
2021-09-01 08:41:08 +00:00
|
|
|
return self.platform.sdk_path if self.platform else None
|
2021-01-14 08:34:20 +00:00
|
|
|
|
2021-09-12 07:19:16 +00:00
|
|
|
def sdk_version(self) -> typing.Union[StrictVersion, None]:
|
|
|
|
if sdk_path := self.sdk_path():
|
|
|
|
if match := re.search(r'/MacOSX(\d+.\d+).sdk', str(sdk_path), re.IGNORECASE):
|
|
|
|
return StrictVersion(match[1])
|
|
|
|
|
|
|
|
return None
|
|
|
|
|
2021-08-05 07:02:55 +00:00
|
|
|
def c_compiler(self) -> Path:
|
2021-09-01 08:41:08 +00:00
|
|
|
return self.platform.c_compiler if self.platform else None
|
2021-01-14 08:34:20 +00:00
|
|
|
|
2021-08-05 07:02:55 +00:00
|
|
|
def cxx_compiler(self) -> Path:
|
2021-09-01 08:41:08 +00:00
|
|
|
return self.platform.cxx_compiler if self.platform else None
|
2021-01-14 08:34:20 +00:00
|
|
|
|
2021-04-10 11:35:12 +00:00
|
|
|
def checkout_git(self, url: str, branch: str = None):
|
2021-07-22 06:51:13 +00:00
|
|
|
if self.source.exists():
|
2021-04-10 11:35:12 +00:00
|
|
|
return
|
2021-03-13 14:53:14 +00:00
|
|
|
|
2021-04-10 11:35:12 +00:00
|
|
|
args = ('git', 'clone', '--recurse-submodules', url, self.source)
|
|
|
|
subprocess.run(args, cwd=self.root_path, check=True)
|
|
|
|
|
|
|
|
if branch:
|
|
|
|
args = ('git', 'checkout', '-b', branch, 'origin/' + branch)
|
2021-02-18 11:27:35 +00:00
|
|
|
subprocess.run(args, cwd=self.source, check=True)
|
|
|
|
|
2021-08-04 06:38:54 +00:00
|
|
|
def download_source(self, url: str, checksum: str, patches: typing.Union[tuple, list, str] = None):
|
2021-01-14 08:34:20 +00:00
|
|
|
if self.external_source:
|
|
|
|
return
|
|
|
|
|
2021-01-17 10:37:35 +00:00
|
|
|
os.makedirs(self.source, exist_ok=True)
|
2021-01-14 08:34:20 +00:00
|
|
|
|
|
|
|
data, filepath = self._read_source_package(url)
|
|
|
|
self._verify_checksum(checksum, data, filepath)
|
|
|
|
|
|
|
|
first_path_component, extract_path = self._unpack_source_package(filepath)
|
2021-06-01 07:18:38 +00:00
|
|
|
|
|
|
|
if not patches:
|
|
|
|
pass
|
|
|
|
elif isinstance(patches, str):
|
|
|
|
self._apply_source_patch(extract_path, patches)
|
|
|
|
elif isinstance(patches, (tuple, list)):
|
|
|
|
for patch in patches:
|
|
|
|
self._apply_source_patch(extract_path, patch)
|
|
|
|
else:
|
|
|
|
assert False
|
2021-01-14 08:34:20 +00:00
|
|
|
|
|
|
|
# Adjust source and build paths according to extracted source code
|
2021-01-17 10:37:35 +00:00
|
|
|
self.source = extract_path
|
2021-07-22 06:51:13 +00:00
|
|
|
self.build_path = self.build_path / first_path_component
|
2021-01-14 08:34:20 +00:00
|
|
|
|
2021-08-04 06:38:54 +00:00
|
|
|
def _read_source_package(self, url: str) -> typing.Tuple[bytes, Path]:
|
2021-01-14 08:34:20 +00:00
|
|
|
filename = url.rsplit(os.sep, 1)[1]
|
2021-07-22 06:51:13 +00:00
|
|
|
filepath = self.source / filename
|
2021-01-14 08:34:20 +00:00
|
|
|
|
2021-07-22 06:51:13 +00:00
|
|
|
if filepath.exists():
|
2021-01-14 08:34:20 +00:00
|
|
|
# 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
|
2021-07-22 06:51:13 +00:00
|
|
|
def _verify_checksum(checksum: str, data: bytes, filepath: Path) -> None:
|
2021-01-14 08:34:20 +00:00
|
|
|
file_hasher = hashlib.sha256()
|
|
|
|
file_hasher.update(data)
|
|
|
|
file_checksum = file_hasher.hexdigest()
|
|
|
|
|
|
|
|
if file_checksum != checksum:
|
2021-07-22 06:51:13 +00:00
|
|
|
filepath.unlink()
|
2021-01-14 08:34:20 +00:00
|
|
|
raise Exception(f'Checksum of {filepath} does not match, expected: {checksum}, actual: {file_checksum}')
|
|
|
|
|
2021-08-04 06:38:54 +00:00
|
|
|
def _unpack_source_package(self, filepath: Path) -> typing.Tuple[str, Path]:
|
2021-08-04 06:40:31 +00:00
|
|
|
file_paths_str = subprocess.check_output(['tar', '-tf', filepath]).decode("utf-8")
|
|
|
|
file_paths = file_paths_str.split('\n')
|
2021-01-14 08:34:20 +00:00
|
|
|
first_path_component = None
|
|
|
|
|
2021-08-04 06:40:31 +00:00
|
|
|
for path in file_paths:
|
2021-01-14 08:34:20 +00:00
|
|
|
if os.sep in path:
|
|
|
|
first_path_component = path[:path.find(os.sep)]
|
|
|
|
break
|
|
|
|
|
|
|
|
if not first_path_component:
|
2021-07-22 06:51:13 +00:00
|
|
|
raise Exception(f'Failed to figure out source code path for {filepath}')
|
2021-01-14 08:34:20 +00:00
|
|
|
|
2021-07-22 06:51:13 +00:00
|
|
|
extract_path = self.source / first_path_component
|
2021-01-14 08:34:20 +00:00
|
|
|
|
2021-07-22 06:51:13 +00:00
|
|
|
if not extract_path.exists():
|
2021-01-14 08:34:20 +00:00
|
|
|
# Extract source code package
|
|
|
|
try:
|
2021-01-17 10:37:35 +00:00
|
|
|
subprocess.check_call(['tar', '-xf', filepath], cwd=self.source)
|
2021-01-14 08:34:20 +00:00
|
|
|
except (IOError, subprocess.CalledProcessError):
|
|
|
|
shutil.rmtree(extract_path, ignore_errors=True)
|
|
|
|
raise
|
|
|
|
|
|
|
|
return first_path_component, extract_path
|
|
|
|
|
2021-07-22 06:51:13 +00:00
|
|
|
def _apply_source_patch(self, extract_path: Path, patch: str):
|
|
|
|
patch_path = self.patch_path / (patch + '.diff')
|
|
|
|
assert patch_path.exists()
|
2021-01-14 08:34:20 +00:00
|
|
|
|
|
|
|
# Check if patch is already applied
|
|
|
|
test_arg = '--dry-run'
|
2021-07-22 06:51:13 +00:00
|
|
|
args = ['patch', test_arg, '--strip=1', '--input=' + str(patch_path)]
|
2021-01-14 08:34:20 +00:00
|
|
|
|
|
|
|
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)
|
|
|
|
|
2021-07-22 06:51:13 +00:00
|
|
|
args = (self.bin_path / 'pkg-config',) + args
|
2021-01-14 08:34:20 +00:00
|
|
|
result = subprocess.check_output(args, cwd=self.build_path)
|
|
|
|
|
|
|
|
return result.decode('utf-8').rstrip('\n')
|
2021-07-22 06:51:13 +00:00
|
|
|
|
2021-08-04 06:38:54 +00:00
|
|
|
def has_source_file(self, path: typing.Union[str, Path]):
|
2021-07-22 06:51:13 +00:00
|
|
|
return (self.source / path).exists()
|
2021-08-05 06:53:07 +00:00
|
|
|
|
|
|
|
def update_environment(self, name: str, value: str):
|
|
|
|
env = self.environment
|
|
|
|
env[name] = env[name] + ' ' + value if name in env else value
|
|
|
|
|
|
|
|
def set_sdk(self, var_name: str):
|
|
|
|
sdk_path = self.sdk_path()
|
|
|
|
if sdk_path:
|
|
|
|
self.update_environment(var_name, f'-isysroot {sdk_path}')
|
|
|
|
|
|
|
|
def set_os_version(self, var_name: str):
|
|
|
|
os_version = self.os_version()
|
|
|
|
if os_version:
|
|
|
|
self.update_environment(var_name, f'-mmacosx-version-min={os_version}')
|