def run(self, path_arg=""): single_lib = path_arg found = False for lib in self.config.lib_configs: if not single_lib or single_lib == lib.name: print_info("Cleaning {}".format(lib.name)) found = True if os.path.exists(lib.build_dir): try: shutil.rmtree(lib.build_dir) except Exception: cls, value, traceback = sys.exc_info() if cls == OSError and 'No such file or directory' in str( value ): # TODO see if there is a proper way to do this pass # Nothing to do here, the directory didn't exist else: print_warning( 'Failed to clean the build directory: {}: {}'. format(type, value)) else: print_info( 'Nothing to clean. Path does not exist: {}'.format( lib.build_dir)) if single_lib and not found: raise ValueError( 'Cannot clean unknown library {}. You may add it to the clickable.json' .format(single_lib))
def run(self, path_arg=""): if not self.config.lib_configs: print_warning('No libraries defined.') single_lib = path_arg found = False for lib in self.config.lib_configs: if not single_lib or single_lib == lib.name: print_info("Building {}".format(lib.name)) found = True lib.container_mode = self.config.container_mode lib.docker_image = self.config.docker_image lib.build_arch = self.config.build_arch lib.container = Container(lib, lib.name) lib.container.setup_dependencies() try: os.makedirs(lib.build_dir) except FileExistsError: pass except Exception: print_warning('Failed to create the build directory: {}'.format(str(sys.exc_info()[0]))) if lib.prebuild: run_subprocess_check_call(lib.prebuild, cwd=self.config.cwd, shell=True) self.build(lib) if lib.postbuild: run_subprocess_check_call(lib.postbuild, cwd=lib.build_dir, shell=True) if single_lib and not found: raise ValueError('Cannot build unknown library {}. You may add it to the clickable.json'.format(single_lib))
def run(self, path_arg=''): if not requests_available: raise Exception('Unable to publish app, python requests module is not installed') if not self.config.apikey: raise Exception('No api key specified, use OPENSTORE_API_KEY or --apikey') click = self.config.get_click_filename() click_path = os.path.join(self.config.build_dir, click) url = OPENSTORE_API if 'OPENSTORE_API' in os.environ and os.environ['OPENSTORE_API']: url = os.environ['OPENSTORE_API'] url = url + OPENSTORE_API_PATH.format(self.config.find_package_name()) channel = 'xenial' if self.config.is_xenial else 'vivid' files = {'file': open(click_path, 'rb')} data = { 'channel': channel, 'changelog': path_arg, } params = {'apikey': self.config.apikey} print_info('Uploading version {} of {} for {} to the OpenStore'.format(self.config.find_version(), self.config.find_package_name(), channel)) response = requests.post(url, files=files, data=data, params=params) if response.status_code == requests.codes.ok: print_success('Upload successful') else: if response.text == 'Unauthorized': raise Exception('Failed to upload click: Unauthorized') else: raise Exception('Failed to upload click: {}'.format(response.json()['message']))
def run_docker_command(self, docker_config, verbose_mode): command = docker_config.render_command() if verbose_mode: print_info(command) subprocess.check_call(shlex.split(command), cwd=docker_config.working_directory)
def publish(self): # TODO allow publishing app for the first time if not self.config.apikey: print_error( 'No api key specified, use OPENSTORE_API_KEY or --apikey') return click = '{}_{}_{}.click'.format(self.find_package_name(), self.find_version(), self.config.arch) click_path = os.path.join(self.config.dir, click) url = OPENSTORE_API if 'OPENSTORE_API' in os.environ and os.environ['OPENSTORE_API']: url = os.environ['OPENSTORE_API'] url = url + OPENSTORE_API_PATH.format(self.find_package_name()) channel = 'xenial' if self.config.isXenial else 'vivid' files = {'file': open(click_path, 'rb')} data = {'channel': channel} params = {'apikey': self.config.apikey} print_info('Uploading version {} of {} for {} to the OpenStore'.format( self.find_version(), self.find_package_name(), channel)) response = requests.post(url, files=files, data=data, params=params) if response.status_code == requests.codes.ok: print_success('Upload successful') else: if response.text == 'Unauthorized': print_error('Failed to upload click: Unauthorized') else: print_error('Failed to upload click: {}'.format( response.json()['message']))
def check_lxd(self): name = 'clickable-{}'.format(self.config.build_arch) status = '' try: status = run_subprocess_check_output(shlex.split('usdk-target status {}'.format(name)), stderr=subprocess.STDOUT) status = json.loads(status)['status'] except subprocess.CalledProcessError as e: if e.output.strip() == 'error: Could not connect to the LXD server.' or 'Can\'t establish a working socket connection' in e.output.strip(): started = self.start_lxd() if started: status = 'Running' # Pretend it's started, but we will call this function again to check if it's actually ok time.sleep(3) # Give it a sec to boot up self.check_lxd() else: raise Exception('LXD is not running, please start it') elif e.output.strip() == 'error: Could not query container status. error: not found': raise Exception('No lxd container exists to build in, please run `clickable setup-lxd`') else: print(e.output) raise e if status != 'Running': print_info('Going to start lxd container "{}"'.format(name)) subprocess.check_call(shlex.split('lxc start {}'.format(name)))
def detect_template(self): if not self.template: template = None try: manifest = get_manifest(os.getcwd()) except ValueError: manifest = None except ManifestNotFoundException: manifest = None directory = os.listdir(os.getcwd()) if not template and 'CMakeLists.txt' in directory: template = Config.CMAKE if manifest and manifest.get('architecture', None) == 'all': template = Config.PURE_QML_CMAKE pro_files = [f for f in directory if f.endswith('.pro')] if pro_files: template = Config.QMAKE if manifest and manifest.get('architecture', None) == 'all': template = Config.PURE_QML_QMAKE if not template and 'config.xml' in directory: template = Config.CORDOVA if not template: template = Config.PURE self.template = template print_info('Auto detected template to be "{}"'.format(template))
def check_lxd(self): name = 'clickable-{}'.format(self.config.build_arch) status = '' try: status = run_subprocess_check_output(shlex.split( 'usdk-target status {}'.format(name)), stderr=subprocess.STDOUT) status = json.loads(status)['status'] except subprocess.CalledProcessError as e: if e.output.strip( ) == 'error: Could not connect to the LXD server.' or 'Can\'t establish a working socket connection' in e.output.strip( ): started = self.start_lxd() if started: status = 'Running' # Pretend it's started, but we will call this function again to check if it's actually ok time.sleep(3) # Give it a sec to boot up self.check_lxd() else: raise Exception('LXD is not running, please start it') elif e.output.strip( ) == 'error: Could not query container status. error: not found': raise Exception( 'No lxd container exists to build in, please run `clickable setup-lxd`' ) else: print(e.output) raise e if status != 'Running': print_info('Going to start lxd container "{}"'.format(name)) subprocess.check_call(shlex.split('lxc start {}'.format(name)))
def run(self, path_arg=None): devices = self.device.detect_attached() if len(devices) == 0: print_warning('No attached devices') else: for device in devices: print_info(device)
def devices(self): devices = self.detect_devices() if len(devices) == 0: print_warning('No attached devices') else: for device in devices: print_info(device)
def run(self, path_arg=None): ''' Inspired by http://bazaar.launchpad.net/~phablet-team/phablet-tools/trunk/view/head:/phablet-shell ''' if self.config.ssh: subprocess.check_call(shlex.split('ssh phablet@{}'.format(self.config.ssh))) else: self.device.check_any_attached() adb_args = '' if self.config.device_serial_number: adb_args = '-s {}'.format(self.config.device_serial_number) else: self.device.check_multiple_attached() output = run_subprocess_check_output(shlex.split('adb {} shell pgrep sshd'.format(adb_args))).split() if not output: self.toggle_ssh(on=True) # Use the usb cable rather than worrying about going over wifi port = 0 for p in range(2222, 2299): error_code = run_subprocess_call(shlex.split('adb {} forward tcp:{} tcp:22'.format(adb_args, p)), stdout=subprocess.PIPE, stderr=subprocess.PIPE) if error_code == 0: port = p break if port == 0: raise Exception('Failed to open a port to the device') # Purge the device host key so that SSH doesn't print a scary warning about it # (it changes every time the device is reflashed and this is expected) known_hosts = os.path.expanduser('~/.ssh/known_hosts') subprocess.check_call(shlex.split('touch {}'.format(known_hosts))) subprocess.check_call(shlex.split('ssh-keygen -f {} -R [localhost]:{}'.format(known_hosts, port))) id_pub = os.path.expanduser('~/.ssh/id_rsa.pub') if not os.path.isfile(id_pub): raise Exception('Could not find a ssh public key at "{}", please generate one and try again'.format(id_pub)) with open(id_pub, 'r') as f: public_key = f.read().strip() self.device.run_command('[ -d ~/.ssh ] || mkdir ~/.ssh', cwd=self.config.cwd) self.device.run_command('touch ~/.ssh/authorized_keys', cwd=self.config.cwd) output = run_subprocess_check_output('adb {} shell "grep \\"{}\\" ~/.ssh/authorized_keys"'.format(adb_args, public_key), shell=True).strip() if not output or 'No such file or directory' in output: print_info('Inserting ssh public key on the connected device') self.device.run_command('echo \"{}\" >>~/.ssh/authorized_keys'.format(public_key), cwd=self.config.cwd) self.device.run_command('chmod 700 ~/.ssh', cwd=self.config.cwd) self.device.run_command('chmod 600 ~/.ssh/authorized_keys', cwd=self.config.cwd) subprocess.check_call(shlex.split('ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -p {} phablet@localhost'.format(port))) self.toggle_ssh(on=False)
def start_lxd(self): started = False error_code = run_subprocess_call(shlex.split('which systemctl'), stdout=subprocess.PIPE, stderr=subprocess.PIPE) if error_code == 0: print_info('Asking for root to start lxd') error_code = run_subprocess_call(shlex.split('sudo systemctl start lxd')) started = (error_code == 0) return started
def setup_docker(self): self.check_command('docker') self.start_docker() group_exists = False with open('/etc/group', 'r') as f: lines = f.readlines() for line in lines: if line.startswith('docker:'): group_exists = True if not group_exists: print_info('Asking for root to create docker group') subprocess.check_call(shlex.split('sudo groupadd docker')) output = run_subprocess_check_output( shlex.split('groups {}'.format(getpass.getuser()))).strip() # Test for exactly docker in the group list if ' docker ' in output or output.endswith( ' docker') or output.startswith( 'docker ') or output == 'docker': print_info('Setup has already been completed') else: print_info( 'Asking for root to add the current user to the docker group') subprocess.check_call( shlex.split('sudo usermod -aG docker {}'.format( getpass.getuser()))) print_info('Log out or restart to apply changes')
def start_lxd(self): started = False error_code = run_subprocess_call(shlex.split('which systemctl'), stdout=subprocess.PIPE, stderr=subprocess.PIPE) if error_code == 0: print_info('Asking for root to start lxd') error_code = run_subprocess_call( shlex.split('sudo systemctl start lxd')) started = (error_code == 0) return started
def run(self, path_arg=None): command = 'click build {} --no-validate'.format(os.path.dirname(self.config.find_manifest())) self.container.run_command(command) if self.config.click_output: click = '{}_{}_{}.click'.format(self.config.find_package_name(), self.config.find_version(), self.config.arch) click_path = os.path.join(self.config.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) print_info('Click outputted to {}'.format(output_file)) shutil.copyfile(click_path, output_file)
def run(self, path_arg=None): command = 'click build {} --no-validate'.format(os.path.dirname(self.config.find_manifest())) self.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) print_info('Click outputted to {}'.format(output_file)) shutil.copyfile(click_path, output_file)
def run(self, path_arg=None): if not cookiecutter_available: raise Exception('Cookiecutter is not available on your computer, more information can be found here: https://cookiecutter.readthedocs.io/en/latest/installation.html#install-cookiecutter') app_template = None if path_arg: for template in APP_TEMPLATES: if template['name'] == path_arg: app_template = template if not app_template: print_info('Available app templates:') for (index, template) in enumerate(APP_TEMPLATES): print('[{}] {} - {}'.format(index + 1, template['name'], template['display'])) choice = input('Choose an app template [1]: ').strip() if not choice: choice = '1' try: choice = int(choice) except ValueError: raise Exception('Invalid choice') if choice > len(APP_TEMPLATES) or choice < 1: raise Exception('Invalid choice') app_template = APP_TEMPLATES[choice - 1] print_info('Generating new app from template: {}'.format(app_template['display'])) cookiecutter(app_template['url']) print_info('Your new app has been generated, go to the app\'s directory and run clickable to get started')
def click_build(self): command = 'click build {} --no-validate'.format( os.path.dirname(self.find_manifest())) if self.config.chroot: subprocess.check_call(shlex.split(command), cwd=self.config.dir) else: # Run this in the container so the host doesn't need to have click installed self.run_container_command(command) if self.config.click_output: click = '{}_{}_{}.click'.format(self.find_package_name(), self.find_version(), self.config.arch) click_path = os.path.join(self.config.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) print_info('Click outputted to {}'.format(output_file)) shutil.copyfile(click_path, output_file)
def run(self, path_arg=''): if not requests_available: raise Exception( 'Unable to publish app, python requests module is not installed' ) if not self.config.apikey: raise Exception( 'No api key specified, use OPENSTORE_API_KEY or --apikey') click = self.config.get_click_filename() click_path = os.path.join(self.config.build_dir, click) url = OPENSTORE_API if 'OPENSTORE_API' in os.environ and os.environ['OPENSTORE_API']: url = os.environ['OPENSTORE_API'] url = url + OPENSTORE_API_PATH.format(self.config.find_package_name()) channel = 'xenial' if self.config.is_xenial else 'vivid' files = {'file': open(click_path, 'rb')} data = { 'channel': channel, 'changelog': path_arg, } params = {'apikey': self.config.apikey} print_info('Uploading version {} of {} for {} to the OpenStore'.format( self.config.find_version(), self.config.find_package_name(), channel)) response = requests.post(url, files=files, data=data, params=params) if response.status_code == requests.codes.ok: print_success('Upload successful') else: if response.text == 'Unauthorized': raise Exception('Failed to upload click: Unauthorized') else: raise Exception('Failed to upload click: {}'.format( response.json()['message']))
def run(self, path_arg=""): if not self.config.lib_configs: print_warning('No libraries defined.') single_lib = path_arg found = False for lib in self.config.lib_configs: if not single_lib or single_lib == lib.name: print_info("Building {}".format(lib.name)) found = True lib.container_mode = self.config.container_mode lib.docker_image = self.config.docker_image lib.build_arch = self.config.build_arch try: os.makedirs(lib.build_dir) except FileExistsError: pass except Exception: print_warning('Failed to create the build directory: {}'.format(str(sys.exc_info()[0]))) container = Container(lib) container.setup_dependencies() if lib.prebuild: run_subprocess_check_call(lib.prebuild, cwd=self.config.cwd, shell=True) self.build(lib, container) if lib.postbuild: run_subprocess_check_call(lib.postbuild, cwd=lib.build_dir, shell=True) if single_lib and not found: raise ValueError('Cannot build unknown library {}. You may add it to the clickable.json'.format(single_lib))
def run(self, path_arg=""): single_lib = path_arg found = False for lib in self.config.lib_configs: if not single_lib or single_lib == lib.name: print_info("Cleaning {}".format(lib.name)) found = True if os.path.exists(lib.build_dir): try: shutil.rmtree(lib.build_dir) except Exception: cls, value, traceback = sys.exc_info() if cls == OSError and 'No such file or directory' in str(value): # TODO see if there is a proper way to do this pass # Nothing to do here, the directory didn't exist else: print_warning('Failed to clean the build directory: {}: {}'.format(type, value)) else: print_info('Nothing to clean. Path does not exist: {}'.format(lib.build_dir)) if single_lib and not found: raise ValueError('Cannot clean unknown library {}. You may add it to the clickable.json'.format(single_lib))
def setup_docker(self): print_info('Setting up docker') check_command('docker') self.start_docker() if not self.docker_group_exists(): print_info('Asking for root to create docker group') subprocess.check_call(shlex.split('sudo groupadd docker')) if self.user_part_of_docker_group(): print_info('Setup has already been completed') else: print_info('Asking for root to add the current user to the docker group') subprocess.check_call(shlex.split('sudo usermod -aG docker {}'.format(getpass.getuser()))) raise Exception('Log out or restart to apply changes')
def setup_docker(self): print_info('Setting up docker') check_command('docker') self.start_docker() if not self.docker_group_exists(): print_info('Asking for root to create docker group') subprocess.check_call(shlex.split('sudo groupadd docker')) if self.user_part_of_docker_group(): print_info('Setup has already been completed') else: print_info( 'Asking for root to add the current user to the docker group') subprocess.check_call( shlex.split('sudo usermod -aG docker {}'.format( getpass.getuser()))) raise Exception('Log out or restart to apply changes')
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): print_info("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: print_info("Found cached container") self.docker_image = cached_container else: print_info("Found outdated container")
def run(self, path_arg=None): if not cookiecutter_available: raise Exception( 'Cookiecutter is not available on your computer, more information can be found here: https://cookiecutter.readthedocs.io/en/latest/installation.html#install-cookiecutter' ) app_template = None if path_arg: for template in APP_TEMPLATES: if template['name'] == path_arg: app_template = template if not app_template: print_info('Available app templates:') for (index, template) in enumerate(APP_TEMPLATES): print('[{}] {} - {}'.format(index + 1, template['name'], template['display'])) choice = input('Choose an app template [1]: ').strip() if not choice: choice = '1' try: choice = int(choice) except ValueError: raise Exception('Invalid choice') if choice > len(APP_TEMPLATES) or choice < 1: raise Exception('Invalid choice') app_template = APP_TEMPLATES[choice - 1] print_info('Generating new app from template: {}'.format( app_template['display'])) cookiecutter(app_template['url']) print_info( 'Your new app has been generated, go to the app\'s directory and run clickable to get started' )
def setup_dependencies(self, force_build=False): if self.config.dependencies_build or self.config.dependencies_target: print_info('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.lxd or 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: print_info('Dependencies already installed') else: # Docker self.check_docker() if self.config.custom_docker_image: print_info( '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) print_info('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: print_info('Dependencies already setup')
def build(self): if os.path.isdir(self.config.install_dir): raise ValueError('Build directory already exists. Please run "clickable clean" before building again!') shutil.copytree(self.config.cwd, self.config.install_dir, ignore=self._ignore) print_info('Copied files to install directory for click building')
def run_command(self, command, force_lxd=False, sudo=False, get_output=False, use_dir=True, cwd=None): wrapped_command = command cwd = cwd if cwd else self.config.cwd if self.config.container_mode: wrapped_command = 'bash -c "{}"'.format(command) elif force_lxd or self.config.lxd: self.check_lxd() target_command = 'exec' if sudo: target_command = 'maint' if use_dir: command = 'cd {}; {}'.format(self.config.dir, command) wrapped_command = 'usdk-target {} clickable-{} -- bash -c "{}"'.format( target_command, self.config.build_arch, command) else: # Docker self.check_docker() if ' ' in cwd or ' ' in self.config.dir: raise Exception( 'There are spaces in the current path, this will cause errors in the build process' ) if self.config.first_docker_info: print_info('Using docker container "{}"'.format( self.docker_image)) self.config.first_docker_info = False go_config = '' if self.config.gopath: go_config = '-v {}:/gopath -e GOPATH=/gopath'.format( self.config.gopath) wrapped_command = 'docker run -v {}:{} {} -w {} -u {} -e HOME=/tmp --rm -i {} bash -c "{}"'.format( cwd, cwd, go_config, self.config.dir if use_dir else cwd, os.getuid(), self.docker_image, command, ) kwargs = {} if use_dir: kwargs['cwd'] = self.config.dir if get_output: return run_subprocess_check_output(shlex.split(wrapped_command), **kwargs) else: subprocess.check_call(shlex.split(wrapped_command), **kwargs)
def setup_dependencies(self): if len(self.config.dependencies) > 0: print_info('Checking dependencies') if self.config.lxd or self.config.container_mode: command = 'apt-get install -y --force-yes' run = False for dep in self.config.dependencies: if self.config.arch == 'armhf' and 'armhf' not in dep and not self.config.specificDependencies: dep = '{}:{}'.format(dep, self.config.arch) 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: print_info('Dependencies already installed') else: # Docker self.check_docker() # TODO make the path to the dockerfile arch dependent if self.config.custom_docker_image: print_info( 'Skipping dependency check, using a custom docker image' ) else: dependencies = '' for dep in self.config.dependencies: if self.config.arch == 'armhf' and 'armhf' not in dep and not self.config.specificDependencies: dependencies = '{} {}:{}'.format( dependencies, dep, self.config.arch) else: dependencies = '{} {}'.format(dependencies, dep) 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, dependencies).strip() build = False if not os.path.exists('.clickable'): os.makedirs('.clickable') if os.path.exists('.clickable/Dockerfile'): with open('.clickable/Dockerfile', '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('.clickable/Dockerfile', 'w') as f: f.write(dockerfile) self.docker_image = '{}-{}'.format( self.base_docker_image, uuid.uuid4()) with open('.clickable/name.txt', 'w') as f: f.write(self.docker_image) print_info('Generating new docker image') try: subprocess.check_call(shlex.split( 'docker build -t {} .'.format( self.docker_image)), cwd='.clickable') except subprocess.CalledProcessError: self.clean_clickable() raise else: print_info('Dependencies already setup')
def run(self, path_arg=None): ''' Inspired by http://bazaar.launchpad.net/~phablet-team/phablet-tools/trunk/view/head:/phablet-shell ''' if self.config.ssh: subprocess.check_call( shlex.split('ssh phablet@{}'.format(self.config.ssh))) else: self.device.check_any_attached() adb_args = '' if self.config.device_serial_number: adb_args = '-s {}'.format(self.config.device_serial_number) else: self.device.check_multiple_attached() output = run_subprocess_check_output( shlex.split( 'adb {} shell pgrep sshd'.format(adb_args))).split() if not output: self.toggle_ssh(on=True) # Use the usb cable rather than worrying about going over wifi port = 0 for p in range(2222, 2299): error_code = run_subprocess_call(shlex.split( 'adb {} forward tcp:{} tcp:22'.format(adb_args, p)), stdout=subprocess.PIPE, stderr=subprocess.PIPE) if error_code == 0: port = p break if port == 0: raise Exception('Failed to open a port to the device') # Purge the device host key so that SSH doesn't print a scary warning about it # (it changes every time the device is reflashed and this is expected) known_hosts = os.path.expanduser('~/.ssh/known_hosts') subprocess.check_call(shlex.split('touch {}'.format(known_hosts))) subprocess.check_call( shlex.split('ssh-keygen -f {} -R [localhost]:{}'.format( known_hosts, port))) id_pub = os.path.expanduser('~/.ssh/id_rsa.pub') if not os.path.isfile(id_pub): raise Exception( 'Could not find a ssh public key at "{}", please generate one and try again' .format(id_pub)) with open(id_pub, 'r') as f: public_key = f.read().strip() self.device.run_command('[ -d ~/.ssh ] || mkdir ~/.ssh', cwd=self.config.cwd) self.device.run_command('touch ~/.ssh/authorized_keys', cwd=self.config.cwd) output = run_subprocess_check_output( 'adb {} shell "grep \\"{}\\" ~/.ssh/authorized_keys"'.format( adb_args, public_key), shell=True).strip() if not output or 'No such file or directory' in output: print_info('Inserting ssh public key on the connected device') self.device.run_command( 'echo \"{}\" >>~/.ssh/authorized_keys'.format(public_key), cwd=self.config.cwd) self.device.run_command('chmod 700 ~/.ssh', cwd=self.config.cwd) self.device.run_command('chmod 600 ~/.ssh/authorized_keys', cwd=self.config.cwd) subprocess.check_call( shlex.split( 'ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -p {} phablet@localhost' .format(port))) self.toggle_ssh(on=False)
def run_command(self, command, force_lxd=False, 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) elif force_lxd or self.config.lxd: self.check_lxd() target_command = 'exec' if sudo: target_command = 'maint' if use_dir: command = 'cd {}; {}'.format(self.config.build_dir, command) wrapped_command = 'usdk-target {} clickable-{} -- bash -c "{}"'.format( target_command, self.config.build_arch, command) else: # Docker self.check_docker() if ' ' in cwd or ' ' in self.config.build_dir: raise Exception( 'There are spaces in the current path, this will cause errors in the build process' ) if self.config.first_docker_info: print_info('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: 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, ) wrapped_command = 'docker run -v {}:{}:Z {} {} -w {} -u {} -e HOME=/tmp --rm -i {} bash -c "{}"'.format( cwd, cwd, 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 _build(self): shutil.copytree(self.cwd, self.temp, ignore=self._ignore) print_info('Copied files to temp directory for click building')
def setup_dependencies(self): if self.config.dependencies_build or self.config.dependencies_target: print_info('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.lxd or 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: print_info('Dependencies already installed') else: # Docker self.check_docker() if self.config.custom_docker_image: print_info('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 = False if not os.path.exists(self.clickableDir): os.makedirs(self.clickableDir) if os.path.exists(self.dockerFile): with open(self.dockerFile, '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.dockerFile, 'w') as f: f.write(dockerfile) self.docker_image = '{}-{}'.format(self.base_docker_image, uuid.uuid4()) with open(self.dockerNameFile, 'w') as f: f.write(self.docker_image) print_info('Generating new docker image') try: subprocess.check_call(shlex.split('docker build -t {} .'.format(self.docker_image)), cwd=self.clickableDir) except subprocess.CalledProcessError: self.clean_clickable() raise else: print_info('Dependencies already setup')
def writable_image(self): command = 'dbus-send --system --print-reply --dest=com.canonical.PropertyService /com/canonical/PropertyService com.canonical.PropertyService.SetProperty string:writable boolean:true' self.run_device_command(command, cwd=self.cwd) print_info('Rebooting for writable image')
def init_app(self, name=None): if not cookiecutter_available: raise Exception( 'Cookiecutter is not available on your computer, more information can be found here: https://cookiecutter.readthedocs.io/en/latest/installation.html#install-cookiecutter' ) app_templates = [{ 'name': 'pure-qml-cmake', 'display': 'Pure QML App (built using CMake)', 'url': 'https://github.com/bhdouglass/ut-app-pure-qml-cmake-template', }, { 'name': 'cmake', 'display': 'C++/QML App (built using CMake)', 'url': 'https://github.com/bhdouglass/ut-app-cmake-template', }, { 'name': 'python-cmake', 'display': 'Python/QML App (built using CMake)', 'url': 'https://github.com/bhdouglass/ut-app-python-cmake-template', }, { 'name': 'html', 'display': 'HTML App', 'url': 'https://github.com/bhdouglass/ut-app-html-template', }, { 'name': 'webapp', 'display': 'Simple Webapp', 'url': 'https://github.com/bhdouglass/ut-app-webapp-template', }, { 'name': 'go', 'display': 'Go/QML App', 'url': 'https://github.com/bhdouglass/ut-app-go-template', }] app_template = None if name: for template in app_templates: if template['name'] == name: app_template = template if not app_template: print_info('Available app templates:') for (index, template) in enumerate(app_templates): print('[{}] {} - {}'.format(index + 1, template['name'], template['display'])) choice = input('Choose an app template [1]: ').strip() if not choice: choice = '1' try: choice = int(choice) except ValueError: raise Exception('Invalid choice') if choice > len(app_templates) or choice < 1: raise Exception('Invalid choice') app_template = app_templates[choice - 1] print_info('Generating new app from template: {}'.format( app_template['display'])) cookiecutter(app_template['url']) print_info( 'Your new app has been generated, go to the app\'s directory and run clickable to get started' )
def build(self): shutil.copytree(self.config.cwd, self.config.temp, ignore=self._ignore) print_info('Copied files to temp directory for click building')
def run_command(self, command, force_lxd=False, 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) elif force_lxd or self.config.lxd: self.check_lxd() target_command = 'exec' if sudo: target_command = 'maint' if use_dir: command = 'cd {}; {}'.format(self.config.build_dir, command) wrapped_command = 'usdk-target {} clickable-{} -- bash -c "{}"'.format(target_command, self.config.build_arch, command) else: # Docker self.check_docker() if ' ' in cwd or ' ' in self.config.build_dir: raise Exception('There are spaces in the current path, this will cause errors in the build process') if self.config.first_docker_info: print_info('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: cargo_registry = os.path.join(self.config.cargo_home, 'registry') cargo_git = os.path.join(self.config.cargo_home, 'git') os.makedirs(cargo_registry, exist_ok=True) os.makedirs(cargo_git, exist_ok=True) rust_config = '-v {}:/opt/rust/cargo/registry:Z -v {}:/opt/rust/cargo/git:Z'.format( cargo_registry, cargo_git, ) wrapped_command = 'docker run -v {}:{}:Z {} {} -w {} -u {} -e HOME=/tmp --rm -i {} bash -c "{}"'.format( cwd, cwd, 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 no_lock(self): print_info('Turning off device activity timeout') command = 'gsettings set com.ubuntu.touch.system activity-timeout 0' self.run_device_command(command, cwd=self.cwd)
def run(self, path_arg=None): print_info('Turning off device activity timeout') command = 'gsettings set com.ubuntu.touch.system activity-timeout 0' self.device.run_command(command, cwd=self.config.cwd)
def run(self, path_arg=None): command = 'dbus-send --system --print-reply --dest=com.canonical.PropertyService /com/canonical/PropertyService com.canonical.PropertyService.SetProperty string:writable boolean:true' self.device.run_command(command, cwd=self.config.cwd) print_info('Rebooting device for writable image')