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