Commit a1dee91e authored by Jonathan Maw's avatar Jonathan Maw
Browse files

Make specifying elements optional in bst commands

Known issues:
* `bst shell` works, but `bst shell COMMANDS...` doesn't, because click
  has no way of separating optional args from variable-length args.
* `bst checkout` and `bst source-checkout`'s usage strings mark LOCATION
  as an optional argument. Because click gets confused if there's an
  optional argument before a mandatory argument, I had to mark LOCATION
  as optional internally.
* `bst workspace open` makes no sense with element being optional, so
  I skipped it.
* `bst workspace close` will probably need to be revisited when multiple
  projects can own one workspace.
* `bst workspace reset` will happily delete the directory you're
  currently in, requiring you to `cd $PWD` to see the contents of your
  directory.
  I could exclude the top-level directory of the workspace being
  deleted, but it is entirely valid to run workspace commands from deeper
  in the workspace.

This is a part of #222
parent 7cf83343
Loading
Loading
Loading
Loading
+15 −1
Original line number Diff line number Diff line
@@ -32,7 +32,7 @@ from ._message import Message, MessageType
from ._profile import Topics, profile_start, profile_end
from ._artifactcache import ArtifactCache
from ._artifactcache.cascache import CASCache
from ._workspaces import Workspaces, WorkspaceProjectCache
from ._workspaces import Workspaces, WorkspaceProjectCache, WORKSPACE_PROJECT_FILE
from .plugin import _plugin_lookup


@@ -649,6 +649,20 @@ class Context():
            self._cascache = CASCache(self.artifactdir)
        return self._cascache

    # guess_element()
    #
    # Attempts to interpret which element the user intended to run commands on
    #
    # Returns:
    #    (str) The name of the element, or None if no element can be guessed
    def guess_element(self):
        workspace_project_dir, _ = utils._search_upward_for_files(self._directory, [WORKSPACE_PROJECT_FILE])
        if workspace_project_dir:
            workspace_project = self._workspace_project_cache.get(workspace_project_dir)
            return workspace_project.get_default_element()
        else:
            return None


