Skip to content

Commit 345db75

Browse files
committed
Add support for creating workspace private project descriptions
Currently importing a project into eclipse requires the creation of a physical file name .project in the root of the project. this has several drawbacks for tools that automatically discover projects and import them for the user as it creates new files and possibly dirty their working tree. Other tools use a single folder for this purpose or don't require any permanent files in the working-tree itself. Even Eclipse has already such concept that is used when a project is located outside the workspace. This now adds a new feature called "workspace private project" that only holds the basic information in the location file in the workspace directory, any additional information might be needed to restore by a tool that uses workspace private projects.
1 parent a791b17 commit 345db75

File tree

9 files changed

+157
-31
lines changed

9 files changed

+157
-31
lines changed

resources/bundles/org.eclipse.core.resources/META-INF/MANIFEST.MF

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ Manifest-Version: 1.0
22
Bundle-ManifestVersion: 2
33
Bundle-Name: %pluginName
44
Bundle-SymbolicName: org.eclipse.core.resources; singleton:=true
5-
Bundle-Version: 3.22.200.qualifier
5+
Bundle-Version: 3.23.0.qualifier
66
Bundle-Activator: org.eclipse.core.resources.ResourcesPlugin
77
Bundle-Vendor: %providerName
88
Bundle-Localization: plugin

resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/FileSystemResourceManager.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -683,7 +683,7 @@ public boolean internalWrite(IProject target, IProjectDescription description, i
683683
if (hasPrivateChanges)
684684
getWorkspace().getMetaArea().writePrivateDescription(target);
685685
//can't do anything if there's no description
686-
if (!hasPublicChanges || (description == null))
686+
if (!hasPublicChanges || (description == null) || description.isWorkspacePrivate())
687687
return false;
688688

689689
//write the model to a byte array
@@ -938,9 +938,15 @@ public ProjectDescription read(IProject target, boolean creation) throws CoreExc
938938
if (creation) {
939939
privateDescription = new ProjectDescription();
940940
getWorkspace().getMetaArea().readPrivateDescription(target, privateDescription);
941+
if (privateDescription.isWorkspacePrivate()) {
942+
return privateDescription;
943+
}
941944
projectLocation = privateDescription.getLocationURI();
942945
} else {
943946
IProjectDescription description = ((Project) target).internalGetDescription();
947+
if (description instanceof ProjectDescription impl && impl.isWorkspacePrivate()) {
948+
return impl;
949+
}
944950
if (description != null && description.getLocationURI() != null) {
945951
projectLocation = description.getLocationURI();
946952
}

resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/LocalMetaArea.java

Lines changed: 85 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,17 @@
2424
import java.io.IOException;
2525
import java.net.URI;
2626
import java.util.HashMap;
27+
import java.util.LinkedHashMap;
2728
import java.util.Map;
29+
import java.util.Map.Entry;
2830
import org.eclipse.core.filesystem.URIUtil;
31+
import org.eclipse.core.internal.events.BuildCommand;
2932
import org.eclipse.core.internal.localstore.SafeChunkyInputStream;
3033
import org.eclipse.core.internal.localstore.SafeChunkyOutputStream;
3134
import org.eclipse.core.internal.utils.Messages;
3235
import org.eclipse.core.internal.utils.Policy;
3336
import org.eclipse.core.resources.IBuildConfiguration;
37+
import org.eclipse.core.resources.ICommand;
3438
import org.eclipse.core.resources.IProject;
3539
import org.eclipse.core.resources.IResource;
3640
import org.eclipse.core.resources.IResourceStatus;
@@ -322,14 +326,15 @@ public ProjectDescription readOldDescription(IProject project) throws CoreExcept
322326
}
323327

324328
/**
325-
* Returns the portions of the project description that are private, and
326-
* adds them to the supplied project description. In particular, the
327-
* project location, the project's dynamic references and build configurations
328-
* are stored here.
329-
* The project location will be set to <code>null</code> if the default
330-
* location should be used. In the case of failure, log the exception and
331-
* return silently, thus reverting to using the default location and no
329+
* Returns the portions of the project description that are private, and adds
330+
* them to the supplied project description. In particular, the project
331+
* location, the project's dynamic references and build configurations are
332+
* stored here. The project location will be set to <code>null</code> if the
333+
* default location should be used. In the case of failure, log the exception
334+
* and return silently, thus reverting to using the default location and no
332335
* dynamic references. The current format of the location file is:
336+
*
337+
* <pre>
333338
* UTF - project location
334339
* int - number of dynamic project references
335340
* UTF - project reference 1
@@ -347,9 +352,24 @@ public ProjectDescription readOldDescription(IProject project) throws CoreExcept
347352
* UTF - configName if hasConfigName
348353
* ... repeat for number of referenced configurations
349354
* ... repeat for number of build configurations with references
355+
* since 3.23:
356+
* bool - private flag if project should only be read from its private project configuration
357+
* int - number of natures
358+
* UTF - nature id
359+
* ... repeated for N natures
360+
* int - number of buildspecs
361+
* byte - type of buildspec
362+
* (type 1) UTF - name of builder
363+
* int - number of arguments
364+
* UTF arg key
365+
* UTF arg value
366+
* UTF - triggers string
367+
* </pre>
350368
*/
351369
public void readPrivateDescription(IProject target, ProjectDescription description) {
352370
IPath locationFile = locationFor(target).append(F_PROJECT_LOCATION);
371+
String name = target.getName();
372+
description.setName(name);
353373
java.io.File file = locationFile.toFile();
354374
if (!file.exists()) {
355375
locationFile = getBackupLocationFor(locationFile);
@@ -370,7 +390,7 @@ public void readPrivateDescription(IProject target, ProjectDescription descripti
370390
}
371391
} catch (Exception e) {
372392
//don't allow failure to read the location to propagate
373-
String msg = NLS.bind(Messages.resources_exReadProjectLocation, target.getName());
393+
String msg = NLS.bind(Messages.resources_exReadProjectLocation, name);
374394
Policy.log(new ResourceStatus(IStatus.ERROR, IResourceStatus.FAILED_READ_METADATA, target.getFullPath(), msg, e));
375395
}
376396
//try to read the dynamic references - will fail for old location files
@@ -408,6 +428,34 @@ public void readPrivateDescription(IProject target, ProjectDescription descripti
408428
m.put(configName, refs);
409429
}
410430
description.setBuildConfigReferences(m);
431+
// read parts since 3.23
432+
description.setWorkspacePrivate(dataIn.readBoolean());
433+
String[] natureIds = new String[dataIn.readInt()];
434+
for (int i = 0; i < natureIds.length; i++) {
435+
natureIds[i] = dataIn.readUTF();
436+
}
437+
description.setNatureIds(natureIds);
438+
int buildspecs = dataIn.readInt();
439+
ICommand[] buildSpecData = new ICommand[buildspecs];
440+
for (int i = 0; i < buildSpecData.length; i++) {
441+
BuildCommand command = new BuildCommand();
442+
buildSpecData[i] = command;
443+
int type = dataIn.read();
444+
if (type == 1) {
445+
command.setName(dataIn.readUTF());
446+
int args = dataIn.readInt();
447+
Map<String, String> map = new LinkedHashMap<>();
448+
for (int j = 0; j < args; j++) {
449+
map.put(dataIn.readUTF(), dataIn.readUTF());
450+
}
451+
command.setArguments(map);
452+
String trigger = dataIn.readUTF();
453+
if (!trigger.isEmpty()) {
454+
ProjectDescriptionReader.parseBuildTriggers(command, trigger);
455+
}
456+
}
457+
description.setBuildSpec(buildSpecData);
458+
}
411459
} catch (IOException e) {
412460
//ignore - this is an old location file or an exception occurred
413461
// closing the stream
@@ -470,6 +518,35 @@ public void writePrivateDescription(IProject target) throws CoreException {
470518
}
471519
}
472520
}
521+
// write parts since 3.23
522+
dataOut.writeBoolean(desc.isWorkspacePrivate());
523+
String[] natureIds = desc.getNatureIds();
524+
dataOut.writeInt(natureIds.length);
525+
for (String id : natureIds) {
526+
dataOut.writeUTF(id);
527+
}
528+
ICommand[] buildSpec = desc.getBuildSpec(false);
529+
dataOut.write(buildSpec.length);
530+
for (ICommand command : buildSpec) {
531+
if (command instanceof BuildCommand b) {
532+
dataOut.write(1);
533+
dataOut.writeUTF(b.getName());
534+
Map<String, String> arguments = b.getArguments();
535+
dataOut.writeInt(arguments.size());
536+
for (Entry<String, String> entry : arguments.entrySet()) {
537+
dataOut.writeUTF(entry.getKey());
538+
dataOut.writeUTF(entry.getValue());
539+
}
540+
if (ModelObjectWriter.shouldWriteTriggers(b)) {
541+
dataOut.writeUTF(ModelObjectWriter.triggerString(b));
542+
} else {
543+
dataOut.writeUTF(""); //$NON-NLS-1$
544+
}
545+
} else {
546+
dataOut.write(0);
547+
}
548+
}
549+
dataOut.flush();
473550
output.succeed();
474551
} catch (IOException e) {
475552
String message = NLS.bind(Messages.resources_exSaveProjectLocation, target.getName());

resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ModelObjectWriter.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ public class ModelObjectWriter implements IModelObjectConstants {
4444
* Returns the string representing the serialized set of build triggers for
4545
* the given command
4646
*/
47-
private static String triggerString(BuildCommand command) {
47+
static String triggerString(BuildCommand command) {
4848
StringBuilder buf = new StringBuilder();
4949
if (command.isBuilding(IncrementalProjectBuilder.AUTO_BUILD))
5050
buf.append(TRIGGER_AUTO).append(',');
@@ -83,7 +83,7 @@ protected void write(BuildCommand command, XMLWriter writer) {
8383
/**
8484
* Returns whether the build triggers for this command should be written.
8585
*/
86-
private boolean shouldWriteTriggers(BuildCommand command) {
86+
static boolean shouldWriteTriggers(BuildCommand command) {
8787
//only write triggers if command is configurable and there exists a trigger
8888
//that the builder does NOT respond to. I.e., don't write out on the default
8989
//cases to avoid dirtying .project files unnecessarily.

resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Project.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -326,7 +326,7 @@ public void create(IProjectDescription description, int updateFlags, IProgressMo
326326
updateDescription();
327327
// make sure the .location file is written
328328
workspace.getMetaArea().writePrivateDescription(this);
329-
} else {
329+
} else if (!desc.isWorkspacePrivate()) {
330330
// write out the project
331331
writeDescription(IResource.FORCE);
332332
}

resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ProjectDescription.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import java.util.List;
3030
import java.util.Map;
3131
import java.util.Map.Entry;
32+
import java.util.Objects;
3233
import java.util.Set;
3334
import org.eclipse.core.filesystem.URIUtil;
3435
import org.eclipse.core.internal.events.BuildCommand;
@@ -124,6 +125,7 @@ public class ProjectDescription extends ModelObject implements IProjectDescripti
124125
protected URI location = null;
125126
protected volatile String[] natures = EMPTY_STRING_ARRAY;
126127
protected URI snapshotLocation = null;
128+
private boolean privateFlag;
127129

128130
public ProjectDescription() {
129131
super();
@@ -546,6 +548,14 @@ public boolean hasPrivateChanges(ProjectDescription description) {
546548
// Configuration level references
547549
if (configRefsHaveChanges(dynamicConfigRefs, description.dynamicConfigRefs))
548550
return true;
551+
// has natures changed?
552+
if (!Set.of(natures).equals(Set.of(description.natures))) {
553+
return true;
554+
}
555+
// has buildspec changed?
556+
if (!Objects.deepEquals(buildSpec, description.buildSpec)) {
557+
return true;
558+
}
549559

550560
return false;
551561
}
@@ -978,4 +988,14 @@ private static IProject[] computeDynamicReferencesForProject(IBuildConfiguration
978988
}
979989
return result.toArray(new IProject[0]);
980990
}
991+
992+
@Override
993+
public boolean isWorkspacePrivate() {
994+
return privateFlag;
995+
}
996+
997+
@Override
998+
public void setWorkspacePrivate(boolean privateFlag) {
999+
this.privateFlag = privateFlag;
1000+
}
9811001
}

resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ProjectDescriptionReader.java

Lines changed: 23 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -233,26 +233,31 @@ private void endBuildTriggersElement(String elementName) {
233233
state = S_BUILD_COMMAND;
234234
BuildCommand command = (BuildCommand) objectStack.peek();
235235
//presence of this element indicates the builder is configurable
236+
String string = charBuffer.toString();
236237
command.setConfigurable(true);
237238
//clear all existing values
238-
command.setBuilding(IncrementalProjectBuilder.AUTO_BUILD, false);
239-
command.setBuilding(IncrementalProjectBuilder.CLEAN_BUILD, false);
240-
command.setBuilding(IncrementalProjectBuilder.FULL_BUILD, false);
241-
command.setBuilding(IncrementalProjectBuilder.INCREMENTAL_BUILD, false);
242-
243-
//set new values according to value in the triggers element
244-
StringTokenizer tokens = new StringTokenizer(charBuffer.toString(), ","); //$NON-NLS-1$
245-
while (tokens.hasMoreTokens()) {
246-
String next = tokens.nextToken();
247-
if (next.equalsIgnoreCase(TRIGGER_AUTO)) {
248-
command.setBuilding(IncrementalProjectBuilder.AUTO_BUILD, true);
249-
} else if (next.equalsIgnoreCase(TRIGGER_CLEAN)) {
250-
command.setBuilding(IncrementalProjectBuilder.CLEAN_BUILD, true);
251-
} else if (next.equalsIgnoreCase(TRIGGER_FULL)) {
252-
command.setBuilding(IncrementalProjectBuilder.FULL_BUILD, true);
253-
} else if (next.equalsIgnoreCase(TRIGGER_INCREMENTAL)) {
254-
command.setBuilding(IncrementalProjectBuilder.INCREMENTAL_BUILD, true);
255-
}
239+
parseBuildTriggers(command, string);
240+
}
241+
}
242+
243+
static void parseBuildTriggers(BuildCommand command, String string) {
244+
command.setBuilding(IncrementalProjectBuilder.AUTO_BUILD, false);
245+
command.setBuilding(IncrementalProjectBuilder.CLEAN_BUILD, false);
246+
command.setBuilding(IncrementalProjectBuilder.FULL_BUILD, false);
247+
command.setBuilding(IncrementalProjectBuilder.INCREMENTAL_BUILD, false);
248+
249+
// set new values according to value in the triggers element
250+
StringTokenizer tokens = new StringTokenizer(string, ","); //$NON-NLS-1$
251+
while (tokens.hasMoreTokens()) {
252+
String next = tokens.nextToken();
253+
if (next.equalsIgnoreCase(TRIGGER_AUTO)) {
254+
command.setBuilding(IncrementalProjectBuilder.AUTO_BUILD, true);
255+
} else if (next.equalsIgnoreCase(TRIGGER_CLEAN)) {
256+
command.setBuilding(IncrementalProjectBuilder.CLEAN_BUILD, true);
257+
} else if (next.equalsIgnoreCase(TRIGGER_FULL)) {
258+
command.setBuilding(IncrementalProjectBuilder.FULL_BUILD, true);
259+
} else if (next.equalsIgnoreCase(TRIGGER_INCREMENTAL)) {
260+
command.setBuilding(IncrementalProjectBuilder.INCREMENTAL_BUILD, true);
256261
}
257262
}
258263
}

resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SaveManager.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1371,6 +1371,9 @@ protected void saveMetaInfo(MultiStatus problems, IProgressMonitor monitor) thro
13711371
* @return Status object containing non-critical warnings, or an OK status.
13721372
*/
13731373
protected IStatus saveMetaInfo(Project project, IProgressMonitor monitor) throws CoreException {
1374+
if (project.internalGetDescription().isWorkspacePrivate()) {
1375+
return Status.OK_STATUS;
1376+
}
13741377
long start = System.currentTimeMillis();
13751378
//if there is nothing on disk, write the description
13761379
if (!workspace.getFileSystemManager().hasSavedDescription(project)) {

resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/resources/IProjectDescription.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,13 @@ public interface IProjectDescription {
175175
*/
176176
ICommand newCommand();
177177

178+
/**
179+
* @return <code>true</code> if this project is only persisted in the private
180+
* workspace area
181+
* @since 3.23
182+
*/
183+
boolean isWorkspacePrivate();
184+
178185
/**
179186
* Sets the active configuration for the described project.
180187
* <p>
@@ -385,4 +392,12 @@ public interface IProjectDescription {
385392
* @see #getReferencedProjects()
386393
*/
387394
void setReferencedProjects(IProject[] projects);
395+
396+
/**
397+
* Sets the project to be only persisted into the private workspace area and not
398+
* into a physical <code>.project</code> file in the root of the project folder.
399+
*
400+
* @since 3.23
401+
*/
402+
void setWorkspacePrivate(boolean privateFlag);
388403
}

0 commit comments

Comments
 (0)