arm_compute v17.04
[platform/upstream/armcl.git] / sconscript
1 # Copyright (c) 2016, 2017 ARM Limited.
2 #
3 # SPDX-License-Identifier: MIT
4 #
5 # Permission is hereby granted, free of charge, to any person obtaining a copy
6 # of this software and associated documentation files (the "Software"), to
7 # deal in the Software without restriction, including without limitation the
8 # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
9 # sell copies of the Software, and to permit persons to whom the Software is
10 # furnished to do so, subject to the following conditions:
11 #
12 # The above copyright notice and this permission notice shall be included in all
13 # copies or substantial portions of the Software.
14 #
15 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 # SOFTWARE.
22 import collections
23 import os.path
24 import re
25 import subprocess
26 import SCons
27
28 VERSION = "v17.04"
29 SONAME_VERSION="1.0.0"
30
31 vars = Variables('scons')
32 vars.Add(EnumVariable('debug','Debug (default=0)', '0', allowed_values=('0','1')))
33 vars.Add(EnumVariable('asserts','Enable asserts (This flag is forced to 1 for debug=1) (default=0)', '0', allowed_values=('0','1')))
34 vars.Add(EnumVariable('arch','Target Architecture (default=armv7a)', 'armv7a', allowed_values=('armv7a','arm64-v8a','arm64-v8.2-a','x86_32','x86_64')))
35 vars.Add(EnumVariable('os','Target OS (default=linux)', 'linux', allowed_values=('linux','android','bare_metal')))
36 vars.Add(EnumVariable('build','Build type: (default=cross_compile)', 'cross_compile', allowed_values=('native','cross_compile')))
37 vars.Add(EnumVariable('Werror','Enable/disable the -Werror compilation flag (Default=1)', '1', allowed_values=('0','1')))
38 vars.Add(EnumVariable('opencl','Enable OpenCL support(Default=1)', '1', allowed_values=('0','1')))
39 vars.Add(EnumVariable('neon','Enable Neon support(Default=0)', '0', allowed_values=('0','1')))
40 vars.Add(EnumVariable('embed_kernels', 'Embed OpenCL kernels in library binary(Default=0)', '0', allowed_values=('0','1')))
41 vars.Add(BoolVariable('set_soname','Set the library\'s soname and shlibversion (Requires SCons 2.4 or above)', 0))
42 vars.Add(('extra_cxx_flags','Extra CXX flags to be appended to the build command', ''))
43
44 env = Environment(platform='posix', variables = vars, ENV = os.environ)
45
46 Help(vars.GenerateHelpText(env))
47
48 def version_at_least(version, required):
49     end = min(len(version), len(required))
50
51     for i in range(0, end, 2):
52         if int(version[i]) < int(required[i]):
53             return False
54         elif int(version[i]) > int(required[i]):
55             return True
56
57     return True
58
59 if not GetOption("help"):
60     flags = ['-D_GLIBCXX_USE_NANOSLEEP','-Wno-deprecated-declarations','-Wall','-DARCH_ARM',
61              '-Wextra','-Wno-unused-parameter','-pedantic','-Wdisabled-optimization','-Wformat=2',
62              '-Winit-self','-Wstrict-overflow=2','-Wswitch-default',
63              '-fpermissive','-std=c++11','-Wno-vla','-Woverloaded-virtual',
64              '-Wctor-dtor-privacy','-Wsign-promo','-Weffc++','-Wno-format-nonliteral','-Wno-overlength-strings','-Wno-strict-overflow']
65
66
67     if env['neon'] == '1' and 'x86' in env['arch']:
68             print "Cannot compile NEON for x86"
69             Exit(1)
70
71     if env['set_soname'] and not version_at_least( SCons.__version__, "2.4"):
72         print "Setting the library's SONAME / SHLIBVERSION requires SCons 2.4 or above"
73         print "Update your version of SCons or use set_soname=0"
74         Exit(1)
75
76     if os.environ.get('CXX','g++') == 'clang++':
77         flags += ['-Wno-format-nonliteral','-Wno-deprecated-increment-bool','-Wno-vla-extension','-Wno-mismatched-tags']
78     else:
79         flags += ['-Wlogical-op','-Wnoexcept','-Wstrict-null-sentinel']
80
81     files_to_delete = []
82
83 #Generate string with build options library version to embed in the library:
84     git_hash="unknown"
85     try:
86         git_hash = subprocess.check_output(["git", "rev-parse","HEAD"])
87     except OSError: # In case git is not present
88         pass
89     except subprocess.CalledProcessError:
90         pass
91
92     version_filename = "%s/arm_compute_version.embed" % os.path.dirname(Glob("src/core/*")[0].rstr())
93     build_info = "\"arm_compute_version=%s Build options: %s Git hash=%s\"" % (VERSION, vars.args, git_hash.strip())
94     open(version_filename,"w").write(build_info)
95     files_to_delete.append( version_filename )
96
97     def build_library(name, sources, libs, static=False):
98         if static:
99             obj = env.StaticLibrary(name, source = sources, LIBS=libs )
100         else:
101             if env['set_soname']:
102                 obj = env.SharedLibrary(name, source = sources, LIBS=libs, SHLIBVERSION=SONAME_VERSION)
103                 symlinks = []
104                 # Manually delete symlinks or SCons will get confused:
105                 directory = os.path.dirname( obj[0].path )
106                 library_prefix = obj[0].path[:-(1+len(SONAME_VERSION))]
107                 real_lib="%s.%s" % (library_prefix, SONAME_VERSION)
108                 for f in Glob( "#%s*" % library_prefix):
109                     if str(f) != real_lib:
110                         symlinks.append("%s/%s" % (directory,str(f)))
111                 clean = env.Command('clean-%s' % str(obj[0]), [], Delete(symlinks))
112                 Default(clean)
113                 Depends(obj, clean)
114             else:
115                 obj = env.SharedLibrary(name, source = sources, LIBS=libs)
116
117         Default(obj)
118         return obj
119
120     def resolve_includes(target, source, env):
121         # File collection
122         FileEntry = collections.namedtuple('FileEntry', 'target_name file_contents')
123
124         # Include pattern
125         pattern = re.compile("#include \"(.*)\"")
126
127         # Get file contents
128         files = []
129         for s in source:
130             name = s.rstr().split("/")[-1]
131             contents = s.get_contents().splitlines()
132             embed_target_name = s.abspath + "embed"
133             entry = FileEntry(target_name=embed_target_name, file_contents=contents)
134             files.append((name,entry))
135
136         # Create dictionary of tupled list
137         files_dict = dict(files)
138
139         # Check for includes (can only be files in the same folder)
140         final_files = []
141         for file in files:
142             done = False
143             tmp_file = file[1].file_contents
144             while not(done):
145                 file_count = 0
146                 updated_file = []
147                 for line in tmp_file:
148                     found = pattern.search(line)
149                     if found:
150                         include_file = found.group(1)
151                         data = files_dict[include_file].file_contents
152                         updated_file.extend(data)
153                     else:
154                         updated_file.append(line)
155                         file_count += 1
156
157                 # Check if all include are replaced.
158                 if file_count == len(tmp_file):
159                     done = True
160
161                 # Update temp file
162                 tmp_file = updated_file
163
164             # Append and prepend string literal identifiers and add expanded file to final list
165             tmp_file.insert(0, "R\"(\n")
166             tmp_file.append("\n)\"")
167             entry = FileEntry(target_name=file[1].target_name, file_contents=tmp_file)
168             final_files.append((file[0], entry))
169
170         # Write output files
171         for file in final_files:
172             out_file = open(file[1].target_name, 'w+')
173             contents = file[1].file_contents
174             for line in contents:
175                 out_file.write("%s\n" % line)
176
177     core_libs = []
178     libs = []
179
180     prefix=""
181
182     if env['arch'] == 'armv7a':
183         flags += ['-march=armv7-a','-mthumb','-mfpu=neon']
184
185         if env['os'] in ['linux','bare_metal']:
186             prefix = "arm-linux-gnueabihf-"
187             flags += ['-mfloat-abi=hard']
188         elif env['os'] == 'android':
189             prefix = "arm-linux-androideabi-"
190             flags += ['-mfloat-abi=softfp']
191     elif env['arch'] == 'arm64-v8a':
192         flags += ['-march=armv8-a']
193         if env['os'] in ['linux','bare_metal']:
194             prefix = "aarch64-linux-gnu-"
195         elif env['os'] == 'android':
196             prefix = "aarch64-linux-android-"
197     elif env['arch'] == 'arm64-v8.2-a':
198         flags += ['-march=armv8.2-a+fp16+simd']
199         flags += ['-DARM_COMPUTE_ENABLE_FP16']
200         if env['os'] in ['linux','bare_metal']:
201             prefix = "aarch64-linux-gnu-"
202         elif env['os'] == 'android':
203             prefix = "aarch64-linux-android-"
204     elif env['arch'] == 'x86_32':
205         flags += ['-m32']
206     elif env['arch'] == 'x86_64':
207         flags += ['-m64']
208
209     if env['build'] == 'native':
210         prefix = ""
211
212     env['CC'] = prefix + os.environ.get('CC','gcc')
213     env['CXX'] = prefix + os.environ.get('CXX','g++')
214     env['LD'] = prefix + "ld"
215     env['AS'] = prefix + "as"
216     env['AR'] = prefix + "ar"
217     env['RANLIB'] = prefix + "ranlib"
218
219     try:
220         compiler_ver = subprocess.check_output( [env['CXX'] , "-dumpversion"] ).strip()
221     except OSError:
222         print "ERROR: Compiler '%s' not found" % env['CXX']
223         Exit(1)
224
225     if os.environ.get('CXX','g++') == 'g++':
226         if env['arch'] == 'arm64-v8.2-a' and not version_at_least(compiler_ver, '6.2.1'):
227             print "GCC 6.2.1 or newer is required to compile armv8.2-a code"
228             Exit(1)
229
230         if env['arch'] == 'arm64-v8a' and not version_at_least(compiler_ver, '4.9'):
231             print "GCC 4.9 or newer is required to compile NEON code for AArch64"
232             Exit(1)
233
234         if version_at_least(compiler_ver, '6.1'):
235             flags += ['-Wno-ignored-attributes']
236
237         if compiler_ver == '4.8.3':
238             flags += ['-Wno-array-bounds']
239
240     if env['Werror'] == '1':
241         flags += ['-Werror']
242
243     example_libs = []
244     if env['os'] == 'android':
245         flags += ['-DANDROID']
246         env.Append(LINKFLAGS=['-pie','-static-libstdc++'])
247         example_libs = ['arm_compute-static']
248     elif env['os'] == 'bare_metal':
249         env.Append(LINKFLAGS=['-static'])
250         flags += ['-fPIC','-DNO_MULTI_THREADING']
251         example_libs = ['arm_compute-static']
252     else:
253         libs += ['pthread']
254         example_libs = ['arm_compute']
255
256     if env['opencl'] == '1':
257         if env['os'] == 'bare_metal':
258             raise Exception("Cannot link OpenCL statically, which is required on bare metal")
259         core_libs += ['OpenCL']
260         if env['embed_kernels'] == '1':
261             flags += ['-DEMBEDDED_KERNELS']
262
263     if env['debug'] == '1':
264         env['asserts'] = '1'
265         flags += ['-O0','-g','-gdwarf-2']
266     else:
267         flags += ['-O3','-ftree-vectorize']
268
269     if env['asserts'] == '1':
270         flags += ['-DARM_COMPUTE_ASSERTS_ENABLED']
271
272     env.Append(CPPPATH=['.','#include'])
273     env.Append(LIBPATH=['#build','.'])
274     env.Append(CXXFLAGS=flags)
275     env.Append(CXXFLAGS=env['extra_cxx_flags'])
276
277     core_files = Glob('src/core/*.cpp')
278     core_files += Glob('src/core/CPP/*.cpp')
279
280     files = Glob('src/runtime/*.cpp')
281
282     embed_files = []
283     core_files += Glob('src/core/CPP/kernels/*.cpp')
284
285     files += Glob('src/runtime/CPP/*.cpp')
286
287     if env['opencl'] == '1':
288         example_libs += ['OpenCL']
289         core_files += Glob('src/core/CL/*.cpp')
290         core_files += Glob('src/core/CL/kernels/*.cpp')
291         files += Glob('src/runtime/CL/*.cpp')
292         files += Glob('src/runtime/CL/functions/*.cpp')
293         # Generate embed files
294         if env['embed_kernels'] == '1':
295             cl_files  = Glob('src/core/CL/cl_kernels/*.cl') + Glob('src/core/CL/cl_kernels/*.h')
296             source_list = []
297             for file in cl_files:
298                 source_name = file.rstr()
299                 source_list.append(source_name)
300                 embed_files.append(source_name + "embed")
301             generate_embed = env.Command(embed_files, source_list, action=resolve_includes)
302             Default(generate_embed)
303             files_to_delete += embed_files
304
305     if env['neon'] == '1':
306         core_files += Glob('src/core/NEON/*.cpp')
307         core_files += Glob('src/core/NEON/kernels/*.cpp')
308         files += Glob('src/runtime/NEON/*.cpp')
309         files += Glob('src/runtime/NEON/functions/*.cpp')
310
311     objects=[]
312     static_core_objects = [ env.StaticObject( f ) for f in core_files ]
313     shared_core_objects = [ env.SharedObject( f ) for f in core_files ]
314
315     arm_compute_core_a = build_library('arm_compute_core-static', static_core_objects, core_libs, static=True)
316     objects.append(arm_compute_core_a)
317     Export('arm_compute_core_a')
318
319     if env['os'] != 'bare_metal':
320         arm_compute_core_so = build_library('arm_compute_core', shared_core_objects, core_libs, static=False)
321         objects.append(arm_compute_core_so)
322         Export('arm_compute_core_so')
323     shared_objects = [ env.SharedObject( f ) for f in files ]
324     static_objects = [ env.StaticObject( f ) for f in files ]
325
326     arm_compute_a = build_library('arm_compute-static', static_core_objects + static_objects, libs, static=True)
327     objects.append(arm_compute_a)
328     Export('arm_compute_a')
329     if env['os'] != 'bare_metal':
330         arm_compute_so = build_library('arm_compute', shared_core_objects + shared_objects, libs, static=False)
331         objects.append(arm_compute_so)
332         Export('arm_compute_so')
333
334 # Delete produced embed files
335     clean_embed = env.Command('clean-embed', [], Delete(files_to_delete))
336     Default(clean_embed)
337     env.Depends(clean_embed, objects)
338     alias = env.Alias("arm_compute",objects)
339     Default(alias)
340
341 # Build examples
342     test_helpers = env.Object("test_helpers/Utils.cpp")
343
344     if env['opencl'] == '1' and env['neon'] == '1':
345         for file in Glob("examples/neoncl_*.cpp"):
346             example =  os.path.basename( os.path.splitext(str(file))[0])
347             prog = env.Program(example, ['examples/%s.cpp' % example, test_helpers], LIBS=example_libs)
348             alias = env.Alias(example, prog)
349             Depends(prog, objects)
350             Default( alias )
351
352     if env['opencl'] == '1':
353         for file in Glob("examples/cl_*.cpp"):
354             example =  os.path.basename( os.path.splitext(str(file))[0])
355             prog = env.Program(example, ['examples/%s.cpp' % example, test_helpers], LIBS=example_libs)
356             alias = env.Alias(example, prog)
357             Depends(prog, objects)
358             Default( alias )
359
360     if env['neon'] == '1':
361         for file in Glob("examples/neon_*.cpp"):
362             example =  os.path.basename( os.path.splitext(str(file))[0])
363             prog = env.Program(example, ['examples/%s.cpp' % example, test_helpers], LIBS=example_libs)
364             alias = env.Alias(example, prog)
365             Depends(prog, objects)
366             Default( alias )