You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@buildstream.apache.org by no...@apache.org on 2020/12/29 12:43:38 UTC

[buildstream] 02/04: Implement basic handling of multiple projects to a single workspace

This is an automated email from the ASF dual-hosted git repository.

not-in-ldap pushed a commit to branch jonathan/workspace-fragment-multi-project
in repository https://gitbox.apache.org/repos/asf/buildstream.git

commit 965da28f342944181b60a3dce72e76cfd153bffe
Author: Jonathan Maw <jo...@codethink.co.uk>
AuthorDate: Wed Dec 12 15:19:23 2018 +0000

    Implement basic handling of multiple projects to a single workspace
    
    This change is related to #222
---
 buildstream/_stream.py     |  14 ++++--
 buildstream/_workspaces.py | 115 +++++++++++++++++++++++++++++++++++----------
 2 files changed, 100 insertions(+), 29 deletions(-)

diff --git a/buildstream/_stream.py b/buildstream/_stream.py
index caaa489..e107a30 100644
--- a/buildstream/_stream.py
+++ b/buildstream/_stream.py
@@ -656,6 +656,9 @@ class Stream():
             self._message(MessageType.INFO, "Creating workspace for element {}"
                           .format(target.name))
 
+            # Ensure the WorkspaceProject is loaded before we delete it
+            workspaces.get_workspace_project(directory)
+
             workspace = workspaces.get_workspace(target._get_full_name())
             if workspace:
                 workspaces.delete_workspace(target._get_full_name())
@@ -670,7 +673,7 @@ class Stream():
                     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, directory, checkout=not no_checkout)
+            workspaces.create_workspace(target, directory, checkout=not no_checkout, append=force)
             self._message(MessageType.INFO, "Created a workspace for element: {}"
                           .format(target._get_full_name()))
 
@@ -744,6 +747,9 @@ class Stream():
                               .format(element.name, workspace_path))
                 continue
 
+            # Ensure the WorkspaceProject is in the cache before it gets deleted
+            workspaces.get_workspace_project(workspace_path)
+
             with element.timed_activity("Removing workspace directory {}"
                                         .format(workspace_path)):
                 try:
@@ -752,8 +758,10 @@ class Stream():
                     raise StreamError("Could not remove  '{}': {}"
                                       .format(workspace_path, e)) from e
 
