diff --git a/kiwixbuild/_global.py b/kiwixbuild/_global.py index 0b5aa35..6e4dcad 100644 --- a/kiwixbuild/_global.py +++ b/kiwixbuild/_global.py @@ -1,4 +1,8 @@ +from collections import OrderedDict as _OrderedDict + _neutralEnv = None +_target_steps = _OrderedDict() +_plt_steps = _OrderedDict() def set_neutralEnv(env): global _neutralEnv @@ -6,3 +10,29 @@ def set_neutralEnv(env): def neutralEnv(what): return getattr(_neutralEnv, what) + +def add_target_step(key, what): + _target_steps[key] = what + +def get_target_step(key, default_context=None): + try: + context, target = key + except ValueError: + context, target = default_context, key + return _target_steps[(context, target)] + +def target_steps(): + return _target_steps + +def add_plt_step(key, what): + _plt_steps[key] = what + +def get_plt_step(key, default_context=None): + try: + context, target = key + except ValueError: + context, target = default_context, key + return _plt_steps[(context, target)] + +def plt_steps(): + return _plt_steps diff --git a/kiwixbuild/buildenv.py b/kiwixbuild/buildenv.py index afbb6a5..672c906 100644 --- a/kiwixbuild/buildenv.py +++ b/kiwixbuild/buildenv.py @@ -76,17 +76,18 @@ class PlatformNeutralEnv: class BuildEnv: - def __init__(self, platformInfo, targetsDict): + def __init__(self, platformInfo): build_dir = "BUILD_{}".format(platformInfo.name) self.platformInfo = platformInfo self.build_dir = pj(neutralEnv('working_dir'), build_dir) self.install_dir = pj(self.build_dir, "INSTALL") + self.log_dir = pj(self.build_dir, 'LOGS') for d in (self.build_dir, - self.install_dir): + self.install_dir, + self.log_dir): os.makedirs(d, exist_ok=True) self.libprefix = neutralEnv('libprefix') or self._detect_libdir() - self.targetsDict = targetsDict def clean_intermediate_directories(self): for subdir in os.listdir(self.build_dir): diff --git a/kiwixbuild/builder.py b/kiwixbuild/builder.py index eefa670..d56724c 100644 --- a/kiwixbuild/builder.py +++ b/kiwixbuild/builder.py @@ -3,8 +3,14 @@ import sys from collections import OrderedDict from .buildenv import * +from .platforms import PlatformInfo from .utils import remove_duplicates, StopBuild from .dependencies import Dependency +from ._global import ( + neutralEnv, + add_target_step, get_target_step, target_steps, + add_plt_step, get_plt_step, plt_steps) +from . import _global class Builder: def __init__(self, options): @@ -15,78 +21,115 @@ class Builder: 'Select another target platform, or change your host system.' ).format(options.target_platform, self.distname)) sys.exit(-1) - self.targets = OrderedDict() - self.platform = platform = platformClass(self.targets) + self.platform = platform = platformClass() _targets = {} - targetDef = options.targets + targetDef = (options.target_platform, options.targets) self.add_targets(targetDef, _targets) dependencies = self.order_dependencies(_targets, targetDef) dependencies = list(remove_duplicates(dependencies)) if options.build_nodeps: - self.targets[targetDef] = _targets[targetDef] + add_target_step(targetDef, _targets[targetDef]) else: for dep in dependencies: if self.options.build_deps_only and dep == targetDef: continue - self.targets[dep] = _targets[dep] + add_target_step(dep, _targets[dep]) - def add_targets(self, targetName, targets): - if targetName in targets: + def add_targets(self, targetDef, targets): + if targetDef in targets: return + targetPlatformName, targetName = targetDef + targetPlatform = PlatformInfo.all_platforms[targetPlatformName] targetClass = Dependency.all_deps[targetName] - target = targetClass(self.platform.buildEnv) - targets[targetName] = target - for dep in target.builder.get_dependencies(self.platform): - self.add_targets(dep, targets) + targets[('source', targetName)] = targetClass.Source + targets[targetDef] = targetClass.Builder + for dep in targetClass.Builder.get_dependencies(targetPlatform): + try: + depPlatform, depName = dep + except ValueError: + depPlatform, depName = targetPlatformName, dep + self.add_targets((depPlatform, depName), targets) - def order_dependencies(self, _targets, targetName): - target = _targets[targetName] - for depName in target.builder.dependencies(self.platform): - yield from self.order_dependencies(_targets, depName) - yield targetName + def order_dependencies(self, _targets, targetDef): + targetPlatformName, targetName = targetDef + if targetPlatformName == 'source': + # Do not try to order sources, they will be added as dep by the + # build step two lines later. + return + target = _targets[targetDef] + targetPlatform = PlatformInfo.all_platforms[targetPlatformName] + yield ('source', targetName) + for dep in target.get_dependencies(targetPlatform): + try: + depPlatform, depName = dep + except ValueError: + depPlatform, depName = targetPlatformName, dep + yield from self.order_dependencies(_targets, (depPlatform, depName)) + yield targetDef + + def prepare_toolchain_sources(self): + tlsourceDefs = (tlDef for tlDef in plt_steps() if tlDef[0]=='source') + for tlsourceDef in tlsourceDefs: + print("prepare sources for toolchain {} :".format(tlsourceDef[1])) + toolchainClass = Toolchain.all_toolchains[tlsourceDef[1]] + source = get_plt_step(tlsourceDef)(toolchainClass) + add_plt_step(tlsourceDef, source) + source.prepare() def prepare_sources(self): if self.options.skip_source_prepare: print("SKIP") return - toolchain_sources = (tlc.source for tlc in self.platform.buildEnv.toolchains if tlc.source) - for toolchain_source in toolchain_sources: - print("prepare sources for toolchain {} :".format(toolchain_source.name)) - toolchain_source.prepare() + sourceDefs = remove_duplicates(tDef for tDef in target_steps() if tDef[0]=='source') + for sourceDef in sourceDefs: - sources = (dep.source for dep in self.targets.values() if not dep.skip) - sources = remove_duplicates(sources, lambda s: s.__class__) - for source in sources: - print("prepare sources {} :".format(source.name)) + print("prepare sources {} :".format(sourceDef[1])) + depClass = Dependency.all_deps[sourceDef[1]] + source = get_target_step(sourceDef)(depClass) + add_target_step(sourceDef, source) source.prepare() - def build(self): - toolchain_builders = (tlc.builder for tlc in self.platform.buildEnv.toolchains if tlc.builder) - for toolchain_builder in toolchain_builders: - print("build toolchain {} :".format(toolchain_builder.name)) - toolchain_builder.build() - - builders = (dep.builder for dep in self.targets.values() if (dep.builder and not dep.skip)) - for builder in builders: - if self.options.make_dist and builder.name == self.options.targets: - continue - print("build {} :".format(builder.name)) + def build_toolchains(self): + tlbuilderDefs = (tlDef for tlDef in plt_steps() if tlDef[0] != 'source') + for tlbuilderDef in tlbuilderDefs: + print("build toolchain {} :".format(tlbuilderDef[1])) + toolchainClass = Toolchain.all_toolchains[tlbuilderDef[1]] + source = get_plt_step(tlbuilderDef[1], 'source') + if tlbuilderDef[0] == 'neutral': + env = _global._neutralEnv + else: + env = PlatformInfo.all_running_platforms[tlbuilderDef[0]].buildEnv + builder = get_plt_step(tlbuilderDef)(toolchainClass, source, env) + add_plt_step(tlbuilderDef, builder) + builder.build() + + def build(self): + builderDefs = (tDef for tDef in target_steps() if tDef[0] != 'source') + for builderDef in builderDefs: + depClass = Dependency.all_deps[builderDef[1]] + source = get_target_step(builderDef[1], 'source') + env = PlatformInfo.all_running_platforms[builderDef[0]].buildEnv + builder = get_target_step(builderDef)(depClass, source, env) + if self.options.make_dist and builderDef[1] == self.options.targets: + print("make dist {}:".format(builder.name)) + builder.make_dist() + continue + print("build {} ({}):".format(builder.name, builderDef[0])) + add_target_step(builderDef, builder) builder.build() - if self.options.make_dist: - dep = self.targets[self.options.targets] - builder = dep.builder - print("make dist {}:".format(builder.name)) - builder.make_dist() def run(self): try: + print("[SETUP PLATFORMS]") + self.prepare_toolchain_sources() + self.build_toolchains() + self.platform.finalize_setup() print("[INSTALL PACKAGES]") self.platform.install_packages() - self.platform.finalize_setup() print("[PREPARE]") self.prepare_sources() print("[BUILD]") diff --git a/kiwixbuild/dependencies/base.py b/kiwixbuild/dependencies/base.py index 45fb39f..9d93795 100644 --- a/kiwixbuild/dependencies/base.py +++ b/kiwixbuild/dependencies/base.py @@ -22,34 +22,51 @@ class Dependency(metaclass=_MetaDependency): all_deps = {} force_native_build = False - def __init__(self, buildEnv): - self.buildEnv = buildEnv - self.source = self.Source(self) - self.builder = self.Builder(self) - self.skip = False + @classmethod + def version(cls): + return base_deps_versions.get(cls.name, None) + + @classmethod + def full_name(cls): + if cls.version(): + return "{}-{}".format(cls.name, cls.version()) + return cls.name + + +class Source: + """Base Class to the real preparator + A source preparator must install source in the self.source_dir attribute + inside the neutralEnv.source_dir.""" + def __init__(self, target): + self.target = target @property - def version(self): - return base_deps_versions.get(self.name, None) + def name(self): + return self.target.name @property - def full_name(self): - if self.version: - return "{}-{}".format(self.name, self.version) - return self.name + def source_dir(self): + return self.target.full_name() @property def source_path(self): - return pj(neutralEnv('source_dir'), self.source.source_dir) + return pj(neutralEnv('source_dir'), self.source_dir) @property def _log_dir(self): - return self.buildEnv.log_dir + return neutralEnv('log_dir') + + def _patch(self, context): + context.try_skip(self.source_path) + context.force_native_build = True + for p in self.patches: + with open(pj(SCRIPT_DIR, 'patches', p), 'r') as patch_input: + run_command("patch -p1", self.source_path, context, input=patch_input.read()) def command(self, name, function, *args): print(" {} {} : ".format(name, self.name), end="", flush=True) log = pj(self._log_dir, 'cmd_{}_{}.log'.format(name, self.name)) - context = Context(name, log, self.force_native_build) + context = Context(name, log, True) try: ret = function(*args, context=context) context._finalise() @@ -70,33 +87,6 @@ class Dependency(metaclass=_MetaDependency): raise -class Source: - """Base Class to the real preparator - A source preparator must install source in the self.source_dir attribute - inside the neutralEnv.source_dir.""" - def __init__(self, target): - self.target = target - - @property - def name(self): - return self.target.name - - @property - def source_dir(self): - return self.target.full_name - - def _patch(self, context): - source_path = pj(neutralEnv('source_dir'), self.source_dir) - context.try_skip(source_path) - context.force_native_build = True - for p in self.patches: - with open(pj(SCRIPT_DIR, 'patches', p), 'r') as patch_input: - neutralEnv('run_command')("patch -p1", source_path, context, input=patch_input.read()) - - def command(self, *args, **kwargs): - return self.target.command(*args, **kwargs) - - class NoopSource(Source): def prepare(self): pass @@ -206,9 +196,10 @@ class Builder: subsource_dir = None dependencies = [] - def __init__(self, target): + def __init__(self, target, source, buildEnv): self.target = target - self.buildEnv = target.buildEnv + self.source = source + self.buildEnv = buildEnv @classmethod def get_dependencies(cls, platformInfo): @@ -220,17 +211,41 @@ class Builder: @property def source_path(self): - base_source_path = self.target.source_path + base_source_path = self.source.source_path if self.subsource_dir: return pj(base_source_path, self.subsource_dir) return base_source_path @property def build_path(self): - return pj(self.buildEnv.build_dir, self.target.full_name) + return pj(self.buildEnv.build_dir, self.target.full_name()) - def command(self, *args, **kwargs): - return self.target.command(*args, **kwargs) + @property + def _log_dir(self): + return self.buildEnv.log_dir + + def command(self, name, function, *args): + print(" {} {} : ".format(name, self.name), end="", flush=True) + log = pj(self._log_dir, 'cmd_{}_{}.log'.format(name, self.name)) + context = Context(name, log, self.target.force_native_build) + try: + ret = function(*args, context=context) + context._finalise() + print("OK") + return ret + except SkipCommand: + print("SKIP") + except subprocess.CalledProcessError: + print("ERROR") + try: + with open(log, 'r') as f: + print(f.read()) + except: + pass + raise StopBuild() + except: + print("ERROR") + raise def build(self): if hasattr(self, '_pre_build_script'): @@ -274,8 +289,8 @@ class MakeBuilder(Builder): def all_configure_option(self): option = self.configure_option_template.format( dep_options=self.configure_option, - static_option=self.static_configure_option if self.buildEnv.platform_info.static else self.dynamic_configure_option, - env_option=self.buildEnv.configure_option if not self.target.force_native_build else "", + static_option=self.static_configure_option if self.buildEnv.platformInfo.static else self.dynamic_configure_option, + env_option=self.buildEnv.platformInfo.configure_option if not self.target.force_native_build else "", install_dir=self.buildEnv.install_dir, libdir=pj(self.buildEnv.install_dir, self.buildEnv.libprefix) ) @@ -289,7 +304,7 @@ class MakeBuilder(Builder): configure_option=self.all_configure_option ) env = Defaultdict(str, os.environ) - if self.buildEnv.platform_info.static: + if self.buildEnv.platformInfo.static: env['CFLAGS'] = env['CFLAGS'] + ' -fPIC' if self.configure_env: for k in self.configure_env: @@ -342,7 +357,7 @@ class CMakeBuilder(MakeBuilder): cross_option=cross_option ) env = Defaultdict(str, os.environ) - if self.buildEnv.platform_info.static: + if self.buildEnv.platformInfo.static: env['CFLAGS'] = env['CFLAGS'] + ' -fPIC' if self.configure_env: for k in self.configure_env: @@ -360,7 +375,7 @@ class MesonBuilder(Builder): @property def library_type(self): - return 'static' if self.buildEnv.platform_info.static else 'shared' + return 'static' if self.buildEnv.platformInfo.static else 'shared' def _configure(self, context): context.try_skip(self.build_path) @@ -393,9 +408,9 @@ class MesonBuilder(Builder): run_command(command, self.build_path, context, buildEnv=self.buildEnv) def _test(self, context): - if ( self.buildEnv.platform_info.build == 'android' - or (self.buildEnv.platform_info.build != 'native' - and not self.buildEnv.platform_info.static) + if ( self.buildEnv.platformInfo.build == 'android' + or (self.buildEnv.platformInfo.build != 'native' + and not self.buildEnv.platformInfo.static) ): raise SkipCommand() command = "{} --verbose {}".format(neutralEnv('mesontest_command'), self.test_option) diff --git a/kiwixbuild/dependencies/icu4c.py b/kiwixbuild/dependencies/icu4c.py index b3029d1..d9c8a0b 100644 --- a/kiwixbuild/dependencies/icu4c.py +++ b/kiwixbuild/dependencies/icu4c.py @@ -5,7 +5,7 @@ from .base import ( ) from kiwixbuild.utils import SkipCommand - +from kiwixbuild._global import get_target_step class Icu(Dependency): name = "icu4c" @@ -54,5 +54,7 @@ class Icu_cross_compile(Icu): @property def configure_option(self): - icu_native_dep = self.buildEnv.targetsDict['icu4c_native'] - return super().configure_option + " --with-cross-build={} --disable-tools".format(icu_native_dep.builder.build_path) + icu_native_builder = get_target_step('icu4c_native', self.buildEnv.platformInfo.name) + return (super().configure_option + + " --with-cross-build={} --disable-tools" + ).format(icu_native_builder.build_path) diff --git a/kiwixbuild/dependencies/kiwix_custom_app.py b/kiwixbuild/dependencies/kiwix_custom_app.py index 5c52efe..43b90a9 100644 --- a/kiwixbuild/dependencies/kiwix_custom_app.py +++ b/kiwixbuild/dependencies/kiwix_custom_app.py @@ -7,6 +7,7 @@ from .base import ( GradleBuilder) from kiwixbuild.utils import Remotefile, pj, SkipCommand +from kiwixbuild._global import get_target_step class KiwixCustomApp(Dependency): name = "kiwix-android-custom" @@ -78,9 +79,9 @@ class KiwixCustomApp(Dependency): def _configure(self, context): # Copy kiwix-android in build dir. - kiwix_android_dep = self.buildEnv.targetsDict['kiwix-android'] + kiwix_android_source = get_target_step('kiwix-android', 'source') if not os.path.exists(self.build_path): - shutil.copytree(kiwix_android_dep.source_path, self.build_path) + shutil.copytree(kiwix_android_source.source_path, self.build_path) # Copy kiwix-lib application in build dir try: diff --git a/kiwixbuild/dependencies/libmagic.py b/kiwixbuild/dependencies/libmagic.py index b093191..ab0f88e 100644 --- a/kiwixbuild/dependencies/libmagic.py +++ b/kiwixbuild/dependencies/libmagic.py @@ -7,6 +7,7 @@ from .base import ( ) from kiwixbuild.utils import Remotefile, pj, Defaultdict, SkipCommand +from kiwixbuild._global import get_target_step class LibMagicBase(Dependency): name = "libmagic" @@ -48,7 +49,7 @@ class LibMagic_cross_compile(LibMagicBase): make_target=self.make_target, make_option=self.make_option ) - libmagic_native_dep = self.buildEnv.targetsDict['libmagic_native'] + libmagic_native_builder = get_target_step('libmagic_native', self.buildEnv.platformInfo.name) env = Defaultdict(str, os.environ) - env['PATH'] = ':'.join([pj(libmagic_native_dep.builder.build_path, 'src'), env['PATH']]) + env['PATH'] = ':'.join([pj(libmagic_native_builder.build_path, 'src'), env['PATH']]) self.buildEnv.run_command(command, self.build_path, context, env=env) diff --git a/kiwixbuild/platforms/android.py b/kiwixbuild/platforms/android.py index 8504d27..4187a19 100644 --- a/kiwixbuild/platforms/android.py +++ b/kiwixbuild/platforms/android.py @@ -1,21 +1,13 @@ from .base import PlatformInfo +from kiwixbuild.utils import pj +from kiwixbuild._global import get_plt_step class AndroidPlatformInfo(PlatformInfo): - __arch_infos = { - 'arm' : ('arm-linux-androideabi', 'arm', 'armeabi'), - 'arm64': ('aarch64-linux-android', 'aarch64', 'arm64-v8a'), - 'mips': ('mipsel-linux-android', 'mipsel', 'mips'), - 'mips64': ('mips64el-linux-android', 'mips64el', 'mips64'), - 'x86': ('i686-linux-android', 'i686', 'x86'), - 'x86_64': ('x86_64-linux-android', 'x86_64', 'x86_64'), - } - - def __init__(self, name, arch): - super().__init__(name, 'android', True, ['android_ndk', 'android_sdk'], - hosts=['fedora', 'debian']) - self.arch = arch - self.arch_full, self.cpu, self.abi = self.__arch_infos[arch] + build = 'android' + static = True + toolchain_names = ['android-ndk', 'android-sdk'] + compatible_hosts = ['fedora', 'debian'] def __str__(self): return "android" @@ -35,11 +27,11 @@ class AndroidPlatformInfo(PlatformInfo): @property def ndk_builder(self): - return self.toolchains[0].builder + return get_plt_step('android_ndk', self.name) @property def sdk_builder(self): - return self.toolchains[1].builder + return get_plt_step('android_sdk', 'neutral') def get_cross_config(self): install_path = self.ndk_builder.install_path @@ -77,9 +69,53 @@ class AndroidPlatformInfo(PlatformInfo): env['NDK_DEBUG'] = '0' env['ANDROID_HOME'] = self.sdk_builder.install_path -AndroidPlatformInfo('android_arm', 'arm') -AndroidPlatformInfo('android_arm64', 'arm64') -AndroidPlatformInfo('android_mips', 'mips') -AndroidPlatformInfo('android_mips64', 'mips64') -AndroidPlatformInfo('android_x86', 'x86') -AndroidPlatformInfo('android_x86_64', 'x86_64') + def set_compiler(self, env): + binaries = self.binaries(self.ndk_builder.install_path) + env['CC'] = binaries['CC'] + env['CXX'] = binaries['CXX'] + + @property + def configure_option(self): + return '--host={}'.format(self.arch_full) + + def finalize_setup(self): + super().finalize_setup() + self.buildEnv.cmake_crossfile = self._gen_crossfile('cmake_android_cross_file.txt') + self.buildEnv.meson_crossfile = self._gen_crossfile('meson_cross_file.txt') + + +class AndroidArm(AndroidPlatformInfo): + name = 'android_arm' + arch = cpu = 'arm' + arch_full = 'arm-linux-androideabi' + abi = 'armeabi' + +class AndroidArm(AndroidPlatformInfo): + name = 'android_arm64' + arch = 'arm64' + arch_full = 'aarch64-linux-android' + cpu = 'aarch64' + abi = 'arm64-v8a' + +class AndroidArm(AndroidPlatformInfo): + name = 'android_mips' + arch = abi = 'mips' + arch_full = 'mipsel-linux-android' + cpu = 'mipsel' + +class AndroidArm(AndroidPlatformInfo): + name = 'android_mips64' + arch = abi = 'mips64' + arch_full = 'mips64el-linux-android' + cpu = 'mips64el' + +class AndroidArm(AndroidPlatformInfo): + name = 'android_x86' + arch = abi = 'x86' + arch_full = 'i686-linux-android' + cpu = 'i686' + +class AndroidArm(AndroidPlatformInfo): + name = 'android_x86_64' + arch = cpu = abi = 'x86_64' + arch_full = 'x86_64-linux-android' diff --git a/kiwixbuild/platforms/armhf.py b/kiwixbuild/platforms/armhf.py index 3c13d0c..e3077d9 100644 --- a/kiwixbuild/platforms/armhf.py +++ b/kiwixbuild/platforms/armhf.py @@ -1,10 +1,14 @@ from .base import PlatformInfo +from kiwixbuild.utils import pj +from kiwixbuild._global import get_plt_step + class ArmhfPlatformInfo(PlatformInfo): + build = 'armhf' arch_full = 'arm-linux-gnueabihf' - def __init__(self, name, static): - super().__init__(name, 'armhf', static, ['armhf_toolchain'], ['fedora', 'debian']) + toolchain_names = ['armhf'] + compatible_hosts = ['fedora', 'debian'] def get_cross_config(self): return { @@ -25,7 +29,7 @@ class ArmhfPlatformInfo(PlatformInfo): @property def tlc_source(self): - return self.toolchains[0].source + return get_plt_step('armhf', 'source') @property def root_path(self): @@ -83,5 +87,10 @@ class ArmhfPlatformInfo(PlatformInfo): self.buildEnv.cmake_crossfile = self._gen_crossfile('cmake_cross_file.txt') self.buildEnv.meson_crossfile = self._gen_crossfile('meson_cross_file.txt') -ArmhfPlatformInfo('armhf_dyn', False) -ArmhfPlatformInfo('armhf_static', True) +class ArmhfDyn(ArmhfPlatformInfo): + name = 'armhf_dyn' + static = False + +class ArmhfStatic(ArmhfPlatformInfo): + name = 'armhf_static' + static = True diff --git a/kiwixbuild/platforms/base.py b/kiwixbuild/platforms/base.py index 1d252e2..2d1babe 100644 --- a/kiwixbuild/platforms/base.py +++ b/kiwixbuild/platforms/base.py @@ -6,20 +6,28 @@ from kiwixbuild.toolchains import Toolchain from kiwixbuild.packages import PACKAGE_NAME_MAPPERS from kiwixbuild.utils import pj, remove_duplicates from kiwixbuild.buildenv import BuildEnv -from kiwixbuild._global import neutralEnv +from kiwixbuild._global import neutralEnv, add_plt_step, target_steps _SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__)) TEMPLATES_DIR = pj(os.path.dirname(_SCRIPT_DIR), 'templates') -class PlatformInfo: - all_platforms = {} +class _MetaPlatform(type): + def __new__(cls, name, bases, dct): + _class = type.__new__(cls, name, bases, dct) + if name not in ('PlatformInfo', 'MetaPlatformInfo') and 'name' in dct: + dep_name = dct['name'] + PlatformInfo.all_platforms[dep_name] = _class + return _class - def __init__(self, name, build, static, toolchains, hosts=None): - self.all_platforms[name] = self - self.build = build - self.static = static - self.toolchains = toolchains - self.compatible_hosts = hosts + +class PlatformInfo(metaclass=_MetaPlatform): + all_platforms = {} + all_running_platforms = {} + toolchain_names = [] + configure_option = "" + + def __init__(self): + self.all_running_platforms[self.name] = self self.buildEnv = BuildEnv(self) self.setup_toolchains() @@ -28,9 +36,14 @@ class PlatformInfo: def setup_toolchains(self): self.toolchains = {} - for tlc_name in self.toolchains: + for tlc_name in self.toolchain_names: ToolchainClass = Toolchain.all_toolchains[tlc_name] self.toolchains[tlc_name] = ToolchainClass() + if ToolchainClass.Source is not None: + add_plt_step(('source', tlc_name), ToolchainClass.Source) + if ToolchainClass.Builder is not None: + plt_name = 'neutral' if ToolchainClass.neutral else self.name + add_plt_step((plt_name, tlc_name), ToolchainClass.Builder) def get_cross_config(self): return {} diff --git a/kiwixbuild/platforms/i586.py b/kiwixbuild/platforms/i586.py index e12c953..b52dd9e 100644 --- a/kiwixbuild/platforms/i586.py +++ b/kiwixbuild/platforms/i586.py @@ -1,11 +1,13 @@ +import os from .base import PlatformInfo +from kiwixbuild.utils import which class I586PlatformInfo(PlatformInfo): + build = 'i586' arch_full = 'i586-linux-gnu' - def __init__(self, name, static): - super().__init__(name, 'i586', static, [], ['fedora', 'debian']) + compatible_hosts = ['fedora', 'debian'] def get_cross_config(self): return { @@ -52,5 +54,10 @@ class I586PlatformInfo(PlatformInfo): self.buildEnv.cmake_crossfile = self._gen_crossfile('cmake_i586_cross_file.txt') self.buildEnv.meson_crossfile = self._gen_crossfile('meson_cross_file.txt') -I586PlatformInfo('i586_dyn', False) -I586PlatformInfo('i586_static', True) +class I586Dyn(I586PlatformInfo): + name = 'i586_dyn' + static = False + +class I586Static(I586PlatformInfo): + name = 'i586_static' + static = True diff --git a/kiwixbuild/platforms/ios.py b/kiwixbuild/platforms/ios.py index bbbe804..499a8b3 100644 --- a/kiwixbuild/platforms/ios.py +++ b/kiwixbuild/platforms/ios.py @@ -6,18 +6,12 @@ from kiwixbuild.utils import pj, xrun_find class iOSPlatformInfo(PlatformInfo): - __arch_infos = { - 'armv7': ('arm-apple-darwin', 'armv7', 'iphoneos'), - 'arm64': ('arm-apple-darwin', 'arm64', 'iphoneos'), - 'i386': ('', 'i386', 'iphonesimulator'), - 'x86_64': ('', 'x86_64', 'iphonesimulator'), - } + build = 'iOS' + static = True + compatible_hosts = ['Darwin'] - def __init__(self, name, arch): - super().__init__(name, 'iOS', True, [], - hosts=['Darwin']) - self.arch = arch - self.arch_full, self.cpu, self.sdk_name = self.__arch_infos[arch] + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) self._root_path = None @property @@ -81,7 +75,26 @@ class iOSPlatformInfo(PlatformInfo): env['CXX'] = self.binaries['CXX'] -iOSPlatformInfo('iOS_armv7', 'armv7') -iOSPlatformInfo('iOS_arm64', 'arm64') -iOSPlatformInfo('iOS_i386', 'i386') -iOSPlatformInfo('iOS_x86_64', 'x86_64') +class iOSArmv7(iOSPlatformInfo): + name = 'iOS_armv7' + arch = cpu = 'armv7' + arch_full = 'arm-apple-darwin' + sdk_name = 'iphoneos' + +class iOSArm64(iOSPlatformInfo): + name = 'iOS_arm64' + arch = cpu = 'arm64' + arch_full = 'arm-apple-darwin' + sdk_name = 'iphoneos' + +class iOSi386(iOSPlatformInfo): + name = 'iOS_i386' + arch = cpu = 'i386' + arch_full = '' + sdk_name = 'iphonesimulator' + +class iOSx64(iOSPlatformInfo): + name = 'iOS_x86_64' + arch = cpu = 'x86_64' + arch_full = '' + sdk_name = 'iphonesimulator' diff --git a/kiwixbuild/platforms/native.py b/kiwixbuild/platforms/native.py index bd324e5..069d9f2 100644 --- a/kiwixbuild/platforms/native.py +++ b/kiwixbuild/platforms/native.py @@ -2,9 +2,15 @@ from .base import PlatformInfo class NativePlatformInfo(PlatformInfo): - def __init__(self, name, static, hosts): - super().__init__(name, 'native', static, [], hosts) + build = 'native' -NativePlatformInfo('native_dyn', False, ['fedora', 'debian', 'Darwin']) -NativePlatformInfo('native_static', True, ['fedora', 'debian']) +class NativeDyn(NativePlatformInfo): + name = 'native_dyn' + static = False + compatible_hosts = ['fedora', 'debian', 'Darwin'] + +class NativeStatic(NativePlatformInfo): + name = 'native_static' + static = True + compatible_hosts = ['fedora', 'debian'] diff --git a/kiwixbuild/platforms/win32.py b/kiwixbuild/platforms/win32.py index 785acc3..81ec903 100644 --- a/kiwixbuild/platforms/win32.py +++ b/kiwixbuild/platforms/win32.py @@ -7,9 +7,9 @@ from kiwixbuild._global import neutralEnv class Win32PlatformInfo(PlatformInfo): extra_libs = ['-lwinmm', '-lws2_32', '-lshlwapi', '-lrpcrt4', '-lmsvcr90', '-liphlpapi'] + build = 'win32' + compatible_hosts = ['fedora', 'debian'] arch_full = 'i686-w64-mingw32' - def __init__(self, name, static): - super().__init__(name, 'win32', static, [], ['fedora', 'debian']) def get_cross_config(self): return { @@ -76,5 +76,10 @@ class Win32PlatformInfo(PlatformInfo): env['PKG_CONFIG_LIBDIR'] = pj(self.root_path, 'lib', 'pkgconfig') env['LIBS'] = " ".join(self.extra_libs) + " " +env['LIBS'] -Win32PlatformInfo('win32_dyn', False) -Win32PlatformInfo('win32_static', True) +class Win32Dyn(Win32PlatformInfo): + name = 'win32_dyn' + static = False + +class Win32Static(Win32PlatformInfo): + name = 'win32_static' + static = True diff --git a/kiwixbuild/toolchains/android_ndk.py b/kiwixbuild/toolchains/android_ndk.py index f33e7e8..597f82f 100644 --- a/kiwixbuild/toolchains/android_ndk.py +++ b/kiwixbuild/toolchains/android_ndk.py @@ -12,55 +12,11 @@ class android_ndk(Toolchain): version = 'r13b' gccver = '4.9.x' - @property - def api(self): - return '21' if self.arch in ('arm64', 'mips64', 'x86_64') else '14' - - @property - def platform(self): - return 'android-'+self.api - - @property - def arch(self): - return self.buildEnv.platform_info.arch - - @property - def arch_full(self): - return self.buildEnv.platform_info.arch_full - - @property - def toolchain(self): - return self.arch_full+"-4.9" - - @property - def root_path(self): - return pj(self.builder.install_path, 'sysroot') - - @property - def binaries(self): - binaries = ((k,'{}-{}'.format(self.arch_full, v)) - for k, v in (('CC', 'gcc'), - ('CXX', 'g++'), - ('AR', 'ar'), - ('STRIP', 'strip'), - ('WINDRES', 'windres'), - ('RANLIB', 'ranlib'), - ('LD', 'ld')) - ) - return {k:pj(self.builder.install_path, 'bin', v) - for k,v in binaries} - - @property - def configure_option(self): - return '--host={}'.format(self.arch_full) - - @property - def full_name(self): - return "{name}-{version}-{arch}-{api}".format( - name = self.name, - version = self.version, - arch = self.arch, - api = self.api) + @classmethod + def full_name(cls): + return "{name}-{version}".format( + name = cls.name, + version = cls.version) class Source(ReleaseDownload): archive = Remotefile('android-ndk-r13b-linux-x86_64.zip', @@ -75,11 +31,34 @@ class android_ndk(Toolchain): class Builder(Builder): - @property def install_path(self): return self.build_path + @property + def api(self): + return '21' if self.arch in ('arm64', 'mips64', 'x86_64') else '14' + + @property + def platform(self): + return 'android-'+self.api + + @property + def arch(self): + return self.buildEnv.platformInfo.arch + + @property + def arch_full(self): + return self.buildEnv.platformInfo.arch_full + + @property + def toolchain(self): + return self.arch_full+"-4.9" + + @property + def configure_option(self): + return '--host={}'.format(self.arch_full) + def _build_platform(self, context): context.try_skip(self.build_path) script = pj(self.source_path, 'build/tools/make_standalone_toolchain.py') @@ -87,17 +66,18 @@ class android_ndk(Toolchain): command = '{script} --arch={arch} --api={api} --install-dir={install_dir} --force' command = command.format( script=script, - arch=self.target.arch, - api=self.target.api, + arch=self.arch, + api=self.api, install_dir=self.install_path ) + context.force_native_build = True run_command(command, self.build_path, context, buildEnv=self.buildEnv) def _fix_permission_right(self, context): context.try_skip(self.build_path) bin_dirs = [pj(self.install_path, 'bin'), - pj(self.install_path, self.target.arch_full, 'bin'), - pj(self.install_path, 'libexec', 'gcc', self.target.arch_full, self.target.gccver) + pj(self.install_path, self.arch_full, 'bin'), + pj(self.install_path, 'libexec', 'gcc', self.arch_full, self.target.gccver) ] for root, dirs, files in os.walk(self.install_path): if not root in bin_dirs: diff --git a/kiwixbuild/toolchains/android_sdk.py b/kiwixbuild/toolchains/android_sdk.py index de4b0b7..c58e05f 100644 --- a/kiwixbuild/toolchains/android_sdk.py +++ b/kiwixbuild/toolchains/android_sdk.py @@ -20,7 +20,7 @@ class android_sdk(Toolchain): @property def install_path(self): - return pj(self.buildEnv.toolchain_dir, self.target.full_name) + return pj(self.buildEnv.toolchain_dir, self.target.full_name()) def _build_platform(self, context): context.try_skip(self.install_path) diff --git a/kiwixbuild/toolchains/base_toolchain.py b/kiwixbuild/toolchains/base_toolchain.py index 3c626e0..c51de7a 100644 --- a/kiwixbuild/toolchains/base_toolchain.py +++ b/kiwixbuild/toolchains/base_toolchain.py @@ -10,11 +10,12 @@ class _MetaToolchain(type): def __new__(cls, name, bases, dct): _class = type.__new__(cls, name, bases, dct) if name != 'Toolchain': - Toolchain.all_toolchains[name] = _class + Toolchain.all_toolchains[dct['name']] = _class return _class class Toolchain(metaclass=_MetaToolchain): + force_native_build = False neutral = True all_toolchains = {} configure_option = "" @@ -23,15 +24,12 @@ class Toolchain(metaclass=_MetaToolchain): Builder = None Source = None - def __init__(self): - self.source = self.Source(self) if self.Source else None - self.builder = self.Builder(self) if self.Builder else None - @property - def full_name(self): + @classmethod + def full_name(cls): return "{name}-{version}".format( - name = self.name, - version = self.version) + name = cls.name, + version = cls.version) @property def source_path(self):