Exemplo n.º 1
0
    def execute(self):
        if len(self.actions) == 0:
            ui.logger(__name__).warning('No actions necessary.')
            return
        location = self.project.location
        tfd, tfn = tempfile.mkstemp(prefix='pros-project-', suffix=f'-{self.project.name}.zip', text='w+b')
        with os.fdopen(tfd, 'w+b') as tf:
            with zipfile.ZipFile(tf, mode='w') as zf:
                files, length = it.tee(location.glob('**/*'), 2)
                length = len(list(length))
                with ui.progressbar(files, length=length, label=f'Backing up {self.project.name} to {tfn}') as pb:
                    for file in pb:
                        zf.write(file, arcname=file.relative_to(location))

        try:
            with ui.Notification():
                for action in self.actions:
                    ui.logger(__name__).debug(action.describe(self.conductor, self.project))
                    rv = action.execute(self.conductor, self.project)
                    ui.logger(__name__).debug(f'{action} returned {rv}')
                    if rv is not None and not rv:
                        raise ValueError('Action did not complete successfully')
            ui.echo('All actions performed successfully')
        except Exception as e:
            ui.logger(__name__).warning(f'Failed to perform transaction, restoring project to previous state')

            with zipfile.ZipFile(tfn) as zf:
                with ui.progressbar(zf.namelist(), label=f'Restoring {self.project.name} from {tfn}') as pb:
                    for file in pb:
                        zf.extract(file, path=location)

            ui.logger(__name__).exception(e)
        finally:
            ui.echo(f'Removing {tfn}')
            os.remove(tfn)
Exemplo n.º 2
0
 def wake_me(self):
     """
     Hack to wake up input thread to know to shut down
     """
     ui.logger(__name__).debug(f'Broadcasting WAKEME for {self.app}')
     if ui.ismachineoutput():
         ui._machineoutput({'type': 'wakeme'})
     else:
         ui.echo('Wake up the renderer!')
Exemplo n.º 3
0
def version(ctx: click.Context, param, value):
    if not value:
        return
    ctx.ensure_object(dict)
    if ctx.obj.get('machine_output', False):
        ui.echo(get_version())
    else:
        ui.echo('pros, version {}'.format(get_version()))
    ctx.exit(0)
Exemplo n.º 4
0
def user_script(script_file):
    """
    Run a script file with the PROS CLI package
    """
    import os.path
    import importlib.util
    package_name = os.path.splitext(os.path.split(script_file)[0])[0]
    package_path = os.path.abspath(script_file)
    ui.echo(f'Loading {package_name} from {package_path}')
    spec = importlib.util.spec_from_file_location(package_name, package_path)
    spec.loader.load_module()
Exemplo n.º 5
0
 def create_query(cls, name: str = None, **kwargs) -> 'BaseTemplate':
     if not isinstance(name, str):
         return cls(**kwargs)
     if name.count('@') > 1:
         raise ValueError(f'Malformed identifier: {name}')
     if '@' in name:
         name, kwargs['version'] = name.split('@')
     if name == 'kernal':
         ui.echo("Assuming 'kernal' is the British spelling of kernel.")
         name = 'kernel'
     return cls(name=name, **kwargs)
Exemplo n.º 6
0
    def confirm(self, *args, **kwargs):
        assert self.can_confirm
        self.exit()
        project = self.conductor.new_project(
            path=self.directory.value,
            target=self.targets.value,
            version=self.kernel_versions.value,
            no_default_libs=not self.install_default_libraries.value,
            project_name=self.project_name.value)

        from pros.conductor.project import ProjectReport
        report = ProjectReport(project)
        ui.finalize('project-report', report)

        with ui.Notification():
            ui.echo('Building project...')
            project.compile([])
