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