# Copyright (c) 2016, 2017 ARM Limited. # # SPDX-License-Identifier: MIT # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to # deal in the Software without restriction, including without limitation the # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or # sell copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import collections import os.path import re import subprocess import SCons VERSION = "v17.04" SONAME_VERSION="1.0.0" vars = Variables('scons') vars.Add(EnumVariable('debug','Debug (default=0)', '0', allowed_values=('0','1'))) vars.Add(EnumVariable('asserts','Enable asserts (This flag is forced to 1 for debug=1) (default=0)', '0', allowed_values=('0','1'))) vars.Add(EnumVariable('arch','Target Architecture (default=armv7a)', 'armv7a', allowed_values=('armv7a','arm64-v8a','arm64-v8.2-a','x86_32','x86_64'))) vars.Add(EnumVariable('os','Target OS (default=linux)', 'linux', allowed_values=('linux','android','bare_metal'))) vars.Add(EnumVariable('build','Build type: (default=cross_compile)', 'cross_compile', allowed_values=('native','cross_compile'))) vars.Add(EnumVariable('Werror','Enable/disable the -Werror compilation flag (Default=1)', '1', allowed_values=('0','1'))) vars.Add(EnumVariable('opencl','Enable OpenCL support(Default=1)', '1', allowed_values=('0','1'))) vars.Add(EnumVariable('neon','Enable Neon support(Default=0)', '0', allowed_values=('0','1'))) vars.Add(EnumVariable('embed_kernels', 'Embed OpenCL kernels in library binary(Default=0)', '0', allowed_values=('0','1'))) vars.Add(BoolVariable('set_soname','Set the library\'s soname and shlibversion (Requires SCons 2.4 or above)', 0)) vars.Add(('extra_cxx_flags','Extra CXX flags to be appended to the build command', '')) env = Environment(platform='posix', variables = vars, ENV = os.environ) Help(vars.GenerateHelpText(env)) def version_at_least(version, required): end = min(len(version), len(required)) for i in range(0, end, 2): if int(version[i]) < int(required[i]): return False elif int(version[i]) > int(required[i]): return True return True if not GetOption("help"): flags = ['-D_GLIBCXX_USE_NANOSLEEP','-Wno-deprecated-declarations','-Wall','-DARCH_ARM', '-Wextra','-Wno-unused-parameter','-pedantic','-Wdisabled-optimization','-Wformat=2', '-Winit-self','-Wstrict-overflow=2','-Wswitch-default', '-fpermissive','-std=c++11','-Wno-vla','-Woverloaded-virtual', '-Wctor-dtor-privacy','-Wsign-promo','-Weffc++','-Wno-format-nonliteral','-Wno-overlength-strings','-Wno-strict-overflow'] if env['neon'] == '1' and 'x86' in env['arch']: print "Cannot compile NEON for x86" Exit(1) if env['set_soname'] and not version_at_least( SCons.__version__, "2.4"): print "Setting the library's SONAME / SHLIBVERSION requires SCons 2.4 or above" print "Update your version of SCons or use set_soname=0" Exit(1) if os.environ.get('CXX','g++') == 'clang++': flags += ['-Wno-format-nonliteral','-Wno-deprecated-increment-bool','-Wno-vla-extension','-Wno-mismatched-tags'] else: flags += ['-Wlogical-op','-Wnoexcept','-Wstrict-null-sentinel'] files_to_delete = [] #Generate string with build options library version to embed in the library: git_hash="unknown" try: git_hash = subprocess.check_output(["git", "rev-parse","HEAD"]) except OSError: # In case git is not present pass except subprocess.CalledProcessError: pass version_filename = "%s/arm_compute_version.embed" % os.path.dirname(Glob("src/core/*")[0].rstr()) build_info = "\"arm_compute_version=%s Build options: %s Git hash=%s\"" % (VERSION, vars.args, git_hash.strip()) open(version_filename,"w").write(build_info) files_to_delete.append( version_filename ) def build_library(name, sources, libs, static=False): if static: obj = env.StaticLibrary(name, source = sources, LIBS=libs ) else: if env['set_soname']: obj = env.SharedLibrary(name, source = sources, LIBS=libs, SHLIBVERSION=SONAME_VERSION) symlinks = [] # Manually delete symlinks or SCons will get confused: directory = os.path.dirname( obj[0].path ) library_prefix = obj[0].path[:-(1+len(SONAME_VERSION))] real_lib="%s.%s" % (library_prefix, SONAME_VERSION) for f in Glob( "#%s*" % library_prefix): if str(f) != real_lib: symlinks.append("%s/%s" % (directory,str(f))) clean = env.Command('clean-%s' % str(obj[0]), [], Delete(symlinks)) Default(clean) Depends(obj, clean) else: obj = env.SharedLibrary(name, source = sources, LIBS=libs) Default(obj) return obj def resolve_includes(target, source, env): # File collection FileEntry = collections.namedtuple('FileEntry', 'target_name file_contents') # Include pattern pattern = re.compile("#include \"(.*)\"") # Get file contents files = [] for s in source: name = s.rstr().split("/")[-1] contents = s.get_contents().splitlines() embed_target_name = s.abspath + "embed" entry = FileEntry(target_name=embed_target_name, file_contents=contents) files.append((name,entry)) # Create dictionary of tupled list files_dict = dict(files) # Check for includes (can only be files in the same folder) final_files = [] for file in files: done = False tmp_file = file[1].file_contents while not(done): file_count = 0 updated_file = [] for line in tmp_file: found = pattern.search(line) if found: include_file = found.group(1) data = files_dict[include_file].file_contents updated_file.extend(data) else: updated_file.append(line) file_count += 1 # Check if all include are replaced. if file_count == len(tmp_file): done = True # Update temp file tmp_file = updated_file # Append and prepend string literal identifiers and add expanded file to final list tmp_file.insert(0, "R\"(\n") tmp_file.append("\n)\"") entry = FileEntry(target_name=file[1].target_name, file_contents=tmp_file) final_files.append((file[0], entry)) # Write output files for file in final_files: out_file = open(file[1].target_name, 'w+') contents = file[1].file_contents for line in contents: out_file.write("%s\n" % line) core_libs = [] libs = [] prefix="" if env['arch'] == 'armv7a': flags += ['-march=armv7-a','-mthumb','-mfpu=neon'] if env['os'] in ['linux','bare_metal']: prefix = "arm-linux-gnueabihf-" flags += ['-mfloat-abi=hard'] elif env['os'] == 'android': prefix = "arm-linux-androideabi-" flags += ['-mfloat-abi=softfp'] elif env['arch'] == 'arm64-v8a': flags += ['-march=armv8-a'] if env['os'] in ['linux','bare_metal']: prefix = "aarch64-linux-gnu-" elif env['os'] == 'android': prefix = "aarch64-linux-android-" elif env['arch'] == 'arm64-v8.2-a': flags += ['-march=armv8.2-a+fp16+simd'] flags += ['-DARM_COMPUTE_ENABLE_FP16'] if env['os'] in ['linux','bare_metal']: prefix = "aarch64-linux-gnu-" elif env['os'] == 'android': prefix = "aarch64-linux-android-" elif env['arch'] == 'x86_32': flags += ['-m32'] elif env['arch'] == 'x86_64': flags += ['-m64'] if env['build'] == 'native': prefix = "" env['CC'] = prefix + os.environ.get('CC','gcc') env['CXX'] = prefix + os.environ.get('CXX','g++') env['LD'] = prefix + "ld" env['AS'] = prefix + "as" env['AR'] = prefix + "ar" env['RANLIB'] = prefix + "ranlib" try: compiler_ver = subprocess.check_output( [env['CXX'] , "-dumpversion"] ).strip() except OSError: print "ERROR: Compiler '%s' not found" % env['CXX'] Exit(1) if os.environ.get('CXX','g++') == 'g++': if env['arch'] == 'arm64-v8.2-a' and not version_at_least(compiler_ver, '6.2.1'): print "GCC 6.2.1 or newer is required to compile armv8.2-a code" Exit(1) if env['arch'] == 'arm64-v8a' and not version_at_least(compiler_ver, '4.9'): print "GCC 4.9 or newer is required to compile NEON code for AArch64" Exit(1) if version_at_least(compiler_ver, '6.1'): flags += ['-Wno-ignored-attributes'] if compiler_ver == '4.8.3': flags += ['-Wno-array-bounds'] if env['Werror'] == '1': flags += ['-Werror'] example_libs = [] if env['os'] == 'android': flags += ['-DANDROID'] env.Append(LINKFLAGS=['-pie','-static-libstdc++']) example_libs = ['arm_compute-static'] elif env['os'] == 'bare_metal': env.Append(LINKFLAGS=['-static']) flags += ['-fPIC','-DNO_MULTI_THREADING'] example_libs = ['arm_compute-static'] else: libs += ['pthread'] example_libs = ['arm_compute'] if env['opencl'] == '1': if env['os'] == 'bare_metal': raise Exception("Cannot link OpenCL statically, which is required on bare metal") core_libs += ['OpenCL'] if env['embed_kernels'] == '1': flags += ['-DEMBEDDED_KERNELS'] if env['debug'] == '1': env['asserts'] = '1' flags += ['-O0','-g','-gdwarf-2'] else: flags += ['-O3','-ftree-vectorize'] if env['asserts'] == '1': flags += ['-DARM_COMPUTE_ASSERTS_ENABLED'] env.Append(CPPPATH=['.','#include']) env.Append(LIBPATH=['#build','.']) env.Append(CXXFLAGS=flags) env.Append(CXXFLAGS=env['extra_cxx_flags']) core_files = Glob('src/core/*.cpp') core_files += Glob('src/core/CPP/*.cpp') files = Glob('src/runtime/*.cpp') embed_files = [] core_files += Glob('src/core/CPP/kernels/*.cpp') files += Glob('src/runtime/CPP/*.cpp') if env['opencl'] == '1': example_libs += ['OpenCL'] core_files += Glob('src/core/CL/*.cpp') core_files += Glob('src/core/CL/kernels/*.cpp') files += Glob('src/runtime/CL/*.cpp') files += Glob('src/runtime/CL/functions/*.cpp') # Generate embed files if env['embed_kernels'] == '1': cl_files = Glob('src/core/CL/cl_kernels/*.cl') + Glob('src/core/CL/cl_kernels/*.h') source_list = [] for file in cl_files: source_name = file.rstr() source_list.append(source_name) embed_files.append(source_name + "embed") generate_embed = env.Command(embed_files, source_list, action=resolve_includes) Default(generate_embed) files_to_delete += embed_files if env['neon'] == '1': core_files += Glob('src/core/NEON/*.cpp') core_files += Glob('src/core/NEON/kernels/*.cpp') files += Glob('src/runtime/NEON/*.cpp') files += Glob('src/runtime/NEON/functions/*.cpp') objects=[] static_core_objects = [ env.StaticObject( f ) for f in core_files ] shared_core_objects = [ env.SharedObject( f ) for f in core_files ] arm_compute_core_a = build_library('arm_compute_core-static', static_core_objects, core_libs, static=True) objects.append(arm_compute_core_a) Export('arm_compute_core_a') if env['os'] != 'bare_metal': arm_compute_core_so = build_library('arm_compute_core', shared_core_objects, core_libs, static=False) objects.append(arm_compute_core_so) Export('arm_compute_core_so') shared_objects = [ env.SharedObject( f ) for f in files ] static_objects = [ env.StaticObject( f ) for f in files ] arm_compute_a = build_library('arm_compute-static', static_core_objects + static_objects, libs, static=True) objects.append(arm_compute_a) Export('arm_compute_a') if env['os'] != 'bare_metal': arm_compute_so = build_library('arm_compute', shared_core_objects + shared_objects, libs, static=False) objects.append(arm_compute_so) Export('arm_compute_so') # Delete produced embed files clean_embed = env.Command('clean-embed', [], Delete(files_to_delete)) Default(clean_embed) env.Depends(clean_embed, objects) alias = env.Alias("arm_compute",objects) Default(alias) # Build examples test_helpers = env.Object("test_helpers/Utils.cpp") if env['opencl'] == '1' and env['neon'] == '1': for file in Glob("examples/neoncl_*.cpp"): example = os.path.basename( os.path.splitext(str(file))[0]) prog = env.Program(example, ['examples/%s.cpp' % example, test_helpers], LIBS=example_libs) alias = env.Alias(example, prog) Depends(prog, objects) Default( alias ) if env['opencl'] == '1': for file in Glob("examples/cl_*.cpp"): example = os.path.basename( os.path.splitext(str(file))[0]) prog = env.Program(example, ['examples/%s.cpp' % example, test_helpers], LIBS=example_libs) alias = env.Alias(example, prog) Depends(prog, objects) Default( alias ) if env['neon'] == '1': for file in Glob("examples/neon_*.cpp"): example = os.path.basename( os.path.splitext(str(file))[0]) prog = env.Program(example, ['examples/%s.cpp' % example, test_helpers], LIBS=example_libs) alias = env.Alias(example, prog) Depends(prog, objects) Default( alias )