Exemplo n.º 7
0
    def write_program(self, file: typing.BinaryIO, **kwargs):
        action_string = ''
        if hasattr(file, 'name'):
            action_string += f' {Path(file.name).name}'
        action_string += f' to Cortex on {self.port}'
        ui.echo(f'Uploading {action_string}')

        logger(__name__).info('Writing program to Cortex')
        status = self.query_system()
        logger(__name__).info(status)
        if not status.flags | self.SystemStatusFlags.TETH_USB and not status.flags | self.SystemStatusFlags.DL_MODE:
            self.send_to_download_channel()

        bootloader = self.expose_bootloader()
        rv = bootloader.write_program(file, **kwargs)

        ui.finalize('upload', f'Finished uploading {action_string}')
        return rv
Exemplo n.º 8
0
def new_project(ctx: click.Context,
                path: str,
                target: str,
                version: str,
                force_user: bool = False,
                force_system: bool = False,
                no_default_libs: bool = False,
                compile_after: bool = True,
                build_cache: bool = None,
                **kwargs):
    """
    Create a new PROS project

    Visit https://pros.cs.purdue.edu/v5/cli/conductor.html to learn more
    """
    if version.lower() == 'latest' or not version:
        version = '>0'
    if not force_system and c.Project.find_project(path) is not None:
        logger(__name__).error(
            'A project already exists in this location! Delete it first',
            extra={'sentry': False})
        ctx.exit(-1)
    try:
        _conductor = c.Conductor()
        if target is None:
            target = _conductor.default_target
        project = _conductor.new_project(path,
                                         target=target,
                                         version=version,
                                         force_user=force_user,
                                         force_system=force_system,
                                         no_default_libs=no_default_libs,
                                         **kwargs)
        ui.echo('New PROS Project was created:', output_machine=False)
        ctx.invoke(info_project, project=project)

        if compile_after or build_cache:
            with ui.Notification():
                ui.echo('Building project...')
                ctx.exit(project.compile([], scan_build=build_cache))

    except Exception as e:
        pros.common.logger(__name__).exception(e)
        ctx.exit(-1)
Exemplo n.º 9
0
 def get_remote_templates(self,
                          auto_check_freq: Optional[timedelta] = None,
                          force_check: bool = False,
                          **kwargs):
     if auto_check_freq is None:
         auto_check_freq = getattr(self, 'update_frequency',
                                   cli_config().update_frequency)
     logger(__name__).info(
         f'Last check of {self.name} was {self.last_remote_update} '
         f'({datetime.now() - self.last_remote_update} vs {auto_check_freq}).'
     )
     if force_check or datetime.now(
     ) - self.last_remote_update > auto_check_freq:
         with ui.Notification():
             ui.echo(f'Updating {self.name}... ', nl=False)
             self.update_remote_templates(**kwargs)
             ui.echo('Done', color='green')
     for t in self.remote_templates:
         t.metadata['origin'] = self.name
     return self.remote_templates
Exemplo n.º 10
0
    def get_manifest(self, force: bool = False) -> UpgradeManifestV1:
        if not force and not self.has_stale_manifest:
            return self._manifest

        ui.echo('Fetching upgrade manifest...')
        import requests
        import jsonpickle
        import json

        channel_url = f'https://purduesigbots.github.io/pros-mainline/{self.release_channel.value}'
        self._manifest = None

        manifest_urls = [
            f"{channel_url}/{manifest.__name__}.json" for manifest in manifests
        ]
        for manifest_url in manifest_urls:
            resp = requests.get(manifest_url)
            if resp.status_code == 200:
                try:
                    self._manifest = jsonpickle.decode(resp.text, keys=True)
                    logger(__name__).debug(self._manifest)
                    self._last_check = datetime.now()
                    self.save()
                    break
                except json.decoder.JSONDecodeError as e:
                    logger(__name__).warning(
                        f'Failed to decode {manifest_url}')
                    logger(__name__).debug(e)
            else:
                logger(__name__).debug(
                    f'Failed to get {manifest_url} ({resp.status_code})')
        if not self._manifest:
            manifest_list = "\n".join(manifest_urls)
            logger(__name__).warning(
                f'Could not access any upgrade manifests from any of:\n{manifest_list}'
            )
        return self._manifest
