aboutsummaryrefslogtreecommitdiff
path: root/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/ResourceManagerBuilder.java
blob: 8e01cca296406480c112351bee98cf0d061348d9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
/*
 * Copyright (C) 2007 The Android Open Source Project
 *
 * Licensed under the Eclipse Public License, Version 1.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.eclipse.org/org/documents/epl-v10.php
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.ide.eclipse.adt.internal.build.builders;

import com.android.SdkConstants;
import com.android.ide.eclipse.adt.AdtConstants;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.internal.build.Messages;
import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs;
import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs.BuildVerbosity;
import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper;
import com.android.ide.eclipse.adt.internal.project.ProjectHelper;
import com.android.ide.eclipse.adt.internal.sdk.ProjectState;
import com.android.ide.eclipse.adt.internal.sdk.Sdk;
import com.android.sdklib.BuildToolInfo;
import com.android.sdklib.IAndroidTarget;
import com.android.utils.Pair;

import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.IncrementalProjectBuilder;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaCore;

import java.util.List;
import java.util.Map;

/**
 * Resource manager builder whose only purpose is to refresh the resource folder
 * so that the other builder use an up to date version.
 */
public class ResourceManagerBuilder extends BaseBuilder {

    public static final String ID = "com.android.ide.eclipse.adt.ResourceManagerBuilder"; //$NON-NLS-1$

    public ResourceManagerBuilder() {
        super();
    }

    @Override
    protected void clean(IProgressMonitor monitor) throws CoreException {
        super.clean(monitor);

        // Get the project.
        IProject project = getProject();

        // Clear the project of the generic markers
        removeMarkersFromContainer(project, AdtConstants.MARKER_ADT);
    }

    // build() returns a list of project from which this project depends for future compilation.
    @SuppressWarnings("unchecked")
    @Override
    protected IProject[] build(int kind, Map args, IProgressMonitor monitor)
            throws CoreException {
        // Get the project.
        final IProject project = getProject();
        IJavaProject javaProject = JavaCore.create(project);

        // Clear the project of the generic markers
        removeMarkersFromContainer(project, AdtConstants.MARKER_ADT);

        // check for existing target marker, in which case we abort.
        // (this means: no SDK, no target, or unresolvable target.)
        try {
            abortOnBadSetup(javaProject, null);
        } catch (AbortBuildException e) {
            return null;
        }

        // Check the compiler compliance level, displaying the error message
        // since this is the first builder.
        Pair<Integer, String> result = ProjectHelper.checkCompilerCompliance(project);
        String errorMessage = null;
        switch (result.getFirst().intValue()) {
            case ProjectHelper.COMPILER_COMPLIANCE_LEVEL:
                errorMessage = Messages.Requires_Compiler_Compliance_s;
                break;
            case ProjectHelper.COMPILER_COMPLIANCE_SOURCE:
                errorMessage = Messages.Requires_Source_Compatibility_s;
                break;
            case ProjectHelper.COMPILER_COMPLIANCE_CODEGEN_TARGET:
                errorMessage = Messages.Requires_Class_Compatibility_s;
                break;
        }

        if (errorMessage != null) {
            errorMessage = String.format(errorMessage,
                    result.getSecond() == null ? "(no value)" : result.getSecond());

            if (JavaCore.VERSION_1_7.equals(result.getSecond())) {
                // If the user is trying to target 1.7 but compiling with something older,
                // the error message can be a bit misleading; instead point them in the
                // direction of updating the project's build target.
                Sdk currentSdk = Sdk.getCurrent();
                if (currentSdk != null) {
                    IAndroidTarget target = currentSdk.getTarget(project.getProject());
                    if (target != null && target.getVersion().getApiLevel() < 19) {
                        errorMessage = "Using 1.7 requires compiling with Android 4.4 " +
                                "(KitKat); currently using " + target.getVersion();
                    }

                    ProjectState projectState = Sdk.getProjectState(project);
                    if (projectState != null) {
                        BuildToolInfo buildToolInfo = projectState.getBuildToolInfo();
                        if (buildToolInfo == null) {
                            buildToolInfo = currentSdk.getLatestBuildTool();
                        }
                        if (buildToolInfo != null && buildToolInfo.getRevision().getMajor() < 19) {
                            errorMessage = "Using 1.7 requires using Android Build Tools " +
                                    "version 19 or later; currently using " +
                                    buildToolInfo.getRevision();
                        }
                    }
                }
            }

            markProject(AdtConstants.MARKER_ADT, errorMessage, IMarker.SEVERITY_ERROR);
            AdtPlugin.printErrorToConsole(project, errorMessage);

            return null;
        }

        // Check that the SDK directory has been setup.
        String osSdkFolder = AdtPlugin.getOsSdkFolder();

        if (osSdkFolder == null || osSdkFolder.length() == 0) {
            AdtPlugin.printErrorToConsole(project, Messages.No_SDK_Setup_Error);
            markProject(AdtConstants.MARKER_ADT, Messages.No_SDK_Setup_Error,
                    IMarker.SEVERITY_ERROR);

            return null;
        }

        // check the 'gen' source folder is present
        boolean hasGenSrcFolder = false; // whether the project has a 'gen' source folder setup

        IClasspathEntry[] classpaths = javaProject.readRawClasspath();
        if (classpaths != null) {
            for (IClasspathEntry e : classpaths) {
                if (e.getEntryKind() == IClasspathEntry.CPE_SOURCE) {
                    IPath path = e.getPath();
                    if (path.segmentCount() == 2 &&
                            path.segment(1).equals(SdkConstants.FD_GEN_SOURCES)) {
                        hasGenSrcFolder = true;
                        break;
                    }
                }
            }
        }

        boolean genFolderPresent = false; // whether the gen folder actually exists
        IResource resource = project.findMember(SdkConstants.FD_GEN_SOURCES);
        genFolderPresent = resource != null && resource.exists();

        if (hasGenSrcFolder == false && genFolderPresent) {
            // No source folder setup for 'gen' in the project, but there's already a
            // 'gen' resource (file or folder).
            String message;
            if (resource.getType() == IResource.FOLDER) {
                // folder exists already! This is an error. If the folder had been created
                // by the NewProjectWizard, it'd be a source folder.
                message = String.format("%1$s already exists but is not a source folder. Convert to a source folder or rename it.",
                        resource.getFullPath().toString());
            } else {
                // resource exists but is not a folder.
                message = String.format(
                        "Resource %1$s is in the way. ADT needs a source folder called 'gen' to work. Rename or delete resource.",
                        resource.getFullPath().toString());
            }

            AdtPlugin.printErrorToConsole(project, message);
            markProject(AdtConstants.MARKER_ADT, message, IMarker.SEVERITY_ERROR);

            return null;
        } else if (hasGenSrcFolder == false || genFolderPresent == false) {
            // either there is no 'gen' source folder in the project (older SDK),
            // or the folder does not exist (was deleted, or was a fresh svn checkout maybe.)

            // In case we are migrating from an older SDK, we go through the current source
            // folders and delete the generated Java files.
            List<IPath> sourceFolders = BaseProjectHelper.getSourceClasspaths(javaProject);
            IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
            for (IPath path : sourceFolders) {
                IResource member = root.findMember(path);
                if (member != null) {
                    removeDerivedResources(member, monitor);
                }
            }

            // create the new source folder, if needed
            IFolder genFolder = project.getFolder(SdkConstants.FD_GEN_SOURCES);
            if (genFolderPresent == false) {
                AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, project,
                        "Creating 'gen' source folder for generated Java files");
                genFolder.create(true /* force */, true /* local */,
                        new SubProgressMonitor(monitor, 10));
            }

            // add it to the source folder list, if needed only (or it will throw)
            if (hasGenSrcFolder == false) {
                IClasspathEntry[] entries = javaProject.getRawClasspath();
                entries = ProjectHelper.addEntryToClasspath(entries,
                        JavaCore.newSourceEntry(genFolder.getFullPath()));
                javaProject.setRawClasspath(entries, new SubProgressMonitor(monitor, 10));
            }

            // refresh specifically the gen folder first, as it may break the build
            // if it doesn't arrive in time then refresh the whole project as usual.
            genFolder.refreshLocal(IResource.DEPTH_ZERO, new SubProgressMonitor(monitor, 10));
            project.refreshLocal(IResource.DEPTH_INFINITE, new SubProgressMonitor(monitor, 10));

            // it seems like doing this fails to properly rebuild the project. the Java builder
            // running right after this builder will not see the gen folder, and will not be
            // restarted after this build. Therefore in this particular case, we start another
            // build asynchronously so that it's rebuilt after this build.
            launchJob(new Job("rebuild") {
                @Override
                protected IStatus run(IProgressMonitor m) {
                    try {
                        project.build(IncrementalProjectBuilder.INCREMENTAL_BUILD, m);
                        return Status.OK_STATUS;
                    } catch (CoreException e) {
                        return e.getStatus();
                    }
                }
            });

        }

