def gdb(self, command, src_dir=True): """ Run a command with gdb in the build folder. ``` self.gdb('bin/NanoMeshTests') ``` """ if self.android or self.ios or self.raspi: console('Cannot run tests for Android, iOS, Raspi builds.') return # nothing to run split = command.split(' ', 1) cmd = split[0].lstrip('.') args = split[1] if len(split) >= 2 else '' path = self.dep.src_dir if src_dir else self.dep.build_dir path = f"{path}/{os.path.dirname(cmd).lstrip('/')}" exe = os.path.basename(cmd) if self.windows: if not src_dir: path = f'{path}/{self.cmake_build_type}' gdb = f'{exe} {args}' elif self.macos: # b: batch, q: quiet, -o r: run # -k bt: on crash, backtrace # -k q: on crash, quit gdb = f'lldb -b -o r -k bt -k q -- ./{exe} {args}' else: # linux # r: run; bt: give backtrace; q: quit when done; gdb = f'gdb -batch -return-child-result -ex=r -ex=bt -ex=q --args ./{exe} {args}' if not (os.path.exists(f'{path}/{exe}') or os.path.exists(f'{path}/{exe}.exe')): raise IOError(f'Could not find {path}/{exe}') execute_echo(path, gdb)
def current_commit(self): result = execute_piped(['git', 'show', '--oneline', '-s'], cwd=self.dep.src_dir) if self.dep.config.verbose: console( f' {self.dep.name: <16} git show --oneline -s: {result}') return result
def cmake_install(self): if self.config.print: console( '\n\n#############################################################' ) console(f"CMake install {self.name} ...") cmake.run_build(self, install=True)
def export_lib(target, relative_path, src_dir): path = target_root_path(target, relative_path, src_dir) if os.path.exists(path): target.exported_libs.append(path) target.exported_libs = _get_unique_basenames(target.exported_libs) else: console(f'export_lib failed to find: {path}')
def _should_build(self, conf, target, is_target, git_changed): def build(r): if conf.print: args = f'{target.args}' if target.args else '' console(f' - Target {target.name: <16} BUILD [{r}] {args}') return True if conf.target and not is_target: # if we called: "target=SpecificProject" return False # skip build if target doesn't match ## build also entails packaging if conf.clean and is_target: return build('cleaned target') if self.is_root: return build('root target') if conf.target and is_target: return build('target=' + conf.target) if self.always_build: return build('always build') if update_mamafile_tag(self.mamafile_path(), self.build_dir): return build(target.name + '/mamafile.py modified') if update_cmakelists_tag(self.cmakelists_path(), self.build_dir): return build(target.name + '/CMakeLists.txt modified') if git_changed: return build('git commit changed') if not self.nothing_to_build: if not self.has_build_files(): return build('not built yet') if not target.build_dependencies: return build('no build dependencies') missing_product = self.find_first_missing_build_product() if missing_product: return build(f'{missing_product} does not exist') missing_dep = self.find_missing_dependency() if missing_dep: return build(f'{missing_dep} was removed') if conf.print: console(f' - Target {target.name: <16} OK') return False # do not build, all is ok
def export_asset(target, asset, category, src_dir): full_asset = target_root_path(target, asset, src_dir) if os.path.exists(full_asset): target.exported_assets.append(Asset(asset, full_asset, category)) return True else: console(f'export_asset failed to find: {full_asset}') return False
def has_tag_changed(old_tag_file, new_tag): if not os.path.exists(old_tag_file): return True old_tag = pathlib.Path(old_tag_file).read_text() if old_tag != new_tag: console(f" tagchange '{old_tag.strip()}'\n"+ f" ---> '{new_tag.strip()}'") return True return False
def find_default_fortran_compiler(self): paths = [] if System.linux: paths += [find_executable_from_system('gfortran')] for fortran_path in paths: if fortran_path and os.path.exists(fortran_path): if self.verbose: console(f'Found Fortran: {fortran_path}') return fortran_path return None
def get_visualstudio_cmake_id(self): if self._visualstudio_cmake_id: return self._visualstudio_cmake_id path = self.get_visualstudio_path() if '\\2019\\' in path: self._visualstudio_cmake_id = 'Visual Studio 16 2019' else: self._visualstudio_cmake_id = 'Visual Studio 15 2017' if self.verbose: console(f'Detected CMake Generator: -G"{self._visualstudio_cmake_id}" -A {self.get_visualstudio_cmake_arch()}') return self._visualstudio_cmake_id
def cmake_build(self): if self.config.print: console( '\n\n#############################################################' ) console(f"CMakeBuild {self.name} ({self.cmake_build_type})") self.dep.ensure_cmakelists_exists() cmake.inject_env(self) cmake.run_config(self) # THROWS on CMAKE failure cmake.run_build(self, install=True) # THROWS on CMAKE failure
def prefer_gcc(self, target_name): if not self.linux or self.raspi or self.gcc: return if not self.compiler_cmd: self.clang = False self.gcc = True self.compiler_cmd = True if self.print: console(f'Target {target_name} requests GCC. Using GCC since no explicit compiler set.') else: if self.print: console(f'Target {target_name} requested GCC but compiler already set to Clang.')
def copy(self, src, dst): """ Utility for copying files and folders ``` # copies built .so into an android archive self.copy(self.build_dir('libAwesome.so'), self.source_dir('deploy/Awesome.aar/jni/armeabi-v7a')) ``` """ console(f'copy {src} --> {dst}') copy_if_needed(src, dst)
def deploy_framework(framework, deployFolder): if not os.path.exists(framework): raise IOError(f'no framework found at: {framework}') if os.path.exists(deployFolder): name = os.path.basename(framework) deployPath = os.path.join(deployFolder, name) console(f'Deploying framework to {deployPath}') execute(f'rm -rf {deployPath}') shutil.copytree(framework, deployPath) return True return False
def after_load(self): if self.config.no_specific_target(): first_changed = next( (c for c in self.children if c.should_rebuild), None) if first_changed and not self.should_rebuild: self.should_rebuild = True if self.config.print: console( f' - Target {self.name: <16} BUILD [{first_changed.name} changed]' ) self.create_build_dir_if_needed() # in case we just cleaned
def clean(self): if self.config.print: console( f' - Target {self.name: <16} CLEAN {self.config.build_folder()}' ) if self.build_dir == '/' or not os.path.exists(self.build_dir): return self.target.clean() # Customization point shutil.rmtree(self.build_dir, ignore_errors=True)
def reclone_wipe(self): if self.dep.config.print: console(f' - Target {self.dep.name: <16} RECLONE WIPE') if os.path.exists(self.dep.dep_dir): if System.windows: # chmod everything to user so we can delete: for root, dirs, files in os.walk(self.dep.dep_dir): for d in dirs: os.chmod(os.path.join(root, d), stat.S_IWUSR) for f in files: os.chmod(os.path.join(root, f), stat.S_IWUSR) shutil.rmtree(self.dep.dep_dir)
def find_ninja_build(self): ninja_executables = [ os.getenv('NINJA'), find_executable_from_system('ninja'), '/Projects/ninja.exe' ] for ninja_exe in ninja_executables: if ninja_exe and os.path.isfile(ninja_exe): if self.verbose: console(f'Found Ninja Build System: {ninja_exe}') return ninja_exe return ''
def _execute_run_tasks(self): if self.is_test_target(): test_args = self.config.test.lstrip() if self.config.print: console(f' - Testing {self.name} {test_args}') self.test(test_args) if self.dep.is_root and self.config.start: start_args = self.config.start.lstrip() if self.config.print: console(f' - Starting {self.name} {start_args}') self.start(start_args)
def _execute_tasks(self): if self.dep.already_executed: return try: self.dep.already_executed = True self._execute_build_tasks() self._execute_deploy_tasks() self._execute_run_tasks() except Exception as err: import traceback tb = traceback.format_exc().splitlines(True) tb = tb[-3] + tb[-2] if len(tb) >= 3 else ''.join(tb) console( f' [BUILD FAILED] {self.dep.name} \n{err}\n\nError Source:\n{tb}' ) exit(-1) # exit without stack trace
def get_msvc_tools_path(self): if self._msvctools_path: return self._msvctools_path if not System.windows: raise EnvironmentError('MSVC tools not available on this platform!') tools_root = f"{self.get_visualstudio_path()}\\VC\\Tools\\MSVC" tools = os.listdir(tools_root) if not tools: raise EnvironmentError('Could not detect MSVC Tools') tools_path = os.path.join(tools_root, tools[0]) #tools_path = forward_slashes(tools_path) self._msvctools_path = tools_path if self.verbose: console(f'Detected MSVC Tools: {tools_path}') return tools_path
def install_clang6(self): if System.windows: raise OSError('Install Visual Studio 2019 with Clang support') if System.macos: raise OSError('Install Xcode to get Clang on macOS') suffix = '1404' try: dist = distro.info() if dist['id'] != "ubuntu": raise OSError('install_clang6 only supports Ubuntu') majorVersion = int(dist['version_parts']['major']) if majorVersion >= 16: suffix = '1604' console(f'Choosing {suffix} for kernel major={majorVersion}') except Exception as err: console(f'Failed to parse linux distro; falling back to {suffix}: {err}') self.install_clang(clang_major='6', clang_ver='6.0', suffix=suffix)
def init_ndk_path(self): paths = [] self.append_env_path(paths, 'ANDROID_HOME') if System.windows: paths += [f'{os.getenv("LOCALAPPDATA")}\\Android\\Sdk'] elif System.linux: paths += [f'{os.getenv("HOME")}/Android/Sdk', '/usr/bin/android-sdk', '/opt/android-sdk'] elif System.macos: paths += [f'{os.getenv("HOME")}/Library/Android/sdk'] ext = '.cmd' if System.windows else '' for sdk_path in paths: if os.path.exists(f'{sdk_path}/ndk-bundle/ndk-build{ext}'): self.android_sdk_path = sdk_path self.android_ndk_path = sdk_path + '/ndk-bundle' self.android_ndk_release = 'r16b' if self.print: console(f'Found Android NDK: {self.android_ndk_path}') return raise EnvironmentError(f'''Could not detect any Android NDK installations. Default search paths: {paths} Define env ANDROID_HOME with path to Android SDK with NDK at ${{ANDROID_HOME}}/ndk-bundle.''')
def install_clang(self, clang_major, clang_ver, suffix): clang_major = '11' clang_ver = '11.0' clangpp = f'clang++{clang_major}' clang_zip = download_file(f'http://ateh10.net/dev/{clangpp}-{suffix}.zip', tempfile.gettempdir()) console(f'Installing to /usr/local/{clangpp}') execute(f'sudo rm -rf /usr/local/{clangpp}') # get rid of any old stuff execute(f'cd /usr/local && sudo unzip -oq {clang_zip}') # extract /usr/local/clang++11/ os.remove(clang_zip) execute(f'sudo ln -sf /usr/local/{clangpp}/lib/libc++.so.1 /usr/lib') execute(f'sudo ln -sf /usr/local/{clangpp}/lib/libc++abi.so.1 /usr/lib') execute(f'sudo ln -sf /usr/local/{clangpp}/bin/clang /usr/bin/clang-{clang_ver}') execute(f'sudo ln -sf /usr/local/{clangpp}/bin/clang++ /usr/bin/clang++-{clang_ver}') execute(f'sudo ln -sf /usr/local/{clangpp}/include/c++/v1 /usr/include/c++/v1') execute(f'sudo update-alternatives --install /usr/bin/clang clang /usr/bin/clang-{clang_ver} 100') execute(f'sudo update-alternatives --install /usr/bin/clang++ clang++ /usr/bin/clang++-{clang_ver} 100') execute(f'sudo update-alternatives --set clang /usr/bin/clang-{clang_ver}') execute(f'sudo update-alternatives --set clang++ /usr/bin/clang++-{clang_ver}')
def run_with_timeout(executable, argstring, workingDir, timeoutSeconds=None): args = [executable] args += shlex.split(argstring) start = time.time() proc = subprocess.Popen(args, shell=True, cwd=workingDir) try: proc.wait(timeout=timeoutSeconds) console(f'{executable} elapsed: {round(time.time()-start, 1)}s') except subprocess.TimeoutExpired: console('TIMEOUT, sending break signal') if System.windows: proc.send_signal(subprocess.signal.CTRL_C_EVENT) else: proc.send_signal(subprocess.signal.SIGINT) raise if proc.returncode == 0: return raise subprocess.CalledProcessError(proc.returncode, ' '.join(args))
def copy_files(fromFolder, toFolder, fileNames): for file in fileNames: sourceFile = os.path.join(fromFolder, file) if not os.path.exists(sourceFile): continue destFile = os.path.join(toFolder, os.path.basename(file)) destFileExists = os.path.exists(destFile) if destFileExists and is_file_modified(sourceFile, destFile): console(f"skipping copy '{destFile}'") continue console(f"copyto '{toFolder}' '{sourceFile}'") if System.windows and destFileExists: # note: windows crashes if dest file is in use tempCopy = f'{destFile}.{random.randrange(1000)}.deleted' shutil.move(destFile, tempCopy) try: os.remove(tempCopy) except Exception: pass shutil.copy2(sourceFile, destFile) # copy while preserving metadata
def _print_exports(self): if not self.config.print: return if not (self.exported_includes and self.exported_libs and self.exported_syslibs and self.exported_assets): return console(f' - Package {self.name}') for include in self.exported_includes: self._print_ws_path('<I>', include) for library in self.exported_libs: self._print_ws_path('[L]', library) for library in self.exported_syslibs: self._print_ws_path('[S]', library, check_exists=False) if self.config.deploy: for asset in self.exported_assets: self._print_ws_path('[A]', str(asset), check_exists=False) elif self.exported_assets: assets = 'assets' if len(self.exported_assets) > 1 else 'asset' console(f' [A] ({len(self.exported_assets)} {assets})')
def get_msbuild_path(self): if self._msbuild_path: return self._msbuild_path paths = [ find_executable_from_system('msbuild') ] if System.windows: vswhere = '"C:\\Program Files (x86)\\Microsoft Visual Studio\\Installer\\vswhere.exe" -latest -nologo -property installationPath' paths.append(f"{execute_piped(vswhere)}\\MSBuild\\Current\\Bin\\MSBuild.exe") paths.append(f"{execute_piped(vswhere)}\\MSBuild\\15.0\\Bin\\amd64\\MSBuild.exe") vs_variants = [ 'Enterprise', 'Professional', 'Community' ] for variant in vs_variants: paths.append(f'C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\{variant}\\MSBuild\\Current\\Bin\\MSBuild.exe') paths.append(f'C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\{variant}\\MSBuild\\15.0\\Bin\\amd64\\MSBuild.exe') for path in paths: if path and os.path.exists(path): self._msbuild_path = path if self.verbose: console(f'Detected MSBuild: {path}') return path raise EnvironmentError('Failed to find MSBuild from system PATH. You can easily configure msbuild by running `mama install-msbuild`.')
def init_raspi_path(self): paths = [] self.append_env_path(paths, 'RASPI_HOME') self.append_env_path(paths, 'RASPBERRY_HOME') if System.windows: paths += ['/SysGCC/raspberry'] elif System.linux: paths += ['/usr/bin/raspberry', '/usr/local/bin/raspberry', '/opt/raspberry'] compiler = '' if System.windows: compiler = 'bin/arm-linux-gnueabihf-gcc.exe' elif System.linux: compiler = 'arm-bcm2708/arm-linux-gnueabihf/bin/arm-linux-gnueabihf-gcc' for raspi_path in paths: if os.path.exists(f'{raspi_path}/{compiler}'): if not System.windows: raspi_path = f'{raspi_path}/arm-bcm2708/arm-linux-gnueabihf/' self.raspi_compilers = f'{raspi_path}/bin/' self.raspi_system = f'{raspi_path}/arm-linux-gnueabihf/sysroot' self.raspi_include_paths = [f'{raspi_path}/arm-linux-gnueabihf/lib/include'] if self.print: console(f'Found RASPI TOOLS: {self.raspi_compilers}\n sysroot: {self.raspi_system}') return raise EnvironmentError(f'''No Raspberry PI toolchain compilers detected! Default search paths: {paths} Define env RASPI_HOME with path to Raspberry tools.''')
def clone_or_pull(self, wiped=False): if is_dir_empty(self.dep.src_dir): if not wiped and self.dep.config.print: console( f" - Target {self.dep.name: <16} CLONE because src is missing" ) branch = self.branch_or_tag() if branch: branch = f" --branch {self.branch_or_tag()}" execute( f"git clone --recurse-submodules --depth 1 {branch} {self.url} {self.dep.src_dir}", self.dep.config.verbose) self.checkout_current_branch() else: if self.dep.config.print: console( f" - Pulling {self.dep.name: <16} SCM change detected") self.checkout_current_branch() execute("git submodule update --init --recursive") if not self.tag: # pull if not a tag self.run_git("reset --hard -q") self.run_git("pull")
def ms_build(self, projectfile, properties: dict = dict()): """ Invokes MSBuild on the specificied projectfile and passes specified properties to MSBuild. ``` def build(self): self.cmake_build() self.ms_build('extras/csharp/CSharpTests.sln', { 'Configuration': 'Debug', 'Platform': 'Any CPU', }) ``` Default properties set by Mama if not specified via properties dict: /p:PreferredToolArchitecture=x64 /p:Configuration=Release /p:Platform=x64 """ if self.config.print: console('\n#########################################') console(f'MSBuild {self.name} {projectfile}') msbuild_build(self.config, self.source_dir(projectfile), properties)