Commit 26be4dfd authored by William Salmon's avatar William Salmon
Browse files

Workspace CLI update

This is to update the workspace CLI to as agreed on the mailing list
https://mail.gnome.org/archives/buildstream-list/2018-September/msg00046.html

This patch also introduces the default workspace directory.
parent 6b6d735c
Loading
Loading
Loading
Loading
+5 −2
Original line number Diff line number Diff line
@@ -60,6 +60,9 @@ class Context():
        # The directory where build sandboxes will be created
        self.builddir = None

        # Default root location for workspaces
        self.workspacedir = None

        # The local binary artifact cache directory
        self.artifactdir = None

@@ -161,10 +164,10 @@ class Context():
        _yaml.node_validate(defaults, [
            'sourcedir', 'builddir', 'artifactdir', 'logdir',
            'scheduler', 'artifacts', 'logging', 'projects',
            'cache'
            'cache', 'workspacedir',
        ])

        for directory in ['sourcedir', 'builddir', 'artifactdir', 'logdir']:
        for directory in ['sourcedir', 'builddir', 'artifactdir', 'logdir', 'workspacedir']:
            # Allow the ~ tilde expansion and any environment variables in
            # path specification in the config files.
            #
+23 −14
Original line number Diff line number Diff line
@@ -6,6 +6,7 @@ from .. import _yaml
from .._exceptions import BstError, LoadError, AppError
from .._versions import BST_FORMAT_VERSION
from .complete import main_bashcomplete, complete_path, CompleteUnhandled
from ..utils import DirectoryDescription


##################################################################
@@ -678,28 +679,36 @@ def workspace():
@click.option('--no-checkout', default=False, is_flag=True,
              help="Do not checkout the source, only link to the given directory")
@click.option('--force', '-f', default=False, is_flag=True,
              help="Overwrite files existing in checkout directory")
              help="The workspace will be created even if the directory in which it will be created is not empty " +
              "or if a workspace for that element already exists")
@click.option('--track', 'track_', default=False, is_flag=True,
              help="Track and fetch new source references before checking out the workspace")
@click.argument('element',
                type=click.Path(readable=False))
@click.argument('directory', type=click.Path(file_okay=False))
@click.option('--directory', type=click.Path(file_okay=False), default=None,
              help="Only for use when a single Element is given: Set the directory to use to create the workspace")
