1#!/usr/bin/env python
2
3"""Ninja toolchain abstraction for Clang compiler suite"""
4
5import os
6import subprocess
7
8import toolchain
9
10class ClangToolchain(toolchain.Toolchain):
11
12  def initialize(self, project, archs, configs, includepaths, dependlibs, libpaths, variables, subninja):
13    #Local variable defaults
14    self.toolchain = ''
15    self.sdkpath = ''
16    self.includepaths = []
17    self.libpaths = libpaths
18    self.ccompiler = 'clang'
19    self.cxxcompiler = 'clang++'
20    self.archiver = 'ar'
21    self.linker = 'clang'
22    self.cxxlinker = 'clang++'
23    if self.target.is_windows():
24      self.archiver = 'llvm-ar'
25
26    #Default variables
27    self.sysroot = ''
28    if self.target.is_ios():
29      self.deploymenttarget = '9.0'
30    if self.target.is_macos():
31      self.deploymenttarget = '10.7'
32
33    #Command definitions
34    self.cccmd = '$toolchain$cc -MMD -MT $out -MF $out.d $includepaths $moreincludepaths $cflags $carchflags $cconfigflags $cmoreflags -c $in -o $out'
35    self.cxxcmd = '$toolchain$cxx -MMD -MT $out -MF $out.d $includepaths $moreincludepaths $cxxflags $carchflags $cconfigflags $cmoreflags -c $in -o $out'
36    self.ccdeps = 'gcc'
37    self.ccdepfile = '$out.d'
38    self.arcmd = self.rmcmd('$out') + ' && $toolchain$ar crsD $ararchflags $arflags $out $in'
39    self.linkcmd = '$toolchain$link $libpaths $configlibpaths $linkflags $linkarchflags $linkconfigflags -o $out $in $libs $archlibs $oslibs $frameworks'
40
41    #Base flags
42    self.cflags = ['-D' + project.upper() + '_COMPILE=1',
43                   '-funit-at-a-time', '-fstrict-aliasing', '-fvisibility=hidden', '-fno-stack-protector',
44                   '-fomit-frame-pointer', '-fno-math-errno','-ffinite-math-only', '-funsafe-math-optimizations',
45                   '-fno-trapping-math', '-ffast-math']
46    self.cwarnflags = ['-W', '-Werror', '-pedantic', '-Wall', '-Weverything',
47                       '-Wno-padded', '-Wno-documentation-unknown-command', '-Wno-static-in-inline']
48    self.cmoreflags = []
49    self.mflags = []
50    self.arflags = []
51    self.linkflags = ['-fomit-frame-pointer']
52    self.oslibs = []
53    self.frameworks = []
54
55    self.initialize_subninja(subninja)
56    self.initialize_archs(archs)
57    self.initialize_configs(configs)
58    self.initialize_project(project)
59    self.initialize_toolchain()
60    self.initialize_depends(dependlibs)
61
62    self.parse_default_variables(variables)
63    self.read_build_prefs()
64
65    if self.target.is_linux() or self.target.is_bsd() or self.target.is_raspberrypi():
66      self.cflags += ['-D_GNU_SOURCE=1']
67      self.linkflags += ['-pthread']
68    if self.target.is_linux() or self.target.is_raspberrypi():
69      self.oslibs += ['dl']
70    if self.target.is_bsd():
71      self.oslibs += ['execinfo']
72
73    self.includepaths = self.prefix_includepaths((includepaths or []) + ['.'])
74
75    if self.is_monolithic():
76      self.cflags += ['-DBUILD_MONOLITHIC=1']
77    if self.use_coverage():
78      self.cflags += ['--coverage']
79      self.linkflags += ['--coverage']
80
81    if not 'nowarning' in variables or not variables['nowarning']:
82      self.cflags += self.cwarnflags
83    else:
84      self.cflags += ['-w']
85    self.cxxflags = list(self.cflags)
86
87    self.cflags += ['-std=gnu11']
88    if self.target.is_macos() or self.target.is_ios():
89      self.cxxflags += ['-std=c++14', '-stdlib=libc++']
90    else:
91      self.cxxflags += ['-std=gnu++14']
92
93    #Overrides
94    self.objext = '.o'
95
96    #Builders
97    self.builders['c'] = self.builder_cc
98    self.builders['cc'] = self.builder_cxx
99    self.builders['cpp'] = self.builder_cxx
100    self.builders['lib'] = self.builder_lib
101    self.builders['sharedlib'] = self.builder_sharedlib
102    self.builders['bin'] = self.builder_bin
103    if self.target.is_macos() or self.target.is_ios():
104      self.builders['m'] = self.builder_cm
105      self.builders['multilib'] = self.builder_apple_multilib
106      self.builders['multisharedlib'] = self.builder_apple_multisharedlib
107      self.builders['multibin'] = self.builder_apple_multibin
108    else:
109      self.builders['multilib'] = self.builder_multicopy
110      self.builders['multisharedlib'] = self.builder_multicopy
111      self.builders['multibin'] = self.builder_multicopy
112
113    #Setup target platform
114    self.build_toolchain()
115
116  def name(self):
117    return 'clang'
118
119  def parse_prefs(self, prefs):
120    super(ClangToolchain, self).parse_prefs(prefs)
121    if 'clang' in prefs:
122      clangprefs = prefs['clang']
123      if 'toolchain' in clangprefs:
124        self.toolchain = clangprefs['toolchain']
125        if os.path.split(self.toolchain)[1] != 'bin':
126          self.toolchain = os.path.join(self.toolchain, 'bin')
127      if 'archiver' in clangprefs:
128        self.archiver = clangprefs['archiver']
129    if self.target.is_ios() and 'ios' in prefs:
130      iosprefs = prefs['ios']
131      if 'deploymenttarget' in iosprefs:
132        self.deploymenttarget = iosprefs['deploymenttarget']
133    if self.target.is_macos() and 'macos' in prefs:
134      macosprefs = prefs['macos']
135      if 'deploymenttarget' in macosprefs:
136        self.deploymenttarget = macosprefs['deploymenttarget']
137
138  def write_variables(self, writer):
139    super(ClangToolchain, self).write_variables(writer)
140    writer.variable('toolchain', self.toolchain)
141    writer.variable('sdkpath', self.sdkpath)
142    writer.variable('sysroot', self.sysroot)
143    writer.variable('cc', self.ccompiler)
144    writer.variable('cxx', self.cxxcompiler)
145    writer.variable('ar', self.archiver)
146    writer.variable('link', self.linker)
147    if self.target.is_macos() or self.target.is_ios():
148      writer.variable('lipo', self.lipo)
149    writer.variable('includepaths', self.make_includepaths(self.includepaths))
150    writer.variable('moreincludepaths', '')
151    writer.variable('cflags', self.cflags)
152    writer.variable('cxxflags', self.cxxflags)
153    if self.target.is_macos() or self.target.is_ios():
154      writer.variable('mflags', self.mflags)
155    writer.variable('carchflags', '')
156    writer.variable('cconfigflags', '')
157    writer.variable('cmoreflags', self.cmoreflags)
158    writer.variable('arflags', self.arflags)
159    writer.variable('ararchflags', '')
160    writer.variable('arconfigflags', '')
161    writer.variable('linkflags', self.linkflags)
162    writer.variable('linkarchflags', '')
163    writer.variable('linkconfigflags', '')
164    writer.variable('libs', '')
165    writer.variable('libpaths', self.make_libpaths(self.libpaths))
166    writer.variable('configlibpaths', '')
167    writer.variable('archlibs', '')
168    writer.variable('oslibs', self.make_libs(self.oslibs))
169    writer.variable('frameworks', '')
170    writer.newline()
171
172  def write_rules(self, writer):
173    super(ClangToolchain, self).write_rules(writer)
174    writer.rule('cc', command = self.cccmd, depfile = self.ccdepfile, deps = self.ccdeps, description = 'CC $in')
175    writer.rule('cxx', command = self.cxxcmd, depfile = self.ccdepfile, deps = self.ccdeps, description = 'CXX $in')
176    if self.target.is_macos() or self.target.is_ios():
177      writer.rule('cm', command = self.cmcmd, depfile = self.ccdepfile, deps = self.ccdeps, description = 'CM $in')
178      writer.rule( 'lipo', command = self.lipocmd, description = 'LIPO $out' )
179    writer.rule('ar', command = self.arcmd, description = 'LIB $out')
180    writer.rule('link', command = self.linkcmd, description = 'LINK $out')
181    writer.rule('so', command = self.linkcmd, description = 'SO $out')
182    writer.newline()
183
184  def build_toolchain(self):
185    super(ClangToolchain, self).build_toolchain()
186    if self.target.is_windows():
187      self.build_windows_toolchain()
188    elif self.target.is_android():
189      self.build_android_toolchain()
190    elif self.target.is_macos() or self.target.is_ios():
191      self.build_xcode_toolchain()
192    if self.toolchain != '' and not self.toolchain.endswith('/') and not self.toolchain.endswith('\\'):
193      self.toolchain += os.sep
194
195  def build_windows_toolchain(self):
196    self.cflags += ['-U__STRICT_ANSI__', '-Wno-reserved-id-macro']
197    self.oslibs = ['kernel32', 'user32', 'shell32', 'advapi32']
198
199  def build_android_toolchain(self):
200    self.archiver = 'ar'
201
202    self.cccmd += ' --sysroot=$sysroot'
203    self.linkcmd += ' -shared -Wl,-soname,$liblinkname --sysroot=$sysroot'
204    self.cflags += ['-fpic', '-ffunction-sections', '-funwind-tables', '-fstack-protector', '-fomit-frame-pointer',
205                    '-no-canonical-prefixes', '-Wa,--noexecstack']
206
207    self.linkflags += ['-no-canonical-prefixes', '-Wl,--no-undefined', '-Wl,-z,noexecstack', '-Wl,-z,relro', '-Wl,-z,now']
208
209    self.includepaths += [os.path.join('$ndk', 'sources', 'android', 'native_app_glue'),
210                          os.path.join('$ndk', 'sources', 'android', 'cpufeatures')]
211
212    self.oslibs += ['log']
213
214    self.toolchain = os.path.join('$ndk', 'toolchains', 'llvm', 'prebuilt', self.android.hostarchname, 'bin', '')
215
216  def build_xcode_toolchain(self):
217    if self.target.is_macos():
218      sdk = 'macosx'
219      deploytarget = 'MACOSX_DEPLOYMENT_TARGET=' + self.deploymenttarget
220      self.cflags += ['-fasm-blocks', '-mmacosx-version-min=' + self.deploymenttarget, '-isysroot', '$sysroot']
221      self.cxxflags += ['-fasm-blocks', '-mmacosx-version-min=' + self.deploymenttarget, '-isysroot', '$sysroot']
222      self.arflags += ['-static', '-no_warning_for_no_symbols']
223      self.linkflags += ['-isysroot', '$sysroot']
224    elif self.target.is_ios():
225      sdk = 'iphoneos'
226      deploytarget = 'IPHONEOS_DEPLOYMENT_TARGET=' + self.deploymenttarget
227      self.cflags += ['-fasm-blocks', '-miphoneos-version-min=' + self.deploymenttarget, '-isysroot', '$sysroot']
228      self.cxxflags += ['-fasm-blocks', '-miphoneos-version-min=' + self.deploymenttarget, '-isysroot', '$sysroot']
229      self.arflags += ['-static', '-no_warning_for_no_symbols']
230      self.linkflags += ['-isysroot', '$sysroot']
231    self.cflags += ['-fembed-bitcode-marker']
232
233    platformpath = subprocess.check_output(['xcrun', '--sdk', sdk, '--show-sdk-platform-path']).strip()
234    localpath = platformpath + "/Developer/usr/bin:/Applications/Xcode.app/Contents/Developer/usr/bin:/usr/bin:/bin:/usr/sbin:/sbin"
235
236    self.sysroot = subprocess.check_output(['xcrun', '--sdk', sdk, '--show-sdk-path']).strip()
237
238    self.ccompiler = "PATH=" + localpath + " " + subprocess.check_output(['xcrun', '--sdk', sdk, '-f', 'clang']).strip()
239    self.archiver = "PATH=" + localpath + " " + subprocess.check_output(['xcrun', '--sdk', sdk, '-f', 'libtool']).strip()
240    self.linker = deploytarget + " " + self.ccompiler
241    self.lipo = "PATH=" + localpath + " " + subprocess.check_output(['xcrun', '--sdk', sdk, '-f', 'lipo']).strip()
242
243    self.mflags += list(self.cflags) + ['-fobjc-arc', '-fno-objc-exceptions', '-x', 'objective-c']
244    self.cflags += ['-x', 'c']
245    self.cxxflags += ['-x', 'c++']
246
247    self.cmcmd = self.cccmd.replace('$cflags', '$mflags')
248    self.arcmd = self.rmcmd('$out') + ' && $ar $ararchflags $arflags $in -o $out'
249    self.lipocmd = '$lipo $in -create -output $out'
250
251    if self.target.is_macos():
252      self.frameworks = ['Cocoa', 'CoreFoundation']
253    if self.target.is_ios():
254      self.frameworks = ['CoreGraphics', 'UIKit', 'Foundation']
255
256  def make_includepaths(self, includepaths):
257    if not includepaths is None:
258      return ['-I' + path for path in list(includepaths)]
259    return []
260
261  def make_libpath(self, path):
262    return self.path_escape(path)
263
264  def make_libpaths(self, libpaths):
265    if not libpaths is None:
266      if self.target.is_windows():
267        return ['-Xlinker /LIBPATH:' + self.path_escape(path) for path in libpaths]
268      return ['-L' + self.make_libpath(path) for path in libpaths]
269    return []
270
271  def make_targetarchflags(self, arch, targettype):
272    flags = []
273    if self.target.is_android():
274      if arch == 'x86':
275        flags += ['-target', 'i686-none-linux-android']
276        flags += ['-march=i686', '-mtune=intel', '-mssse3', '-mfpmath=sse', '-m32']
277      elif arch == 'x86-64':
278        flags += ['-target', 'x86_64-none-linux-android']
279        flags += ['-march=x86-64', '-msse4.2', '-mpopcnt', '-m64', '-mtune=intel']
280      elif arch == 'arm6':
281        flags += ['-target', 'armv5te-none-linux-androideabi']
282        flags += ['-march=armv5te', '-mtune=xscale', '-msoft-float', '-marm']
283      elif arch == 'arm7':
284        flags += ['-target', 'armv7-none-linux-androideabi']
285        flags += ['-march=armv7-a', '-mhard-float', '-mfpu=vfpv3-d16', '-mfpu=neon', '-D_NDK_MATH_NO_SOFTFP=1', '-marm']
286      elif arch == 'arm64':
287        flags += ['-target', 'aarch64-none-linux-android']
288      elif arch == 'mips':
289        flags += ['-target', 'mipsel-none-linux-android']
290      elif arch == 'mips64':
291        flags += ['-target', 'mips64el-none-linux-android']
292      flags += ['-gcc-toolchain', self.android.make_gcc_toolchain_path(arch)]
293    elif self.target.is_macos() or self.target.is_ios():
294      if arch == 'x86':
295        flags += [' -arch x86']
296      elif arch == 'x86-64':
297        flags += [' -arch x86_64']
298      elif arch == 'arm7':
299        flags += [' -arch armv7']
300      elif arch == 'arm64':
301        flags += [' -arch arm64']
302    else:
303      if arch == 'x86':
304        flags += ['-m32']
305      elif arch == 'x86-64':
306        flags += ['-m64']
307    return flags
308
309  def make_carchflags(self, arch, targettype):
310    flags = []
311    if targettype == 'sharedlib':
312      flags += ['-DBUILD_DYNAMIC_LINK=1']
313      if self.target.is_linux() or self.target.is_bsd():
314       flags += ['-fPIC']
315    flags += self.make_targetarchflags(arch, targettype)
316    return flags
317
318  def make_cconfigflags(self, config, targettype):
319    flags = []
320    if config == 'debug':
321      flags += ['-DBUILD_DEBUG=1', '-g']
322    elif config == 'release':
323      flags += ['-DBUILD_RELEASE=1', '-DNDEBUG', '-O3', '-g', '-funroll-loops']
324    elif config == 'profile':
325      flags += ['-DBUILD_PROFILE=1', '-DNDEBUG', '-O3', '-g', '-funroll-loops']
326    elif config == 'deploy':
327      flags += ['-DBUILD_DEPLOY=1', '-DNDEBUG', '-O3', '-g', '-funroll-loops']
328    return flags
329
330  def make_ararchflags(self, arch, targettype):
331    flags = []
332    return flags
333
334  def make_arconfigflags(self, config, targettype):
335    flags = []
336    return flags
337
338  def make_linkarchflags(self, arch, targettype, variables):
339    flags = []
340    flags += self.make_targetarchflags(arch, targettype)
341    if self.target.is_android():
342      if arch == 'arm7':
343        flags += ['-Wl,--no-warn-mismatch', '-Wl,--fix-cortex-a8']
344    if self.target.is_windows():
345      if arch == 'x86':
346        flags += ['-Xlinker', '/MACHINE:X86']
347      elif arch == 'x86-64':
348        flags += ['-Xlinker', '/MACHINE:X64']
349    if self.target.is_macos() and variables != None and 'support_lua' in variables and variables['support_lua']:
350      flags += ['-pagezero_size', '10000', '-image_base', '100000000']
351    return flags
352
353  def make_linkconfigflags(self, config, targettype, variables):
354    flags = []
355    if self.target.is_windows():
356      if targettype == 'sharedlib':
357        flags += ['-Xlinker', '/DLL']
358      elif targettype == 'bin':
359        flags += ['-Xlinker', '/SUBSYSTEM:CONSOLE']
360    elif self.target.is_macos() or self.target.is_ios():
361      if targettype == 'sharedlib' or targettype == 'multisharedlib':
362        flags += ['-dynamiclib']
363    else:
364      if targettype == 'sharedlib':
365        flags += ['-shared', '-fPIC']
366    if config == 'release':
367      flags += ['-DNDEBUG', '-O3']
368    return flags
369
370  def make_linkarchlibs(self, arch, targettype):
371    archlibs = []
372    if self.target.is_android():
373      if arch == 'arm7':
374        archlibs += ['m_hard']
375      else:
376        archlibs += ['m']
377      archlibs += ['gcc', 'android']
378    return archlibs
379
380  def make_libs(self, libs):
381    if libs != None:
382      return ['-l' + lib for lib in libs]
383    return []
384
385  def make_frameworks(self, frameworks):
386    if frameworks != None:
387      return ['-framework ' + framework for framework in frameworks]
388    return []
389
390  def make_configlibpaths(self, config, arch, extralibpaths):
391    libpaths = [self.libpath, os.path.join(self.libpath, config)]
392    if not self.target.is_macos() and not self.target.is_ios():
393      libpaths += [os.path.join(self.libpath, arch)]
394      libpaths += [os.path.join(self.libpath, config, arch)]
395    if extralibpaths != None:
396      libpaths += [os.path.join(libpath, self.libpath) for libpath in extralibpaths]
397      libpaths += [os.path.join(libpath, self.libpath, config) for libpath in extralibpaths]
398      if not self.target.is_macos() and not self.target.is_ios():
399        libpaths += [os.path.join(libpath, self.libpath, arch) for libpath in extralibpaths]
400        libpaths += [os.path.join(libpath, self.libpath, config, arch) for libpath in extralibpaths]
401    return self.make_libpaths(libpaths)
402
403  def cc_variables(self, config, arch, targettype, variables):
404    localvariables = []
405    if 'includepaths' in variables:
406      moreincludepaths = self.make_includepaths(variables['includepaths'])
407      if not moreincludepaths == []:
408        localvariables += [('moreincludepaths', moreincludepaths)]
409    carchflags = self.make_carchflags(arch, targettype)
410    if carchflags != []:
411      localvariables += [('carchflags', carchflags)]
412    cconfigflags = self.make_cconfigflags(config, targettype)
413    if cconfigflags != []:
414      localvariables += [('cconfigflags', cconfigflags)]
415    if self.target.is_android():
416      localvariables += [('sysroot', self.android.make_sysroot_path(arch))]
417    if 'defines' in variables:
418      localvariables += [('cmoreflags', ['-D' + define for define in variables['defines']])]
419    return localvariables
420
421  def ar_variables(self, config, arch, targettype, variables):
422    localvariables = []
423    ararchflags = self.make_ararchflags(arch, targettype)
424    if ararchflags != []:
425      localvariables += [('ararchflags', ararchflags)]
426    arconfigflags = self.make_arconfigflags(config, targettype)
427    if arconfigflags != []:
428      localvariables += [('arconfigflags', arconfigflags)]
429    if self.target.is_android():
430      localvariables += [('toolchain', self.android.make_gcc_bin_path(arch))]
431    return localvariables
432
433  def link_variables(self, config, arch, targettype, variables):
434    if variables == None:
435        variables = {}
436    localvariables = []
437    linkarchflags = self.make_linkarchflags(arch, targettype, variables)
438    if linkarchflags != []:
439      localvariables += [('linkarchflags', linkarchflags)]
440    linkconfigflags = self.make_linkconfigflags(config, targettype, variables)
441    if linkconfigflags != []:
442      localvariables += [('linkconfigflags', linkconfigflags)]
443    if 'libs' in variables:
444      libvar = self.make_libs(variables['libs'])
445      if libvar != []:
446        localvariables += [('libs', libvar)]
447
448    localframeworks = self.frameworks or []
449    if 'frameworks' in variables and variables['frameworks'] != None:
450      localframeworks += list(variables['frameworks'])
451    if len(localframeworks) > 0:
452      localvariables += [('frameworks', self.make_frameworks(list(localframeworks)))]
453
454    libpaths = []
455    if 'libpaths' in variables:
456      libpaths = variables['libpaths']
457    localvariables += [('configlibpaths', self.make_configlibpaths(config, arch, libpaths))]
458    if self.target.is_android():
459      localvariables += [('sysroot', self.android.make_sysroot_path(arch))]
460    archlibs = self.make_linkarchlibs(arch, targettype)
461    if archlibs != []:
462      localvariables += [('archlibs', self.make_libs(archlibs))]
463
464    if 'runtime' in variables and variables['runtime'] == 'c++':
465      localvariables += [('link', self.cxxlinker)]
466
467    return localvariables
468
469  def builder_cc(self, writer, config, arch, targettype, infile, outfile, variables):
470    return writer.build(outfile, 'cc', infile, implicit = self.implicit_deps(config, variables), variables = self.cc_variables(config, arch, targettype, variables))
471
472  def builder_cxx(self, writer, config, arch, targettype, infile, outfile, variables):
473    return writer.build(outfile, 'cxx', infile, implicit = self.implicit_deps(config, variables), variables = self.cc_variables(config, arch, targettype, variables))
474
475  def builder_cm(self, writer, config, arch, targettype, infile, outfile, variables):
476    return writer.build(outfile, 'cm', infile, implicit = self.implicit_deps(config, variables), variables = self.cc_variables(config, arch, targettype, variables))
477
478  def builder_lib(self, writer, config, arch, targettype, infiles, outfile, variables):
479    return writer.build(outfile, 'ar', infiles, implicit = self.implicit_deps(config, variables), variables = self.ar_variables(config, arch, targettype, variables))
480
481  def builder_sharedlib(self, writer, config, arch, targettype, infiles, outfile, variables):
482    return writer.build(outfile, 'so', infiles, implicit = self.implicit_deps(config, variables), variables = self.link_variables(config, arch, targettype, variables))
483
484  def builder_bin(self, writer, config, arch, targettype, infiles, outfile, variables):
485    return writer.build(outfile, 'link', infiles, implicit = self.implicit_deps(config, variables), variables = self.link_variables(config, arch, targettype, variables))
486
487  #Apple universal targets
488  def builder_apple_multilib(self, writer, config, arch, targettype, infiles, outfile, variables):
489    localvariables = [('arflags', '-static -no_warning_for_no_symbols')]
490    if variables != None:
491      localvariables = variables + localvariables
492    return writer.build(os.path.join(outfile, self.buildtarget), 'ar', infiles, variables = localvariables);
493
494  def builder_apple_multisharedlib(self, writer, config, arch, targettype, infiles, outfile, variables):
495    return writer.build(os.path.join(outfile, self.buildtarget), 'so', infiles, implicit = self.implicit_deps(config, variables), variables = self.link_variables(config, arch, targettype, variables))
496
497  def builder_apple_multibin(self, writer, config, arch, targettype, infiles, outfile, variables):
498    return writer.build(os.path.join(outfile, self.buildtarget), 'lipo', infiles, variables = variables)
499
500def create(host, target, toolchain):
501  return ClangToolchain(host, target, toolchain)
502