Community
Participate
Working Groups
1. Create simple project SP1 2. Add x.jar in SP1 3. Create Java project JP2 3. Add SP1/x.jar on JP2's classpath 4. Close SP1 5. Do ctrl+B (errors appears) 6. Open SP1 x.jar is present in package view but there is errors in task view and ctrl+B does not remove these errors.
Instead of deleting SP1's build state when its closed... its being saved instead. So when SP1 is reopened, no build is 'needed'... no deltas are generated/propagated to its dependent projects. We should either stop saving a project's build state when its closed or delete the state when its reopened. PM: comments?
This wouldn't make a difference if the closed project is a non-java project (like in this scenario). There must be a delta for the prereq project which isn't a Java one which is ignored.
Indeed, when SP1 is closed, the builder can no longer find SP1/x.jar, which is thus removed from the binary resources. Therefore, SP1 is no longer claimed to be an interesting prereq project. So when reopening it later, its delta won't even be considered.
Proposed fix is: NameEnvironment#computeLocations ----- public static ClasspathLocation[] computeLocations( IWorkspaceRoot workspaceRoot, IJavaProject javaProject, String outputFolderLocation, ArrayList sourceFolders, SimpleLookupTable binaryResources) throws JavaModelException { IClasspathEntry[] classpathEntries = ((JavaProject) javaProject).getExpandedClasspath(true, true); int cpCount = 0; int max = classpathEntries.length; ClasspathLocation[] classpathLocations = new ClasspathLocation[max]; boolean firstSourceFolder = true; nextEntry : for (int i = 0; i < max; i++) { IClasspathEntry entry = classpathEntries[i]; IPath path = entry.getPath(); Object target = JavaModel.getTarget(workspaceRoot, path, true); if (target == null){ if (entry.getEntryKind() == IClasspathEntry.CPE_LIBRARY) { if (path.segmentCount() > 1) { IResource rsc = workspaceRoot.findMember (path.segment(0)); if (rsc instanceof IProject && rsc.exists()) { binaryResources.put((IProject) rsc, new IResource[0]); } } } continue nextEntry; } if (target instanceof IResource) { IResource resource = (IResource) target; switch(entry.getEntryKind()) { case IClasspathEntry.CPE_SOURCE : if (outputFolderLocation == null || ! (resource instanceof IContainer)) continue nextEntry; if (sourceFolders != null) { // normal builder mode sourceFolders.add(resource); classpathLocations[cpCount++] = ClasspathLocation.forSourceFolder(resource.getLocation().toString(), outputFolderLocation); } else if (firstSourceFolder) { // add the output folder only once firstSourceFolder = false; classpathLocations[cpCount++] = ClasspathLocation.forBinaryFolder(outputFolderLocation); } continue nextEntry; case IClasspathEntry.CPE_PROJECT : if (!(resource instanceof IProject)) continue nextEntry; IProject prereqProject = (IProject) resource; if (!prereqProject.isAccessible()) continue nextEntry; IPath outputLocation = JavaCore.create (prereqProject).getOutputLocation(); IResource prereqOutputFolder; if (prereqProject.getFullPath().equals (outputLocation)) { prereqOutputFolder = prereqProject; } else { prereqOutputFolder = workspaceRoot.findMember(outputLocation); if (prereqOutputFolder == null || !prereqOutputFolder.exists() || !(prereqOutputFolder instanceof IFolder)) continue nextEntry; } if (prereqOutputFolder.getLocation() == null) // sanity check continue nextEntry; if (binaryResources != null) { // normal builder mode IResource[] existingResources = (IResource[]) binaryResources.get(prereqProject); if (existingResources == null) binaryResources.put (prereqProject, new IResource[] {prereqOutputFolder}); else existingResources[0] = prereqOutputFolder; // project's output folder is always first } classpathLocations[cpCount++] = ClasspathLocation.forBinaryFolder(prereqOutputFolder.getLocation().toString()); continue nextEntry; case IClasspathEntry.CPE_LIBRARY : if (resource.getLocation() == null) // sanity check continue nextEntry; if (resource instanceof IFile) { String extension = path.getFileExtension(); if (! (JavaBuilder.JAR_EXTENSION.equalsIgnoreCase(extension) || JavaBuilder.ZIP_EXTENSION.equalsIgnoreCase(extension))) continue nextEntry; classpathLocations[cpCount++] = ClasspathLocation.forLibrary(resource.getLocation().toString()); } else if (resource instanceof IFolder) { classpathLocations[cpCount++] = ClasspathLocation.forBinaryFolder(resource.getLocation().toString()); } if (binaryResources != null) { // normal builder mode IProject p = resource.getProject (); // can be the project being built IResource[] existingResources = (IResource[]) binaryResources.get(p); if (existingResources == null) { existingResources = new IResource[] {null, resource}; // project's output folder is always first, null if not included } else { int size = existingResources.length; System.arraycopy (existingResources, 0, existingResources = new IResource[size + 1], 0, size); existingResources[size] = resource; } binaryResources.put(p, existingResources); } continue nextEntry; } } else if (target instanceof File) { String extension = path.getFileExtension(); if (!(JavaBuilder.JAR_EXTENSION.equalsIgnoreCase (extension) || JavaBuilder.ZIP_EXTENSION.equalsIgnoreCase(extension))) continue nextEntry; classpathLocations[cpCount++] = ClasspathLocation.forLibrary(path.toString()); } } if (cpCount < max) System.arraycopy(classpathLocations, 0, (classpathLocations = new ClasspathLocation[cpCount]), 0, cpCount); return classpathLocations; }
Actually, my fix shouldn't be necessary, since it should rather be detected as a classpath change.
I take my previous comment back, the proposed fix is necessary for the Java builder do get invoked at all. From thereon, it can react to the change as it wants (in this case it will have discarded the original build state when aborting the build due to classpath error).
Better fix below. Found some suspicious code near management of binaryResources for output folder (did not grow existing list if any). NameEnvironment#computeLocations [public static ClasspathLocation[] computeLocations( IWorkspaceRoot workspaceRoot, IJavaProject javaProject, String outputFolderLocation, ArrayList sourceFolders, SimpleLookupTable binaryResources) throws JavaModelException { IClasspathEntry[] classpathEntries = ((JavaProject) javaProject).getExpandedClasspath(true, true); int cpCount = 0; int max = classpathEntries.length; ClasspathLocation[] classpathLocations = new ClasspathLocation[max]; boolean firstSourceFolder = true; nextEntry : for (int i = 0; i < max; i++) { IClasspathEntry entry = classpathEntries[i]; IPath path = entry.getPath(); Object target = JavaModel.getTarget(workspaceRoot, path, true); if (target == null){ // still remember a dependency onto the container project (20158) if (entry.getEntryKind() == IClasspathEntry.CPE_LIBRARY) { if (path.segmentCount() > 1) { IResource rsc = workspaceRoot.findMember (path.segment(0)); if (rsc instanceof IProject) { binaryResources.put((IProject) rsc, new IResource[0]); } } } continue nextEntry; } if (target instanceof IResource) { IResource resource = (IResource) target; switch(entry.getEntryKind()) { case IClasspathEntry.CPE_SOURCE : if (outputFolderLocation == null || ! (resource instanceof IContainer)) continue nextEntry; if (sourceFolders != null) { // normal builder mode sourceFolders.add(resource); classpathLocations[cpCount++] = ClasspathLocation.forSourceFolder(resource.getLocation().toString(), outputFolderLocation); } else if (firstSourceFolder) { // add the output folder only once firstSourceFolder = false; classpathLocations[cpCount++] = ClasspathLocation.forBinaryFolder(outputFolderLocation); } continue nextEntry; case IClasspathEntry.CPE_PROJECT : if (!(resource instanceof IProject)) continue nextEntry; IProject prereqProject = (IProject) resource; if (!prereqProject.isAccessible()) continue nextEntry; IPath outputLocation = JavaCore.create (prereqProject).getOutputLocation(); IResource prereqOutputFolder; if (prereqProject.getFullPath().equals (outputLocation)) { prereqOutputFolder = prereqProject; } else { prereqOutputFolder = workspaceRoot.findMember(outputLocation); if (prereqOutputFolder == null || !prereqOutputFolder.exists() || !(prereqOutputFolder instanceof IFolder)) continue nextEntry; } if (prereqOutputFolder.getLocation() == null) // sanity check continue nextEntry; if (binaryResources != null) { // normal builder mode IResource[] existingResources = (IResource[]) binaryResources.get(prereqProject); if (existingResources == null) existingResources = new IResource[] {prereqOutputFolder}; else { int size = existingResources.length; System.arraycopy (existingResources, 0, existingResources = new IResource[size + 1], 1, size); existingResources[0] = prereqOutputFolder; // project's output folder is always first } binaryResources.put (prereqProject, existingResources); } classpathLocations[cpCount++] = ClasspathLocation.forBinaryFolder(prereqOutputFolder.getLocation().toString()); continue nextEntry; case IClasspathEntry.CPE_LIBRARY : if (resource.getLocation() == null) // sanity check continue nextEntry; if (resource instanceof IFile) { String extension = path.getFileExtension(); if (! (JavaBuilder.JAR_EXTENSION.equalsIgnoreCase(extension) || JavaBuilder.ZIP_EXTENSION.equalsIgnoreCase(extension))) continue nextEntry; classpathLocations[cpCount++] = ClasspathLocation.forLibrary(resource.getLocation().toString()); } else if (resource instanceof IFolder) { classpathLocations[cpCount++] = ClasspathLocation.forBinaryFolder(resource.getLocation().toString()); } if (binaryResources != null) { // normal builder mode IProject p = resource.getProject (); // can be the project being built IResource[] existingResources = (IResource[]) binaryResources.get(p); if (existingResources == null) { existingResources = new IResource[] {null, resource}; // project's output folder is always first, null if not included } else { int size = existingResources.length; System.arraycopy (existingResources, 0, existingResources = new IResource[size + 1], 0, size); existingResources[size] = resource; } binaryResources.put(p, existingResources); } continue nextEntry; } } else if (target instanceof File) { String extension = path.getFileExtension(); if (!(JavaBuilder.JAR_EXTENSION.equalsIgnoreCase (extension) || JavaBuilder.ZIP_EXTENSION.equalsIgnoreCase(extension))) continue nextEntry; classpathLocations[cpCount++] = ClasspathLocation.forLibrary(path.toString()); } } if (cpCount < max) System.arraycopy(classpathLocations, 0, (classpathLocations = new ClasspathLocation[cpCount]), 0, cpCount); return classpathLocations; } ]
Fixed
Replaced fix with one on JavaBuilder.getRequiredProjects(). It now searches for Library entries as well as Project entries.
Last fix is indeed the most safe. It doesn't interact with the binary resources collection, which did work but wasn't as clean as new fix which only changes the method for computing prereq project names (which used to use the binary resources collection) by simply walking the classpath. Did both (KJ and PM) write the new fix separately and it was the same implementation. Removing 'FIXED' status, until it gets approved for F4
If we don't fix it, then the Java builder wouldn't notice additions/removals of internal JARs contained in distinct projects onto which it has no other explicit references (as a project prereq on its classpath). Fix is fairly simple in the end.
Verified. It works great.
No veto. Marking as fixed.
Verified.