diff --git a/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/events/BuildManager.java b/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/events/BuildManager.java index 70a3e01bc17..e42bcdf8403 100644 --- a/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/events/BuildManager.java +++ b/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/events/BuildManager.java @@ -411,9 +411,10 @@ private void basicBuild(final IBuildConfiguration buildConfiguration, final int try { final IProject project = buildConfiguration.getProject(); final ICommand[] commands; - if (project.isAccessible()) - commands = ((Project) project).internalGetDescription().getBuildSpec(false); - else + if (project.isAccessible()) { + ProjectDescription description = ((Project) project).internalGetDescription(); + commands = description == null ? null : description.getBuildSpec(false); + } else commands = null; int work = commands == null ? 0 : commands.length; monitor.beginTask(NLS.bind(Messages.events_building_1, project.getFullPath()), work); @@ -708,6 +709,9 @@ public ArrayList createBuildersPersistentInfo(IProject pr ArrayList oldInfos = getBuildersPersistentInfo(project); ProjectDescription desc = ((Project) project).internalGetDescription(); + if (desc == null) { + return null; + } ICommand[] commands = desc.getBuildSpec(false); if (commands.length == 0) return null; @@ -1577,7 +1581,8 @@ public ISchedulingRule getRule(IBuildConfiguration buildConfiguration, int trigg final ICommand[] commands; if (project.isAccessible()) { Set rules = new HashSet<>(); - commands = ((Project) project).internalGetDescription().getBuildSpec(false); + ProjectDescription description = ((Project) project).internalGetDescription(); + commands = description == null ? new ICommand[0] : description.getBuildSpec(false); boolean hasNullBuildRule = false; BuildContext context = new BuildContext(buildConfiguration); for (int i = 0; i < commands.length; i++) { 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..5bd8ad39fb8 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; @@ -330,6 +334,8 @@ public ProjectDescription readOldDescription(IProject project) throws CoreExcept * 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,6 +353,19 @@ 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); @@ -357,6 +376,17 @@ public void readPrivateDescription(IProject target, ProjectDescription descripti if (!file.exists()) return; } + try { + readFromFile(target, description, file); + } catch (IOException e) { + //ignore - this is an old location file or an exception occurred + // closing the stream + } + } + + @SuppressWarnings("deprecation") + public void readFromFile(IProject target, ProjectDescription description, java.io.File file) throws IOException { + description.setName(target.getName()); try (DataInputStream dataIn = new DataInputStream(new SafeChunkyInputStream(file, 500))) { try { String location = dataIn.readUTF(); @@ -408,9 +438,34 @@ public void readPrivateDescription(IProject target, ProjectDescription descripti m.put(configName, refs); } description.setBuildConfigReferences(m); - } catch (IOException e) { - //ignore - this is an old location file or an exception occurred - // closing the stream + // read parts since 3.23 + int natures = dataIn.readInt(); + String[] natureIds = new String[natures]; + for (int i = 0; i < natures; i++) { + natureIds[i] = dataIn.readUTF(); + } + description.setNatureIds(natureIds); + int buildspecs = dataIn.readInt(); + ICommand[] buildSpecData = new ICommand[buildspecs]; + for (int i = 0; i < buildspecs; 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); } } @@ -426,13 +481,25 @@ public void writePrivateDescription(IProject target) throws CoreException { Workspace.clear(file); //don't write anything if there is no interesting private metadata ProjectDescription desc = ((Project) target).internalGetDescription(); + try { + writeToFile(desc, file); + } catch (IOException e) { + String message = NLS.bind(Messages.resources_exSaveProjectLocation, target.getName()); + throw new ResourceException(IResourceStatus.INTERNAL_ERROR, null, message, e); + } + } + + public void writeToFile(ProjectDescription desc, java.io.File file) throws IOException { if (desc == null) return; final URI projectLocation = desc.getLocationURI(); final IProject[] prjRefs = desc.getDynamicReferences(false); final String[] buildConfigs = desc.configNames; final Map configRefs = desc.getBuildConfigReferences(false); - if (projectLocation == null && prjRefs.length == 0 && buildConfigs.length == 0 && configRefs.isEmpty()) + final String[] natureIds = desc.getNatureIds(); + final ICommand[] buildSpec = desc.getBuildSpec(false); + if (projectLocation == null && prjRefs.length == 0 && buildConfigs.length == 0 && configRefs.isEmpty() + && natureIds.length == 0 && buildSpec.length == 0) return; //write the private metadata file try (SafeChunkyOutputStream output = new SafeChunkyOutputStream(file); DataOutputStream dataOut = new DataOutputStream(output);) { @@ -470,10 +537,33 @@ public void writePrivateDescription(IProject target) throws CoreException { } } } + // write parts since 3.23 + dataOut.writeInt(natureIds.length); + for (String id : natureIds) { + dataOut.writeUTF(id); + } + dataOut.writeInt(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()); - throw new ResourceException(IResourceStatus.INTERNAL_ERROR, null, message, e); } } } 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..c90a1b43f7c 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 @@ -787,7 +787,11 @@ protected void internalCopyProjectOnly(IResource destination, IProjectDescriptio * @see #getActiveBuildConfig() */ IBuildConfiguration internalGetActiveBuildConfig() { - String configName = internalGetDescription().activeConfiguration; + ProjectDescription description = internalGetDescription(); + if (description == null) { + return new BuildConfiguration(this, IBuildConfiguration.DEFAULT_CONFIG_NAME); + } + String configName = description.activeConfiguration; try { if (configName != null) return getBuildConfig(configName); @@ -827,6 +831,9 @@ public ProjectDescription internalGetDescription() { */ public IBuildConfiguration[] internalGetReferencedBuildConfigs(String configName, boolean includeMissing) { ProjectDescription description = internalGetDescription(); + if (description == null) { + return new IBuildConfiguration[0]; + } IBuildConfiguration[] refs = description.getAllBuildConfigReferences(this, configName, false); Collection configs = new LinkedHashSet<>(refs.length); for (IBuildConfiguration ref : refs) { 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..a094c923566 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; @@ -546,6 +547,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 +987,5 @@ private static IProject[] computeDynamicReferencesForProject(IBuildConfiguration } return result.toArray(new IProject[0]); } + } 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 1a3f9222f42..8bbf7d64d36 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/tests/org.eclipse.core.tests.resources/META-INF/MANIFEST.MF b/resources/tests/org.eclipse.core.tests.resources/META-INF/MANIFEST.MF index f968edebda1..daa4f6ff8ea 100644 --- a/resources/tests/org.eclipse.core.tests.resources/META-INF/MANIFEST.MF +++ b/resources/tests/org.eclipse.core.tests.resources/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: Eclipse Core Tests Resources Bundle-SymbolicName: org.eclipse.core.tests.resources; singleton:=true -Bundle-Version: 3.11.900.qualifier +Bundle-Version: 3.11.1000.qualifier Bundle-Vendor: Eclipse.org Export-Package: org.eclipse.core.tests.filesystem, org.eclipse.core.tests.internal.alias, diff --git a/resources/tests/org.eclipse.core.tests.resources/pom.xml b/resources/tests/org.eclipse.core.tests.resources/pom.xml index 63b01f65ca7..ab94812a335 100644 --- a/resources/tests/org.eclipse.core.tests.resources/pom.xml +++ b/resources/tests/org.eclipse.core.tests.resources/pom.xml @@ -18,7 +18,7 @@ 4.37.0-SNAPSHOT org.eclipse.core.tests.resources - 3.11.900-SNAPSHOT + 3.11.1000-SNAPSHOT eclipse-test-plugin diff --git a/resources/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/internal/resources/ModelObjectReaderWriterTest.java b/resources/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/internal/resources/ModelObjectReaderWriterTest.java index b25ea17dd34..6eddb2f0af5 100644 --- a/resources/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/internal/resources/ModelObjectReaderWriterTest.java +++ b/resources/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/internal/resources/ModelObjectReaderWriterTest.java @@ -22,8 +22,11 @@ import static org.eclipse.core.tests.resources.ResourceTestUtil.createInputStream; import static org.eclipse.core.tests.resources.ResourceTestUtil.createTestMonitor; import static org.eclipse.core.tests.resources.ResourceTestUtil.removeFromFileSystem; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; import java.io.ByteArrayOutputStream; +import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; @@ -36,11 +39,14 @@ import org.eclipse.core.filesystem.EFS; import org.eclipse.core.filesystem.IFileStore; import org.eclipse.core.filesystem.URIUtil; +import org.eclipse.core.internal.events.BuildCommand; import org.eclipse.core.internal.resources.LinkDescription; +import org.eclipse.core.internal.resources.LocalMetaArea; import org.eclipse.core.internal.resources.ModelObjectWriter; import org.eclipse.core.internal.resources.Project; import org.eclipse.core.internal.resources.ProjectDescription; import org.eclipse.core.internal.resources.ProjectDescriptionReader; +import org.eclipse.core.internal.resources.Workspace; import org.eclipse.core.resources.ICommand; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; @@ -54,6 +60,7 @@ import org.eclipse.core.tests.resources.WorkspaceTestRule; import org.junit.Rule; import org.junit.Test; +import org.junit.rules.TemporaryFolder; import org.xml.sax.InputSource; public class ModelObjectReaderWriterTest { @@ -61,6 +68,9 @@ public class ModelObjectReaderWriterTest { @Rule public WorkspaceTestRule workspaceRule = new WorkspaceTestRule(); + @Rule + public TemporaryFolder folder = new TemporaryFolder(); + static final IPath LONG_LOCATION = IPath.fromOSString("/eclipse/dev/i0218/eclipse/pffds/fds//fds///fdsfsdfsd///fdsfdsf/fsdfsdfsd/lugi/dsds/fsd//f/ffdsfdsf/fsdfdsfsd/fds//fdsfdsfdsf/fdsfdsfds/fdsfdsfdsf/fdsfdsfdsds/ns/org.eclipse.help.ui_2.1.0/contexts.xml").setDevice(OS.isWindows() ? "D:" : null); static final URI LONG_LOCATION_URI = LONG_LOCATION.toFile().toURI(); private static final String PATH_STRING = IPath.fromOSString("/abc/def").setDevice(OS.isWindows() ? "D:" : null).toString(); @@ -321,6 +331,45 @@ public void testConsistentWrite() throws Throwable { assertThat(result).isEqualTo(expected); } + @Test + public void testLocalMetaAreaReadWriteNatures() throws CoreException, IOException { + IWorkspace workspace = ResourcesPlugin.getWorkspace(); + LocalMetaArea area = new LocalMetaArea((Workspace) workspace); + ProjectDescription description = new ProjectDescription(); + description.setName("Testme"); + String[] value = new String[] { "org.eclipse.jdt.core.javanature", "org.eclipse.pde.PluginNature" }; + description.setNatureIds(value); + File file = folder.newFile(); + area.writeToFile(description, file); + IProject proj = workspace.getRoot().getProject("tmpproject"); + ProjectDescription readme = new ProjectDescription(); + area.readFromFile(proj, readme, file); + assertArrayEquals(value, readme.getNatureIds()); + } + + @Test + public void testLocalMetaAreaReadWriteBuildSpec() throws CoreException, IOException { + IWorkspace workspace = ResourcesPlugin.getWorkspace(); + LocalMetaArea area = new LocalMetaArea((Workspace) workspace); + ProjectDescription description = new ProjectDescription(); + description.setName("Testme"); + BuildCommand buildCommand = new BuildCommand(); + buildCommand.setBuilderName("testBuilder"); + Map args = Map.of("1", "2"); + buildCommand.setArguments(args); + description.setBuildSpec(new ICommand[] { buildCommand }); + File file = folder.newFile(); + area.writeToFile(description, file); + IProject proj = workspace.getRoot().getProject("tmpproject"); + ProjectDescription readme = new ProjectDescription(); + area.readFromFile(proj, readme, file); + ICommand[] buildSpec = readme.getBuildSpec(); + assertEquals(1, buildSpec.length); + ICommand read = buildSpec[0]; + assertEquals("testBuilder", read.getBuilderName()); + assertEquals(args, read.getArguments()); + } + @Test public void testInvalidProjectDescription1() throws Throwable { String invalidProjectDescription = "\n" + "\n" + " abc\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " org.eclipse.jdt.core.javabuilder\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " org.eclipse.jdt.core.javanature\n" + " \n" + " \n" + " \n" + " newLink\n" + " 2\n" + " " + PATH_STRING + "\n" + " \n" + " \n" + "";