# _node_get_option_str()
#
+85 −13
Original line number Diff line number Diff line
@@ -316,10 +316,15 @@ def build(app, elements, all_, track_, track_save, track_all, track_except, trac
    if track_save:
        click.echo("WARNING: --track-save is deprecated, saving is now unconditional", err=True)

    with app.initialized(session_name="Build"):
        if not all_ and not elements:
            guessed_target = app.context.guess_element()
            if guessed_target:
                elements = (guessed_target,)

        if track_all:
            track_ = elements

    with app.initialized(session_name="Build"):
        app.stream.build(elements,
                         track_targets=track_,
                         track_except=track_except,
@@ -371,6 +376,11 @@ def fetch(app, elements, deps, track_, except_, track_cross_junctions):
        deps = PipelineSelection.ALL

    with app.initialized(session_name="Fetch"):
        if not elements:
            guessed_target = app.context.guess_element()
            if guessed_target:
                elements = (guessed_target,)

        app.stream.fetch(elements,
                         selection=deps,
                         except_targets=except_,
@@ -407,6 +417,11 @@ def track(app, elements, deps, except_, cross_junctions):
        all:   All dependencies of all specified elements
    """
    with app.initialized(session_name="Track"):
        if not elements:
            guessed_target = app.context.guess_element()
            if guessed_target:
                elements = (guessed_target,)

        # Substitute 'none' for 'redirect' so that element redirections
        # will be done
        if deps == 'none':
@@ -442,7 +457,13 @@ def pull(app, elements, deps, remote):
        none:  No dependencies, just the element itself
        all:   All dependencies
    """

    with app.initialized(session_name="Pull"):
        if not elements:
            guessed_target = app.context.guess_element()
            if guessed_target:
                elements = (guessed_target,)

        app.stream.pull(elements, selection=deps, remote=remote)


@@ -475,6 +496,11 @@ def push(app, elements, deps, remote):
        all:   All dependencies
    """
    with app.initialized(session_name="Push"):
        if not elements:
            guessed_target = app.context.guess_element()
            if guessed_target:
                elements = (guessed_target,)

        app.stream.push(elements, selection=deps, remote=remote)


@@ -545,6 +571,11 @@ def show(app, elements, deps, except_, order, format_):
            $'---------- %{name} ----------\\n%{vars}'
    """
    with app.initialized():
        if not elements:
            guessed_target = app.context.guess_element()
            if guessed_target:
                elements = (guessed_target,)

        dependencies = app.stream.load_selection(elements,
                                                 selection=deps,
                                                 except_targets=except_)
@@ -573,7 +604,7 @@ def show(app, elements, deps, except_, order, format_):
              help="Mount a file or directory into the sandbox")
@click.option('--isolate', is_flag=True, default=False,
              help='Create an isolated build sandbox')
@click.argument('element',
@click.argument('element', required=False,
                type=click.Path(readable=False))
@click.argument('command', type=click.STRING, nargs=-1)
@click.pass_obj
@@ -604,6 +635,11 @@ def shell(app, element, sysroot, mount, isolate, build_, command):
        scope = Scope.RUN

    with app.initialized():
        if not element:
            element = app.context.guess_element()
            if not element:
                raise AppError('Missing argument "ELEMENT".')

        dependencies = app.stream.load_selection((element,), selection=PipelineSelection.NONE)
        element = dependencies[0]
        prompt = app.shell_prompt(element)
@@ -641,15 +677,24 @@ def shell(app, element, sysroot, mount, isolate, build_, command):
              help="Create a tarball from the artifact contents instead "
                   "of a file tree. If LOCATION is '-', the tarball "
                   "will be dumped to the standard output.")
@click.argument('element',
@click.argument('element', required=False,
                type=click.Path(readable=False))
@click.argument('location', type=click.Path())
@click.argument('location', type=click.Path(), required=False)
@click.pass_obj
def checkout(app, element, location, force, deps, integrate, hardlinks, tar):
    """Checkout a built artifact to the specified location
    """
    from ..element import Scope

    if not element and not location:
        click.echo("ERROR: LOCATION is not specified", err=True)
        sys.exit(-1)

    if element and not location:
        # Nasty hack to get around click's optional args
        location = element
        element = None

    if hardlinks and tar:
        click.echo("ERROR: options --hardlinks and --tar conflict", err=True)
        sys.exit(-1)
@@ -662,6 +707,11 @@ def checkout(app, element, location, force, deps, integrate, hardlinks, tar):
        scope = Scope.NONE

    with app.initialized():
        if not element:
            element = app.context.guess_element()
            if not element:
                raise AppError('Missing argument "ELEMENT".')

        app.stream.checkout(element,
                            location=location,
                            force=force,
@@ -683,14 +733,28 @@ def checkout(app, element, location, force, deps, integrate, hardlinks, tar):
              help='The dependencies whose sources to checkout (default: none)')
@click.option('--fetch', 'fetch_', default=False, is_flag=True,
              help='Fetch elements if they are not fetched')
@click.argument('element',
@click.argument('element', required=False,
                type=click.Path(readable=False))
@click.argument('location', type=click.Path())
@click.argument('location', type=click.Path(), required=False)
@click.pass_obj
def source_checkout(app, element, location, deps, fetch_, except_):
    """Checkout sources of an element to the specified location
    """
    if not element and not location:
        click.echo("ERROR: LOCATION is not specified", err=True)
        sys.exit(-1)

    if element and not location:
        # Nasty hack to get around click's optional args
        location = element
        element = None

    with app.initialized():
        if not element:
            element = app.context.guess_element()
            if not element:
                raise AppError('Missing argument "ELEMENT".')

        app.stream.source_checkout(element,
                                   location=location,
                                   deps=deps,
@@ -747,11 +811,15 @@ def workspace_open(app, no_checkout, force, track_, directory, elements):
def workspace_close(app, remove_dir, all_, elements):
    """Close a workspace"""

    if not (all_ or elements):
        click.echo('ERROR: no elements specified', err=True)
        sys.exit(-1)

    with app.initialized():
        if not (all_ or elements):
            # NOTE: I may need to revisit this when implementing multiple projects
            # opening one workspace.
            element = app.context.guess_element()
            if element:
                elements = (element,)
            else:
                raise AppError('No elements specified')

        # Early exit if we specified `all` and there are no workspaces
        if all_ and not app.stream.workspace_exists():
@@ -808,6 +876,10 @@ def workspace_reset(app, soft, track_, all_, elements):
    with app.initialized():

        if not (all_ or elements):
            element = app.context.guess_element()
            if element:
                elements = (element,)
            else:
                raise AppError('No elements specified to reset')

        if all_ and not app.stream.workspace_exists():