-            workspaces.delete_workspace(element._get_full_name())
-            workspaces.create_workspace(element, workspace_path, checkout=True)
+            workspaces.delete_workspace(element._get_full_name(),
+                                        preserve_workspace_project=True)
+            workspaces.create_workspace(element, workspace_path, checkout=True,
+                                        append=False, preserve_workspace_project=True)
 
             self._message(MessageType.INFO,
                           "Reset workspace for {} at: {}".format(element.name,
diff --git a/buildstream/_workspaces.py b/buildstream/_workspaces.py
index 24a3cc8..0eac81e 100644
--- a/buildstream/_workspaces.py
+++ b/buildstream/_workspaces.py
@@ -143,8 +143,38 @@ class WorkspaceProject():
     #    element_name (str): The name of the element that the workspace belongs to.
     #
     def add_project(self, project_path, element_name):
-        assert (project_path and element_name)
-        self._projects.append({'project-path': project_path, 'element-name': element_name})
+        # TODO: Decide whether to raise an exception if the project already exists.
+        project = {'project-path': project_path, 'element-name': element_name}
+        if project not in self._projects:
+            self._projects.append(project)
+
+    # set_project()
+    #
+    # Sets the project to only contain the project_path and element_name specified.
+    #
+    # Args:
+    #    project_path (str): the path to the project that opened the workspace.
+    #    element_name (str): the name of the element that the workspace belongs to.
+    #
+    def set_project(self, project_path, element_name):
+        self._projects = [{'project-path': project_path, 'element-name': element_name}]
+
+    # remove_project()
+    #
+    # Removes the first project entry that matches the project_path and element_name
+    def remove_project(self, project_path, element_name):
+        # NOTE: This will need revisiting if projects' data format changes
+        # TODO: Figure out what to do if there is no project for those parameters
+        self._projects.remove({'project-path': project_path, 'element-name': element_name})
+
+    # has_projects()
+    #
+    # Returns whether there are any projects in this WorkspaceProject
+    #
+    # Returns:
+    #    (bool): True if there are any projects, or False if there aren't any
+    def has_projects(self):
+        return any(self._projects)
 
 
 # WorkspaceProjectCache()
@@ -186,17 +216,21 @@ class WorkspaceProjectCache():
     #    directory (str): The directory to search for a WorkspaceProject.
     #    project_path (str): The path to the project that refers to this workspace
     #    element_name (str): The element in the project that was refers to this workspace
+    #    append (bool): Whether the project_path and element_name should be appended
     #
     # Returns:
     #    (WorkspaceProject): The WorkspaceProject that was found for that directory.
     #
-    def add(self, directory, project_path, element_name):
+    def add(self, directory, project_path, element_name, *, append):
         workspace_project = self.get(directory)
         if not workspace_project:
             workspace_project = WorkspaceProject(directory)
             self._projects[directory] = workspace_project
 
-        workspace_project.add_project(project_path, element_name)
+        if append:
+            workspace_project.add_project(project_path, element_name)
+        else:
+            workspace_project.set_project(project_path, element_name)
         return workspace_project
 
     # remove()
@@ -204,23 +238,28 @@ class WorkspaceProjectCache():
     # Removes the project path and element name from the WorkspaceProject that exists
     # for that directory.
     #
-    # NOTE: This currently just deletes the file, but with support for multiple
-    # projects opening the same workspace, this will involve decreasing the count
-    # and deleting the file if there are no more projects.
-    #
     # Args:
     #    directory (str): The directory to search for a WorkspaceProject.
+    #    project_path (str): the path to the project that should be removed.
+    #    element_name (str): the name of the element in the project that should be removed.
     #
-    def remove(self, directory):
+    def remove(self, directory, project_path, element_name):
         workspace_project = self.get(directory)
         if not workspace_project:
             raise LoadError(LoadErrorReason.MISSING_FILE,
                             "Failed to find a {} file to remove".format(WORKSPACE_PROJECT_FILE))
-        path = workspace_project.get_filename()
-        try:
-            os.unlink(path)
-        except FileNotFoundError:
-            pass
+
+        workspace_project.remove_project(project_path, element_name)
+
+        if workspace_project.has_projects():
+            workspace_project.write()
+        else:
+            # Remove the WorkspaceProject file if it's now empty
+            path = workspace_project.get_filename()
+            try:
+                os.unlink(path)
+            except FileNotFoundError:
+                pass
 
 
 # Workspace()
@@ -429,8 +468,10 @@ class Workspaces():
     #    target (Element) - The element to create a workspace for
     #    path (str) - The path in which the workspace should be kept
     #    checkout (bool): Whether to check-out the element's sources into the directory
+    #    append (bool): Whether the WorkspaceProject file should append this project
+    #    preserve_workspace_project (bool): Whether the WorkspaceProject should be altered
     #
-    def create_workspace(self, target, path, *, checkout):
+    def create_workspace(self, target, path, *, checkout, append, preserve_workspace_project=False):
         element_name = target._get_full_name()
         project_dir = self._toplevel_project.directory
         if path.startswith(project_dir):
@@ -444,7 +485,11 @@ class Workspaces():
             with target.timed_activity("Staging sources to {}".format(path)):
                 target._open_workspace()
 
-        workspace_project = self._workspace_project_cache.add(path, project_dir, element_name)
+        if preserve_workspace_project:
+            workspace_project = self._workspace_project_cache.get(path)
+        else:
+            workspace_project = self._workspace_project_cache.add(path, project_dir, element_name, append=append)
+
         project_file_path = workspace_project.get_filename()
 
         if os.path.exists(project_file_path):
@@ -469,6 +514,21 @@ class Workspaces():
             return None
         return self._workspaces[element_name]
 
+    # get_workspace_project()
+    #
+    # Returns a WorkspaceProject for a given directory, retrieving from the cache if
+    # present.
+    #
+    # Args:
+    #    directory (str): The directory to search for a WorkspaceProject.
+    #
+    # Returns:
+    #    (WorkspaceProject): The WorkspaceProject that was found for that directory.
+    #    or      (NoneType): None, if no WorkspaceProject can be found.
+    #
+    def get_workspace_project(self, directory):
+        return self._workspace_project_cache.get(directory)
+
     # update_workspace()
     #
     # Update the datamodel with a new Workspace instance
@@ -498,20 +558,23 @@ class Workspaces():
     #
     # Args:
     #    element_name (str) - The element name whose workspace to delete
+    #    preserve_workspace_project (bool): Whether the WorkspaceProject should be altered
     #
-    def delete_workspace(self, element_name):
+    def delete_workspace(self, element_name, preserve_workspace_project=False):
         workspace = self.get_workspace(element_name)
         del self._workspaces[element_name]
 
-        # Remove from the cache if it exists
-        try:
-            self._workspace_project_cache.remove(workspace.get_absolute_path())
-        except LoadError as e:
-            # We might be closing a workspace with a deleted directory
-            if e.reason == LoadErrorReason.MISSING_FILE:
-                pass
-            else:
-                raise
+        if not preserve_workspace_project:
+            # Remove from the cache if it exists
+            project_dir = self._toplevel_project.directory
+            try:
+                self._workspace_project_cache.remove(workspace.get_absolute_path(), project_dir, element_name)
+            except LoadError as e:
+                # We might be closing a workspace with a deleted directory
+                if e.reason == LoadErrorReason.MISSING_FILE:
+                    pass
+                else:
+                    raise
 
     # save_config()
     #