blob: 30fda0835ea6a8c8083be3353b219efd26f65964 [file] [log] [blame]
#!/usr/bin/env python
"""Ninja toolchain abstraction"""
import sys
import os
import subprocess
import platform
import random
import string
import json
import zlib
import version
import android
import xcode
def check_output(args):
import subprocess
return subprocess.check_output(args).decode().strip()
def supported_toolchains():
return ['msvc', 'gcc', 'clang', 'intel']
def supported_architectures():
return ['x86', 'x86-64', 'ppc', 'ppc64', 'arm6', 'arm7', 'arm64', 'mips', 'mips64', 'generic']
def get_boolean_flag(val):
return (val == True or val == "True" or val == "true" or val == "1" or val == 1)
def make_toolchain(host, target, toolchain):
if toolchain is None:
if target.is_raspberrypi():
toolchain = 'gcc'
elif host.is_windows() and target.is_windows():
toolchain = 'msvc'
else:
toolchain = 'clang'
toolchainmodule = __import__(toolchain, globals(), locals())
return toolchainmodule.create(host, target, toolchain)
def make_pathhash(path, targettype):
return '-' + hex(zlib.adler32((path + targettype).encode()) & 0xffffffff)[2:-1]
class Toolchain(object):
def __init__(self, host, target, toolchain):
self.host = host
self.target = target
self.toolchain = toolchain
self.subninja = ''
self.buildprefs = ''
#Set default values
self.build_monolithic = False
self.build_coverage = False
self.build_lto = False
self.support_lua = False
self.internal_deps = False
self.python = 'python'
self.objext = '.o'
if target.is_windows():
self.libprefix = ''
self.staticlibext = '.lib'
self.dynamiclibext = '.dll'
self.binprefix = ''
self.binext = '.exe'
elif target.is_android():
self.libprefix = 'lib'
self.staticlibext = '.a'
self.dynamiclibext = '.so'
self.binprefix = 'lib'
self.binext = '.so'
else:
self.libprefix = 'lib'
self.staticlibext = '.a'
if target.is_macos() or target.is_ios():
self.dynamiclibext = '.dylib'
else:
self.dynamiclibext = '.so'
self.binprefix = ''
self.binext = ''
#Paths
self.buildpath = os.path.join('build', 'ninja', target.platform)
self.libpath = os.path.join('lib', target.platform)
self.binpath = os.path.join('bin', target.platform)
#Dependency paths
self.depend_includepaths = []
self.depend_libpaths = []
#Target helpers
self.android = None
self.xcode = None
#Command wrappers
if host.is_windows():
self.rmcmd = lambda p: 'cmd /C (IF exist ' + p + ' (del /F /Q ' + p + '))'
self.cdcmd = lambda p: 'cmd /C cd ' + p
self.mkdircmd = lambda p: 'cmd /C (IF NOT exist ' + p + ' (mkdir ' + p + '))'
self.copycmd = lambda p, q: 'cmd /C (IF exist ' + q + ' (del /F /Q ' + q + ')) & copy /Y ' + p + ' ' + q + ' > NUL'
else:
self.rmcmd = lambda p: 'rm -f ' + p
self.cdcmd = lambda p: 'cd ' + p
self.mkdircmd = lambda p: 'mkdir -p ' + p
self.copycmd = lambda p, q: 'cp -f ' + p + ' ' + q
#Target functionality
if target.is_android():
self.android = android.make_target(self, host, target)
if target.is_macos() or target.is_ios():
self.xcode = xcode.make_target(self, host, target)
#Builders
self.builders = {}
#Paths created
self.paths_created = {}
def initialize_subninja(self, path):
self.subninja = path
def initialize_project(self, project):
self.project = project
version.generate_version(self.project, self.project)
def initialize_archs(self, archs):
self.archs = list(archs)
if self.archs is None or self.archs == []:
self.initialize_default_archs()
def initialize_default_archs(self):
if self.target.is_windows():
self.archs = ['x86-64']
elif self.target.is_linux() or self.target.is_bsd() or self.target.is_sunos() or self.target.is_haiku():
localarch = subprocess.check_output(['uname', '-m']).decode().strip()
if localarch == 'x86_64' or localarch == 'amd64':
self.archs = ['x86-64']
elif localarch == 'i686':
self.archs = ['x86']
else:
self.archs = [localarch]
elif self.target.is_macos():
self.archs = ['x86-64']
elif self.target.is_ios():
self.archs = ['arm7', 'arm64']
elif self.target.is_raspberrypi():
self.archs = ['arm6']
elif self.target.is_android():
self.archs = ['arm7', 'arm64', 'x86', 'x86-64'] #'mips', 'mips64'
elif self.target.is_tizen():
self.archs = ['x86', 'arm7']
def initialize_configs(self, configs):
self.configs = list(configs)
if self.configs is None or self.configs == []:
self.initialize_default_configs()
def initialize_default_configs(self):
self.configs = ['debug', 'release']#, 'profile', 'deploy']
def initialize_toolchain(self):
if self.android != None:
self.android.initialize_toolchain()
if self.xcode != None:
self.xcode.initialize_toolchain()
def initialize_depends(self, dependlibs):
for lib in dependlibs:
includepath = ''
libpath = ''
testpaths = [
os.path.join('..', lib),
os.path.join('..', lib + '_lib')
]
for testpath in testpaths:
if os.path.isfile(os.path.join(testpath, lib, lib + '.h')):
if self.subninja != '':
basepath, _ = os.path.split(self.subninja)
_, libpath = os.path.split(testpath)
testpath = os.path.join(basepath, libpath)
includepath = testpath
libpath = testpath
break
if includepath == '':
print("Unable to locate dependent lib: " + lib)
sys.exit(-1)
else:
self.depend_includepaths += [includepath]
if self.subninja == '':
self.depend_libpaths += [libpath]
def build_toolchain(self):
if self.android != None:
self.android.build_toolchain()
if self.xcode != None:
self.xcode.build_toolchain()
def parse_default_variables(self, variables):
if not variables:
return
if isinstance(variables, dict):
iterator = iter(variables.items())
else:
iterator = iter(variables)
for key, val in iterator:
if key == 'monolithic':
self.build_monolithic = get_boolean_flag(val)
elif key == 'coverage':
self.build_coverage = get_boolean_flag(val)
elif key == 'lto':
self.build_lto = get_boolean_flag(val)
elif key == 'support_lua':
self.support_lua = get_boolean_flag(val)
elif key == 'internal_deps':
self.internal_deps = get_boolean_flag(val)
if self.xcode != None:
self.xcode.parse_default_variables(variables)
def read_build_prefs(self):
self.read_prefs('build.json')
self.read_prefs(os.path.join('build', 'ninja', 'build.json'))
if self.buildprefs != '':
self.read_prefs(self.buildprefs)
def read_prefs(self, filename):
if not os.path.isfile( filename ):
return
file = open(filename, 'r')
prefs = json.load(file)
file.close()
self.parse_prefs(prefs)
def parse_prefs(self, prefs):
if 'monolithic' in prefs:
self.build_monolithic = get_boolean_flag(prefs['monolithic'])
if 'coverage' in prefs:
self.build_coverage = get_boolean_flag( prefs['coverage'] )
if 'lto' in prefs:
self.build_lto = get_boolean_flag( prefs['lto'] )
if 'support_lua' in prefs:
self.support_lua = get_boolean_flag(prefs['support_lua'])
if 'python' in prefs:
self.python = prefs['python']
if self.android != None:
self.android.parse_prefs(prefs)
if self.xcode != None:
self.xcode.parse_prefs(prefs)
def archs(self):
return self.archs
def configs(self):
return self.configs
def project(self):
return self.project
def is_monolithic(self):
return self.build_monolithic
def use_coverage(self):
return self.build_coverage
def use_lto(self):
return self.build_lto
def write_variables(self, writer):
writer.variable('buildpath', self.buildpath)
writer.variable('target', self.target.platform)
writer.variable('config', '')
if self.android != None:
self.android.write_variables(writer)
if self.xcode != None:
self.xcode.write_variables(writer)
def write_rules(self, writer):
writer.pool('serial_pool', 1)
writer.rule('copy', command = self.copycmd('$in', '$out'), description = 'COPY $in -> $out')
writer.rule('mkdir', command = self.mkdircmd('$out'), description = 'MKDIR $out')
if self.android != None:
self.android.write_rules(writer)
if self.xcode != None:
self.xcode.write_rules(writer)
def cdcmd(self):
return self.cdcmd
def mkdircmd(self):
return self.mkdircmd
def mkdir(self, writer, path, implicit = None, order_only = None):
if path in self.paths_created:
return self.paths_created[path]
if self.subninja != '':
return
cmd = writer.build(path, 'mkdir', None, implicit = implicit, order_only = order_only)
self.paths_created[path] = cmd
return cmd
def copy(self, writer, src, dst, implicit = None, order_only = None):
return writer.build(dst, 'copy', src, implicit = implicit, order_only = order_only)
def builder_multicopy(self, writer, config, archs, targettype, infiles, outpath, variables):
output = []
rootdir = self.mkdir(writer, outpath)
for file in infiles:
path, targetfile = os.path.split(file)
archpath = outpath
#Find which arch we are copying from and append to target path
#unless on generic arch targets, then re-add if not self.target.is_generic():
for arch in archs:
remainpath, subdir = os.path.split(path)
while remainpath != '':
if subdir == arch:
archpath = os.path.join(outpath, arch)
break
remainpath, subdir = os.path.split(remainpath)
if remainpath != '':
break
targetpath = os.path.join(archpath, targetfile)
if os.path.normpath(file) != os.path.normpath(targetpath):
archdir = self.mkdir(writer, archpath, implicit = rootdir)
output += self.copy(writer, file, targetpath, order_only = archdir)
return output
def path_escape(self, path):
if self.host.is_windows():
return "\"%s\"" % path.replace("\"", "'")
return path
def paths_forward_slash(self, paths):
return [path.replace('\\', '/') for path in paths]
def prefix_includepath(self, path):
if os.path.isabs(path) or self.subninja == '':
return path
if path == '.':
return self.subninja
return os.path.join(self.subninja, path)
def prefix_includepaths(self, includepaths):
return [self.prefix_includepath(path) for path in includepaths]
def list_per_config(self, config_dicts, config):
if config_dicts is None:
return []
config_list = []
for config_dict in config_dicts:
config_list += config_dict[config]
return config_list
def implicit_deps(self, config, variables):
if variables == None:
return None
if 'implicit_deps' in variables:
return self.list_per_config(variables['implicit_deps'], config)
return None
def make_implicit_deps(self, outpath, arch, config, dependlibs):
deps = {}
deps[config] = []
for lib in dependlibs:
if self.target.is_macos() or self.target.is_ios():
finalpath = os.path.join(self.libpath, config, self.libprefix + lib + self.staticlibext)
else:
finalpath = os.path.join(self.libpath, config, arch, self.libprefix + lib + self.staticlibext)
deps[config] += [finalpath]
return [deps]
def compile_file(self, writer, config, arch, targettype, infile, outfile, variables):
extension = os.path.splitext(infile)[1][1:]
if extension in self.builders:
return self.builders[extension](writer, config, arch, targettype, infile, outfile, variables)
return []
def compile_node(self, writer, nodetype, config, arch, infiles, outfile, variables):
if nodetype in self.builders:
return self.builders[nodetype](writer, config, arch, nodetype, infiles, outfile, variables)
return []
def build_sources(self, writer, nodetype, multitype, module, sources, binfile, basepath, outpath, configs, includepaths, libpaths, dependlibs, libs, implicit_deps, variables, frameworks):
if module != '':
decoratedmodule = module + make_pathhash(self.subninja + module + binfile, nodetype)
else:
decoratedmodule = basepath + make_pathhash(self.subninja + basepath + binfile, nodetype)
built = {}
if includepaths is None:
includepaths = []
if libpaths is None:
libpaths = []
sourcevariables = (variables or {}).copy()
sourcevariables.update({
'includepaths': self.depend_includepaths + self.prefix_includepaths(list(includepaths))})
if not libs and dependlibs != None:
libs = []
if dependlibs != None:
libs = (dependlibs or []) + libs
nodevariables = (variables or {}).copy()
nodevariables.update({
'libs': libs,
'implicit_deps': implicit_deps,
'libpaths': self.depend_libpaths + list(libpaths),
'frameworks': frameworks})
self.module = module
self.buildtarget = binfile
for config in configs:
archnodes = []
built[config] = []
for arch in self.archs:
objs = []
buildpath = os.path.join('$buildpath', config, arch)
modulepath = os.path.join(buildpath, basepath, decoratedmodule)
sourcevariables['modulepath'] = modulepath
nodevariables['modulepath'] = modulepath
#Make per-arch-and-config list of final implicit deps, including dependent libs
if self.internal_deps and dependlibs != None:
dep_implicit_deps = []
if implicit_deps:
dep_implicit_deps += implicit_deps
dep_implicit_deps += self.make_implicit_deps(outpath, arch, config, dependlibs)
nodevariables['implicit_deps'] = dep_implicit_deps
#Compile all sources
for name in sources:
if os.path.isabs(name):
infile = name
outfile = os.path.join(modulepath, os.path.splitext(os.path.basename(name))[0] + make_pathhash(infile, nodetype) + self.objext)
else:
infile = os.path.join(basepath, module, name)
outfile = os.path.join(modulepath, os.path.splitext(name)[0] + make_pathhash(infile, nodetype) + self.objext)
if self.subninja != '':
infile = os.path.join(self.subninja, infile)
objs += self.compile_file(writer, config, arch, nodetype, infile, outfile, sourcevariables)
#Build arch node (per-config-and-arch binary)
archoutpath = os.path.join(modulepath, binfile)
archnodes += self.compile_node(writer, nodetype, config, arch, objs, archoutpath, nodevariables)
#Build final config node (per-config binary)
built[config] += self.compile_node(writer, multitype, config, self.archs, archnodes, os.path.join(outpath, config), None)
writer.newline()
return built
def lib(self, writer, module, sources, libname, basepath, configs, includepaths, variables, outpath = None):
built = {}
if basepath == None:
basepath = ''
if configs is None:
configs = list(self.configs)
if libname is None:
libname = module
libfile = self.libprefix + libname + self.staticlibext
if outpath is None:
outpath = self.libpath
return self.build_sources(writer, 'lib', 'multilib', module, sources, libfile, basepath, outpath, configs, includepaths, None, None, None, None, variables, None)
def sharedlib(self, writer, module, sources, libname, basepath, configs, includepaths, libpaths, implicit_deps, dependlibs, libs, frameworks, variables, outpath = None):
built = {}
if basepath == None:
basepath = ''
if configs is None:
configs = list(self.configs)
if libname is None:
libname = module
libfile = self.libprefix + libname + self.dynamiclibext
if outpath is None:
outpath = self.binpath
return self.build_sources(writer, 'sharedlib', 'multisharedlib', module, sources, libfile, basepath, outpath, configs, includepaths, libpaths, dependlibs, libs, implicit_deps, variables, frameworks)
def bin(self, writer, module, sources, binname, basepath, configs, includepaths, libpaths, implicit_deps, dependlibs, libs, frameworks, variables, outpath = None):
built = {}
if basepath == None:
basepath = ''
if configs is None:
configs = list(self.configs)
binfile = self.binprefix + binname + self.binext
if outpath is None:
outpath = self.binpath
return self.build_sources(writer, 'bin', 'multibin', module, sources, binfile, basepath, outpath, configs, includepaths, libpaths, dependlibs, libs, implicit_deps, variables, frameworks)
def app(self, writer, module, sources, binname, basepath, configs, includepaths, libpaths, implicit_deps, dependlibs, libs, frameworks, variables, resources):
builtbin = []
# Filter out platforms that do not have app concept
if not (self.target.is_macos() or self.target.is_ios() or self.target.is_android() or self.target.is_tizen()):
return builtbin
if basepath is None:
basepath = ''
if binname is None:
binname = module
if configs is None:
configs = list(self.configs)
for config in configs:
archbins = self.bin(writer, module, sources, binname, basepath, [config], includepaths, libpaths, implicit_deps, dependlibs, libs, frameworks, variables, '$buildpath')
if self.target.is_macos() or self.target.is_ios():
binpath = os.path.join(self.binpath, config, binname + '.app')
builtbin += self.xcode.app(self, writer, module, archbins, self.binpath, binname, basepath, config, None, resources, True)
if self.target.is_android():
javasources = [name for name in sources if name.endswith('.java')]
builtbin += self.android.apk(self, writer, module, archbins, javasources, self.binpath, binname, basepath, config, None, resources)
#elif self.target.is_tizen():
# builtbin += self.tizen.tpk( writer, config, basepath, module, binname = binname, archbins = archbins, resources = resources )
return builtbin