Exemplo n.º 11
0
def prompt_to_send(event: Dict[str, Any], hint: Optional[Dict[str, Any]]) -> Optional[Dict[str, Any]]:
    """
    Asks the user for permission to send data to Sentry
    """
    global cli_config
    with ui.Notification():
        if cli_config is None or (cli_config.offer_sentry is not None and not cli_config.offer_sentry):
            return

        if 'extra' in event and not event['extra'].get('sentry', True):
            ui.logger(__name__).debug('Not sending candidate event because event was tagged with extra.sentry = False')
            return
        if 'exc_info' in hint and (not getattr(hint['exc_info'][1], 'sentry', True) or
                                   any(isinstance(hint['exc_info'][1], t) for t in SUPPRESSED_EXCEPTIONS)):
            ui.logger(__name__).debug('Not sending candidate event because exception was tagged with sentry = False')
            return

        if not event['tags']:
            event['tags'] = dict()

        extra_text = ''
        if 'message' in event:
            extra_text += event['message'] + '\n'
        if 'culprit' in event:
            extra_text += event['culprit'] + '\n'
        if 'logentry' in event and 'message' in event['logentry']:
            extra_text += event['logentry']['message'] + '\n'
        if 'exc_info' in hint:
            import traceback
            extra_text += ''.join(traceback.format_exception(*hint['exc_info'], limit=4))

        event['tags']['confirmed'] = ui.confirm('We detected something went wrong! Do you want to send a report?',
                                                log=extra_text)
        if event['tags']['confirmed']:
            ui.echo('Sending bug report.')

            ui.echo(f'Want to get updates? Visit https://pros.cs.purdue.edu/report.html?event={event["event_id"]}')
            return event
        else:
            ui.echo('Not sending bug report.')
Exemplo n.º 12
0
 def _output(data: dict):
     data['type'] = 'input/interactive'
     if ui.ismachineoutput():
         ui._machineoutput(data)
     else:
         ui.echo(str(data))
Exemplo n.º 13
0
def upload(path: str, project: Optional[c.Project], port: str, **kwargs):
    """
    Upload a binary to a microcontroller.

    [PATH] may be a directory or file. If a directory, finds a PROS project root and uploads the binary for the correct
    target automatically. If a file, then the file is uploaded. Note that --target must be specified in this case.

    [PORT] may be any valid communication port file, such as COM1 or /dev/ttyACM0. If left blank, then a port is
    automatically detected based on the target (or as supplied by the PROS project)
    """
    import pros.serial.devices.vex as vex
    from pros.serial.ports import DirectPort
    args = []
    if path is None or os.path.isdir(path):
        if project is None:
            project_path = c.Project.find_project(path or os.getcwd())
            if project_path is None:
                raise click.UsageError(
                    'Specify a file to upload or set the cwd inside a PROS project'
                )
            project = c.Project(project_path)
        path = os.path.join(project.location, project.output)
        if project.target == 'v5' and not kwargs['name']:
            kwargs['name'] = project.name

        # apply upload_options as a template
        options = dict(**project.upload_options)
        options.update(kwargs)
        kwargs = options

        kwargs[
            'target'] = project.target  # enforce target because uploading to the wrong uC is VERY bad
        if 'program-version' in kwargs:
            kwargs['version'] = kwargs['program-version']
        if 'name' not in kwargs:
            kwargs['name'] = project.name
    if 'target' not in kwargs:
        logger(__name__).debug(
            f'Target not specified. Arguments provided: {kwargs}')
        raise click.UsageError(
            'Target not specified. specify a project (using the file argument) or target manually'
        )

    if kwargs['target'] == 'v5':
        port = resolve_v5_port(port, 'system')
    elif kwargs['target'] == 'cortex':
        port = resolve_cortex_port(port)
    else:
        logger(__name__).debug(f"Invalid target provided: {kwargs['target']}")
        logger(__name__).debug('Target should be one of ("v5" or "cortex").')
    if not port:
        return -1

    if kwargs['target'] == 'v5':
        if kwargs['name'] is None:
            kwargs['name'] = os.path.splitext(os.path.basename(path))[0]
        args.append(kwargs.pop('name').replace('@', '_'))
        kwargs['slot'] -= 1
        if kwargs['run_after'] and kwargs['run_screen']:
            kwargs['run_after'] = vex.V5Device.FTCompleteOptions.RUN_SCREEN
        elif kwargs['run_after'] and not kwargs['run_screen']:
            kwargs[
                'run_after'] = vex.V5Device.FTCompleteOptions.RUN_IMMEDIATELY
        else:
            kwargs['run_after'] = vex.V5Device.FTCompleteOptions.DONT_RUN
    elif kwargs['target'] == 'cortex':
        pass

    # print what was decided
    ui.echo('Uploading {} to {} device on {}'.format(path, kwargs['target'],
                                                     port),
            nl=False)
    if kwargs['target'] == 'v5':
        ui.echo(f' as {args[0]} to slot {kwargs["slot"] + 1}', nl=False)
    ui.echo('')

    logger(__name__).debug('Arguments: {}'.format(str(kwargs)))
    if not os.path.isfile(path) and path is not '-':
        logger(__name__).error(
            '{} is not a valid file! Make sure it exists (e.g. by building your project)'
            .format(path))
        return -1

    # Do the actual uploading!
    try:
        ser = DirectPort(port)
        device = None
        if kwargs['target'] == 'v5':
            device = vex.V5Device(ser)
        elif kwargs['target'] == 'cortex':
            device = vex.CortexDevice(ser).get_connected_device()
        with click.open_file(path, mode='rb') as pf:
            device.write_program(pf, *args, **kwargs)
    except Exception as e:
        logger(__name__).exception(e, exc_info=True)
        exit(1)

    ui.finalize('upload',
                f'Finished uploading {path} to {kwargs["target"]} on {port}')