@click.argument('elements', nargs=-1, type=click.Path(readable=False))
@click.pass_obj
def workspace_open(app, no_checkout, force, track_, element, directory):
def workspace_open(app, no_checkout, force, track_, directory, elements):
    """Open a workspace for manual source modification"""

    directories = []
    if directory is not None:
        if len(elements) > 1:
            click.echo("Directory option can only be used if a single element is given", err=True)
            sys.exit(-1)
        if os.path.exists(directory):

            if not os.path.isdir(directory):
            click.echo("Checkout directory is not a directory: {}".format(directory), err=True)
                click.echo("Directory path is not a directory: {}".format(directory), err=True)
                sys.exit(-1)

            if not (no_checkout or force) and os.listdir(directory):
            click.echo("Checkout directory is not empty: {}".format(directory), err=True)
                click.echo("Directory path is not empty: {}".format(directory), err=True)
                sys.exit(-1)
        directories.append(DirectoryDescription(directory, use_default=False))
    else:
        for element in elements:
            directories.append(DirectoryDescription(element.rstrip('.bst')))

    with app.initialized():
        app.stream.workspace_open(element, directory,
        app.stream.workspace_open(elements, directories,
                                  no_checkout=no_checkout,
                                  track_first=track_,
                                  force=force)
+65 −40
Original line number Diff line number Diff line
@@ -448,37 +448,49 @@ class Stream():
    # Open a project workspace
    #
    # Args:
    #    target (str): The target element to open the workspace for
    #    directory (str): The directory to stage the source in
    #    target (list): List of target elements to open workspaces for
    #    directory (list): List of DirectoryDescription objects to stage the source in
    #    no_checkout (bool): Whether to skip checking out the source
    #    track_first (bool): Whether to track and fetch first
    #    force (bool): Whether to ignore contents in an existing directory
    #
    def workspace_open(self, target, directory, *,
    def workspace_open(self, targets, directories, *,
                       no_checkout,
                       track_first,
                       force):
        # This function is a little funny but it is trying to be as atomic as possible.

        if track_first:
            track_targets = (target,)
            track_targets = targets
        else:
            track_targets = ()

        elements, track_elements = self._load((target,), track_targets,
        elements, track_elements = self._load(targets, track_targets,
                                              selection=PipelineSelection.REDIRECT,
                                              track_selection=PipelineSelection.REDIRECT)
        target = elements[0]
        directory = os.path.abspath(directory)

        workspaces = self._context.get_workspaces()

        # If we're going to checkout, we need at least a fetch,
        # if we were asked to track first, we're going to fetch anyway.
        #
        if not no_checkout or track_first:
            track_elements = []
            if track_first:
                track_elements = elements
            self._fetch(elements, track_elements=track_elements)

        expanded_directories = []
        #  To try to be more atomic, loop through the elements and raise any errors we can early
        for target, directory_obj in zip(elements, directories):

            if not list(target.sources()):
                build_depends = [x.name for x in target.dependencies(Scope.BUILD, recurse=False)]
                if not build_depends:
                raise StreamError("The given element has no sources")
                    raise StreamError("The element {}  has no sources".format(target.name))
                detail = "Try opening a workspace on one of its dependencies instead:\n"
                detail += "  \n".join(build_depends)
            raise StreamError("The given element has no sources", detail=detail)

        workspaces = self._context.get_workspaces()
                raise StreamError("The element {} has no sources".format(target.name), detail=detail)

            # Check for workspace config
            workspace = workspaces.get_workspace(target._get_full_name())
@@ -486,21 +498,27 @@ class Stream():
                raise StreamError("Workspace '{}' is already defined at: {}"
                                  .format(target.name, workspace.get_absolute_path()))

        # If we're going to checkout, we need at least a fetch,
        # if we were asked to track first, we're going to fetch anyway.
        #
        if not no_checkout or track_first:
            track_elements = []
            if track_first:
                track_elements = elements
            self._fetch(elements, track_elements=track_elements)

            if not no_checkout and target._get_consistency() != Consistency.CACHED:
            raise StreamError("Could not stage uncached source. " +
                raise StreamError("Could not stage uncached source. For {} ".format(target.name) +
                                  "Use `--track` to track and " +
                                  "fetch the latest version of the " +
                                  "source.")

            if directory_obj.use_default:
                directory = os.path.abspath(os.path.join(self._context.workspacedir, directory_obj.directory))
            else:
                directory = directory_obj.directory

            expanded_directories.append(directory)

        # So far this function has tried to catch as many issues as possible with out making any changes
        # Now it dose the bits that can not be made atomic.
        targetGenerator = zip(elements, expanded_directories)
        for target, directory in targetGenerator:
            self._message(MessageType.INFO, "Creating workspace for element {}"
                          .format(target.name))

            workspace = workspaces.get_workspace(target._get_full_name())
            if workspace:
                workspaces.delete_workspace(target._get_full_name())
                workspaces.save_config()
@@ -508,7 +526,11 @@ class Stream():
            try:
                os.makedirs(directory, exist_ok=True)
            except OSError as e:
            raise StreamError("Failed to create workspace directory: {}".format(e)) from e
                todo_elements = " ".join([str(target.name) for target, directory_dict in targetGenerator])
                if todo_elements:
                    # This output should make creating the remaining workspaces as easy as possible.
                    todo_elements = "\nDid not try to create workspaces for " + todo_elements
                raise StreamError("Failed to create workspace directory: {}".format(e) + todo_elements) from e

            workspaces.create_workspace(target._get_full_name(), directory)

@@ -516,8 +538,11 @@ class Stream():
                with target.timed_activity("Staging sources to {}".format(directory)):
                    target._open_workspace()

            # Saving the workspace once it is set up means that if the next workspace fails to be created before
            # the configuration gets saved. The successfully created workspace still gets saved.
            workspaces.save_config()
        self._message(MessageType.INFO, "Saved workspace configuration")
            self._message(MessageType.INFO, "Added element {} to the workspace configuration"
                          .format(target._get_full_name()))

    # workspace_close
    #
+3 −0
Original line number Diff line number Diff line
@@ -22,6 +22,9 @@ artifactdir: ${XDG_CACHE_HOME}/buildstream/artifacts
# Location to store build logs
logdir: ${XDG_CACHE_HOME}/buildstream/logs

# Default root location for workspaces, blank for no default set.
workspacedir: .

#
#    Cache
#
+14 −0
Original line number Diff line number Diff line
@@ -105,6 +105,20 @@ class FileListResult():
        return ret


class DirectoryDescription():
    """
    This object is used to keep information about directories in a nice tidy object.
    """
    def __init__(self, directory, *, use_default=True):
        """
        Args:
            directory (str): The path to the directory this object describes
            use_default (bool): Weather to process the directory so it is in the default folder.
        """
        self.directory = directory
        self.use_default = use_default


def list_relative_paths(directory, *, list_dirs=True):
    """A generator for walking directory relative paths

Loading