def setup_customized_image(self): logger.debug('Checking dependencies and container setup') self.check_docker() commands = [] env_vars = self.config.image_setup.get('env', {}) commands += self.get_ppa_adding_commands() dependencies = self.get_dependency_packages() if dependencies: commands.append( 'echo set debconf/frontend Noninteractive | debconf-communicate && echo set debconf/priority critical | debconf-communicate' ) commands.append('apt-get update && {} && apt-get clean'.format( self.get_apt_install_cmd(dependencies))) if self.config.image_setup: commands.extend(self.config.image_setup.get('run', [])) dockerfile_content = self.construct_dockerfile_content( commands, env_vars) if self.is_dockerfile_outdated(dockerfile_content): self.create_custom_container(dockerfile_content) else: logger.debug('Image already setup')
def setup_container_mode(self): ppa_commands = self.get_ppa_adding_commands() if ppa_commands: self.run_command(' && '.join(ppa_commands)) dependencies = self.get_dependency_packages() if dependencies: self.run_command('apt-get update', use_build_dir=False) run = False for dep in dependencies: exists = '' try: exists = self.run_command( 'dpkg -s {} | grep Status'.format(dep), get_output=True, use_build_dir=False) except subprocess.CalledProcessError: exists = '' if exists.strip() != 'Status: install ok installed': run = True break if run: self.run_command(self.get_apt_install_cmd(dependencies), use_build_dir=False) else: logger.debug('Dependencies already installed') if self.config.image_setup: for command in self.config.image_setup.get('run', []): self.run_command(command, use_build_dir=False)
def kill_gdbserver(self): processes = self.device.run_command("ps aux | grep gdbserver", get_output=True) for line in processes.splitlines(): if "gdbserver localhost:{}".format(self.port) in line: logger.debug("Killing running gdbserver on device") pid = line.split()[1] self.device.run_command("kill -9 {}".format(pid))
def run_app(self): docker_config = self.setup_docker() if self.ide_delegate is not None: self.ide_delegate.before_run(self.config, docker_config) command = docker_config.render_command() logger.debug(command) subprocess.check_call(shlex.split(command), cwd=docker_config.working_directory)
def run(self, commands=[], args=None): self.config = Config( args=args, clickable_version=__version__, desktop=('desktop' in commands or 'test' in commands), ) self.config.container = Container(self.config) VALID_COMMANDS = self.command_names + list(self.config.scripts.keys()) if len(commands) == 0: commands = self.config.default.split(' ') ''' Detect senarios when an argument is passed to a command. For example: `clickable install /path/to/click`. Since clickable allows commands to be strung together it makes detecting this harder. This check has been limited to just the case when we have 2 values in args.commands as stringing together multiple commands and a command with an argument is unlikely to occur. TODO determine if there is a better way to do this. ''' command_arg = '' if len(commands) == 2 and commands[1] not in VALID_COMMANDS: command_arg = commands[1] commands = commands[:1] commands = [ self.command_aliases[command] if command in self.command_aliases else command for command in commands ] for command in commands: if command in self.command_names: cmd = self.command_classes[command](self.config) cmd.preprocess(command_arg) # TODO consider removing the ability to string together multiple commands # This should help clean up the arguments & command_arg for command in commands: if command in self.config.scripts: logger.debug('Running the "{}" script'.format(command)) subprocess.check_call(self.config.scripts[command], cwd=self.config.cwd, shell=True) elif command in self.command_names: logger.debug('Running the "{}" command'.format(command)) cmd = self.command_classes[command](self.config) cmd.run(command_arg) else: logger.error( 'There is no builtin or custom command named "{}"'.format( command)) self.print_valid_commands() sys.exit(1)
def run(self, path_arg=None): if not path_arg: raise ClickableException('No command supplied for `clickable ide`') #get the preprocessor according to command if any if 'qtcreator' in path_arg.split(): self.ide_delegate = QtCreatorDelegate() path_arg = self.ide_delegate.override_command(path_arg) logger.debug( 'QtCreator command detected. Changing command to: {}'.format( path_arg)) self.command = path_arg super().run()
def get_time_zone(self): try: return run_subprocess_check_output( 'timedatectl show -p Timezone --value', stderr=subprocess.DEVNULL) except: logger.debug( 'timedatectl show command failed. Falling back to alternative way to detect timezone...' ) if os.path.exists('/etc/timezone'): with open('/etc/timezone') as host_timezone_file: return host_timezone_file.readline().strip() else: logger.debug( '/etc/timezone does not exist. Falling back to alternative way to detect timezone...' ) try: output = run_subprocess_check_output('timedatectl status') for line in output.splitlines(): line = line.strip() if line.startswith('Time zone:'): start = line.find(':') + 1 end = line.find('(') return line[start:end].strip() except: logger.debug( "timedatctl status method failed to set timezone from host in desktop mode..." ) logger.debug("Falling back to UTC as timezone.") return 'UTC'
def run(self, path_arg=None): if self.config.is_desktop_mode(): logger.debug('Skipping logs, running in desktop mode') return elif self.config.container_mode: logger.debug('Skipping logs, running in container mode') return log = '~/.cache/upstart/application-click-{}.log'.format( self.config.install_files.find_full_package_name(), ) if self.config.log: log = self.config.log self.device.run_command('tail -f {}'.format(log))
def run(self, path_arg=None): if self.config.desktop: logger.debug('Skipping log, running in desktop mode') return elif self.config.container_mode: logger.debug('Skipping log, running in container mode') return log = '~/.cache/upstart/application-click-{}_{}_{}.log'.format( self.config.find_package_name(), self.config.find_app_name(), self.config.find_version()) if self.config.log: log = self.config.log self.device.run_command('cat {}'.format(log))
def kill(self): if self.config.is_desktop_mode(): logger.debug('Skipping kill, running in desktop mode') return elif self.config.container_mode: logger.debug('Skipping kill, running in container mode') return if self.config.kill: try: # Enclose first character in square brackets to prevent # spurious error when running `pkill -f` over `adb` kill = '[' + self.config.kill[:1] + ']' + self.config.kill[1:] self.device.run_command('pkill -f \\"{}\\"'.format(kill)) except Exception: pass # Nothing to do, the process probably wasn't running
def click_build(self): command = 'click build {} --no-validate'.format( self.config.install_dir) self.config.container.run_command(command) if self.config.click_output: click = self.config.get_click_filename() click_path = os.path.join(self.config.build_dir, click) output_file = os.path.join(self.config.click_output, click) if not os.path.exists(self.config.click_output): os.makedirs(self.config.click_output) logger.debug('Click outputted to {}'.format(output_file)) shutil.copyfile(click_path, output_file)
def run(self, path_arg=None): if self.config.is_desktop_mode(): logger.debug('Skipping install, running in desktop mode') return elif self.config.container_mode: logger.debug('Skipping install, running in container mode') return cwd = '.' if path_arg: click = os.path.basename(path_arg) click_path = path_arg else: click = self.config.install_files.get_click_filename() click_path = os.path.join(self.config.build_dir, click) cwd = self.config.build_dir if self.config.ssh: command = 'scp {} phablet@{}:/home/phablet/'.format( click_path, self.config.ssh) run_subprocess_check_call(command, cwd=cwd, shell=True) else: self.device.check_any_attached() if self.config.device_serial_number: command = 'adb -s {} push {} /home/phablet/'.format( self.config.device_serial_number, click_path) else: self.device.check_multiple_attached() command = 'adb push {} /home/phablet/'.format(click_path) run_subprocess_check_call(command, cwd=cwd, shell=True) if path_arg: logger.info( "Skipping uninstall step, because you specified a click package." ) else: self.try_uninstall() self.device.run_command( 'pkcon install-local --allow-untrusted /home/phablet/{}'.format( click), cwd=cwd) self.device.run_command('rm /home/phablet/{}'.format(click), cwd=cwd)
def __init__(self, name, json_config, arch, root_dir, debug_build): self.debug_build = debug_build self.config = { 'name': name, 'arch': arch, 'arch_triplet': None, 'template': None, 'postmake': None, 'prebuild': None, 'build': None, 'postbuild': None, 'build_dir': '$ROOT/build/$ARCH_TRIPLET/$NAME', 'src_dir': '$ROOT/libs/$NAME', 'root_dir': root_dir, 'dependencies_build': [], 'dependencies_target': [], 'dependencies_ppa': [], 'make_jobs': 0, 'docker_image': None, 'build_args': [], 'make_args': [], 'install_dir': '$BUILD_DIR/install' } self.config.update(json_config) self.cleanup_config() self.config['arch_triplet'] = self.arch_triplet_mapping[ self.config['arch']] for key in self.path_keys: if key not in self.accepts_placeholders and self.config[key]: self.config[key] = os.path.abspath(self.config[key]) self.substitute_placeholders() self.set_env_vars() self.check_config_errors() for key, value in self.config.items(): logger.debug('Lib {} config value {}: {}'.format(name, key, value))
def restore_cached_container(self): with open(self.docker_name_file, 'r') as f: cached_container = f.read().strip() if not image_exists(cached_container): logger.warning("Cached container does not exist anymore") return command_base = 'docker images -q {}'.format(self.base_docker_image) command_cached = 'docker history -q {}'.format(cached_container) hash_base = run_subprocess_check_output(command_base).strip() history_cached = run_subprocess_check_output( command_cached).strip() if hash_base in history_cached: logger.debug("Found cached container") self.docker_image = cached_container else: logger.warning("Found outdated container")
def restore_cached_image(self): if not os.path.exists(self.docker_name_file): return with open(self.docker_name_file, 'r') as f: cached_image = None cached_base_image = None try: image_file = json.load(f) cached_image = image_file.get('name', None) cached_base_image = image_file.get('base_image', None) except ValueError: pass if not cached_image: logger.warning("Cached image file is invalid") return if not image_exists(cached_image): logger.warning("Cached container does not exist anymore") return if self.base_docker_image != cached_base_image: logger.warning("Cached image has a different base image") self.check_docker() command_base = 'docker images -q {}'.format(self.base_docker_image) command_cached = 'docker history -q {}'.format(cached_image) hash_base = run_subprocess_check_output(command_base).strip() history_cached = run_subprocess_check_output( command_cached).strip() if hash_base in history_cached: logger.debug("Found cached container") self.docker_image = cached_image else: logger.warning("Cached container is outdated")
def main(): clickable = Clickable() args = clickable.parse_args() if args.verbose: console_handler.setLevel(logging.DEBUG) logger.debug('Clickable v' + __version__) try: clickable.run(args.commands, args) except ClickableException as e: logger.error(str(e)) sys.exit(1) except subprocess.CalledProcessError as e: logger.debug('Command exited with an error:' + str(e.cmd), exc_info=e) logger.critical( 'Command exited with non-zero exit status {}, see above for details. This is most likely not a problem with Clickable.' .format(e.returncode, )) sys.exit(2) except Exception as e: logger.debug('Encountered an unknown error', exc_info=e) if not args.verbose: logger.critical('Encountered an unknown error: ' + str(e)) logger.critical( 'If you believe this is a bug, please file a report at https://gitlab.com/clickable/clickable/issues with the log file located at ' + log_file) sys.exit(3)
def check_base_image_version(self): if not self.minimum_version: return if not image_exists(self.docker_image): return version = 0 try: format_string = '{{ index .Config.Labels "image_version"}}' command = "docker inspect --format '{}' {}".format( format_string, self.docker_image) logger.debug( 'Checking docker image version via: {}'.format(command)) version_string = run_subprocess_check_output(command) version = int(version_string) except (ValueError, subprocess.CalledProcessError): logger.warn("Could not read the image version from the container") if version < self.minimum_version: raise ClickableException( 'This version of Clickable requires Clickable docker image {} in version {} or higher (found version {}). Please run "clickable update" to update your local images.' .format(self.docker_image, self.minimum_version, version))
def create_custom_container(self, dockerfile_content): if not os.path.exists(self.clickable_dir): os.makedirs(self.clickable_dir) with open(self.docker_file, 'w') as f: f.write(dockerfile_content) self.docker_image = '{}-{}'.format(self.base_docker_image, uuid.uuid4()) with open(self.docker_name_file, 'w') as f: json.dump( { 'name': self.docker_image, 'base_image': self.base_docker_image, }, f) logger.debug('Generating new docker image') try: subprocess.check_call(shlex.split('docker build -t {} .'.format( self.docker_image)), cwd=self.clickable_dir) except subprocess.CalledProcessError: self.clean_clickable() raise
def main(): clickable = Clickable() args = clickable.parse_args() if args.verbose: console_handler.setLevel(logging.DEBUG) logger.debug('Clickable v' + __version__) clickable.check_version(quiet=True) try: clickable.run(args.commands, args) except ClickableException as e: logger.error(str(e)) sys.exit(1) except subprocess.CalledProcessError as e: logger.debug('Command exited with an error:' + str(e.cmd), exc_info=e) logger.critical( 'Command exited with non-zero exit status {}, see above for details. This is most likely not a problem with Clickable.' .format(e.returncode, )) sys.exit(2) except KeyboardInterrupt as e: logger.info( '') # Print an empty space at then end so the cli prompt is nicer sys.exit(0) except Exception as e: if isinstance(e, OSError) and '28' in str(e): logger.critical('No space left on device') sys.exit(2) return logger.debug('Encountered an unknown error', exc_info=e) if not args.verbose: logger.critical('Encountered an unknown error: ' + str(e)) logger.critical( 'If you believe this is a bug, please file a report at https://gitlab.com/clickable/clickable/issues with the log file located at ' + log_file) sys.exit(3)
def __init__(self, name, json_config, arch, root_dir, qt_version, debug_build, verbose): # Must come after ARCH_TRIPLET to avoid breaking it self.placeholders.update({"ARCH": "arch"}) self.qt_version = qt_version self.debug_build = debug_build self.verbose = verbose self.set_host_arch() self.container_list = list( Constants.container_mapping[self.host_arch].values()) self.config = { 'name': name, 'arch': arch, 'arch_triplet': None, 'template': None, 'builder': None, 'postmake': None, 'prebuild': None, 'build': None, 'postbuild': None, 'build_dir': '${ROOT}/build/${ARCH_TRIPLET}/${NAME}', 'build_home': '${BUILD_DIR}/.clickable/home', 'src_dir': '${ROOT}/libs/${NAME}', 'root_dir': root_dir, 'dependencies_build': [], 'dependencies_host': [], 'dependencies_target': [], 'dependencies_ppa': [], 'make_jobs': None, 'docker_image': None, 'build_args': [], 'env_vars': {}, 'make_args': [], 'install_dir': '${BUILD_DIR}/install', 'image_setup': {}, 'test': 'ctest', } # TODO remove support for deprecated "template" in clickable.json if "template" in json_config: logger.warning( 'Parameter "template" is deprecated in clickable.json. Use "builder" as drop-in replacement instead.' ) json_config["builder"] = json_config["template"] json_config["template"] = None self.config.update(json_config) if self.config["docker_image"]: self.is_custom_docker_image = True else: self.is_custom_docker_image = False self.cleanup_config() self.config['arch_triplet'] = Constants.arch_triplet_mapping[ self.config['arch']] for key in self.path_keys: if key not in self.accepts_placeholders and self.config[key]: self.config[key] = os.path.abspath(self.config[key]) self.substitute_placeholders() self.set_env_vars() self.check_config_errors() for key, value in self.config.items(): logger.debug('Lib {} config value {}: {}'.format(name, key, value))
def setup_dependencies(self, force_build=False): if self.config.dependencies_build or self.config.dependencies_target: logger.debug('Checking dependencies') dependencies = self.config.dependencies_build for dep in self.config.dependencies_target: if ':' in dep: dependencies.append(dep) else: dependencies.append('{}:{}'.format(dep, self.config.arch)) if self.config.container_mode: self.run_command('apt-get update', sudo=True, use_dir=False) command = 'apt-get install -y --force-yes' run = False for dep in dependencies: exists = '' try: exists = self.run_command( 'dpkg -s {} | grep Status'.format(dep), get_output=True, use_dir=False) except subprocess.CalledProcessError: exists = '' if exists.strip() != 'Status: install ok installed': run = True command = '{} {}'.format(command, dep) if run: self.run_command(command, sudo=True, use_dir=False) else: logger.debug('Dependencies already installed') else: self.check_docker() if self.config.custom_docker_image: logger.warning( 'Skipping dependency check, using a custom docker image' ) else: command_ppa = '' if self.config.dependencies_ppa: command_ppa = 'RUN add-apt-repository {}'.format( ' '.join(self.config.dependencies_ppa)) dockerfile = ''' FROM {} RUN echo set debconf/frontend Noninteractive | debconf-communicate && echo set debconf/priority critical | debconf-communicate {} RUN apt-get update && apt-get install -y --force-yes --no-install-recommends {} && apt-get clean '''.format(self.base_docker_image, command_ppa, ' '.join(dependencies)).strip() build = force_build if not os.path.exists(self.clickable_dir): os.makedirs(self.clickable_dir) if self.docker_image != self.base_docker_image and os.path.exists( self.docker_file): with open(self.docker_file, 'r') as f: if dockerfile.strip() != f.read().strip(): build = True else: build = True if not build: command = 'docker images -q {}'.format( self.docker_image) image_exists = run_subprocess_check_output( command).strip() build = not image_exists if build: with open(self.docker_file, 'w') as f: f.write(dockerfile) self.docker_image = '{}-{}'.format( self.base_docker_image, uuid.uuid4()) with open(self.docker_name_file, 'w') as f: f.write(self.docker_image) logger.debug('Generating new docker image') try: subprocess.check_call(shlex.split( 'docker build -t {} .'.format( self.docker_image)), cwd=self.clickable_dir) except subprocess.CalledProcessError: self.clean_clickable() raise else: logger.debug('Dependencies already setup')
def check_version(self, quiet=False): if requests_available: version = None check = True version_check = expanduser('~/.clickable/version_check.json') if isfile(version_check): with open(version_check, 'r') as f: try: version_check_data = json.load(f) except ValueError: version_check_data = None if version_check_data and 'version' in version_check_data and 'datetime' in version_check_data: last_check = datetime.strptime( version_check_data['datetime'], DATE_FORMAT) if last_check > (datetime.now() - timedelta(days=2)) and \ 'current_version' in version_check_data and \ version_check_data['current_version'] == __version__: check = False version = version_check_data['version'] logger.debug('Using cached version check') if check: logger.debug('Checking for updates to clickable') try: response = requests.get( 'https://clickable-ut.dev/en/latest/_static/version.json', timeout=5) response.raise_for_status() data = response.json() version = data['version'] except requests.exceptions.Timeout as e: logger.warning( 'Unable to check for updates to clickable, the request timedout' ) except Exception as e: logger.debug('Version check failed:' + str(e.cmd), exc_info=e) logger.warning( 'Unable to check for updates to clickable, an unknown error occurred' ) if version: with open(version_check, 'w') as f: json.dump( { 'version': version, 'datetime': datetime.now().strftime(DATE_FORMAT), 'current_version': __version__, }, f) if version: if version != __version__: logger.info( 'v{} of clickable is available, update to get the latest features and improvements!' .format(version)) else: if not quiet: logger.info( 'You are running the latest version of clickable!') else: if not quiet: logger.warning( 'Unable to check for updates to clickable, please install "requests"' )
def run_command(self, command, sudo=False, get_output=False, use_dir=True, cwd=None): wrapped_command = command cwd = cwd if cwd else os.path.abspath(self.config.root_dir) if self.config.container_mode: wrapped_command = 'bash -c "{}"'.format(command) else: # Docker self.check_docker() if ' ' in cwd or ' ' in self.config.build_dir: raise ClickableException( 'There are spaces in the current path, this will cause errors in the build process' ) if self.config.first_docker_info: logger.debug('Using docker container "{}"'.format( self.docker_image)) self.config.first_docker_info = False go_config = '' if self.config.gopath: gopaths = self.config.gopath.split(':') docker_gopaths = [] go_configs = [] for (index, path) in enumerate(gopaths): go_configs.append('-v {}:/gopath/path{}:Z'.format( path, index)) docker_gopaths.append('/gopath/path{}'.format(index)) go_config = '{} -e GOPATH={}'.format( ' '.join(go_configs), ':'.join(docker_gopaths), ) rust_config = '' if self.config.config[ 'template'] == Config.RUST and self.config.cargo_home: logger.info("Caching cargo related files in {}".format( self.config.cargo_home)) cargo_registry = os.path.join(self.config.cargo_home, 'registry') cargo_git = os.path.join(self.config.cargo_home, 'git') cargo_package_cache_lock = os.path.join( self.config.cargo_home, '.package-cache') os.makedirs(cargo_registry, exist_ok=True) os.makedirs(cargo_git, exist_ok=True) # create .package-cache if it doesn't exist with open(cargo_package_cache_lock, "a"): pass rust_config = '-v {}:/opt/rust/cargo/registry:Z -v {}:/opt/rust/cargo/git:Z -v {}:/opt/rust/cargo/.package-cache'.format( cargo_registry, cargo_git, cargo_package_cache_lock, ) env_vars = self.config.prepare_docker_env_vars() wrapped_command = 'docker run -v {}:{}:Z {} {} {} -w {} -u {} -e HOME=/tmp --rm -i {} bash -c "{}"'.format( cwd, cwd, env_vars, go_config, rust_config, self.config.build_dir if use_dir else cwd, os.getuid(), self.docker_image, command, ) kwargs = {} if use_dir: kwargs['cwd'] = self.config.build_dir if get_output: return run_subprocess_check_output(shlex.split(wrapped_command), **kwargs) else: subprocess.check_call(shlex.split(wrapped_command), **kwargs)
def run_docker_command(self, docker_config): command = docker_config.render_command() logger.debug(command) subprocess.check_call(shlex.split(command), cwd=docker_config.working_directory)
def run_command(self, command, root_user=False, get_output=False, use_build_dir=True, cwd=None, tty=False, localhost=False): wrapped_command = command cwd = cwd if cwd else os.path.abspath(self.config.root_dir) if self.config.container_mode: wrapped_command = 'bash -c "{}"'.format(command) else: # Docker self.check_docker() if ' ' in cwd or ' ' in self.config.build_dir: raise ClickableException( 'There are spaces in the current path, this will cause errors in the build process' ) if self.config.first_docker_info: logger.debug('Using docker container "{}"'.format( self.docker_image)) self.config.first_docker_info = False go_config = '' if self.config.builder == Constants.GO and self.config.gopath: gopaths = self.config.gopath.split(':') docker_gopaths = [ '/gopath/path{}'.format(index) for index in range(len(gopaths)) ] go_config = '-e GOPATH={}'.format(':'.join(docker_gopaths), ) rust_config = '' if self.config.builder == Constants.RUST and self.config.cargo_home: logger.info("Caching cargo related files in {}".format( self.config.cargo_home)) env_vars = self.config.prepare_docker_env_vars() user = "" if not root_user: user = "******".format(os.getuid()) mounts = self.render_mounts( self.get_docker_mounts(transparent=[cwd])) wrapped_command = 'docker run {mounts} {env} {go} {rust} {user} -w {cwd} --rm {tty} {network} -i {image} bash -c "{cmd}"'.format( mounts=mounts, env=env_vars, go=go_config, rust=rust_config, cwd=self.config.build_dir if use_build_dir else cwd, user=user, image=self.docker_image, cmd=command, tty="-t" if tty else "", network='--network="host"' if localhost else "", ) kwargs = {} if use_build_dir: kwargs['cwd'] = self.config.build_dir if get_output: return run_subprocess_check_output(shlex.split(wrapped_command), **kwargs) else: subprocess.check_call(shlex.split(wrapped_command), **kwargs)
def init_cmake_project(self, config, docker_config): executable = config.project_files.find_any_executable() exec_args = " ".join(config.project_files.find_any_exec_args()) #don't do all that if exec line not found if not executable: return choice = input( Colors.INFO + 'Do you want Clickable to setup a QtCreator project for you? [Y/n]: ' + Colors.CLEAR).strip().lower() if choice != 'y' and choice != 'yes' and choice != '': return #CLICK_EXE can be a variable match_exe_var = re.match("@([-\w]+)@", executable) if match_exe_var: #catch the variable name and try to get it from CMakeLists.txt cmd_var = match_exe_var.group(1) final_cmd = self.cmake_guess_exec_command(cmd_var) if final_cmd is not None: try: exe, exe_arg = final_cmd.split(' ', maxsplit=1) except: exe, exe_arg = final_cmd, '' executable = exe exec_args = exe_arg logger.debug( 'found that executable is {} with args: {}'.format( exe, exe_arg)) else: #was not able to guess executable logger.warning( "Could not determine executable command '{}', please adjust your project's run settings" .format(executable)) # work around for qtcreator bug when first run of a project to avoid qtcreator hang # we need to create the build directory first if not os.path.isdir(config.build_dir): os.makedirs(config.build_dir) env_vars = docker_config.environment clickable_env_path = '{}:{}'.format(env_vars["PATH"], env_vars["CLICK_PATH"]) clickable_ld_library_path = '{}:{}'.format( env_vars["LD_LIBRARY_PATH"], env_vars["CLICK_LD_LIBRARY_PATH"]) clickable_qml2_import_path = '{}:{}:{}'.format( env_vars["QML2_IMPORT_PATH"], env_vars["CLICK_QML2_IMPORT_PATH"], os.path.join(config.install_dir, 'lib')) template_replacement = { "CLICKABLE_LD_LIBRARY_PATH": clickable_ld_library_path, "CLICKABLE_QML2_IMPORT_PATH": clickable_qml2_import_path, "CLICKABLE_BUILD_DIR": config.build_dir, "CLICKABLE_INSTALL_DIR": config.install_dir, "CLICKABLE_EXEC_CMD": executable, "CLICKABLE_EXEC_ARGS": exec_args, "CLICKABLE_SRC_DIR": config.src_dir, "CLICKABLE_BUILD_ARGS": " ".join(config.build_args), "CLICKABLE_PATH": clickable_env_path } output_path = os.path.join(self.project_path, 'CMakeLists.txt.user.shared') #now read template and generate the .shared file to the root project dir with open(self.template_path, "r") as infile2, open(output_path, "w") as outfile: for line in infile2: for f_key, f_value in template_replacement.items(): if f_key in line: line = line.replace(f_key, f_value) outfile.write(line) logger.info( 'generated default build/run template to {}'.format(output_path))
def run(self, arg_commands=[], args=None): self.config = self.setup_config(args, arg_commands) self.config.container = Container( self.config, minimum_version=__container_minimum_required__) commands = self.config.commands VALID_COMMANDS = self.command_names + list(self.config.scripts.keys()) is_default = not arg_commands ''' Detect senarios when an argument is passed to a command. For example: `clickable install /path/to/click`. Since clickable allows commands to be strung together it makes detecting this harder. This check has been limited to just the case when we have 2 values in args.commands as stringing together multiple commands and a command with an argument is unlikely to occur. TODO remove chaining and clean this up ''' command_arg = '' if len(commands) == 2 and commands[1] not in VALID_COMMANDS: command_arg = commands[1] commands = commands[:1] commands = [ self.command_aliases[command] if command in self.command_aliases else command for command in commands ] if len(commands) > 1 and not is_default: logger.warning( 'Chaining multiple commands is deprecated and will be rejected in a future version of Clickable.' ) for command in commands: if command in self.command_names: cmd = self.command_classes[command](self.config) cmd.preprocess(command_arg) for command in commands: if command == 'bash-completion': cli_args = [ '--serial-number', '--config', '--ssh', '--arch', '--verbose', '--container-mode', '--apikey', '--docker-image', '--dirty', '--debug', ] print(' '.join(sorted(VALID_COMMANDS + cli_args))) elif command == 'bash-completion-desktop': cli_args = [ '--nvidia', '--no-nvidia' '--gdbserver', '--gdb', '--dark-mode', '--lang', '--skip-build', '--dirty', '--verbose', '--config', ] print(' '.join(sorted(cli_args))) elif command in self.config.scripts: logger.debug('Running the "{}" script'.format(command)) subprocess.check_call(self.config.scripts[command], cwd=self.config.cwd, shell=True) elif command in self.command_names: logger.debug('Running the "{}" command'.format(command)) cmd = self.command_classes[command](self.config) cmd.run(command_arg) else: logger.error( 'There is no builtin or custom command named "{}"'.format( command)) self.print_valid_commands() sys.exit(1)