diff --git a/resources/bundles/org.eclipse.core.resources/META-INF/MANIFEST.MF b/resources/bundles/org.eclipse.core.resources/META-INF/MANIFEST.MF index aa042e2f420..919bb4d52db 100644 --- a/resources/bundles/org.eclipse.core.resources/META-INF/MANIFEST.MF +++ b/resources/bundles/org.eclipse.core.resources/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: %pluginName Bundle-SymbolicName: org.eclipse.core.resources; singleton:=true -Bundle-Version: 3.22.200.qualifier +Bundle-Version: 3.23.0.qualifier Bundle-Activator: org.eclipse.core.resources.ResourcesPlugin Bundle-Vendor: %providerName Bundle-Localization: plugin diff --git a/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/FileSystemResourceManager.java b/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/FileSystemResourceManager.java index 2b200644e34..9ba5f0b5590 100644 --- a/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/FileSystemResourceManager.java +++ b/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/FileSystemResourceManager.java @@ -683,7 +683,7 @@ public boolean internalWrite(IProject target, IProjectDescription description, i if (hasPrivateChanges) getWorkspace().getMetaArea().writePrivateDescription(target); //can't do anything if there's no description - if (!hasPublicChanges || (description == null)) + if (!hasPublicChanges || (description == null) || description.isWorkspacePrivate()) return false; //write the model to a byte array @@ -938,9 +938,15 @@ public ProjectDescription read(IProject target, boolean creation) throws CoreExc if (creation) { privateDescription = new ProjectDescription(); getWorkspace().getMetaArea().readPrivateDescription(target, privateDescription); + if (privateDescription.isWorkspacePrivate()) { + return privateDescription; + } projectLocation = privateDescription.getLocationURI(); } else { IProjectDescription description = ((Project) target).internalGetDescription(); + if (description instanceof ProjectDescription impl && impl.isWorkspacePrivate()) { + return impl; + } if (description != null && description.getLocationURI() != null) { projectLocation = description.getLocationURI(); } diff --git a/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/LocalMetaArea.java b/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/LocalMetaArea.java index 7ea32c18c11..bb3d9841d44 100644 --- a/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/LocalMetaArea.java +++ b/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/LocalMetaArea.java @@ -24,13 +24,17 @@ import java.io.IOException; import java.net.URI; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.Map; +import java.util.Map.Entry; import org.eclipse.core.filesystem.URIUtil; +import org.eclipse.core.internal.events.BuildCommand; import org.eclipse.core.internal.localstore.SafeChunkyInputStream; import org.eclipse.core.internal.localstore.SafeChunkyOutputStream; import org.eclipse.core.internal.utils.Messages; import org.eclipse.core.internal.utils.Policy; import org.eclipse.core.resources.IBuildConfiguration; +import org.eclipse.core.resources.ICommand; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.IResourceStatus; @@ -322,14 +326,15 @@ public ProjectDescription readOldDescription(IProject project) throws CoreExcept } /** - * Returns the portions of the project description that are private, and - * adds them to the supplied project description. In particular, the - * project location, the project's dynamic references and build configurations - * are stored here. - * The project location will be set to null if the default - * location should be used. In the case of failure, log the exception and - * return silently, thus reverting to using the default location and no + * Returns the portions of the project description that are private, and adds + * them to the supplied project description. In particular, the project + * location, the project's dynamic references and build configurations are + * stored here. The project location will be set to null if the + * default location should be used. In the case of failure, log the exception + * and return silently, thus reverting to using the default location and no * dynamic references. The current format of the location file is: + * + *
 	 *    UTF - project location
 	 *    int - number of dynamic project references
 	 *    UTF - project reference 1
@@ -347,9 +352,24 @@ public ProjectDescription readOldDescription(IProject project) throws CoreExcept
 	 *        UTF - configName if hasConfigName
 	 *        ... repeat for number of referenced configurations
 	 *      ... repeat for number of build configurations with references
+	 * since 3.23:
+	 *    bool - private flag if project should only be read from its private project configuration
+	 *    int - number of natures
+	 *      UTF - nature id
+	 *      ... repeated for N natures
+	 *    int - number of buildspecs
+	 *      byte - type of buildspec
+	 *        (type 1) UTF - name of builder
+	 *                 int - number of arguments
+	 *                  UTF arg key
+	 *                  UTF arg value
+	 *                 UTF - triggers string
+	 * 
*/ public void readPrivateDescription(IProject target, ProjectDescription description) { IPath locationFile = locationFor(target).append(F_PROJECT_LOCATION); + String name = target.getName(); + description.setName(name); java.io.File file = locationFile.toFile(); if (!file.exists()) { locationFile = getBackupLocationFor(locationFile); @@ -370,7 +390,7 @@ public void readPrivateDescription(IProject target, ProjectDescription descripti } } catch (Exception e) { //don't allow failure to read the location to propagate - String msg = NLS.bind(Messages.resources_exReadProjectLocation, target.getName()); + String msg = NLS.bind(Messages.resources_exReadProjectLocation, name); Policy.log(new ResourceStatus(IStatus.ERROR, IResourceStatus.FAILED_READ_METADATA, target.getFullPath(), msg, e)); } //try to read the dynamic references - will fail for old location files @@ -408,6 +428,34 @@ public void readPrivateDescription(IProject target, ProjectDescription descripti m.put(configName, refs); } description.setBuildConfigReferences(m); + // read parts since 3.23 + description.setWorkspacePrivate(dataIn.readBoolean()); + String[] natureIds = new String[dataIn.readInt()]; + for (int i = 0; i < natureIds.length; i++) { + natureIds[i] = dataIn.readUTF(); + } + description.setNatureIds(natureIds); + int buildspecs = dataIn.readInt(); + ICommand[] buildSpecData = new ICommand[buildspecs]; + for (int i = 0; i < buildSpecData.length; i++) { + BuildCommand command = new BuildCommand(); + buildSpecData[i] = command; + int type = dataIn.read(); + if (type == 1) { + command.setName(dataIn.readUTF()); + int args = dataIn.readInt(); + Map map = new LinkedHashMap<>(); + for (int j = 0; j < args; j++) { + map.put(dataIn.readUTF(), dataIn.readUTF()); + } + command.setArguments(map); + String trigger = dataIn.readUTF(); + if (!trigger.isEmpty()) { + ProjectDescriptionReader.parseBuildTriggers(command, trigger); + } + } + description.setBuildSpec(buildSpecData); + } } catch (IOException e) { //ignore - this is an old location file or an exception occurred // closing the stream @@ -470,6 +518,35 @@ public void writePrivateDescription(IProject target) throws CoreException { } } } + // write parts since 3.23 + dataOut.writeBoolean(desc.isWorkspacePrivate()); + String[] natureIds = desc.getNatureIds(); + dataOut.writeInt(natureIds.length); + for (String id : natureIds) { + dataOut.writeUTF(id); + } + ICommand[] buildSpec = desc.getBuildSpec(false); + dataOut.write(buildSpec.length); + for (ICommand command : buildSpec) { + if (command instanceof BuildCommand b) { + dataOut.write(1); + dataOut.writeUTF(b.getName()); + Map arguments = b.getArguments(); + dataOut.writeInt(arguments.size()); + for (Entry entry : arguments.entrySet()) { + dataOut.writeUTF(entry.getKey()); + dataOut.writeUTF(entry.getValue()); + } + if (ModelObjectWriter.shouldWriteTriggers(b)) { + dataOut.writeUTF(ModelObjectWriter.triggerString(b)); + } else { + dataOut.writeUTF(""); //$NON-NLS-1$ + } + } else { + dataOut.write(0); + } + } + dataOut.flush(); output.succeed(); } catch (IOException e) { String message = NLS.bind(Messages.resources_exSaveProjectLocation, target.getName()); diff --git a/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ModelObjectWriter.java b/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ModelObjectWriter.java index 697e64a2f4a..43e113667d5 100644 --- a/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ModelObjectWriter.java +++ b/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ModelObjectWriter.java @@ -44,7 +44,7 @@ public class ModelObjectWriter implements IModelObjectConstants { * Returns the string representing the serialized set of build triggers for * the given command */ - private static String triggerString(BuildCommand command) { + static String triggerString(BuildCommand command) { StringBuilder buf = new StringBuilder(); if (command.isBuilding(IncrementalProjectBuilder.AUTO_BUILD)) buf.append(TRIGGER_AUTO).append(','); @@ -83,7 +83,7 @@ protected void write(BuildCommand command, XMLWriter writer) { /** * Returns whether the build triggers for this command should be written. */ - private boolean shouldWriteTriggers(BuildCommand command) { + static boolean shouldWriteTriggers(BuildCommand command) { //only write triggers if command is configurable and there exists a trigger //that the builder does NOT respond to. I.e., don't write out on the default //cases to avoid dirtying .project files unnecessarily. diff --git a/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Project.java b/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Project.java index 99600609b46..3764f3d68f1 100644 --- a/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Project.java +++ b/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Project.java @@ -326,7 +326,7 @@ public void create(IProjectDescription description, int updateFlags, IProgressMo updateDescription(); // make sure the .location file is written workspace.getMetaArea().writePrivateDescription(this); - } else { + } else if (!desc.isWorkspacePrivate()) { // write out the project writeDescription(IResource.FORCE); } diff --git a/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ProjectDescription.java b/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ProjectDescription.java index 3502ac2f9a7..4f299b1249b 100644 --- a/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ProjectDescription.java +++ b/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ProjectDescription.java @@ -29,6 +29,7 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.Objects; import java.util.Set; import org.eclipse.core.filesystem.URIUtil; import org.eclipse.core.internal.events.BuildCommand; @@ -124,6 +125,7 @@ public class ProjectDescription extends ModelObject implements IProjectDescripti protected URI location = null; protected volatile String[] natures = EMPTY_STRING_ARRAY; protected URI snapshotLocation = null; + private boolean privateFlag; public ProjectDescription() { super(); @@ -546,6 +548,14 @@ public boolean hasPrivateChanges(ProjectDescription description) { // Configuration level references if (configRefsHaveChanges(dynamicConfigRefs, description.dynamicConfigRefs)) return true; + // has natures changed? + if (!Set.of(natures).equals(Set.of(description.natures))) { + return true; + } + // has buildspec changed? + if (!Objects.deepEquals(buildSpec, description.buildSpec)) { + return true; + } return false; } @@ -978,4 +988,14 @@ private static IProject[] computeDynamicReferencesForProject(IBuildConfiguration } return result.toArray(new IProject[0]); } + + @Override + public boolean isWorkspacePrivate() { + return privateFlag; + } + + @Override + public void setWorkspacePrivate(boolean privateFlag) { + this.privateFlag = privateFlag; + } } diff --git a/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ProjectDescriptionReader.java b/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ProjectDescriptionReader.java index 5b968235e76..e3a05685cc9 100644 --- a/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ProjectDescriptionReader.java +++ b/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ProjectDescriptionReader.java @@ -233,26 +233,31 @@ private void endBuildTriggersElement(String elementName) { state = S_BUILD_COMMAND; BuildCommand command = (BuildCommand) objectStack.peek(); //presence of this element indicates the builder is configurable + String string = charBuffer.toString(); command.setConfigurable(true); //clear all existing values - command.setBuilding(IncrementalProjectBuilder.AUTO_BUILD, false); - command.setBuilding(IncrementalProjectBuilder.CLEAN_BUILD, false); - command.setBuilding(IncrementalProjectBuilder.FULL_BUILD, false); - command.setBuilding(IncrementalProjectBuilder.INCREMENTAL_BUILD, false); - - //set new values according to value in the triggers element - StringTokenizer tokens = new StringTokenizer(charBuffer.toString(), ","); //$NON-NLS-1$ - while (tokens.hasMoreTokens()) { - String next = tokens.nextToken(); - if (next.equalsIgnoreCase(TRIGGER_AUTO)) { - command.setBuilding(IncrementalProjectBuilder.AUTO_BUILD, true); - } else if (next.equalsIgnoreCase(TRIGGER_CLEAN)) { - command.setBuilding(IncrementalProjectBuilder.CLEAN_BUILD, true); - } else if (next.equalsIgnoreCase(TRIGGER_FULL)) { - command.setBuilding(IncrementalProjectBuilder.FULL_BUILD, true); - } else if (next.equalsIgnoreCase(TRIGGER_INCREMENTAL)) { - command.setBuilding(IncrementalProjectBuilder.INCREMENTAL_BUILD, true); - } + parseBuildTriggers(command, string); + } + } + + static void parseBuildTriggers(BuildCommand command, String string) { + command.setBuilding(IncrementalProjectBuilder.AUTO_BUILD, false); + command.setBuilding(IncrementalProjectBuilder.CLEAN_BUILD, false); + command.setBuilding(IncrementalProjectBuilder.FULL_BUILD, false); + command.setBuilding(IncrementalProjectBuilder.INCREMENTAL_BUILD, false); + + // set new values according to value in the triggers element + StringTokenizer tokens = new StringTokenizer(string, ","); //$NON-NLS-1$ + while (tokens.hasMoreTokens()) { + String next = tokens.nextToken(); + if (next.equalsIgnoreCase(TRIGGER_AUTO)) { + command.setBuilding(IncrementalProjectBuilder.AUTO_BUILD, true); + } else if (next.equalsIgnoreCase(TRIGGER_CLEAN)) { + command.setBuilding(IncrementalProjectBuilder.CLEAN_BUILD, true); + } else if (next.equalsIgnoreCase(TRIGGER_FULL)) { + command.setBuilding(IncrementalProjectBuilder.FULL_BUILD, true); + } else if (next.equalsIgnoreCase(TRIGGER_INCREMENTAL)) { + command.setBuilding(IncrementalProjectBuilder.INCREMENTAL_BUILD, true); } } } diff --git a/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SaveManager.java b/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SaveManager.java index 25493cf919c..bfe3ca99f60 100644 --- a/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SaveManager.java +++ b/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SaveManager.java @@ -1371,6 +1371,9 @@ protected void saveMetaInfo(MultiStatus problems, IProgressMonitor monitor) thro * @return Status object containing non-critical warnings, or an OK status. */ protected IStatus saveMetaInfo(Project project, IProgressMonitor monitor) throws CoreException { + if (project.internalGetDescription().isWorkspacePrivate()) { + return Status.OK_STATUS; + } long start = System.currentTimeMillis(); //if there is nothing on disk, write the description if (!workspace.getFileSystemManager().hasSavedDescription(project)) { diff --git a/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/resources/IProjectDescription.java b/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/resources/IProjectDescription.java index 7554ff0fa40..d66e645887c 100644 --- a/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/resources/IProjectDescription.java +++ b/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/resources/IProjectDescription.java @@ -175,6 +175,13 @@ public interface IProjectDescription { */ ICommand newCommand(); + /** + * @return true if this project is only persisted in the private + * workspace area + * @since 3.23 + */ + boolean isWorkspacePrivate(); + /** * Sets the active configuration for the described project. *

@@ -385,4 +392,12 @@ public interface IProjectDescription { * @see #getReferencedProjects() */ void setReferencedProjects(IProject[] projects); + + /** + * Sets the project to be only persisted into the private workspace area and not + * into a physical .project file in the root of the project folder. + * + * @since 3.23 + */ + void setWorkspacePrivate(boolean privateFlag); }