Exemplo n.º 14
0
def create_template(ctx, path: str, destination: str, do_zip: bool, **kwargs):
    """
    Create a template to be used in other projects

    Templates primarily consist of the following fields: name, version, and
    files to install.

    Templates have two types of files: system files and user files. User files
    are files in a template intended to be modified by users - they are not
    replaced during upgrades or removed by default when a library is uninstalled.
    System files are files that are for the "system." They get replaced every
    time the template is upgraded. The default PROS project is a template. The user
    files are files like src/opcontrol.c and src/initialize.c, and the system files
    are files like firmware/libpros.a and include/api.h.

    You should specify the --system and --user options multiple times to include
    more than one file. Both flags also accept glob patterns. When a glob pattern is
    provided and inside a PROS project, then all files that match the pattern that
    are NOT supplied by another template are included.


    Example usage:

    pros conduct create-template . libblrs 2.0.1 --system "firmware/*.a" --system "include/*.h"
    """
    project = c.Project.find_project(path, recurse_times=1)
    if project:
        project = c.Project(project)
        path = project.location
        if not kwargs['supported_kernels'] and kwargs['name'] != 'kernel':
            kwargs['supported_kernels'] = f'^{project.kernel}'
        kwargs['target'] = project.target
    if not destination:
        if os.path.isdir(path):
            destination = path
        else:
            destination = os.path.dirname(path)
    kwargs['system_files'] = list(kwargs['system_files'])
    kwargs['user_files'] = list(kwargs['user_files'])
    kwargs['metadata'] = {
        ctx.args[i][2:]: ctx.args[i + 1]
        for i in range(0,
                       int(len(ctx.args) / 2) * 2, 2)
    }

    def get_matching_files(globs: List[str]) -> Set[str]:
        matching_files: List[str] = []
        _path = os.path.normpath(path) + os.path.sep
        for g in [g for g in globs if glob.has_magic(g)]:
            files = glob.glob(f'{path}/{g}', recursive=True)
            files = filter(lambda f: os.path.isfile(f), files)
            files = [
                os.path.normpath(os.path.normpath(f).split(_path)[-1])
                for f in files
            ]
            matching_files.extend(files)

        # matches things like src/opcontrol.{c,cpp} so that we can expand to src/opcontrol.c and src/opcontrol.cpp
        pattern = re.compile(r'^([\w{}]+.){{((?:\w+,)*\w+)}}$'.format(
            os.path.sep.replace('\\', '\\\\')))
        for f in [os.path.normpath(f) for f in globs if not glob.has_magic(f)]:
            if re.match(pattern, f):
                matches = re.split(pattern, f)
                logger(__name__).debug(f'Matches on {f}: {matches}')
                matching_files.extend(
                    [f'{matches[1]}{ext}' for ext in matches[2].split(',')])
            else:
                matching_files.append(f)

        matching_files: Set[str] = set(matching_files)
        return matching_files

    matching_system_files: Set[str] = get_matching_files(
        kwargs['system_files'])
    matching_user_files: Set[str] = get_matching_files(kwargs['user_files'])

    matching_system_files: Set[
        str] = matching_system_files - matching_user_files

    # exclude existing project.pros and template.pros from the template,
    # and name@*.zip so that we don't redundantly include ZIPs
    exclude_files = {
        'project.pros', 'template.pros',
        *get_matching_files([f"{kwargs['name']}@*.zip"])
    }
    if project:
        exclude_files = exclude_files.union(project.list_template_files())
    matching_system_files = matching_system_files - exclude_files
    matching_user_files = matching_user_files - exclude_files

    def filename_remap(file_path: str) -> str:
        if os.path.dirname(file_path) == 'bin':
            return file_path.replace('bin', 'firmware', 1)
        return file_path

    kwargs['system_files'] = list(map(filename_remap, matching_system_files))
    kwargs['user_files'] = list(map(filename_remap, matching_user_files))

    if do_zip:
        if not os.path.isdir(
                destination) and os.path.splitext(destination)[-1] != '.zip':
            logger(__name__).error(
                f'{destination} must be a zip file or an existing directory.')
            return -1
        with tempfile.TemporaryDirectory() as td:
            template = ExternalTemplate(file=os.path.join(td, 'template.pros'),
                                        **kwargs)
            template.save()
            if os.path.isdir(destination):
                destination = os.path.join(destination,
                                           f'{template.identifier}.zip')
            with zipfile.ZipFile(destination, mode='w') as z:
                z.write(template.save_file, arcname='template.pros')

                for file in matching_user_files:
                    source_path = os.path.join(path, file)
                    dest_file = filename_remap(file)
                    if os.path.exists(source_path):
                        ui.echo(f'U: {file}' + (
                            f' -> {dest_file}' if file != dest_file else ''))
                        z.write(f'{path}/{file}', arcname=dest_file)
                for file in matching_system_files:
                    source_path = os.path.join(path, file)
                    dest_file = filename_remap(file)
                    if os.path.exists(source_path):
                        ui.echo(f'S: {file}' + (
                            f' -> {dest_file}' if file != dest_file else ''))
                        z.write(f'{path}/{file}', arcname=dest_file)
    else:
        if os.path.isdir(destination):
            destination = os.path.join(destination, 'template.pros')
        template = ExternalTemplate(file=destination, **kwargs)
        template.save()
Exemplo n.º 15
0
def test():
    ui.echo('Hello World!')
    with ui.Notification():
        ui.echo('Hello from another box')
    ui.echo('Back on the other one', nl=False)
    ui.echo('Whoops I missed a newline')
    with ui.Notification():
        ui.echo('Yet another box')
        with ui.progressbar(range(20)) as bar:
            for _ in bar:
                time.sleep(0.1)
        ui.echo('more below the ', nl=False)
        ui.echo('progressbar')
    ui.echo('Back in the other notification')

    logger(__name__).warning('Hello')
    try:
        raise Exception('Hey')
    except Exception as e:
        logger(__name__).exception(e)

    ui.finalize('test', {'hello': 'world'}, human_prefix='Created ')