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);
}