1 /* 2 * Copyright (C) 2007 The Android Open Source Project 3 * 4 * Licensed under the Eclipse Public License, Version 1.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.eclipse.org/org/documents/epl-v10.php 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.ide.eclipse.adt.internal.build.builders; 18 19 import com.android.SdkConstants; 20 import com.android.ide.eclipse.adt.AdtConstants; 21 import com.android.ide.eclipse.adt.AdtPlugin; 22 import com.android.ide.eclipse.adt.internal.build.Messages; 23 import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs; 24 import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs.BuildVerbosity; 25 import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper; 26 import com.android.ide.eclipse.adt.internal.project.ProjectHelper; 27 import com.android.utils.Pair; 28 29 import org.eclipse.core.resources.IFolder; 30 import org.eclipse.core.resources.IMarker; 31 import org.eclipse.core.resources.IProject; 32 import org.eclipse.core.resources.IResource; 33 import org.eclipse.core.resources.IWorkspaceRoot; 34 import org.eclipse.core.resources.IncrementalProjectBuilder; 35 import org.eclipse.core.resources.ResourcesPlugin; 36 import org.eclipse.core.runtime.CoreException; 37 import org.eclipse.core.runtime.IPath; 38 import org.eclipse.core.runtime.IProgressMonitor; 39 import org.eclipse.core.runtime.IStatus; 40 import org.eclipse.core.runtime.Status; 41 import org.eclipse.core.runtime.SubProgressMonitor; 42 import org.eclipse.core.runtime.jobs.Job; 43 import org.eclipse.jdt.core.IClasspathEntry; 44 import org.eclipse.jdt.core.IJavaProject; 45 import org.eclipse.jdt.core.JavaCore; 46 47 import java.util.List; 48 import java.util.Map; 49 50 /** 51 * Resource manager builder whose only purpose is to refresh the resource folder 52 * so that the other builder use an up to date version. 53 */ 54 public class ResourceManagerBuilder extends BaseBuilder { 55 56 public static final String ID = "com.android.ide.eclipse.adt.ResourceManagerBuilder"; //$NON-NLS-1$ 57 58 public ResourceManagerBuilder() { 59 super(); 60 } 61 62 @Override 63 protected void clean(IProgressMonitor monitor) throws CoreException { 64 super.clean(monitor); 65 66 // Get the project. 67 IProject project = getProject(); 68 69 // Clear the project of the generic markers 70 removeMarkersFromContainer(project, AdtConstants.MARKER_ADT); 71 } 72 73 // build() returns a list of project from which this project depends for future compilation. 74 @SuppressWarnings("unchecked") 75 @Override 76 protected IProject[] build(int kind, Map args, IProgressMonitor monitor) 77 throws CoreException { 78 // Get the project. 79 final IProject project = getProject(); 80 IJavaProject javaProject = JavaCore.create(project); 81 82 // Clear the project of the generic markers 83 removeMarkersFromContainer(project, AdtConstants.MARKER_ADT); 84 85 // check for existing target marker, in which case we abort. 86 // (this means: no SDK, no target, or unresolvable target.) 87 try { 88 abortOnBadSetup(javaProject, null); 89 } catch (AbortBuildException e) { 90 return null; 91 } 92 93 // Check the compiler compliance level, displaying the error message 94 // since this is the first builder. 95 Pair<Integer, String> result = ProjectHelper.checkCompilerCompliance(project); 96 String errorMessage = null; 97 switch (result.getFirst().intValue()) { 98 case ProjectHelper.COMPILER_COMPLIANCE_LEVEL: 99 errorMessage = Messages.Requires_Compiler_Compliance_s; 100 break; 101 case ProjectHelper.COMPILER_COMPLIANCE_SOURCE: 102 errorMessage = Messages.Requires_Source_Compatibility_s; 103 break; 104 case ProjectHelper.COMPILER_COMPLIANCE_CODEGEN_TARGET: 105 errorMessage = Messages.Requires_Class_Compatibility_s; 106 break; 107 } 108 109 if (errorMessage != null) { 110 errorMessage = String.format(errorMessage, 111 result.getSecond() == null ? "(no value)" : result.getSecond()); 112 113 markProject(AdtConstants.MARKER_ADT, errorMessage, IMarker.SEVERITY_ERROR); 114 AdtPlugin.printErrorToConsole(project, errorMessage); 115 116 return null; 117 } 118 119 // Check that the SDK directory has been setup. 120 String osSdkFolder = AdtPlugin.getOsSdkFolder(); 121 122 if (osSdkFolder == null || osSdkFolder.length() == 0) { 123 AdtPlugin.printErrorToConsole(project, Messages.No_SDK_Setup_Error); 124 markProject(AdtConstants.MARKER_ADT, Messages.No_SDK_Setup_Error, 125 IMarker.SEVERITY_ERROR); 126 127 return null; 128 } 129 130 // check the 'gen' source folder is present 131 boolean hasGenSrcFolder = false; // whether the project has a 'gen' source folder setup 132 133 IClasspathEntry[] classpaths = javaProject.readRawClasspath(); 134 if (classpaths != null) { 135 for (IClasspathEntry e : classpaths) { 136 if (e.getEntryKind() == IClasspathEntry.CPE_SOURCE) { 137 IPath path = e.getPath(); 138 if (path.segmentCount() == 2 && 139 path.segment(1).equals(SdkConstants.FD_GEN_SOURCES)) { 140 hasGenSrcFolder = true; 141 break; 142 } 143 } 144 } 145 } 146 147 boolean genFolderPresent = false; // whether the gen folder actually exists 148 IResource resource = project.findMember(SdkConstants.FD_GEN_SOURCES); 149 genFolderPresent = resource != null && resource.exists(); 150 151 if (hasGenSrcFolder == false && genFolderPresent) { 152 // No source folder setup for 'gen' in the project, but there's already a 153 // 'gen' resource (file or folder). 154 String message; 155 if (resource.getType() == IResource.FOLDER) { 156 // folder exists already! This is an error. If the folder had been created 157 // by the NewProjectWizard, it'd be a source folder. 158 message = String.format("%1$s already exists but is not a source folder. Convert to a source folder or rename it.", 159 resource.getFullPath().toString()); 160 } else { 161 // resource exists but is not a folder. 162 message = String.format( 163 "Resource %1$s is in the way. ADT needs a source folder called 'gen' to work. Rename or delete resource.", 164 resource.getFullPath().toString()); 165 } 166 167 AdtPlugin.printErrorToConsole(project, message); 168 markProject(AdtConstants.MARKER_ADT, message, IMarker.SEVERITY_ERROR); 169 170 return null; 171 } else if (hasGenSrcFolder == false || genFolderPresent == false) { 172 // either there is no 'gen' source folder in the project (older SDK), 173 // or the folder does not exist (was deleted, or was a fresh svn checkout maybe.) 174 175 // In case we are migrating from an older SDK, we go through the current source 176 // folders and delete the generated Java files. 177 List<IPath> sourceFolders = BaseProjectHelper.getSourceClasspaths(javaProject); 178 IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot(); 179 for (IPath path : sourceFolders) { 180 IResource member = root.findMember(path); 181 if (member != null) { 182 removeDerivedResources(member, monitor); 183 } 184 } 185 186 // create the new source folder, if needed 187 IFolder genFolder = project.getFolder(SdkConstants.FD_GEN_SOURCES); 188 if (genFolderPresent == false) { 189 AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, project, 190 "Creating 'gen' source folder for generated Java files"); 191 genFolder.create(true /* force */, true /* local */, 192 new SubProgressMonitor(monitor, 10)); 193 } 194 195 // add it to the source folder list, if needed only (or it will throw) 196 if (hasGenSrcFolder == false) { 197 IClasspathEntry[] entries = javaProject.getRawClasspath(); 198 entries = ProjectHelper.addEntryToClasspath(entries, 199 JavaCore.newSourceEntry(genFolder.getFullPath())); 200 javaProject.setRawClasspath(entries, new SubProgressMonitor(monitor, 10)); 201 } 202 203 // refresh specifically the gen folder first, as it may break the build 204 // if it doesn't arrive in time then refresh the whole project as usual. 205 genFolder.refreshLocal(IResource.DEPTH_ZERO, new SubProgressMonitor(monitor, 10)); 206 project.refreshLocal(IResource.DEPTH_INFINITE, new SubProgressMonitor(monitor, 10)); 207 208 // it seems like doing this fails to properly rebuild the project. the Java builder 209 // running right after this builder will not see the gen folder, and will not be 210 // restarted after this build. Therefore in this particular case, we start another 211 // build asynchronously so that it's rebuilt after this build. 212 launchJob(new Job("rebuild") { 213 @Override 214 protected IStatus run(IProgressMonitor m) { 215 try { 216 project.build(IncrementalProjectBuilder.INCREMENTAL_BUILD, m); 217 return Status.OK_STATUS; 218 } catch (CoreException e) { 219 return e.getStatus(); 220 } 221 } 222 }); 223 224 } 225 226 // convert older projects which use bin as the eclipse output folder into projects 227 // using bin/classes 228 IFolder androidOutput = BaseProjectHelper.getAndroidOutputFolder(project); 229 IFolder javaOutput = BaseProjectHelper.getJavaOutputFolder(project); 230 if (androidOutput.exists() == false || javaOutput == null || 231 javaOutput.getParent().equals(androidOutput) == false) { 232 // get what we want as the new java output. 233 IFolder newJavaOutput = androidOutput.getFolder(SdkConstants.FD_CLASSES_OUTPUT); 234 235 if (androidOutput.exists() == false) { 236 androidOutput.create(true /*force*/, true /*local*/, monitor); 237 } 238 239 if (newJavaOutput.exists() == false) { 240 newJavaOutput.create(true /*force*/, true /*local*/, monitor); 241 } 242 243 // set the java output to this project. 244 javaProject.setOutputLocation(newJavaOutput.getFullPath(), monitor); 245 246 // need to do a full build. Can't build while we're already building, so launch a 247 // job to build it right after this build 248 launchJob(new Job("rebuild") { 249 @Override 250 protected IStatus run(IProgressMonitor jobMonitor) { 251 try { 252 project.build(IncrementalProjectBuilder.CLEAN_BUILD, jobMonitor); 253 return Status.OK_STATUS; 254 } catch (CoreException e) { 255 return e.getStatus(); 256 } 257 } 258 }); 259 } 260 261 // check that we have bin/res/ 262 IFolder binResFolder = androidOutput.getFolder(SdkConstants.FD_RESOURCES); 263 if (binResFolder.exists() == false) { 264 binResFolder.create(true /* force */, true /* local */, 265 new SubProgressMonitor(monitor, 10)); 266 project.refreshLocal(IResource.DEPTH_ONE, new SubProgressMonitor(monitor, 10)); 267 } 268 269 // Check the preference to be sure we are supposed to refresh 270 // the folders. 271 if (AdtPrefs.getPrefs().getBuildForceResResfresh()) { 272 AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, project, Messages.Refreshing_Res); 273 274 // refresh the res folder. 275 IFolder resFolder = project.getFolder(AdtConstants.WS_RESOURCES); 276 resFolder.refreshLocal(IResource.DEPTH_INFINITE, monitor); 277 278 // Also refresh the assets folder to make sure the ApkBuilder 279 // will now it's changed and will force a new resource packaging. 280 IFolder assetsFolder = project.getFolder(AdtConstants.WS_ASSETS); 281 assetsFolder.refreshLocal(IResource.DEPTH_INFINITE, monitor); 282 } 283 284 return null; 285 } 286 } 287