        // convert older projects which use bin as the eclipse output folder into projects
        // using bin/classes
        IFolder androidOutput = BaseProjectHelper.getAndroidOutputFolder(project);
        IFolder javaOutput = BaseProjectHelper.getJavaOutputFolder(project);
        if (androidOutput.exists() == false || javaOutput == null ||
                javaOutput.getParent().equals(androidOutput) == false) {
            // get what we want as the new java output.
            IFolder newJavaOutput = androidOutput.getFolder(SdkConstants.FD_CLASSES_OUTPUT);

            if (androidOutput.exists() == false) {
                androidOutput.create(true /*force*/, true /*local*/, monitor);
            }

            if (newJavaOutput.exists() == false) {
                newJavaOutput.create(true /*force*/, true /*local*/, monitor);
            }

            // set the java output to this project.
            javaProject.setOutputLocation(newJavaOutput.getFullPath(), monitor);

            // need to do a full build. Can't build while we're already building, so launch a
            // job to build it right after this build
            launchJob(new Job("rebuild") {
                @Override
                protected IStatus run(IProgressMonitor jobMonitor) {
                    try {
                        project.build(IncrementalProjectBuilder.CLEAN_BUILD, jobMonitor);
                        return Status.OK_STATUS;
                    } catch (CoreException e) {
                        return e.getStatus();
                    }
                }
            });
        }

        // check that we have bin/res/
        IFolder binResFolder = androidOutput.getFolder(SdkConstants.FD_RESOURCES);
        if (binResFolder.exists() == false) {
            binResFolder.create(true /* force */, true /* local */,
                    new SubProgressMonitor(monitor, 10));
            project.refreshLocal(IResource.DEPTH_ONE, new SubProgressMonitor(monitor, 10));
        }

        // Check the preference to be sure we are supposed to refresh
        // the folders.
        if (AdtPrefs.getPrefs().getBuildForceResResfresh()) {
            AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, project, Messages.Refreshing_Res);

            // refresh the res folder.
            IFolder resFolder = project.getFolder(AdtConstants.WS_RESOURCES);
            resFolder.refreshLocal(IResource.DEPTH_INFINITE, monitor);

            // Also refresh the assets folder to make sure the ApkBuilder
            // will now it's changed and will force a new resource packaging.
            IFolder assetsFolder = project.getFolder(AdtConstants.WS_ASSETS);
            assetsFolder.refreshLocal(IResource.DEPTH_INFINITE, monitor);
        }

        return null;
    }
}