diff options
46 files changed, 2322 insertions, 2331 deletions
diff --git a/anttasks/src/com/android/ant/AaptExecLoopTask.java b/anttasks/src/com/android/ant/AaptExecLoopTask.java index ebefde552..6b438bb73 100644 --- a/anttasks/src/com/android/ant/AaptExecLoopTask.java +++ b/anttasks/src/com/android/ant/AaptExecLoopTask.java @@ -88,6 +88,8 @@ public final class AaptExecLoopTask extends BaseTask { private String mResourceFilter; private String mRFolder; private final ArrayList<NoCompress> mNoCompressList = new ArrayList<NoCompress>(); + private String mProjectLibrariesResName; + private String mProjectLibrariesPackageName; /** * Sets the value of the "executable" attribute. @@ -235,6 +237,15 @@ public final class AaptExecLoopTask extends BaseTask { } } + public void setProjectLibrariesResName(String projectLibrariesResName) { + mProjectLibrariesResName = projectLibrariesResName; + } + + public void setProjectLibrariesPackageName(String projectLibrariesPackageName) { + mProjectLibrariesPackageName = projectLibrariesPackageName; + } + + /** * Returns an object representing a nested <var>nocompress</var> element. */ @@ -268,6 +279,13 @@ public final class AaptExecLoopTask extends BaseTask { */ @Override public void execute() throws BuildException { + if (mProjectLibrariesResName == null) { + throw new BuildException("Missing attribute projectLibrariesResName"); + } + if (mProjectLibrariesPackageName == null) { + throw new BuildException("Missing attribute projectLibrariesPackageName"); + } + Project taskProject = getProject(); String libPkgProp = null; @@ -275,7 +293,7 @@ public final class AaptExecLoopTask extends BaseTask { // if the parameters indicate generation of the R class, check if // more R classes need to be created for libraries. if (mRFolder != null && new File(mRFolder).isDirectory()) { - libPkgProp = taskProject.getProperty(AntConstants.PROP_PROJECT_LIBS_PKG); + libPkgProp = taskProject.getProperty(mProjectLibrariesPackageName); if (libPkgProp != null) { // Replace ";" with ":" since that's what aapt expects libPkgProp = libPkgProp.replace(';', ':'); @@ -285,6 +303,11 @@ public final class AaptExecLoopTask extends BaseTask { callAapt(libPkgProp); } + @Override + protected String getExecTaskName() { + return "aapt"; + } + /** * Calls aapt with the given parameters. * @param resourceFilter the resource configuration filter to pass to aapt (if configName is @@ -298,7 +321,7 @@ public final class AaptExecLoopTask extends BaseTask { final boolean generateRClass = mRFolder != null && new File(mRFolder).isDirectory(); // Get whether we have libraries - Object libResRef = taskProject.getReference(AntConstants.PROP_PROJECT_LIBS_RES_REF); + Object libResRef = taskProject.getReference(mProjectLibrariesResName); // Set up our folders to check for changed files ArrayList<File> watchPaths = new ArrayList<File>(); @@ -358,8 +381,7 @@ public final class AaptExecLoopTask extends BaseTask { task.setExecutable(mExecutable); task.setFailonerror(true); - File exe = new File(mExecutable); - task.setTaskName(exe.getName()); + task.setTaskName(getExecTaskName()); // aapt command. Only "package" is supported at this time really. task.createArg().setValue(mCommand); diff --git a/anttasks/src/com/android/ant/AidlExecTask.java b/anttasks/src/com/android/ant/AidlExecTask.java index f2fa094c7..5fa1f30dc 100644 --- a/anttasks/src/com/android/ant/AidlExecTask.java +++ b/anttasks/src/com/android/ant/AidlExecTask.java @@ -93,9 +93,6 @@ public class AidlExecTask extends Task { } } - File exe = new File(mExecutable); - String execTaskName = exe.getName(); - // now loop on all the source folders to find all the aidl to compile // and compile them for (String sourceFolder : sourceFolders) { @@ -115,7 +112,7 @@ public class AidlExecTask extends Task { task.setProject(taskProject); task.setOwningTarget(getOwningTarget()); task.setExecutable(mExecutable); - task.setTaskName(execTaskName); + task.setTaskName("aidl"); task.setFailonerror(true); task.createArg().setValue("-p" + mFramework); diff --git a/anttasks/src/com/android/ant/AntConstants.java b/anttasks/src/com/android/ant/AntConstants.java deleted file mode 100644 index a87d0d572..000000000 --- a/anttasks/src/com/android/ant/AntConstants.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0 - * - * 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.ant; - -/** - * Constants used by custom tasks and the rules files. - */ -public interface AntConstants { - - /** ant property with the path to the android.jar file */ - public final static String PROP_ANDROID_JAR = "android.jar"; - - /** ant property with the path to the framework.aidl file */ - public final static String PROP_ANDROID_AIDL = "android.aidl"; - - /** ant property with the path to the renderscript framework include folder. */ - public final static String PROP_ANDROID_RENDERSCRIPT = "android.rs"; - - /** ant property with the path to the aapt tool */ - public final static String PROP_AAPT = "aapt"; - /** ant property with the path to the aidl tool */ - public final static String PROP_AIDL = "aidl"; - /** ant property with the path to the dx tool */ - public final static String PROP_DX = "dx"; - /** ant property with the path to the renderscript tool */ - public final static String PROP_RENDERSCRIPT = "renderscript"; - /** ref id to the <path> object containing all the boot classpaths. */ - public final static String PROP_CLASSPATH_REF = "android.target.classpath"; - - /** ant property ref to the list of source folder for the project libraries */ - public static final String PROP_PROJECT_LIBS_SRC_REF = "project.libraries.src"; - /** ant property ref to the list of jars for the project libraries */ - public static final String PROP_PROJECT_LIBS_JARS_REF = "project.libraries.jars"; - /** ant property ref to the list of libs folder for the project libraries */ - public static final String PROP_PROJECT_LIBS_LIBS_REF = "project.libraries.libs"; - /** ant property ref to the list of res folder for the project libraries */ - public static final String PROP_PROJECT_LIBS_RES_REF = "project.libraries.res"; - /** ant property for semi-colon separated packages for the project libraries */ - public static final String PROP_PROJECT_LIBS_PKG = "project.libraries.package"; - /** ant property for the test project directory */ - public static final String PROP_TESTED_PROJECT_DIR = "tested.project.dir"; - - public static final String PROP_MANIFEST_PACKAGE = "manifest.package"; - - public static final String PROP_OUT_ABS_DIR = "out.absolute.dir"; - - public static final String PROP_KEY_STORE_PASSWORD = "key.store.password"; - public static final String PROP_KEY_ALIAS_PASSWORD = "key.alias.password"; -} diff --git a/anttasks/src/com/android/ant/BaseTask.java b/anttasks/src/com/android/ant/BaseTask.java index 2126d3fbb..00b7fcb5f 100644 --- a/anttasks/src/com/android/ant/BaseTask.java +++ b/anttasks/src/com/android/ant/BaseTask.java @@ -25,18 +25,22 @@ import java.util.ArrayList; /** * A base class for the ant task that contains logic for handling dependency files */ -public class BaseTask extends Task { +public abstract class BaseTask extends Task { private DependencyGraph mDependencies; + private String mPreviousBuildType; + private String mBuildType; + + public void setPreviousBuildType(String previousBuildType) { + mPreviousBuildType = previousBuildType; + } + + public void setBuildType(String buildType) { + mBuildType = buildType; + } + + protected abstract String getExecTaskName(); - /* - * (non-Javadoc) - * - * Executes the loop. Based on the values inside default.properties, this will - * create alternate temporary ap_ files. - * - * @see org.apache.tools.ant.Task#execute() - */ @Override public void execute() throws BuildException { @@ -46,9 +50,14 @@ public class BaseTask extends Task { * Set up the dependency graph by passing it the location of the ".d" file * @param dependencyFile path to the dependency file to use * @param watchPaths a list of folders to watch for new files - * @return true if the dependency graph was sucessfully initialized + * @return true if the dependency graph was successfully initialized */ protected boolean initDependencies(String dependencyFile, ArrayList<File> watchPaths) { + if (mBuildType != null && mBuildType.equals(mPreviousBuildType) == false) { + // we don't care about deps, we need to execute the task no matter what. + return true; + } + File depFile = new File(dependencyFile); if (depFile.exists()) { mDependencies = new DependencyGraph(dependencyFile, watchPaths); @@ -64,6 +73,16 @@ public class BaseTask extends Task { * have changed since the last run */ protected boolean dependenciesHaveChanged() { + if (mBuildType != null && mBuildType.equals(mPreviousBuildType) == false) { + String execName = getExecTaskName(); + if (execName == null) { + System.out.println("Current build type is different than previous build: forced task run."); + } else { + System.out.println("Current build type is different than previous build: forced " + execName + " run."); + } + return true; + } + assert mDependencies != null : "Dependencies have not been initialized"; return mDependencies.dependenciesHaveChanged(); } diff --git a/anttasks/src/com/android/ant/MultiApkExportTask.java b/anttasks/src/com/android/ant/MultiApkExportTask.java index a21478e87..6b2316248 100644 --- a/anttasks/src/com/android/ant/MultiApkExportTask.java +++ b/anttasks/src/com/android/ant/MultiApkExportTask.java @@ -52,6 +52,12 @@ import javax.xml.xpath.XPathFactory; */ public class MultiApkExportTask extends Task { + private static final String PROP_OUT_ABS_DIR = "out.absolute.dir"; + + private static final String PROP_KEY_STORE_PASSWORD = "key.store.password"; + private static final String PROP_KEY_ALIAS_PASSWORD = "key.alias.password"; + + private Target mTarget; private XPathFactory mXPathFactory; @@ -117,7 +123,7 @@ public class MultiApkExportTask extends Task { mXPathFactory = XPathFactory.newInstance(); File exportProjectOutput = new File( - getValidatedProperty(antProject, AntConstants.PROP_OUT_ABS_DIR)); + getValidatedProperty(antProject, PROP_OUT_ABS_DIR)); // if there's no error, and we can sign, prompt for the passwords. String keyStorePassword = null; @@ -127,23 +133,21 @@ public class MultiApkExportTask extends Task { Input input = new Input(); input.setProject(antProject); - input.setAddproperty(AntConstants.PROP_KEY_STORE_PASSWORD); + input.setAddproperty(PROP_KEY_STORE_PASSWORD); input.setMessage(String.format("Please enter keystore password (store: %1$s):", keyStore)); input.execute(); input = new Input(); input.setProject(antProject); - input.setAddproperty(AntConstants.PROP_KEY_ALIAS_PASSWORD); + input.setAddproperty(PROP_KEY_ALIAS_PASSWORD); input.setMessage(String.format("Please enter password for alias '%1$s':", keyAlias)); input.execute(); // and now read the property so that they can be set into the sub ant task. - keyStorePassword = getValidatedProperty(antProject, - AntConstants.PROP_KEY_STORE_PASSWORD); - keyAliasPassword = getValidatedProperty(antProject, - AntConstants.PROP_KEY_ALIAS_PASSWORD); + keyStorePassword = getValidatedProperty(antProject, PROP_KEY_STORE_PASSWORD); + keyAliasPassword = getValidatedProperty(antProject, PROP_KEY_ALIAS_PASSWORD); } for (ApkData apk : apks) { diff --git a/anttasks/src/com/android/ant/NewSetupTask.java b/anttasks/src/com/android/ant/NewSetupTask.java new file mode 100644 index 000000000..50dcdabb8 --- /dev/null +++ b/anttasks/src/com/android/ant/NewSetupTask.java @@ -0,0 +1,638 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0 + * + * 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.ant; + +import com.android.io.FileWrapper; +import com.android.io.FolderWrapper; +import com.android.sdklib.AndroidVersion; +import com.android.sdklib.IAndroidTarget; +import com.android.sdklib.ISdkLog; +import com.android.sdklib.SdkConstants; +import com.android.sdklib.SdkManager; +import com.android.sdklib.IAndroidTarget.IOptionalLibrary; +import com.android.sdklib.internal.project.ProjectProperties; +import com.android.sdklib.internal.project.ProjectProperties.PropertyType; +import com.android.sdklib.xml.AndroidManifest; +import com.android.sdklib.xml.AndroidXPathFactory; + +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.types.Path; +import org.apache.tools.ant.types.Path.PathElement; +import org.apache.tools.ant.util.DeweyDecimal; +import org.xml.sax.InputSource; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FilenameFilter; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; + +import javax.xml.xpath.XPath; +import javax.xml.xpath.XPathExpressionException; + +/** + * Setup Ant task. This task accomplishes: + * <ul> + * <li>Gets the project target hash string from {@link ProjectProperties#PROPERTY_TARGET}, + * and resolves it to get the project's {@link IAndroidTarget}.</li> + * + * <li>Sets up properties so that aapt can find the android.jar and other files/folders in + * the resolved target.</li> + * + * <li>Sets up the boot classpath ref so that the <code>javac</code> task knows where to find + * the libraries. This includes the default android.jar from the resolved target but also optional + * libraries provided by the target (if any, when the target is an add-on).</li> + * + * <li>Resolve library dependencies and setup various Path references for them</li> + * </ul> + * + * This is used in the main rules file only. + * + */ +public class NewSetupTask extends Task { + private final static String ANT_MIN_VERSION = "1.8.0"; + + private String mProjectTypeOut; + private String mAndroidJarFileOut; + private String mAndroidAidlFileOut; + private String mRenderScriptExeOut; + private String mRenderScriptIncludeDirOut; + private String mBootclasspathrefOut; + private String mProjectLibrariesRootOut; + private String mProjectLibrariesResOut; + private String mProjectLibrariesPackageOut; + private String mProjectLibrariesJarsOut; + private String mProjectLibrariesLibsOut; + + public void setProjectTypeOut(String projectTypeOut) { + mProjectTypeOut = projectTypeOut; + } + + public void setAndroidJarFileOut(String androidJarFileOut) { + mAndroidJarFileOut = androidJarFileOut; + } + + public void setAndroidAidlFileOut(String androidAidlFileOut) { + mAndroidAidlFileOut = androidAidlFileOut; + } + + public void setRenderScriptExeOut(String renderScriptExeOut) { + mRenderScriptExeOut = renderScriptExeOut; + } + + public void setRenderScriptIncludeDirOut(String renderScriptIncludeDirOut) { + mRenderScriptIncludeDirOut = renderScriptIncludeDirOut; + } + + public void setBootclasspathrefOut(String bootclasspathrefOut) { + mBootclasspathrefOut = bootclasspathrefOut; + } + + public void setProjectLibrariesRootOut(String projectLibrariesRootOut) { + mProjectLibrariesRootOut = projectLibrariesRootOut; + } + + public void setProjectLibrariesResOut(String projectLibrariesResOut) { + mProjectLibrariesResOut = projectLibrariesResOut; + } + + public void setProjectLibrariesPackageOut(String projectLibrariesPackageOut) { + mProjectLibrariesPackageOut = projectLibrariesPackageOut; + } + + public void setProjectLibrariesJarsOut(String projectLibrariesJarsOut) { + mProjectLibrariesJarsOut = projectLibrariesJarsOut; + } + + public void setProjectLibrariesLibsOut(String projectLibrariesLibsOut) { + mProjectLibrariesLibsOut = projectLibrariesLibsOut; + } + + @Override + public void execute() throws BuildException { + if (mProjectTypeOut == null) { + throw new BuildException("Missing attribute projectTypeOut"); + } + if (mAndroidJarFileOut == null) { + throw new BuildException("Missing attribute androidJarFileOut"); + } + if (mAndroidAidlFileOut == null) { + throw new BuildException("Missing attribute androidAidlFileOut"); + } + if (mRenderScriptExeOut == null) { + throw new BuildException("Missing attribute renderScriptExeOut"); + } + if (mRenderScriptIncludeDirOut == null) { + throw new BuildException("Missing attribute renderScriptIncludeDirOut"); + } + if (mBootclasspathrefOut == null) { + throw new BuildException("Missing attribute bootclasspathrefOut"); + } + if (mProjectLibrariesRootOut == null) { + throw new BuildException("Missing attribute projectLibrariesRootOut"); + } + if (mProjectLibrariesResOut == null) { + throw new BuildException("Missing attribute projectLibrariesResOut"); + } + if (mProjectLibrariesPackageOut == null) { + throw new BuildException("Missing attribute projectLibrariesPackageOut"); + } + if (mProjectLibrariesJarsOut == null) { + throw new BuildException("Missing attribute projectLibrariesJarsOut"); + } + if (mProjectLibrariesLibsOut == null) { + throw new BuildException("Missing attribute projectLibrariesLibsOut"); + } + + + Project antProject = getProject(); + + // check the Ant version + DeweyDecimal version = getVersion(antProject); + DeweyDecimal atLeast = new DeweyDecimal(ANT_MIN_VERSION); + if (atLeast.isGreaterThan(version)) { + throw new BuildException( + "The Android Ant-based build system requires Ant " + + ANT_MIN_VERSION + + " or later. Current version is " + + version); + } + + // get the SDK location + File sdkDir = TaskHelper.getSdkLocation(antProject); + String sdkOsPath = sdkDir.getPath(); + + // Make sure the OS sdk path ends with a directory separator + if (sdkOsPath.length() > 0 && !sdkOsPath.endsWith(File.separator)) { + sdkOsPath += File.separator; + } + + // display SDK Tools revision + int toolsRevison = TaskHelper.getToolsRevision(sdkDir); + if (toolsRevison != -1) { + System.out.println("Android SDK Tools Revision " + toolsRevison); + } + + // detect that the platform tools is there. + File platformTools = new File(sdkDir, SdkConstants.FD_PLATFORM_TOOLS); + if (platformTools.isDirectory() == false) { + throw new BuildException(String.format( + "SDK Platform Tools component is missing. " + + "Please install it with the SDK Manager (%1$s%2$c%3$s)", + SdkConstants.FD_TOOLS, + File.separatorChar, + SdkConstants.androidCmdName())); + } + + // get the target property value + String targetHashString = antProject.getProperty(ProjectProperties.PROPERTY_TARGET); + + boolean isTestProject = false; + + if (antProject.getProperty(ProjectProperties.PROPERTY_TESTED_PROJECT) != null) { + isTestProject = true; + } + + if (targetHashString == null) { + throw new BuildException("Android Target is not set."); + } + + // load up the sdk targets. + final ArrayList<String> messages = new ArrayList<String>(); + SdkManager manager = SdkManager.createManager(sdkOsPath, new ISdkLog() { + public void error(Throwable t, String errorFormat, Object... args) { + if (errorFormat != null) { + messages.add(String.format("Error: " + errorFormat, args)); + } + if (t != null) { + messages.add("Error: " + t.getMessage()); + } + } + + public void printf(String msgFormat, Object... args) { + messages.add(String.format(msgFormat, args)); + } + + public void warning(String warningFormat, Object... args) { + messages.add(String.format("Warning: " + warningFormat, args)); + } + }); + + if (manager == null) { + // since we failed to parse the SDK, lets display the parsing output. + for (String msg : messages) { + System.out.println(msg); + } + throw new BuildException("Failed to parse SDK content."); + } + + // resolve it + IAndroidTarget androidTarget = manager.getTargetFromHashString(targetHashString); + + if (androidTarget == null) { + throw new BuildException(String.format( + "Unable to resolve target '%s'", targetHashString)); + } + + // display the project info + System.out.println("Project Target: " + androidTarget.getName()); + if (androidTarget.isPlatform() == false) { + System.out.println("Vendor: " + androidTarget.getVendor()); + System.out.println("Platform Version: " + androidTarget.getVersionName()); + } + System.out.println("API level: " + androidTarget.getVersion().getApiString()); + + // check if the project is a library + boolean isLibrary = false; + + String libraryProp = antProject.getProperty(ProjectProperties.PROPERTY_LIBRARY); + if (libraryProp != null) { + isLibrary = Boolean.valueOf(libraryProp).booleanValue(); + } + + if (isLibrary) { + System.out.println("Project Type: Android Library"); + } + + // look for referenced libraries. + processReferencedLibraries(antProject, androidTarget); + + // always check the manifest minSdkVersion. + checkManifest(antProject, androidTarget.getVersion()); + + // sets up the properties to find android.jar/framework.aidl/target tools + String androidJar = androidTarget.getPath(IAndroidTarget.ANDROID_JAR); + antProject.setProperty(mAndroidJarFileOut, androidJar); + + String androidAidl = androidTarget.getPath(IAndroidTarget.ANDROID_AIDL); + antProject.setProperty(mAndroidAidlFileOut, androidAidl); + + Path includePath = new Path(antProject); + PathElement element = includePath.createPathElement(); + element.setPath(androidTarget.getPath(IAndroidTarget.ANDROID_RS)); + element = includePath.createPathElement(); + element.setPath(androidTarget.getPath(IAndroidTarget.ANDROID_RS_CLANG)); + antProject.setProperty(mRenderScriptIncludeDirOut, includePath.toString()); + + // TODO: figure out the actual compiler to use based on the minSdkVersion + antProject.setProperty(mRenderScriptExeOut, + sdkOsPath + SdkConstants.OS_SDK_PLATFORM_TOOLS_FOLDER + + SdkConstants.FN_RENDERSCRIPT); + + // sets up the boot classpath + + // create the Path object + Path bootclasspath = new Path(antProject); + + // create a PathElement for the framework jar + element = bootclasspath.createPathElement(); + element.setPath(androidJar); + + // create PathElement for each optional library. + IOptionalLibrary[] libraries = androidTarget.getOptionalLibraries(); + if (libraries != null) { + HashSet<String> visitedJars = new HashSet<String>(); + for (IOptionalLibrary library : libraries) { + String jarPath = library.getJarPath(); + if (visitedJars.contains(jarPath) == false) { + visitedJars.add(jarPath); + + element = bootclasspath.createPathElement(); + element.setPath(library.getJarPath()); + } + } + } + + // sets the path in the project with a reference + antProject.addReference(mBootclasspathrefOut, bootclasspath); + + // finally set the project type. + if (isLibrary) { + antProject.setProperty(mProjectTypeOut, "library"); + } else if (isTestProject) { + antProject.setProperty(mProjectTypeOut, "test"); + } else { + antProject.setProperty(mProjectTypeOut, "project"); + } + } + + /** + * Checks the manifest <code>minSdkVersion</code> attribute. + * @param antProject the ant project + * @param androidVersion the version of the platform the project is compiling against. + */ + private void checkManifest(Project antProject, AndroidVersion androidVersion) { + try { + File manifest = new File(antProject.getBaseDir(), SdkConstants.FN_ANDROID_MANIFEST_XML); + + XPath xPath = AndroidXPathFactory.newXPath(); + + // check the package name. + String value = xPath.evaluate( + "/" + AndroidManifest.NODE_MANIFEST + + "/@" + AndroidManifest.ATTRIBUTE_PACKAGE, + new InputSource(new FileInputStream(manifest))); + if (value != null) { // aapt will complain if it's missing. + // only need to check that the package has 2 segments + if (value.indexOf('.') == -1) { + throw new BuildException(String.format( + "Application package '%1$s' must have a minimum of 2 segments.", + value)); + } + } + + // check the minSdkVersion value + value = xPath.evaluate( + "/" + AndroidManifest.NODE_MANIFEST + + "/" + AndroidManifest.NODE_USES_SDK + + "/@" + AndroidXPathFactory.DEFAULT_NS_PREFIX + ":" + + AndroidManifest.ATTRIBUTE_MIN_SDK_VERSION, + new InputSource(new FileInputStream(manifest))); + + if (androidVersion.isPreview()) { + // in preview mode, the content of the minSdkVersion must match exactly the + // platform codename. + String codeName = androidVersion.getCodename(); + if (codeName.equals(value) == false) { + throw new BuildException(String.format( + "For '%1$s' SDK Preview, attribute minSdkVersion in AndroidManifest.xml must be '%1$s'", + codeName)); + } + } else if (value.length() > 0) { + // for normal platform, we'll only display warnings if the value is lower or higher + // than the target api level. + // First convert to an int. + int minSdkValue = -1; + try { + minSdkValue = Integer.parseInt(value); + } catch (NumberFormatException e) { + // looks like it's not a number: error! + throw new BuildException(String.format( + "Attribute %1$s in AndroidManifest.xml must be an Integer!", + AndroidManifest.ATTRIBUTE_MIN_SDK_VERSION)); + } + + int projectApiLevel = androidVersion.getApiLevel(); + if (minSdkValue < projectApiLevel) { + System.out.println(String.format( + "WARNING: Attribute %1$s in AndroidManifest.xml (%2$d) is lower than the project target API level (%3$d)", + AndroidManifest.ATTRIBUTE_MIN_SDK_VERSION, + minSdkValue, projectApiLevel)); + } else if (minSdkValue > androidVersion.getApiLevel()) { + System.out.println(String.format( + "WARNING: Attribute %1$s in AndroidManifest.xml (%2$d) is higher than the project target API level (%3$d)", + AndroidManifest.ATTRIBUTE_MIN_SDK_VERSION, + minSdkValue, projectApiLevel)); + } + } else { + // no minSdkVersion? display a warning + System.out.println( + "WARNING: No minSdkVersion value set. Application will install on all Android versions."); + } + + } catch (XPathExpressionException e) { + throw new BuildException(e); + } catch (FileNotFoundException e) { + throw new BuildException(e); + } + } + + private void processReferencedLibraries(Project antProject, IAndroidTarget androidTarget) { + // prepare several paths for future tasks + Path rootPath = new Path(antProject); + Path resPath = new Path(antProject); + Path libsPath = new Path(antProject); + Path jarsPath = new Path(antProject); + StringBuilder packageStrBuilder = new StringBuilder(); + + FilenameFilter filter = new FilenameFilter() { + public boolean accept(File dir, String name) { + return name.toLowerCase().endsWith(".jar"); + } + }; + + System.out.println("\n------------------\nResolving library dependencies:"); + + ArrayList<File> libraries = getProjectLibraries(antProject); + + if (libraries.size() > 0) { + System.out.println("------------------\nOrdered libraries:"); + + for (File library : libraries) { + String libRootPath = library.getAbsolutePath(); + System.out.println(libRootPath); + + // get the root path. + PathElement element = rootPath.createPathElement(); + element.setPath(libRootPath); + + // get the res path. Always $PROJECT/res + element = resPath.createPathElement(); + element.setPath(libRootPath + "/" + SdkConstants.FD_RESOURCES); + + // get the libs path. Always $PROJECT/libs + element = libsPath.createPathElement(); + element.setPath(libRootPath + "/" + SdkConstants.FD_NATIVE_LIBS); + + // get the jars from it too. + // 1. the library code jar + element = jarsPath.createPathElement(); + element.setPath(libRootPath + "/" + SdkConstants.FD_OUTPUT + + "/" + SdkConstants.FN_CLASSES_JAR); + + // 2. the 3rd party jar files + File libsFolder = new File(library, SdkConstants.FD_NATIVE_LIBS); + File[] jarFiles = libsFolder.listFiles(filter); + if (jarFiles != null) { + for (File jarFile : jarFiles) { + element = jarsPath.createPathElement(); + element.setPath(jarFile.getAbsolutePath()); + } + } + + // get the package from the manifest. + FileWrapper manifest = new FileWrapper(library, + SdkConstants.FN_ANDROID_MANIFEST_XML); + + try { + String value = AndroidManifest.getPackage(manifest); + if (value != null) { // aapt will complain if it's missing. + packageStrBuilder.append(';'); + packageStrBuilder.append(value); + } + } catch (Exception e) { + throw new BuildException(e); + } + } + } else { + System.out.println("No library dependencies.\n"); + } + + System.out.println("------------------\n"); + + // even with no libraries, always setup these so that various tasks in Ant don't complain + // (the task themselves can handle a ref to an empty Path) + antProject.addReference(mProjectLibrariesJarsOut, jarsPath); + antProject.addReference(mProjectLibrariesLibsOut, libsPath); + + // the rest is done only if there's a library. + if (jarsPath.list().length > 0) { + System.out.println("DEBUG: " + rootPath.toString()); + antProject.addReference(mProjectLibrariesRootOut, rootPath); + antProject.addReference(mProjectLibrariesResOut, resPath); + antProject.setProperty(mProjectLibrariesPackageOut, packageStrBuilder.toString()); + } + } + + /** + * Returns all the library dependencies of a given Ant project. + * @param antProject the Ant project + * @return a list of properties, sorted from highest priority to lowest. + */ + private ArrayList<File> getProjectLibraries(final Project antProject) { + ArrayList<File> libraries = new ArrayList<File>(); + File baseDir = antProject.getBaseDir(); + + // get the top level list of library dependencies. + List<File> topLevelLibraries = getDirectDependencies(baseDir, new IPropertySource() { + public String getProperty(String name) { + return antProject.getProperty(name); + } + }); + + // process the libraries in case they depend on other libraries. + resolveFullLibraryDependencies(topLevelLibraries, libraries); + + return libraries; + } + + /** + * Resolves a given list of libraries, finds out if they depend on other libraries, and + * returns a full list of all the direct and indirect dependencies in the proper order (first + * is higher priority when calling aapt). + * @param inLibraries the libraries to resolve + * @param outLibraries where to store all the libraries. + */ + private void resolveFullLibraryDependencies(List<File> inLibraries, List<File> outLibraries) { + // loop in the inverse order to resolve dependencies on the libraries, so that if a library + // is required by two higher level libraries it can be inserted in the correct place + for (int i = inLibraries.size() - 1 ; i >= 0 ; i--) { + File library = inLibraries.get(i); + + // get the default.property file for it + final ProjectProperties defaultProp = ProjectProperties.load( + new FolderWrapper(library), PropertyType.DEFAULT); + + // get its libraries + List<File> dependencies = getDirectDependencies(library, new IPropertySource() { + public String getProperty(String name) { + return defaultProp.getProperty(name); + } + }); + + // resolve the dependencies for those libraries + resolveFullLibraryDependencies(dependencies, outLibraries); + + // and add the current one (if needed) in front (higher priority) + if (outLibraries.contains(library) == false) { + outLibraries.add(0, library); + } + } + } + + public interface IPropertySource { + String getProperty(String name); + } + + /** + * Returns the top level library dependencies of a given <var>source</var> representing a + * project properties. + * @param baseFolder the base folder of the project (to resolve relative paths) + * @param source a source of project properties. + */ + private List<File> getDirectDependencies(File baseFolder, IPropertySource source) { + ArrayList<File> libraries = new ArrayList<File>(); + + // first build the list. they are ordered highest priority first. + int index = 1; + while (true) { + String propName = ProjectProperties.PROPERTY_LIB_REF + Integer.toString(index++); + String rootPath = source.getProperty(propName); + + if (rootPath == null) { + break; + } + + try { + File library = new File(baseFolder, rootPath).getCanonicalFile(); + + // check for validity + File defaultProp = new File(library, PropertyType.DEFAULT.getFilename()); + if (defaultProp.isFile() == false) { + // error! + throw new BuildException(String.format( + "%1$s resolve to a path with no %2$s file for project %3$s", rootPath, + PropertyType.DEFAULT.getFilename(), baseFolder.getAbsolutePath())); + } + + if (libraries.contains(library) == false) { + System.out.println(String.format("%1$s: %2$s => %3$s", + baseFolder.getAbsolutePath(), rootPath, library.getAbsolutePath())); + + libraries.add(library); + } + } catch (IOException e) { + throw new BuildException("Failed to resolve library path: " + rootPath, e); + } + } + + return libraries; + } + + /** + * Returns the Ant version as a {@link DeweyDecimal} object. + * + * This is based on the implementation of + * org.apache.tools.ant.taskdefs.condition.AntVersion.getVersion() + * + * @param antProject the current ant project. + * @return the ant version. + */ + private DeweyDecimal getVersion(Project antProject) { + char[] versionString = antProject.getProperty("ant.version").toCharArray(); + StringBuilder sb = new StringBuilder(); + boolean foundFirstDigit = false; + for (int i = 0; i < versionString.length; i++) { + if (Character.isDigit(versionString[i])) { + sb.append(versionString[i]); + foundFirstDigit = true; + } + if (versionString[i] == '.' && foundFirstDigit) { + sb.append(versionString[i]); + } + if (Character.isLetter(versionString[i]) && foundFirstDigit) { + break; + } + } + return new DeweyDecimal(sb.toString()); + } +} diff --git a/anttasks/src/com/android/ant/SetupTask.java b/anttasks/src/com/android/ant/SetupTask.java index e15f77bf5..c0bc55d62 100644 --- a/anttasks/src/com/android/ant/SetupTask.java +++ b/anttasks/src/com/android/ant/SetupTask.java @@ -16,588 +16,31 @@ package com.android.ant; -import com.android.io.FileWrapper; -import com.android.io.FolderWrapper; -import com.android.sdklib.AndroidVersion; -import com.android.sdklib.IAndroidTarget; -import com.android.sdklib.ISdkLog; -import com.android.sdklib.SdkConstants; -import com.android.sdklib.SdkManager; -import com.android.sdklib.IAndroidTarget.IOptionalLibrary; -import com.android.sdklib.internal.project.ProjectProperties; -import com.android.sdklib.internal.project.ProjectProperties.PropertyType; -import com.android.sdklib.xml.AndroidManifest; -import com.android.sdklib.xml.AndroidXPathFactory; - import org.apache.tools.ant.BuildException; -import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; import org.apache.tools.ant.taskdefs.ImportTask; -import org.apache.tools.ant.types.Path; -import org.apache.tools.ant.types.Path.PathElement; -import org.apache.tools.ant.util.DeweyDecimal; -import org.xml.sax.InputSource; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FilenameFilter; -import java.io.IOException; -import java.util.ArrayList; -import java.util.HashSet; - -import javax.xml.xpath.XPath; -import javax.xml.xpath.XPathExpressionException; /** - * Setup/Import Ant task. This task accomplishes: - * <ul> - * <li>Gets the project target hash string from {@link ProjectProperties#PROPERTY_TARGET}, - * and resolves it to get the project's {@link IAndroidTarget}.</li> - * <li>Sets up properties so that aapt can find the android.jar in the resolved target.</li> - * <li>Sets up the boot classpath ref so that the <code>javac</code> task knows where to find - * the libraries. This includes the default android.jar from the resolved target but also optional - * libraries provided by the target (if any, when the target is an add-on).</li> - * <li>Imports the build rules located in the resolved target so that the build actually does - * something. This can be disabled with the attribute <var>import</var> set to <code>false</code> - * </li></ul> - * - * This is used in build.xml/template. + * Legacy setupTask class used by older build system. * + * If this is used it actually only display an error about the need to update the build file. */ -public final class SetupTask extends ImportTask { - private final static String ANT_MIN_VERSION = "1.8.0"; - // main rules file - private final static String RULES_MAIN = "main_rules.xml"; - // test rules file - depends on android_rules.xml - private final static String RULES_TEST = "test_rules.xml"; - // library rules file. - private final static String RULES_LIBRARY = "lib_rules.xml"; - - private boolean mDoImport = true; - - @Override - public void execute() throws BuildException { - Project antProject = getProject(); - - // check the Ant version - DeweyDecimal version = getVersion(antProject); - DeweyDecimal atLeast = new DeweyDecimal(ANT_MIN_VERSION); - if (atLeast.isGreaterThan(version)) { - throw new BuildException( - "The Android Ant-based build system requires Ant " + - ANT_MIN_VERSION + - " or later. Current version is " + - version); - } - - // get the SDK location - File sdkDir = TaskHelper.getSdkLocation(antProject); - String sdkOsPath = sdkDir.getPath(); - - // Make sure the OS sdk path ends with a directory separator - if (sdkOsPath.length() > 0 && !sdkOsPath.endsWith(File.separator)) { - sdkOsPath += File.separator; - } - - // display SDK Tools revision - int toolsRevison = TaskHelper.getToolsRevision(sdkDir); - if (toolsRevison != -1) { - System.out.println("Android SDK Tools Revision " + toolsRevison); - } - - // detect that the platform tools is there. - File platformTools = new File(sdkDir, SdkConstants.FD_PLATFORM_TOOLS); - if (platformTools.isDirectory() == false) { - throw new BuildException(String.format( - "SDK Platform Tools component is missing. " + - "Please install it with the SDK Manager (%1$s%2$c%3$s)", - SdkConstants.FD_TOOLS, - File.separatorChar, - SdkConstants.androidCmdName())); - } - - // get the target property value - String targetHashString = antProject.getProperty(ProjectProperties.PROPERTY_TARGET); - - boolean isTestProject = false; - - if (antProject.getProperty(AntConstants.PROP_TESTED_PROJECT_DIR) != null) { - isTestProject = true; - } - - if (targetHashString == null) { - throw new BuildException("Android Target is not set."); - } - - // load up the sdk targets. - final ArrayList<String> messages = new ArrayList<String>(); - SdkManager manager = SdkManager.createManager(sdkOsPath, new ISdkLog() { - public void error(Throwable t, String errorFormat, Object... args) { - if (errorFormat != null) { - messages.add(String.format("Error: " + errorFormat, args)); - } - if (t != null) { - messages.add("Error: " + t.getMessage()); - } - } - - public void printf(String msgFormat, Object... args) { - messages.add(String.format(msgFormat, args)); - } - - public void warning(String warningFormat, Object... args) { - messages.add(String.format("Warning: " + warningFormat, args)); - } - }); - - if (manager == null) { - // since we failed to parse the SDK, lets display the parsing output. - for (String msg : messages) { - System.out.println(msg); - } - throw new BuildException("Failed to parse SDK content."); - } - - // resolve it - IAndroidTarget androidTarget = manager.getTargetFromHashString(targetHashString); - - if (androidTarget == null) { - throw new BuildException(String.format( - "Unable to resolve target '%s'", targetHashString)); - } - - // display the project info - System.out.println("Project Target: " + androidTarget.getName()); - if (androidTarget.isPlatform() == false) { - System.out.println("Vendor: " + androidTarget.getVendor()); - System.out.println("Platform Version: " + androidTarget.getVersionName()); - } - System.out.println("API level: " + androidTarget.getVersion().getApiString()); - - // check if the project is a library - boolean isLibrary = false; - - String libraryProp = antProject.getProperty(ProjectProperties.PROPERTY_LIBRARY); - if (libraryProp != null) { - isLibrary = Boolean.valueOf(libraryProp).booleanValue(); - } - - if (isLibrary) { - System.out.println("Project Type: Android Library"); - } - - // look for referenced libraries. - processReferencedLibraries(antProject, androidTarget); - - // always check the manifest minSdkVersion. - checkManifest(antProject, androidTarget.getVersion()); - - // sets up the properties to find android.jar/framework.aidl/target tools - String androidJar = androidTarget.getPath(IAndroidTarget.ANDROID_JAR); - antProject.setProperty(AntConstants.PROP_ANDROID_JAR, androidJar); - - String androidAidl = androidTarget.getPath(IAndroidTarget.ANDROID_AIDL); - antProject.setProperty(AntConstants.PROP_ANDROID_AIDL, androidAidl); - - Path includePath = new Path(antProject); - PathElement element = includePath.createPathElement(); - element.setPath(androidTarget.getPath(IAndroidTarget.ANDROID_RS)); - element = includePath.createPathElement(); - element.setPath(androidTarget.getPath(IAndroidTarget.ANDROID_RS_CLANG)); - antProject.setProperty(AntConstants.PROP_ANDROID_RENDERSCRIPT, includePath.toString()); - - antProject.setProperty(AntConstants.PROP_AAPT, androidTarget.getPath(IAndroidTarget.AAPT)); - antProject.setProperty(AntConstants.PROP_AIDL, androidTarget.getPath(IAndroidTarget.AIDL)); - antProject.setProperty(AntConstants.PROP_DX, androidTarget.getPath(IAndroidTarget.DX)); - antProject.setProperty(AntConstants.PROP_RENDERSCRIPT, - sdkOsPath + SdkConstants.OS_SDK_PLATFORM_TOOLS_FOLDER + - SdkConstants.FN_RENDERSCRIPT); - - // sets up the boot classpath - - // create the Path object - Path bootclasspath = new Path(antProject); - - // create a PathElement for the framework jar - element = bootclasspath.createPathElement(); - element.setPath(androidJar); - - // create PathElement for each optional library. - IOptionalLibrary[] libraries = androidTarget.getOptionalLibraries(); - if (libraries != null) { - HashSet<String> visitedJars = new HashSet<String>(); - for (IOptionalLibrary library : libraries) { - String jarPath = library.getJarPath(); - if (visitedJars.contains(jarPath) == false) { - visitedJars.add(jarPath); - - element = bootclasspath.createPathElement(); - element.setPath(library.getJarPath()); - } - } - } - - // finally sets the path in the project with a reference - antProject.addReference(AntConstants.PROP_CLASSPATH_REF, bootclasspath); - - // Now the import section. This is only executed if the task actually has to import a file. - if (mDoImport) { - // check the ant folder exists in the tools folder of the SDK. - File rulesFolder = new File( - new File(sdkOsPath, SdkConstants.FD_TOOLS), - SdkConstants.FD_ANT); - - // make sure the file exists. - if (rulesFolder.isDirectory() == false) { - throw new BuildException(String.format("Rules directory '%s' is missing.", - rulesFolder.getAbsolutePath())); - } - - // name of the rules files to import based on the type of project - String importedRulesFileName = - isLibrary ? RULES_LIBRARY : isTestProject ? RULES_TEST : RULES_MAIN; - - // now check the rules file exists. - File rules = new File(rulesFolder, importedRulesFileName); - - if (rules.isFile() == false) { - throw new BuildException(String.format("Build rules file '%s' is missing.", - rules)); - } - - // display the file being imported. - // figure out the path relative to the SDK - String rulesOsPath = rules.getAbsolutePath(); - if (rulesOsPath.startsWith(sdkOsPath)) { - rulesOsPath = rulesOsPath.substring(sdkOsPath.length()); - if (rulesOsPath.startsWith(File.separator)) { - rulesOsPath = rulesOsPath.substring(1); - } - } - System.out.println("\nImporting rules file: " + rulesOsPath); - - // set the file location to import - setFile(rules.getAbsolutePath()); - - // and import - super.execute(); - } - } - - /** - * Sets the value of the "import" attribute. - * @param value the value. - */ - public void setImport(boolean value) { - mDoImport = value; - } +public final class SetupTask extends Task { /** - * Checks the manifest <code>minSdkVersion</code> attribute. - * @param antProject the ant project - * @param androidVersion the version of the platform the project is compiling against. - */ - private void checkManifest(Project antProject, AndroidVersion androidVersion) { - try { - File manifest = new File(antProject.getBaseDir(), SdkConstants.FN_ANDROID_MANIFEST_XML); - - XPath xPath = AndroidXPathFactory.newXPath(); - - // check the package name. - String value = xPath.evaluate( - "/" + AndroidManifest.NODE_MANIFEST + - "/@" + AndroidManifest.ATTRIBUTE_PACKAGE, - new InputSource(new FileInputStream(manifest))); - if (value != null) { // aapt will complain if it's missing. - // only need to check that the package has 2 segments - if (value.indexOf('.') == -1) { - throw new BuildException(String.format( - "Application package '%1$s' must have a minimum of 2 segments.", - value)); - } - } - - // check the minSdkVersion value - value = xPath.evaluate( - "/" + AndroidManifest.NODE_MANIFEST + - "/" + AndroidManifest.NODE_USES_SDK + - "/@" + AndroidXPathFactory.DEFAULT_NS_PREFIX + ":" + - AndroidManifest.ATTRIBUTE_MIN_SDK_VERSION, - new InputSource(new FileInputStream(manifest))); - - if (androidVersion.isPreview()) { - // in preview mode, the content of the minSdkVersion must match exactly the - // platform codename. - String codeName = androidVersion.getCodename(); - if (codeName.equals(value) == false) { - throw new BuildException(String.format( - "For '%1$s' SDK Preview, attribute minSdkVersion in AndroidManifest.xml must be '%1$s'", - codeName)); - } - } else if (value.length() > 0) { - // for normal platform, we'll only display warnings if the value is lower or higher - // than the target api level. - // First convert to an int. - int minSdkValue = -1; - try { - minSdkValue = Integer.parseInt(value); - } catch (NumberFormatException e) { - // looks like it's not a number: error! - throw new BuildException(String.format( - "Attribute %1$s in AndroidManifest.xml must be an Integer!", - AndroidManifest.ATTRIBUTE_MIN_SDK_VERSION)); - } - - int projectApiLevel = androidVersion.getApiLevel(); - if (minSdkValue < projectApiLevel) { - System.out.println(String.format( - "WARNING: Attribute %1$s in AndroidManifest.xml (%2$d) is lower than the project target API level (%3$d)", - AndroidManifest.ATTRIBUTE_MIN_SDK_VERSION, - minSdkValue, projectApiLevel)); - } else if (minSdkValue > androidVersion.getApiLevel()) { - System.out.println(String.format( - "WARNING: Attribute %1$s in AndroidManifest.xml (%2$d) is higher than the project target API level (%3$d)", - AndroidManifest.ATTRIBUTE_MIN_SDK_VERSION, - minSdkValue, projectApiLevel)); - } - } else { - // no minSdkVersion? display a warning - System.out.println( - "WARNING: No minSdkVersion value set. Application will install on all Android versions."); - } - - } catch (XPathExpressionException e) { - throw new BuildException(e); - } catch (FileNotFoundException e) { - throw new BuildException(e); - } - } - - private void processReferencedLibraries(Project antProject, IAndroidTarget androidTarget) { - // prepare several paths for future tasks - Path sourcePath = new Path(antProject); - Path resPath = new Path(antProject); - Path libsPath = new Path(antProject); - Path jarsPath = new Path(antProject); - StringBuilder sb = new StringBuilder(); - - FilenameFilter filter = new FilenameFilter() { - public boolean accept(File dir, String name) { - return name.toLowerCase().endsWith(".jar"); - } - }; - - System.out.println("\n------------------\nResolving library dependencies:"); - - ArrayList<File> libraries = getProjectLibraries(antProject); - - if (libraries.size() > 0) { - System.out.println("------------------\nOrdered libraries:"); - - for (File library : libraries) { - System.out.println(library.getAbsolutePath()); - - // get the source path. default is src but can be overriden by the property - // "source.dir" in build.properties. - PathElement element = sourcePath.createPathElement(); - ProjectProperties prop = ProjectProperties.load(new FolderWrapper(library), - PropertyType.BUILD); - - String sourceDir = SdkConstants.FD_SOURCES; - if (prop != null) { - String value = prop.getProperty(ProjectProperties.PROPERTY_BUILD_SOURCE_DIR); - if (value != null) { - sourceDir = value; - } - } - - String path = library.getAbsolutePath(); - - element.setPath(path + "/" + sourceDir); - - // get the res path. Always $PROJECT/res - element = resPath.createPathElement(); - element.setPath(path + "/" + SdkConstants.FD_RESOURCES); - - // get the libs path. Always $PROJECT/libs - element = libsPath.createPathElement(); - element.setPath(path + "/" + SdkConstants.FD_NATIVE_LIBS); - - // get the jars from it too - File libsFolder = new File(library, SdkConstants.FD_NATIVE_LIBS); - File[] jarFiles = libsFolder.listFiles(filter); - if (jarFiles != null) { - for (File jarFile : jarFiles) { - element = jarsPath.createPathElement(); - element.setPath(jarFile.getAbsolutePath()); - } - } - - // get the package from the manifest. - FileWrapper manifest = new FileWrapper(library, - SdkConstants.FN_ANDROID_MANIFEST_XML); - - try { - String value = AndroidManifest.getPackage(manifest); - if (value != null) { // aapt will complain if it's missing. - sb.append(';'); - sb.append(value); - } - } catch (Exception e) { - throw new BuildException(e); - } - } - } else { - System.out.println("No library dependencies.\n"); - } - - System.out.println("------------------\n"); - - // even with no libraries, always setup these so that various tasks in Ant don't complain - // (the task themselves can handle a ref to an empty Path) - antProject.addReference(AntConstants.PROP_PROJECT_LIBS_SRC_REF, sourcePath); - antProject.addReference(AntConstants.PROP_PROJECT_LIBS_JARS_REF, jarsPath); - antProject.addReference(AntConstants.PROP_PROJECT_LIBS_LIBS_REF, libsPath); - - // the rest is done only if there's a library. - if (sourcePath.list().length > 0) { - antProject.addReference(AntConstants.PROP_PROJECT_LIBS_RES_REF, resPath); - antProject.setProperty(AntConstants.PROP_PROJECT_LIBS_PKG, sb.toString()); - } - } - - /** - * Returns all the library dependencies of a given Ant project. - * @param antProject the Ant project - * @return a list of properties, sorted from highest priority to lowest. - */ - private ArrayList<File> getProjectLibraries(final Project antProject) { - ArrayList<File> libraries = new ArrayList<File>(); - File baseDir = antProject.getBaseDir(); - - // get the top level list of library dependencies. - ArrayList<File> topLevelLibraries = getDirectDependencies(baseDir, new IPropertySource() { - public String getProperty(String name) { - return antProject.getProperty(name); - } - }); - - // process the libraries in case they depend on other libraries. - resolveFullLibraryDependencies(topLevelLibraries, libraries); - - return libraries; - } - - /** - * Resolves a given list of libraries, finds out if they depend on other libraries, and - * returns a full list of all the direct and indirect dependencies in the proper order (first - * is higher priority when calling aapt). - * @param inLibraries the libraries to resolve - * @param outLibraries where to store all the libraries. - */ - private void resolveFullLibraryDependencies(ArrayList<File> inLibraries, - ArrayList<File> outLibraries) { - // loop in the inverse order to resolve dependencies on the libraries, so that if a library - // is required by two higher level libraries it can be inserted in the correct place - for (int i = inLibraries.size() - 1 ; i >= 0 ; i--) { - File library = inLibraries.get(i); - - // get the default.property file for it - final ProjectProperties defaultProp = ProjectProperties.load( - new FolderWrapper(library), PropertyType.DEFAULT); - - // get its libraries - ArrayList<File> dependencies = getDirectDependencies(library, new IPropertySource() { - public String getProperty(String name) { - return defaultProp.getProperty(name); - } - }); - - // resolve the dependencies for those libraries - resolveFullLibraryDependencies(dependencies, outLibraries); - - // and add the current one (if needed) in front (higher priority) - if (outLibraries.contains(library) == false) { - outLibraries.add(0, library); - } - } - } - - public interface IPropertySource { - String getProperty(String name); - } - - /** - * Returns the top level library dependencies of a given <var>source</var> representing a - * project properties. - * @param baseFolder the base folder of the project (to resolve relative paths) - * @param source a source of project properties. - */ - private ArrayList<File> getDirectDependencies(File baseFolder, IPropertySource source) { - ArrayList<File> libraries = new ArrayList<File>(); - - // first build the list. they are ordered highest priority first. - int index = 1; - while (true) { - String propName = ProjectProperties.PROPERTY_LIB_REF + Integer.toString(index++); - String rootPath = source.getProperty(propName); - - if (rootPath == null) { - break; - } - - try { - File library = new File(baseFolder, rootPath).getCanonicalFile(); - - // check for validity - File defaultProp = new File(library, PropertyType.DEFAULT.getFilename()); - if (defaultProp.isFile() == false) { - // error! - throw new BuildException(String.format( - "%1$s resolve to a path with no %2$s file for project %3$s", rootPath, - PropertyType.DEFAULT.getFilename(), baseFolder.getAbsolutePath())); - } - - if (libraries.contains(library) == false) { - System.out.println(String.format("%1$s: %2$s => %3$s", - baseFolder.getAbsolutePath(), rootPath, library.getAbsolutePath())); - - libraries.add(library); - } - } catch (IOException e) { - throw new BuildException("Failed to resolve library path: " + rootPath, e); - } - } - - return libraries; - } - - /** - * Returns the Ant version as a {@link DeweyDecimal} object. - * - * This is based on the implementation of - * org.apache.tools.ant.taskdefs.condition.AntVersion.getVersion() + * @param b unused. * - * @param antProject the current ant project. - * @return the ant version. + * @deprecated only present because the original {@link SetupTask} extends {@link ImportTask}. */ - private DeweyDecimal getVersion(Project antProject) { - char[] versionString = antProject.getProperty("ant.version").toCharArray(); - StringBuffer sb = new StringBuffer(); - boolean foundFirstDigit = false; - for (int i = 0; i < versionString.length; i++) { - if (Character.isDigit(versionString[i])) { - sb.append(versionString[i]); - foundFirstDigit = true; - } - if (versionString[i] == '.' && foundFirstDigit) { - sb.append(versionString[i]); - } - if (Character.isLetter(versionString[i]) && foundFirstDigit) { - break; - } - } - return new DeweyDecimal(sb.toString()); + @Deprecated + public void setImport(boolean b) { + // do nothing } + @Override + public void execute() throws BuildException { + throw new BuildException("\n\nError. You are using an obsolete build.xml\n" + + "You need to delete it and regenerate it using\n" + + "\tandroid update project\n"); + } } diff --git a/changes.txt b/changes.txt index 73bd92989..fcb0f3cf7 100644 --- a/changes.txt +++ b/changes.txt @@ -1,14 +1,32 @@ Change log for Android SDK Tools. -Revision 11: +Revision 14: + +- Build performance improvements: + * resource compilation and packaging now properly use dependency to only + be executed if a resource changed. + * Optimized resource compilation for projects with libraries. This should + speed up this phase significantly for large projects with libraries. + * PNG files that are optimized during resource packaging are now cached + and only re-optimized if they changed instead of doing at every build. +- New library project mechanism: + * + +Revision 13: + +Revision 12: (07/2011): +- The AVD manager and emulator can now use system images compiled for ARM v7 and + x86 CPUs. + +Revision 11 (05/2011): - See eclipse/changes.txt for ADT related changes. -Revision 10: +Revision 10 (02/2011): - The tools now automatically generate Java Programming Language source files (in the gen directory) and bytecode (in the res/raw directory) from your native .rs files -Revision 9: +Revision 9 (01/2011): - Fix packaging issue that broke draw9patch - Ant build rules will now check the Ant version and fail if it's older than 1.8 - Fix "folder locked" errors when installing packages in SDK Manager on Windows. @@ -22,7 +40,7 @@ Revision 9: - Logcat view in DDMS now properly displays UTF-8 characters. -Revision 8: +Revision 8 (12/2010): - New SDK component: platform-tools. This makes all platforms use the same latest version of aapt/aidl/dx. - Support for true debug build. No need to change the value of debuggable in @@ -36,7 +54,7 @@ Revision 8: (default to "ascii", "1.5" and "1.5") -Revision 7: +Revision 7 (09/2010): - Support for Ant rules provided by the Tools components (override the one in the platform component) - Added support for libraries with library dependencies. diff --git a/eclipse/changes.txt b/eclipse/changes.txt index 9d53db78d..00a5a4683 100644 --- a/eclipse/changes.txt +++ b/eclipse/changes.txt @@ -1,3 +1,9 @@ +13.0.0 +- Build system: + - ADT now uses bin/classes to output the java compilation + and bin/ for Android specific classes. This will make bin show up + in the package explorer. + 12.0.0 - Many bug fixes! - Visual Layout Editor: diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/META-INF/MANIFEST.MF b/eclipse/plugins/com.android.ide.eclipse.adt/META-INF/MANIFEST.MF index 4ab4f583a..093ac9a82 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/META-INF/MANIFEST.MF +++ b/eclipse/plugins/com.android.ide.eclipse.adt/META-INF/MANIFEST.MF @@ -72,7 +72,7 @@ Export-Package: com.android, com.android.ide.eclipse.adt.internal.build;x-friends:="com.android.ide.eclipse.tests", com.android.ide.eclipse.adt.internal.build.builders;x-friends:="com.android.ide.eclipse.tests", com.android.ide.eclipse.adt.internal.editors;x-friends:="com.android.ide.eclipse.tests", - com.android.ide.eclipse.adt.internal.editors.binaryxml, + com.android.ide.eclipse.adt.internal.editors.binaryxml;x-friends:="com.android.ide.eclipse.tests", com.android.ide.eclipse.adt.internal.editors.descriptors;x-friends:="com.android.ide.eclipse.tests", com.android.ide.eclipse.adt.internal.editors.export;x-friends:="com.android.ide.eclipse.tests", com.android.ide.eclipse.adt.internal.editors.layout;x-friends:="com.android.ide.eclipse.tests", diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/plugin.xml b/eclipse/plugins/com.android.ide.eclipse.adt/plugin.xml index e828e51bb..a0b77b4af 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/plugin.xml +++ b/eclipse/plugins/com.android.ide.eclipse.adt/plugin.xml @@ -392,11 +392,11 @@ <extension point="org.eclipse.jdt.core.classpathContainerInitializer"> <classpathContainerInitializer class="com.android.ide.eclipse.adt.internal.project.AndroidClasspathContainerInitializer" - id="com.android.ide.eclipse.adt.project.AndroidClasspathContainerInitializer"> + id="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"> </classpathContainerInitializer> <classpathContainerInitializer class="com.android.ide.eclipse.adt.internal.project.AndroidClasspathContainerInitializer" - id="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"> + id="com.android.ide.eclipse.adt.LIBRARIES"> </classpathContainerInitializer> </extension> <extension point="org.eclipse.jdt.ui.classpathContainerPage"> diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtConstants.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtConstants.java index ac2e4dde4..e3ec83ccb 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtConstants.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtConstants.java @@ -56,6 +56,14 @@ public class AdtConstants { /** Nature of Android export projects */ public final static String NATURE_EXPORT = "com.android.ide.eclipse.adt.AndroidExportNature"; //$NON-NLS-1$ + /** The container id for the android framework jar file */ + public final static String CONTAINER_FRAMEWORK = + "com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"; //$NON-NLS-1$ + + /** The container id for the libraries */ + public final static String CONTAINER_LIBRARIES = "com.android.ide.eclipse.adt.LIBRARIES"; //$NON-NLS-1$ + + /** Separator for workspace path, i.e. "/". */ public final static String WS_SEP = "/"; //$NON-NLS-1$ /** Separator character for workspace path, i.e. '/'. */ diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/BuildHelper.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/BuildHelper.java index 1231de300..af231855e 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/BuildHelper.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/BuildHelper.java @@ -33,6 +33,7 @@ import com.android.sdklib.build.ApkBuilder.JarStatus; import com.android.sdklib.build.ApkBuilder.SigningInfo; import com.android.sdklib.build.ApkCreationException; import com.android.sdklib.build.DuplicateFileException; +import com.android.sdklib.build.IArchiveBuilder; import com.android.sdklib.build.SealedApkException; import com.android.sdklib.internal.build.DebugKeyProvider; import com.android.sdklib.internal.build.DebugKeyProvider.KeytoolException; @@ -51,6 +52,7 @@ import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; +import org.eclipse.jdt.core.IClasspathContainer; import org.eclipse.jdt.core.IClasspathEntry; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.JavaCore; @@ -135,7 +137,6 @@ public class BuildHelper { mVerbose = verbose; } - public void updateCrunchCache() throws AaptExecException, AaptResultException { // Benchmarking start long startCrunchTime = 0; @@ -166,6 +167,12 @@ public class BuildHelper { AdtPlugin.printBuildToConsole(BuildVerbosity.ALWAYS, mProject, msg); } } + + public static void writeResources(IArchiveBuilder builder, IJavaProject javaProject) + throws DuplicateFileException, ApkCreationException, SealedApkException, CoreException { + writeStandardResources(builder, javaProject, null); + } + /** * Packages the resources of the projet into a .ap_ file. * @param manifestFile the manifest of the project. @@ -881,7 +888,7 @@ public class BuildHelper { * Writes the standard resources of a project and its referenced projects * into a {@link SignedJarBuilder}. * Standard resources are non java/aidl files placed in the java package folders. - * @param apkBuilder the {@link ApkBuilder}. + * @param builder the archive builder. * @param javaProject the javaProject object. * @param referencedJavaProjects the java projects that this project references. * @throws ApkCreationException if an error occurred @@ -890,25 +897,24 @@ public class BuildHelper { * at the same location inside the APK archive. * @throws CoreException */ - private void writeStandardResources(ApkBuilder apkBuilder, IJavaProject javaProject, + private static void writeStandardResources(IArchiveBuilder builder, IJavaProject javaProject, List<IJavaProject> referencedJavaProjects) throws DuplicateFileException, ApkCreationException, SealedApkException, CoreException { IWorkspace ws = ResourcesPlugin.getWorkspace(); IWorkspaceRoot wsRoot = ws.getRoot(); - // create a list of path already put into the archive, in order to detect conflict - ArrayList<String> list = new ArrayList<String>(); - - writeStandardProjectResources(apkBuilder, javaProject, wsRoot, list); + writeStandardProjectResources(builder, javaProject, wsRoot); - for (IJavaProject referencedJavaProject : referencedJavaProjects) { - // only include output from non android referenced project - // (This is to handle the case of reference Android projects in the context of - // instrumentation projects that need to reference the projects to be tested). - if (referencedJavaProject.getProject().hasNature( - AdtConstants.NATURE_DEFAULT) == false) { - writeStandardProjectResources(apkBuilder, referencedJavaProject, wsRoot, list); + if (referencedJavaProjects != null) { + for (IJavaProject referencedJavaProject : referencedJavaProjects) { + // only include output from non android referenced project + // (This is to handle the case of reference Android projects in the context of + // instrumentation projects that need to reference the projects to be tested). + if (referencedJavaProject.getProject().hasNature( + AdtConstants.NATURE_DEFAULT) == false) { + writeStandardProjectResources(builder, referencedJavaProject, wsRoot); + } } } } @@ -919,15 +925,14 @@ public class BuildHelper { * @param jarBuilder the {@link ApkBuilder}. * @param javaProject the javaProject object. * @param wsRoot the {@link IWorkspaceRoot}. - * @param list a list of files already added to the archive, to detect conflicts. * @throws ApkCreationException if an error occurred * @throws SealedApkException if the APK is already sealed. * @throws DuplicateFileException if a file conflicts with another already added to the APK * at the same location inside the APK archive. * @throws CoreException */ - private void writeStandardProjectResources(ApkBuilder apkBuilder, - IJavaProject javaProject, IWorkspaceRoot wsRoot, ArrayList<String> list) + private static void writeStandardProjectResources(IArchiveBuilder builder, + IJavaProject javaProject, IWorkspaceRoot wsRoot) throws DuplicateFileException, ApkCreationException, SealedApkException, CoreException { // get the source pathes List<IPath> sourceFolders = BaseProjectHelper.getSourceClasspaths(javaProject); @@ -936,14 +941,14 @@ public class BuildHelper { for (IPath sourcePath : sourceFolders) { IResource sourceResource = wsRoot.findMember(sourcePath); if (sourceResource != null && sourceResource.getType() == IResource.FOLDER) { - writeFolderResources(apkBuilder, javaProject, (IFolder) sourceResource); + writeFolderResources(builder, javaProject, (IFolder) sourceResource); } } } - private void writeFolderResources(ApkBuilder apkBuilder, final IJavaProject javaProject, - IFolder root) throws CoreException, ApkCreationException, - SealedApkException, DuplicateFileException { + private static void writeFolderResources(IArchiveBuilder builder, + final IJavaProject javaProject, IFolder root) throws CoreException, + ApkCreationException, SealedApkException, DuplicateFileException { final List<IPath> pathsToPackage = new ArrayList<IPath>(); root.accept(new IResourceProxyVisitor() { public boolean visit(IResourceProxy proxy) throws CoreException { @@ -969,7 +974,7 @@ public class BuildHelper { IPath rootLocation = root.getLocation(); for (IPath path : pathsToPackage) { IPath archivePath = path.makeRelativeTo(rootLocation); - apkBuilder.addFile(path.toFile(), archivePath.toString()); + builder.addFile(path.toFile(), archivePath.toString()); } } @@ -987,60 +992,35 @@ public class BuildHelper { IWorkspaceRoot wsRoot = ResourcesPlugin.getWorkspace().getRoot(); ArrayList<String> oslibraryList = new ArrayList<String>(); + + // we could use IJavaProject.getResolvedClasspath directly, but we actually + // want to see the containers themselves. IClasspathEntry[] classpaths = javaProject.readRawClasspath(); if (classpaths != null) { for (IClasspathEntry e : classpaths) { - if (e.getEntryKind() == IClasspathEntry.CPE_LIBRARY || - e.getEntryKind() == IClasspathEntry.CPE_VARIABLE) { - // if this is a classpath variable reference, we resolve it. - if (e.getEntryKind() == IClasspathEntry.CPE_VARIABLE) { - e = JavaCore.getResolvedClasspathEntry(e); - } - - // get the IPath - IPath path = e.getPath(); - - IResource resource = wsRoot.findMember(path); - // case of a jar file (which could be relative to the workspace or a full path) - if (AdtConstants.EXT_JAR.equalsIgnoreCase(path.getFileExtension())) { - if (resource != null && resource.exists() && - resource.getType() == IResource.FILE) { - oslibraryList.add(resource.getLocation().toOSString()); - } else { - // if the jar path doesn't match a workspace resource, - // then we get an OSString and check if this links to a valid file. - String osFullPath = path.toOSString(); - - File f = new File(osFullPath); - if (f.isFile()) { - oslibraryList.add(osFullPath); - } else { - String message = String.format( Messages.Couldnt_Locate_s_Error, - path); - // always output to the console - mOutStream.println(message); - - // put a marker - if (resMarker != null) { - resMarker.setWarning(mProject, message); - } - } - } - } else { - // this can be the case for a class folder. - if (resource != null && resource.exists() && - resource.getType() == IResource.FOLDER) { - oslibraryList.add(resource.getLocation().toOSString()); - } else { - // if the path doesn't match a workspace resource, - // then we get an OSString and check if this links to a valid folder. - String osFullPath = path.toOSString(); + // if this is a classpath variable reference, we resolve it. + if (e.getEntryKind() == IClasspathEntry.CPE_VARIABLE) { + e = JavaCore.getResolvedClasspathEntry(e); + } - File f = new File(osFullPath); - if (f.isDirectory()) { - oslibraryList.add(osFullPath); + if (e.getEntryKind() == IClasspathEntry.CPE_LIBRARY) { + handleClasspathEntry(e, wsRoot, oslibraryList, resMarker); + } else if (e.getEntryKind() == IClasspathEntry.CPE_CONTAINER) { + // get the container + try { + IClasspathContainer container = JavaCore.getClasspathContainer( + e.getPath(), javaProject); + // ignore the system and default_system types as they represent + // libraries that are part of the runtime. + if (container.getKind() == IClasspathContainer.K_APPLICATION) { + IClasspathEntry[] entries = container.getClasspathEntries(); + for (IClasspathEntry entry : entries) { + handleClasspathEntry(entry, wsRoot, oslibraryList, resMarker); } } + } catch (JavaModelException jme) { + // can't resolve the container? ignore it. + AdtPlugin.log(jme, "Failed to resolve ClasspathContainer: %s", e.getPath()); } } } @@ -1049,6 +1029,55 @@ public class BuildHelper { return oslibraryList.toArray(new String[oslibraryList.size()]); } + private void handleClasspathEntry(IClasspathEntry e, IWorkspaceRoot wsRoot, + ArrayList<String> oslibraryList, ResourceMarker resMarker) { + // get the IPath + IPath path = e.getPath(); + + IResource resource = wsRoot.findMember(path); + // case of a jar file (which could be relative to the workspace or a full path) + if (AdtConstants.EXT_JAR.equalsIgnoreCase(path.getFileExtension())) { + if (resource != null && resource.exists() && + resource.getType() == IResource.FILE) { + oslibraryList.add(resource.getLocation().toOSString()); + } else { + // if the jar path doesn't match a workspace resource, + // then we get an OSString and check if this links to a valid file. + String osFullPath = path.toOSString(); + + File f = new File(osFullPath); + if (f.isFile()) { + oslibraryList.add(osFullPath); + } else { + String message = String.format( Messages.Couldnt_Locate_s_Error, + path); + // always output to the console + mOutStream.println(message); + + // put a marker + if (resMarker != null) { + resMarker.setWarning(mProject, message); + } + } + } + } else { + // this can be the case for a class folder. + if (resource != null && resource.exists() && + resource.getType() == IResource.FOLDER) { + oslibraryList.add(resource.getLocation().toOSString()); + } else { + // if the path doesn't match a workspace resource, + // then we get an OSString and check if this links to a valid folder. + String osFullPath = path.toOSString(); + + File f = new File(osFullPath); + if (f.isDirectory()) { + oslibraryList.add(osFullPath); + } + } + } + } + /** * Returns the list of the output folders for the specified {@link IJavaProject} objects, if * they are Android projects. diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PostCompilerBuilder.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PostCompilerBuilder.java index 15aedaf1c..07a2a2599 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PostCompilerBuilder.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PostCompilerBuilder.java @@ -38,6 +38,7 @@ import com.android.prefs.AndroidLocation.AndroidLocationException; import com.android.sdklib.SdkConstants; import com.android.sdklib.build.ApkCreationException; import com.android.sdklib.build.DuplicateFileException; +import com.android.sdklib.build.SealedApkException; import com.android.sdklib.internal.build.DebugKeyProvider.KeytoolException; import org.eclipse.core.resources.IContainer; @@ -58,9 +59,18 @@ import org.eclipse.jdt.core.JavaCore; import org.eclipse.jdt.core.JavaModelException; import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.jar.Attributes; +import java.util.jar.JarEntry; +import java.util.jar.JarOutputStream; +import java.util.jar.Manifest; +import java.util.regex.Pattern; public class PostCompilerBuilder extends BaseBuilder { @@ -253,11 +263,14 @@ public class PostCompilerBuilder extends BaseBuilder { try { // get the project info ProjectState projectState = Sdk.getProjectState(project); - if (projectState == null || projectState.isLibrary()) { - // library project do not need to be dexified or packaged. + + // this can happen if the project has no default.properties. + if (projectState == null) { return null; } + boolean isLibrary = projectState.isLibrary(); + // get the libraries List<IProject> libProjects = projectState.getFullLibraryProjects(); @@ -294,6 +307,7 @@ public class PostCompilerBuilder extends BaseBuilder { AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, project, Messages.Start_Full_Apk_Build); + // Full build: we do all the steps. mUpdateCrunchCache = true; mPackageResources = true; mConvertToDex = true; @@ -305,6 +319,7 @@ public class PostCompilerBuilder extends BaseBuilder { // go through the resources and see if something changed. IResourceDelta delta = getDelta(project); if (delta == null) { + // no delta? Same as full build: we do all the steps. mUpdateCrunchCache = true; mPackageResources = true; mConvertToDex = true; @@ -397,6 +412,25 @@ public class PostCompilerBuilder extends BaseBuilder { Messages.Start_Full_Post_Compiler); } + // finished with the common init and tests. Special case of the library. + if (isLibrary) { + // check the jar output file is present, if not create it. + IFile jarIFile = androidOutputFolder.getFile( + project.getName().toLowerCase() + AdtConstants.DOT_JAR); + if (mConvertToDex == false && jarIFile.exists() == false) { + mConvertToDex = true; + } + + if (mConvertToDex) { + IFolder javaOutputFolder = BaseProjectHelper.getJavaOutputFolder(project); + + writeLibraryPackage(jarIFile, project, javaOutputFolder, + referencedJavaProjects); + } + + return allRefProjects; + } + // first thing we do is check that the SDK directory has been setup. String osSdkFolder = AdtPlugin.getOsSdkFolder(); @@ -716,6 +750,141 @@ public class PostCompilerBuilder extends BaseBuilder { return allRefProjects; } + private static class JarBuilder implements IArchiveBuilder { + + private static Pattern R_PATTERN = Pattern.compile("R(\\$.*)?\\.class"); //$NON-NLS-1$ + + private final byte[] buffer = new byte[1024]; + private final JarOutputStream mOutputStream; + + JarBuilder(JarOutputStream outputStream) { + mOutputStream = outputStream; + } + + public void addFile(IFile file, IFolder rootFolder) throws ApkCreationException { + // we only package class file from the output folder + if (AdtConstants.EXT_CLASS.equals(file.getFileExtension()) == false) { + return; + } + + // we don't package any R[$*] classes. + String name = file.getName(); + if (R_PATTERN.matcher(name).matches()) { + return; + } + + IPath path = file.getFullPath().makeRelativeTo(rootFolder.getFullPath()); + try { + addFile(file.getContents(), file.getLocalTimeStamp(), path.toString()); + } catch (ApkCreationException e) { + throw e; + } catch (Exception e) { + throw new ApkCreationException(e, "Failed to add %s", file); + } + } + + public void addFile(File file, String archivePath) throws ApkCreationException, + SealedApkException, DuplicateFileException { + try { + FileInputStream inputStream = new FileInputStream(file); + long lastModified = file.lastModified(); + addFile(inputStream, lastModified, archivePath); + } catch (ApkCreationException e) { + throw e; + } catch (Exception e) { + throw new ApkCreationException(e, "Failed to add %s", file); + } + } + + private void addFile(InputStream content, long lastModified, String archivePath) + throws IOException, ApkCreationException { + // create the jar entry + JarEntry entry = new JarEntry(archivePath); + entry.setTime(lastModified); + + try { + // add the entry to the jar archive + mOutputStream.putNextEntry(entry); + + // read the content of the entry from the input stream, and write + // it into the archive. + int count; + while ((count = content.read(buffer)) != -1) { + mOutputStream.write(buffer, 0, count); + } + } finally { + try { + if (content != null) { + content.close(); + } + } catch (Exception e) { + throw new ApkCreationException(e, "Failed to close stream"); + } + } + } + } + + private void writeLibraryPackage(IFile jarIFile, IProject project, IFolder javaOutputFolder, + List<IJavaProject> referencedJavaProjects) { + + JarOutputStream jos = null; + try { + Manifest manifest = new Manifest(); + Attributes mainAttributes = manifest.getMainAttributes(); + mainAttributes.put(Attributes.Name.CLASS_PATH, "Android ADT"); //$NON-NLS-1$ + mainAttributes.putValue("Created-By", "1.0 (Android)"); //$NON-NLS-1$ //$NON-NLS-2$ + jos = new JarOutputStream( + new FileOutputStream(jarIFile.getLocation().toFile()), manifest); + + JarBuilder jarBuilder = new JarBuilder(jos); + + // write the class files + writeClassFilesIntoJar(jarBuilder, javaOutputFolder, javaOutputFolder); + + // now write the standard Java resources + BuildHelper.writeResources(jarBuilder, JavaCore.create(project)); + + // do the same for all the referencedJava project + for (IJavaProject javaProject : referencedJavaProjects) { + IFolder refProjectOutput = BaseProjectHelper.getJavaOutputFolder( + javaProject.getProject()); + + if (refProjectOutput != null) { + // write the class files + writeClassFilesIntoJar(jarBuilder, refProjectOutput, refProjectOutput); + + // now write the standard Java resources + BuildHelper.writeResources(jarBuilder, javaProject); + } + } + + saveProjectBooleanProperty(PROPERTY_CONVERT_TO_DEX , mConvertToDex); + } catch (Exception e) { + AdtPlugin.log(e, "Failed to write jar file %s", jarIFile.getLocation().toOSString()); + } finally { + if (jos != null) { + try { + jos.close(); + } catch (IOException e) { + // pass + } + } + } + } + + private void writeClassFilesIntoJar(JarBuilder builder, IFolder folder, IFolder rootFolder) + throws CoreException, IOException, ApkCreationException { + IResource[] members = folder.members(); + for (IResource member : members) { + if (member.getType() == IResource.FOLDER) { + writeClassFilesIntoJar(builder, (IFolder) member, rootFolder); + } else if (member.getType() == IResource.FILE) { + IFile file = (IFile) member; + builder.addFile(file, rootFolder); + } + } + } + @Override protected void startupOnInitialize() { super.startupOnInitialize(); diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PreCompilerBuilder.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PreCompilerBuilder.java index 016340117..2a988d104 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PreCompilerBuilder.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PreCompilerBuilder.java @@ -493,7 +493,8 @@ public class PreCompilerBuilder extends BaseBuilder { // generate resources. boolean compiledTheResources = mMustCompileResources; if (mMustCompileResources) { - handleResources(project, javaPackage, projectTarget, manifestFile, libProjects); + handleResources(project, javaPackage, projectTarget, manifestFile, libProjects, + projectState.isLibrary()); saveProjectBooleanProperty(PROPERTY_COMPILE_RESOURCES , false); } @@ -581,11 +582,13 @@ public class PreCompilerBuilder extends BaseBuilder { * @param projectTarget the target of the main project * @param manifest the {@link IFile} representing the project manifest * @param libProjects the library dependencies + * @param isLibrary if the project is a library project * @throws CoreException * @throws AbortBuildException */ private void handleResources(IProject project, String javaPackage, IAndroidTarget projectTarget, - IFile manifest, List<IProject> libProjects) throws CoreException, AbortBuildException { + IFile manifest, List<IProject> libProjects, boolean isLibrary) + throws CoreException, AbortBuildException { // get the resource folder IFolder resFolder = project.getFolder(AdtConstants.WS_RESOURCES); @@ -636,12 +639,15 @@ public class PreCompilerBuilder extends BaseBuilder { } } } + String libPackages = null; if (libJavaPackages != null) { libPackages = libJavaPackages.toString(); + } + execAapt(project, projectTarget, osOutputPath, osResPath, osManifestPath, - mainPackageFolder, libResFolders, libPackages); + mainPackageFolder, libResFolders, libPackages, isLibrary); } } @@ -660,11 +666,13 @@ public class PreCompilerBuilder extends BaseBuilder { * @param libResFolders the list of res folders for the library. * @param libraryPackages an optional list of javapackages to replace the main project java package. * can be null. + * @param isLibrary if the project is a library project * @throws AbortBuildException */ private void execAapt(IProject project, IAndroidTarget projectTarget, String osOutputPath, String osResPath, String osManifestPath, IFolder packageFolder, - ArrayList<IFolder> libResFolders, String libraryPackages) throws AbortBuildException { + ArrayList<IFolder> libResFolders, String libraryPackages, boolean isLibrary) + throws AbortBuildException { // We actually need to delete the manifest.java as it may become empty and // in this case aapt doesn't generate an empty one, but instead doesn't // touch it. @@ -680,6 +688,10 @@ public class PreCompilerBuilder extends BaseBuilder { array.add("-v"); //$NON-NLS-1$ } + if (isLibrary) { + array.add("--non-constant-id"); //$NON-NLS-1$ + } + if (libResFolders.size() > 0) { array.add("--auto-add-overlay"); //$NON-NLS-1$ } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutReloadMonitor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutReloadMonitor.java index ba77ce946..abc389f4c 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutReloadMonitor.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutReloadMonitor.java @@ -18,8 +18,8 @@ package com.android.ide.eclipse.adt.internal.editors.layout; import com.android.ide.common.resources.ResourceFile; import com.android.ide.common.resources.ResourceFolder; -import com.android.ide.eclipse.adt.AdtPlugin; import com.android.ide.eclipse.adt.AdtConstants; +import com.android.ide.eclipse.adt.AdtPlugin; import com.android.ide.eclipse.adt.internal.resources.manager.GlobalProjectMonitor; import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager; import com.android.ide.eclipse.adt.internal.resources.manager.GlobalProjectMonitor.IFileListener; @@ -162,6 +162,9 @@ public final class LayoutReloadMonitor { /** * Implementation of the {@link IFileListener} as an internal class so that the methods * do not appear in the public API of {@link LayoutReloadMonitor}. + * + * This is only to detect code and manifest change. Resource changes (located in res/) + * is done through {@link #mResourceListener}. */ private IFileListener mFileListener = new IFileListener() { /* @@ -183,8 +186,6 @@ public final class LayoutReloadMonitor { if (hasAndroidNature) { // project is an Android project, it's the one being affected // directly by its own file change. - // Note that resource change is handled separately, so there's no need to - // figure out if the project is a library and to update its main project(s). processFileChanged(file, project); } else { // check the projects depending on it, if they are Android project, update them. @@ -248,7 +249,6 @@ public final class LayoutReloadMonitor { } changeFlags.manifest = true; - } } }; diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/AndroidClasspathContainer.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/AndroidClasspathContainer.java index bbef6b9b2..0b891c929 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/AndroidClasspathContainer.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/AndroidClasspathContainer.java @@ -22,26 +22,31 @@ import org.eclipse.jdt.core.IClasspathEntry; /** * Classpath container for the Android projects. + * This supports both the System classpath and the library dependencies. */ class AndroidClasspathContainer implements IClasspathContainer { - - private IClasspathEntry[] mClasspathEntry; - private IPath mContainerPath; - private String mName; - + + private final IClasspathEntry[] mClasspathEntry; + private final IPath mContainerPath; + private final String mName; + private final int mKind; + /** * Constructs the container with the {@link IClasspathEntry} representing the android * framework jar file and the container id * @param entries the entries representing the android framework and optional libraries. * @param path the path containing the classpath container id. * @param name the name of the container to display. + * @param the container kind. Can be {@link IClasspathContainer#K_DEFAULT_SYSTEM} or + * {@link IClasspathContainer#K_APPLICATION} */ - AndroidClasspathContainer(IClasspathEntry[] entries, IPath path, String name) { + AndroidClasspathContainer(IClasspathEntry[] entries, IPath path, String name, int kind) { mClasspathEntry = entries; mContainerPath = path; mName = name; + mKind = kind; } - + public IClasspathEntry[] getClasspathEntries() { return mClasspathEntry; } @@ -51,7 +56,7 @@ class AndroidClasspathContainer implements IClasspathContainer { } public int getKind() { - return IClasspathContainer.K_DEFAULT_SYSTEM; + return mKind; } public IPath getPath() { diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/AndroidClasspathContainerInitializer.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/AndroidClasspathContainerInitializer.java index 9ae60c00c..d83d539ec 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/AndroidClasspathContainerInitializer.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/AndroidClasspathContainerInitializer.java @@ -17,8 +17,8 @@ package com.android.ide.eclipse.adt.internal.project; import com.android.ide.common.sdk.LoadStatus; -import com.android.ide.eclipse.adt.AdtPlugin; import com.android.ide.eclipse.adt.AdtConstants; +import com.android.ide.eclipse.adt.AdtPlugin; import com.android.ide.eclipse.adt.internal.sdk.ProjectState; import com.android.ide.eclipse.adt.internal.sdk.Sdk; import com.android.sdklib.AndroidVersion; @@ -81,10 +81,6 @@ public class AndroidClasspathContainerInitializer extends ClasspathContainerInit private final static String PROPERTY_ANDROID_SOURCE = "androidSource"; //$NON-NLS-1$ - /** The container id for the android framework jar file */ - public final static String CONTAINER_ID = - "com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"; //$NON-NLS-1$ - /** path separator to store multiple paths in a single property. This is guaranteed to not * be in a path. */ @@ -113,10 +109,10 @@ public class AndroidClasspathContainerInitializer extends ClasspathContainerInit */ @Override public void initialize(IPath containerPath, IJavaProject project) throws CoreException { - if (CONTAINER_ID.equals(containerPath.toString())) { + if (AdtConstants.CONTAINER_FRAMEWORK.equals(containerPath.toString())) { IClasspathContainer container = allocateAndroidContainer(project); if (container != null) { - JavaCore.setClasspathContainer(new Path(CONTAINER_ID), + JavaCore.setClasspathContainer(new Path(AdtConstants.CONTAINER_FRAMEWORK), new IJavaProject[] { project }, new IClasspathContainer[] { container }, new NullProgressMonitor()); @@ -125,23 +121,6 @@ public class AndroidClasspathContainerInitializer extends ClasspathContainerInit } /** - * Creates a new {@link IClasspathEntry} of type {@link IClasspathEntry#CPE_CONTAINER} - * linking to the Android Framework. - */ - public static IClasspathEntry getContainerEntry() { - return JavaCore.newContainerEntry(new Path(CONTAINER_ID)); - } - - /** - * Checks the {@link IPath} objects against the android framework container id and - * returns <code>true</code> if they are identical. - * @param path the <code>IPath</code> to check. - */ - public static boolean checkPath(IPath path) { - return CONTAINER_ID.equals(path.toString()); - } - - /** * Updates the {@link IJavaProject} objects with new android framework container. This forces * JDT to recompile them. * @param androidProjects the projects to update. @@ -164,7 +143,7 @@ public class AndroidClasspathContainerInitializer extends ClasspathContainerInit // give each project their new container in one call. JavaCore.setClasspathContainer( - new Path(CONTAINER_ID), + new Path(AdtConstants.CONTAINER_FRAMEWORK), androidProjects, containers, new NullProgressMonitor()); return true; @@ -214,7 +193,9 @@ public class AndroidClasspathContainerInitializer extends ClasspathContainerInit return new AndroidClasspathContainer( createClasspathEntries(iProject, target, targetName), - new Path(CONTAINER_ID), targetName); + new Path(AdtConstants.CONTAINER_FRAMEWORK), + targetName, + IClasspathContainer.K_DEFAULT_SYSTEM); } // In case of error, we'll try different thing to provide the best error message @@ -459,7 +440,8 @@ public class AndroidClasspathContainerInitializer extends ClasspathContainerInit IClasspathEntry[] entries = createClasspathEntriesFromPaths(paths, target); return new AndroidClasspathContainer(entries, - new Path(CONTAINER_ID), targetNameCache); + new Path(AdtConstants.CONTAINER_FRAMEWORK), + targetNameCache, IClasspathContainer.K_DEFAULT_SYSTEM); } /** @@ -520,17 +502,16 @@ public class AndroidClasspathContainerInitializer extends ClasspathContainerInit // create the java doc link. String androidApiURL = ProjectHelper.loadStringProperty(root, PROPERTY_ANDROID_API); String apiURL = null; - if (androidApiURL != null) { + if (androidApiURL != null && testURL(androidApiURL)) { apiURL = androidApiURL; } else { - if (testURL(androidApiURL)) { - apiURL = androidApiURL; - } else if (testURL(paths[CACHE_INDEX_DOCS_URI])) { + if (testURL(paths[CACHE_INDEX_DOCS_URI])) { apiURL = paths[CACHE_INDEX_DOCS_URI]; } else if (testURL(ANDROID_API_REFERENCE)) { apiURL = ANDROID_API_REFERENCE; } } + IClasspathAttribute[] attributes = null; if (apiURL != null && !NULL_API_URL.equals(apiURL)) { IClasspathAttribute cpAttribute = JavaCore.newClasspathAttribute( diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/AndroidClasspathContainerPage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/AndroidClasspathContainerPage.java index 4641c227a..ed4e1edbd 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/AndroidClasspathContainerPage.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/AndroidClasspathContainerPage.java @@ -17,6 +17,8 @@ package com.android.ide.eclipse.adt.internal.project; +import com.android.ide.eclipse.adt.AdtConstants; + import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.IPath; @@ -61,7 +63,7 @@ public class AndroidClasspathContainerPage extends WizardPage implements IClassp } public IClasspathEntry getSelection() { - IPath path = new Path(AndroidClasspathContainerInitializer.CONTAINER_ID); + IPath path = new Path(AdtConstants.CONTAINER_FRAMEWORK); final int index = this.mProjectsCombo.getSelectionIndex(); if (index != -1) { diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/FolderDecorator.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/FolderDecorator.java index 4fd3c3559..2ca42696c 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/FolderDecorator.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/FolderDecorator.java @@ -18,7 +18,6 @@ package com.android.ide.eclipse.adt.internal.project; import com.android.ide.eclipse.adt.AdtConstants; import com.android.ide.eclipse.adt.AdtPlugin; -import com.android.ide.eclipse.adt.internal.sdk.Sdk; import com.android.sdklib.SdkConstants; import org.eclipse.core.resources.IFolder; @@ -68,9 +67,6 @@ public class FolderDecorator implements ILightweightLabelDecorator { doDecoration(decoration, null); } else if (name.equals(SdkConstants.FD_OUTPUT)) { doDecoration(decoration, null); - } else if (folder.isLinked() && Sdk.CREATOR_ADT.equals( - ProjectHelper.loadStringProperty(folder, Sdk.PROP_CREATOR))) { - doDecoration(decoration, " [Android Library]"); } } } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/LibraryClasspathContainerInitializer.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/LibraryClasspathContainerInitializer.java new file mode 100644 index 000000000..3d0d98847 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/LibraryClasspathContainerInitializer.java @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2011 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.project; + +import com.android.ide.eclipse.adt.AdtConstants; +import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.adt.internal.sdk.ProjectState; +import com.android.ide.eclipse.adt.internal.sdk.Sdk; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IFolder; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.NullProgressMonitor; +import org.eclipse.core.runtime.Path; +import org.eclipse.jdt.core.ClasspathContainerInitializer; +import org.eclipse.jdt.core.IClasspathContainer; +import org.eclipse.jdt.core.IClasspathEntry; +import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.core.JavaCore; +import org.eclipse.jdt.core.JavaModelException; + +import java.util.ArrayList; +import java.util.List; + +public class LibraryClasspathContainerInitializer extends ClasspathContainerInitializer { + + public LibraryClasspathContainerInitializer() { + } + + /** + * Updates the {@link IJavaProject} objects with new library. + * @param androidProjects the projects to update. + * @return <code>true</code> if success, <code>false</code> otherwise. + */ + public static boolean updateProjects(IJavaProject[] androidProjects) { + try { + // Allocate a new AndroidClasspathContainer, and associate it to the library + // container id for each projects. + int projectCount = androidProjects.length; + + IClasspathContainer[] containers = new IClasspathContainer[projectCount]; + for (int i = 0 ; i < projectCount; i++) { + containers[i] = allocateAndroidContainer(androidProjects[i]); + } + + // give each project their new container in one call. + JavaCore.setClasspathContainer( + new Path(AdtConstants.CONTAINER_LIBRARIES), + androidProjects, containers, new NullProgressMonitor()); + + return true; + } catch (JavaModelException e) { + return false; + } + } + + @Override + public void initialize(IPath containerPath, IJavaProject project) throws CoreException { + if (AdtConstants.CONTAINER_LIBRARIES.equals(containerPath.toString())) { + IClasspathContainer container = allocateAndroidContainer(project); + if (container != null) { + JavaCore.setClasspathContainer(new Path(AdtConstants.CONTAINER_LIBRARIES), + new IJavaProject[] { project }, + new IClasspathContainer[] { container }, + new NullProgressMonitor()); + } + } + } + + private static IClasspathContainer allocateAndroidContainer(IJavaProject javaProject) { + final IProject iProject = javaProject.getProject(); + + AdtPlugin plugin = AdtPlugin.getDefault(); + if (plugin == null) { // This is totally weird, but I've seen it happen! + return null; + } + + // check if the project has a valid target. + ProjectState state = Sdk.getProjectState(iProject); + + List<IClasspathEntry> entries = new ArrayList<IClasspathEntry>(); + + List<IProject> libProjects = state.getFullLibraryProjects(); + for (IProject libProject : libProjects) { + // get the project output + IFolder outputFolder = BaseProjectHelper.getAndroidOutputFolder(libProject); + + IFile jarIFile = outputFolder.getFile(libProject.getName().toLowerCase() + + AdtConstants.DOT_JAR); + + IClasspathEntry entry = JavaCore.newLibraryEntry( + jarIFile.getLocation(), + libProject.getLocation(), // source attachment path + null); // default source attachment root path. + + entries.add(entry); + } + + return new AndroidClasspathContainer( + entries.toArray(new IClasspathEntry[entries.size()]), + new Path(AdtConstants.CONTAINER_LIBRARIES), + "Library Projects", + IClasspathContainer.K_APPLICATION); + } + +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/ProjectHelper.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/ProjectHelper.java index 2804bac21..428e8d965 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/ProjectHelper.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/ProjectHelper.java @@ -268,7 +268,8 @@ public final class ProjectHelper { // get the output folder IPath outputFolder = javaProject.getOutputLocation(); - boolean foundContainer = false; + boolean foundFrameworkContainer = false; + boolean foundLibrariesContainer = false; for (int i = 0 ; i < entries.length ;) { // get the entry and kind @@ -285,8 +286,12 @@ public final class ProjectHelper { continue; } } else if (kind == IClasspathEntry.CPE_CONTAINER) { - if (AndroidClasspathContainerInitializer.checkPath(entry.getPath())) { - foundContainer = true; + String path = entry.getPath().toString(); + if (AdtConstants.CONTAINER_FRAMEWORK.equals(path)) { + foundFrameworkContainer = true; + } + if (AdtConstants.CONTAINER_LIBRARIES.equals(path)) { + foundLibrariesContainer = true; } } @@ -294,10 +299,17 @@ public final class ProjectHelper { } // if the framework container is not there, we add it - if (foundContainer == false) { + if (foundFrameworkContainer == false) { + // add the android container to the array + entries = ProjectHelper.addEntryToClasspath(entries, + JavaCore.newContainerEntry(new Path(AdtConstants.CONTAINER_FRAMEWORK))); + } + + // same thing for the library container + if (foundLibrariesContainer == false) { // add the android container to the array entries = ProjectHelper.addEntryToClasspath(entries, - AndroidClasspathContainerInitializer.getContainerEntry()); + JavaCore.newContainerEntry(new Path(AdtConstants.CONTAINER_LIBRARIES))); } // set the new list of entries to the project diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/GlobalProjectMonitor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/GlobalProjectMonitor.java index f2e6485ae..6e9881f2a 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/GlobalProjectMonitor.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/GlobalProjectMonitor.java @@ -382,9 +382,14 @@ public final class GlobalProjectMonitor { IJavaProject[] androidProjects = BaseProjectHelper.getAndroidProjects(javaModel, null /*filter*/); + + notifyResourceEventStart(); + for (IJavaProject androidProject : androidProjects) { listener.projectOpenedWithWorkspace(androidProject.getProject()); } + + notifyResourceEventEnd(); } /** @@ -427,7 +432,27 @@ public final class GlobalProjectMonitor { mRawDeltaListeners.remove(listener); } - private final IResourceChangeListener mResourceChangeListener = new IResourceChangeListener() { + private void notifyResourceEventStart() { + for (IResourceEventListener listener : mEventListeners) { + try { + listener.resourceChangeEventStart(); + } catch (Throwable t) { + AdtPlugin.log(t,"Failed to call IResourceEventListener.resourceChangeEventStart"); + } + } + } + + private void notifyResourceEventEnd() { + for (IResourceEventListener listener : mEventListeners) { + try { + listener.resourceChangeEventEnd(); + } catch (Throwable t) { + AdtPlugin.log(t,"Failed to call IResourceEventListener.resourceChangeEventEnd"); + } + } + } + + private IResourceChangeListener mResourceChangeListener = new IResourceChangeListener() { /** * Processes the workspace resource change events. * @@ -435,13 +460,7 @@ public final class GlobalProjectMonitor { */ public synchronized void resourceChanged(IResourceChangeEvent event) { // notify the event listeners of a start. - for (IResourceEventListener listener : mEventListeners) { - try { - listener.resourceChangeEventStart(); - } catch (Throwable t) { - AdtPlugin.log(t,"Failed to call IResourceEventListener.resourceChangeEventStart"); - } - } + notifyResourceEventStart(); if (event.getType() == IResourceChangeEvent.PRE_DELETE) { // a project is being deleted. Lets get the project object and remove @@ -469,13 +488,7 @@ public final class GlobalProjectMonitor { } // we're done, notify the event listeners. - for (IResourceEventListener listener : mEventListeners) { - try { - listener.resourceChangeEventEnd(); - } catch (Throwable t) { - AdtPlugin.log(t,"Failed to call IResourceEventListener.resourceChangeEventEnd"); - } - } + notifyResourceEventEnd(); } }; diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/ProjectClassLoader.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/ProjectClassLoader.java index dc543b861..44955533f 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/ProjectClassLoader.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/ProjectClassLoader.java @@ -18,7 +18,8 @@ package com.android.ide.eclipse.adt.internal.resources.manager; import com.android.ide.eclipse.adt.AdtConstants; import com.android.ide.eclipse.adt.internal.build.BuildHelper; -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 org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; @@ -74,17 +75,24 @@ public final class ProjectClassLoader extends ClassLoader { return clazz; } - // attempt to load the class from the referenced projects. + // attempt to load the class from the libraries try { - List<IProject> javaProjects = ProjectHelper.getReferencedProjects( - mJavaProject.getProject()); - List<IJavaProject> referencedJavaProjects = BuildHelper.getJavaProjects(javaProjects); + // get the project info + ProjectState projectState = Sdk.getProjectState(mJavaProject.getProject()); - for (IJavaProject javaProject : referencedJavaProjects) { - clazz = loadFromProject(javaProject, name); + // this can happen if the project has no default.properties. + if (projectState != null) { - if (clazz != null) { - return clazz; + List<IProject> libProjects = projectState.getFullLibraryProjects(); + List<IJavaProject> referencedJavaProjects = BuildHelper.getJavaProjects( + libProjects); + + for (IJavaProject javaProject : referencedJavaProjects) { + clazz = loadFromProject(javaProject, name); + + if (clazz != null) { + return clazz; + } } } } catch (CoreException e) { diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/ProjectState.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/ProjectState.java index d57cdaff8..5b221fc19 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/ProjectState.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/ProjectState.java @@ -149,7 +149,7 @@ public final class ProjectState { @Override public int hashCode() { - return mRelativePath.hashCode(); + return normalizePath(mRelativePath).hashCode(); } } @@ -554,6 +554,10 @@ public final class ProjectState { mParentProjects.remove(parentState); } + public List<ProjectState> getParentProjects() { + return Collections.unmodifiableList(mParentProjects); + } + /** * Update the value of a library dependency. * <p/>This loops on all current dependency looking for the value to replace and then replaces diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/Sdk.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/Sdk.java index 0002668f6..6d476d958 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/Sdk.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/Sdk.java @@ -24,7 +24,7 @@ import com.android.ide.eclipse.adt.AdtPlugin; import com.android.ide.eclipse.adt.internal.build.DexWrapper; import com.android.ide.eclipse.adt.internal.project.AndroidClasspathContainerInitializer; 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.project.LibraryClasspathContainerInitializer; import com.android.ide.eclipse.adt.internal.resources.manager.GlobalProjectMonitor; import com.android.ide.eclipse.adt.internal.resources.manager.GlobalProjectMonitor.IFileListener; import com.android.ide.eclipse.adt.internal.resources.manager.GlobalProjectMonitor.IProjectListener; @@ -46,22 +46,15 @@ import com.android.sdklib.internal.project.ProjectProperties.PropertyType; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IFolder; import org.eclipse.core.resources.IMarkerDelta; -import org.eclipse.core.resources.IPathVariableManager; import org.eclipse.core.resources.IProject; -import org.eclipse.core.resources.IProjectDescription; -import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.IResourceDelta; -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.Path; import org.eclipse.core.runtime.Status; 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; @@ -70,7 +63,6 @@ import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; -import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -88,11 +80,9 @@ import java.util.Map.Entry; * To get the list of platforms or add-ons present in the SDK, call {@link #getTargets()}. */ public final class Sdk { - private static final String PROP_LIBRARY = "_library"; //$NON-NLS-1$ - private static final String PROP_LIBRARY_NAME = "_library_name"; //$NON-NLS-1$ - public static final String CREATOR_ADT = "ADT"; //$NON-NLS-1$ - public static final String PROP_CREATOR = "_creator"; //$NON-NLS-1$ - private final static Object sLock = new Object(); + private final static boolean DEBUG = false; + + private final static Object LOCK = new Object(); private static Sdk sCurrentSdk = null; @@ -198,7 +188,7 @@ public final class Sdk { * projects. */ public static final Object getLock() { - return sLock; + return LOCK; } /** @@ -207,7 +197,7 @@ public final class Sdk { * @param sdkLocation the OS path to the SDK. */ public static Sdk loadSdk(String sdkLocation) { - synchronized (sLock) { + synchronized (LOCK) { if (sCurrentSdk != null) { sCurrentSdk.dispose(); sCurrentSdk = null; @@ -273,7 +263,7 @@ public final class Sdk { * Returns the current {@link Sdk} object. */ public static Sdk getCurrent() { - synchronized (sLock) { + synchronized (LOCK) { return sCurrentSdk; } } @@ -326,7 +316,7 @@ public final class Sdk { return; } - synchronized (sLock) { + synchronized (LOCK) { // check if there's already a state? ProjectState state = getProjectState(project); @@ -369,7 +359,7 @@ public final class Sdk { return null; } - synchronized (sLock) { + synchronized (LOCK) { ProjectState state = sProjectStateMap.get(project); if (state == null) { // load the default.properties from the project folder. @@ -449,7 +439,7 @@ public final class Sdk { public LoadStatus checkAndLoadTargetData(final IAndroidTarget target, IJavaProject project) { boolean loadData = false; - synchronized (sLock) { + synchronized (LOCK) { if (mDontLoadTargetData) { return LoadStatus.FAILED; } @@ -491,7 +481,7 @@ public final class Sdk { IJavaProject[] javaProjectArray = null; - synchronized (sLock) { + synchronized (LOCK) { TargetLoadBundle bundle = mTargetDataStatusMap.get(target); if (status.getCode() != IStatus.OK) { @@ -516,7 +506,7 @@ public final class Sdk { return status; } catch (Throwable t) { - synchronized (sLock) { + synchronized (LOCK) { TargetLoadBundle bundle = mTargetDataStatusMap.get(target); bundle.status = LoadStatus.FAILED; } @@ -543,7 +533,7 @@ public final class Sdk { * Return the {@link AndroidTargetData} for a given {@link IAndroidTarget}. */ public AndroidTargetData getTargetData(IAndroidTarget target) { - synchronized (sLock) { + synchronized (LOCK) { return mTargetDataMap.get(target); } } @@ -552,7 +542,7 @@ public final class Sdk { * Return the {@link AndroidTargetData} for a given {@link IProject}. */ public AndroidTargetData getTargetData(IProject project) { - synchronized (sLock) { + synchronized (LOCK) { IAndroidTarget target = getTarget(project); if (target != null) { return getTargetData(target); @@ -604,7 +594,7 @@ public final class Sdk { * @return a possibly empty list of ProjectState. */ public static Set<ProjectState> getMainProjectsFor(IProject project) { - synchronized (sLock) { + synchronized (LOCK) { // first get the project directly depending on this. HashSet<ProjectState> list = new HashSet<ProjectState>(); @@ -645,7 +635,7 @@ public final class Sdk { * this {@link Sdk} instance. */ public void unloadTargetData(boolean preventReload) { - synchronized (sLock) { + synchronized (LOCK) { mDontLoadTargetData = preventReload; // dispose of the target data. @@ -664,9 +654,11 @@ public final class Sdk { // listen to projects closing GlobalProjectMonitor monitor = GlobalProjectMonitor.getMonitor(); + // need to register the resource event listener first because the project listener + // is called back during registration with project opened in the workspace. + monitor.addResourceEventListener(mResourceEventListener); monitor.addProjectListener(mProjectListener); monitor.addFileListener(mFileListener, IResourceDelta.CHANGED | IResourceDelta.ADDED); - monitor.addResourceEventListener(mResourceEventListener); // pre-compute some paths mDocBaseUrl = getDocumentationBaseUrl(mManager.getLocation() + @@ -678,7 +670,7 @@ public final class Sdk { loadLayoutDevices(); // update whatever ProjectState is already present with new IAndroidTarget objects. - synchronized (sLock) { + synchronized (LOCK) { for (Entry<IProject, ProjectState> entry: sProjectStateMap.entrySet()) { entry.getValue().setTarget( getTargetFromHashString(entry.getValue().getTargetHashString())); @@ -695,8 +687,8 @@ public final class Sdk { monitor.removeFileListener(mFileListener); monitor.removeResourceEventListener(mResourceEventListener); - synchronized (sLock) { - // the IAndroidTarget objects are now obsolete so update the project states. + // the IAndroidTarget objects are now obsolete so update the project states. + synchronized (LOCK) { for (Entry<IProject, ProjectState> entry: sProjectStateMap.entrySet()) { entry.getValue().setTarget(null); } @@ -711,7 +703,7 @@ public final class Sdk { } void setTargetData(IAndroidTarget target, AndroidTargetData data) { - synchronized (sLock) { + synchronized (LOCK) { mTargetDataMap.put(target, data); } } @@ -783,9 +775,9 @@ public final class Sdk { onProjectRemoved(project, true /*deleted*/); } - private void onProjectRemoved(IProject project, boolean deleted) { + private void onProjectRemoved(IProject removedProject, boolean deleted) { try { - if (project.hasNature(AdtConstants.NATURE_DEFAULT) == false) { + if (removedProject.hasNature(AdtConstants.NATURE_DEFAULT) == false) { return; } } catch (CoreException e) { @@ -794,71 +786,59 @@ public final class Sdk { // which is processed before the project is actually removed/closed. } + if (DEBUG) { + System.out.println(">>> CLOSED: " + removedProject.getName()); + } + // get the target project - synchronized (sLock) { + synchronized (LOCK) { // Don't use getProject() as it could create the ProjectState if it's not // there yet and this is not what we want. We want the current object. // Therefore, direct access to the map. - ProjectState state = sProjectStateMap.get(project); - if (state != null) { + ProjectState removedState = sProjectStateMap.get(removedProject); + if (removedState != null) { // 1. clear the layout lib cache associated with this project - IAndroidTarget target = state.getTarget(); + IAndroidTarget target = removedState.getTarget(); if (target != null) { // get the bridge for the target, and clear the cache for this project. AndroidTargetData data = mTargetDataMap.get(target); if (data != null) { LayoutLibrary layoutLib = data.getLayoutLibrary(); if (layoutLib != null && layoutLib.getStatus() == LoadStatus.LOADED) { - layoutLib.clearCaches(project); + layoutLib.clearCaches(removedProject); } } } // 2. if the project is a library, make sure to update the - // LibraryState for any main project using this. + // LibraryState for any project referencing it. // Also, record the updated projects that are libraries, to update // projects that depend on them. - ArrayList<ProjectState> updatedLibraries = new ArrayList<ProjectState>(); for (ProjectState projectState : sProjectStateMap.values()) { - LibraryState libState = projectState.getLibrary(project); + LibraryState libState = projectState.getLibrary(removedProject); if (libState != null) { - // get the current libraries. - List<IProject> oldLibraries = projectState.getFullLibraryProjects(); - - // the unlink below will work in the job, but we need to close - // the library right away. + // Close the library right away. + // This remove links between the LibraryState and the projectState. // This is because in case of a rename of a project, projectClosed and // projectOpened will be called before any other job is run, so we // need to make sure projectOpened is closed with the main project // state up to date. libState.close(); - - // edit the project to remove the linked source folder. - // this also calls LibraryState.close(); - LinkUpdateBundle bundle = getLinkBundle(projectState, oldLibraries); - if (bundle != null) { - queueLinkUpdateBundle(bundle); - } - - if (projectState.isLibrary()) { - updatedLibraries.add(projectState); - } + // record that this project changed, and in case it's a library + // that its parents need to be updated as well. + markProject(projectState, projectState.isLibrary()); } } - if (deleted) { - // remove the linked path variable - disposeLibraryProject(project); - } - // now remove the project for the project map. - sProjectStateMap.remove(project); - - // update the projects that depend on the updated project - updateProjectsWithNewLibraries(updatedLibraries); + sProjectStateMap.remove(removedProject); } } + + if (DEBUG) { + System.out.println("<<<"); + } } public void projectOpened(IProject project) { @@ -916,115 +896,70 @@ public final class Sdk { ProjectState openedState = getProjectState(openedProject); if (openedState != null) { - if (openedState.hasLibraries()) { - // list of library to link to the opened project. - final ArrayList<IProject> libsToLink = new ArrayList<IProject>(); + if (DEBUG) { + System.out.println(">>> OPENED: " + openedProject.getName()); + } - // Look for all other opened projects to see if any is a library for the opened - // project. - synchronized (sLock) { + synchronized (LOCK) { + final boolean isLibrary = openedState.isLibrary(); + final boolean hasLibraries = openedState.hasLibraries(); + + if (isLibrary || hasLibraries) { + boolean foundLibraries = false; + // loop on all the existing project and update them based on this new + // project for (ProjectState projectState : sProjectStateMap.values()) { if (projectState != openedState) { - // ProjectState#needs() both checks if this is a missing library - // and updates LibraryState to contains the new values. - LibraryState libState = openedState.needs(projectState); - - if (libState != null) { - // we have a match! Add the library to the list (if it was - // not added through an indirect dependency before). - IProject libProject = libState.getProjectState().getProject(); - if (libsToLink.contains(libProject) == false) { - libsToLink.add(libProject); + // If the project has libraries, check if this project + // is a reference. + if (hasLibraries) { + // ProjectState#needs() both checks if this is a missing library + // and updates LibraryState to contains the new values. + // This must always be called. + LibraryState libState = openedState.needs(projectState); + + if (libState != null) { + // found a library! Add the main project to the list of + // modified project + foundLibraries = true; } + } - // now find what this depends on, and add it too. - // The order here doesn't matter - // as it's just to add the linked source folder, so there's no - // need to use ProjectState#getFullLibraryProjects() which - // could return project that have already been added anyway. - fillProjectDependenciesList(libState.getProjectState(), - libsToLink); + // if the project is a library check if the other project depend + // on it. + if (isLibrary) { + // ProjectState#needs() both checks if this is a missing library + // and updates LibraryState to contains the new values. + // This must always be called. + LibraryState libState = projectState.needs(openedState); + + if (libState != null) { + // There's a dependency! Add the project to the list of + // modified project, but also to a list of projects + // that saw one of its dependencies resolved. + markProject(projectState, projectState.isLibrary()); + } } } } - } - // create a link bundle always, because even if there's no libraries to add - // to the CPE, the cleaning of invalid CPE must happen. - LinkUpdateBundle bundle = new LinkUpdateBundle(); - bundle.mProject = openedProject; - bundle.mNewLibraryProjects = libsToLink.toArray( - new IProject[libsToLink.size()]); - bundle.mCleanupCPE = true; - queueLinkUpdateBundle(bundle); + // if the project has a libraries and we found at least one, we add + // the project to the list of modified project. + // Since we already went through the parent, no need to update them. + if (foundLibraries) { + markProject(openedState, false /*updateParents*/); + } + } } - // if the project is a library, then add it to the list of projects being opened. - // They will be processed in IResourceEventListener#resourceChangeEventEnd. - // This is done so that we are sure to process all the projects being opened - // first and only then process projects depending on the projects that were opened. - if (openedState.isLibrary()) { - setupLibraryProject(openedProject); - - mOpenedLibraryProjects.add(openedState); + if (DEBUG) { + System.out.println("<<<"); } } } public void projectRenamed(IProject project, IPath from) { - try { - if (project.hasNature(AdtConstants.NATURE_DEFAULT) == false) { - return; - } - } catch (CoreException e) { - // this can only happen if the project does not exist or is not open, neither - // of which can happen here since we're processing a Project renamed event. - } - - // a project was renamed. - // if the project is a library, look for any project that depended on it - // and update it. (default.properties and linked source folder) - ProjectState renamedState = getProjectState(project); - if (renamedState.isLibrary()) { - // remove the variable - disposeLibraryProject(from.lastSegment()); - - // update the project depending on the library - synchronized (sLock) { - for (ProjectState projectState : sProjectStateMap.values()) { - if (projectState != renamedState && projectState.isMissingLibraries()) { - IPath oldRelativePath = from.makeRelativeTo( - projectState.getProject().getFullPath()); - - IPath newRelativePath = project.getFullPath().makeRelativeTo( - projectState.getProject().getFullPath()); - - // get the current libraries - List<IProject> oldLibraries = projectState.getFullLibraryProjects(); - - // update the library for the main project. - LibraryState libState = projectState.updateLibrary( - oldRelativePath.toString(), newRelativePath.toString(), - renamedState); - if (libState != null) { - // this project depended on the renamed library, create a bundle - // with the whole library difference (in case the renamed library - // also depends on libraries). - - LinkUpdateBundle bundle = getLinkBundle(projectState, - oldLibraries); - queueLinkUpdateBundle(bundle); - - // add it to the opened projects to update whatever depends - // on it - if (projectState.isLibrary()) { - mOpenedLibraryProjects.add(projectState); - } - } - } - } - } - } + // we don't actually care about this anymore. } }; @@ -1052,23 +987,15 @@ public final class Sdk { // get the current library flag boolean wasLibrary = state.isLibrary(); - // get the current list of project dependencies - List<IProject> oldLibraries = state.getFullLibraryProjects(); - LibraryDifference diff = state.reloadProperties(); // load the (possibly new) target. IAndroidTarget newTarget = loadTarget(state); - // check if this is a new library - if (state.isLibrary() && wasLibrary == false) { - setupLibraryProject(iProject); - } - // reload the libraries if needed if (diff.hasDiff()) { if (diff.added) { - synchronized (sLock) { + synchronized (LOCK) { for (ProjectState projectState : sProjectStateMap.values()) { if (projectState != state) { // need to call needs to do the libraryState link, @@ -1081,16 +1008,7 @@ public final class Sdk { } } - // and build the real difference. A list of new projects and a list of - // removed project. - // This is not the same as the added/removed libraries because libraries - // could be indirect dependencies through several different direct - // dependencies so it's easier to compare the full lists before and after - // the reload. - LinkUpdateBundle bundle = getLinkBundle(state, oldLibraries); - if (bundle != null) { - queueLinkUpdateBundle(bundle); - } + markProject(state, wasLibrary || state.isLibrary()); } // apply the new target if needed. @@ -1113,11 +1031,34 @@ public final class Sdk { } }; - /** List of opened project. This is filled in {@link IProjectListener#projectOpened(IProject)} - * and {@link IProjectListener#projectOpenedWithWorkspace(IProject)}, and processed in + /** List of modified projects. This is filled in + * {@link IProjectListener#projectOpened(IProject)}, + * {@link IProjectListener#projectOpenedWithWorkspace(IProject)}, + * {@link IProjectListener#projectClosed(IProject)}, and + * {@link IProjectListener#projectDeleted(IProject)} and processed in * {@link IResourceEventListener#resourceChangeEventEnd()}. */ - private final ArrayList<ProjectState> mOpenedLibraryProjects = new ArrayList<ProjectState>(); + private final List<ProjectState> mModifiedProjects = new ArrayList<ProjectState>(); + private final List<ProjectState> mModifiedChildProjects = new ArrayList<ProjectState>(); + + private void markProject(ProjectState projectState, boolean updateParents) { + if (mModifiedProjects.contains(projectState) == false) { + if (DEBUG) { + System.out.println("\tMARKED: " + projectState.getProject().getName()); + } + mModifiedProjects.add(projectState); + } + + // if the project is resolved also add it to this list. + if (updateParents) { + if (mModifiedChildProjects.contains(projectState) == false) { + if (DEBUG) { + System.out.println("\tMARKED(child): " + projectState.getProject().getName()); + } + mModifiedChildProjects.add(projectState); + } + } + } /** * Delegate listener for resource changes. This is called before and after any calls to the @@ -1125,571 +1066,44 @@ public final class Sdk { */ private IResourceEventListener mResourceEventListener = new IResourceEventListener() { public void resourceChangeEventStart() { - // pass + mModifiedProjects.clear(); + mModifiedChildProjects.clear(); } public void resourceChangeEventEnd() { - updateProjectsWithNewLibraries(mOpenedLibraryProjects); - mOpenedLibraryProjects.clear(); - } - }; - - /** - * Action bundle to update library links on a project. - * - * @see Sdk#queueLinkUpdateBundle(LinkUpdateBundle) - * @see Sdk#updateLibraryLinks(LinkUpdateBundle, IProgressMonitor) - */ - private static class LinkUpdateBundle { - - /** The main project receiving the library links. */ - IProject mProject = null; - /** A list (possibly null/empty) of projects that should be linked. */ - IProject[] mNewLibraryProjects = null; - /** an optional old library path that needs to be removed at the same time as the new - * libraries are added. Can be <code>null</code> in which case no libraries are removed. */ - IPath mDeletedLibraryPath = null; - /** A list (possibly null/empty) of projects that should be unlinked */ - IProject[] mRemovedLibraryProjects = null; - /** Whether unknown IClasspathEntry (that were flagged as being added by ADT) are to be - * removed. This is typically only set to <code>true</code> when the project is opened. */ - boolean mCleanupCPE = false; - - @Override - public String toString() { - return String.format( - "LinkUpdateBundle: %1$s (clean: %2$s) > added: %3$s, removed: %4$s, deleted: %5$s", //$NON-NLS-1$ - mProject.getName(), - mCleanupCPE, - Arrays.toString(mNewLibraryProjects), - Arrays.toString(mRemovedLibraryProjects), - mDeletedLibraryPath); - } - } - private final ArrayList<LinkUpdateBundle> mLinkActionBundleQueue = - new ArrayList<LinkUpdateBundle>(); + // first make sure all the parents are updated + updateParentProjects(); - /** - * Queues a {@link LinkUpdateBundle} bundle to be run by a job. - * - * All action bundles are executed in a job in the exact order they are added. - * This is convenient when several actions must be executed in a job consecutively (instead - * of in parallel as it would happen if each started its own job) but it is impossible - * to manually control the job that's running them (for instance each action is started from - * different callbacks such as {@link IProjectListener#projectOpened(IProject)}. - * - * If the job is not yet started, or has terminated due to lack of action bundle, it is - * restarted. - * - * @param bundle the action bundle to execute - */ - private void queueLinkUpdateBundle(LinkUpdateBundle bundle) { - boolean startJob = false; - synchronized (mLinkActionBundleQueue) { - startJob = mLinkActionBundleQueue.size() == 0; - mLinkActionBundleQueue.add(bundle); - } + // for all modified projects, update their library list + // and gather their IProject + final List<IJavaProject> projectList = new ArrayList<IJavaProject>(); + for (ProjectState state : mModifiedProjects) { + state.updateFullLibraryList(); + projectList.add(JavaCore.create(state.getProject())); + } - if (startJob) { Job job = new Job("Android Library Update") { //$NON-NLS-1$ @Override protected IStatus run(IProgressMonitor monitor) { - // loop until there's no bundle to process - while (true) { - // get the bundle, but don't remove until we're done, or a new job could be - // started. - LinkUpdateBundle bundle = null; - synchronized (mLinkActionBundleQueue) { - // there is always a bundle at this point, as they are only removed - // at the end of this method, and the job is only started after adding - // one - bundle = mLinkActionBundleQueue.get(0); - } + LibraryClasspathContainerInitializer.updateProjects( + projectList.toArray(new IJavaProject[projectList.size()])); - // process the bundle. + for (IJavaProject javaProject : projectList) { try { - updateLibraryLinks(bundle, monitor); - } catch (Exception e) { - AdtPlugin.log(e, "Failed to process bundle: %1$s", //$NON-NLS-1$ - bundle.toString()); - } - - try { - // force a recompile - bundle.mProject.build(IncrementalProjectBuilder.FULL_BUILD, monitor); - } catch (Exception e) { - // no need to log those. - } - - // remove it from the list. - synchronized (mLinkActionBundleQueue) { - mLinkActionBundleQueue.remove(0); - - // no more bundle to process? done. - if (mLinkActionBundleQueue.size() == 0) { - return Status.OK_STATUS; - } + javaProject.getProject().build(IncrementalProjectBuilder.FULL_BUILD, + monitor); + } catch (CoreException e) { + // pass } } + return Status.OK_STATUS; } }; job.setPriority(Job.BUILD); job.schedule(); } - } - - - /** - * Adds to a list the resolved {@link IProject} dependencies for a given {@link ProjectState}. - * This recursively goes down to indirect dependencies. - * - * <strong>The list is filled in an order that is not valid for calling <code>aapt</code> - * </strong>. - * Use {@link ProjectState#getFullLibraryProjects()} for use with <code>aapt</code>. - * - * @param projectState the ProjectState of the project from which to add the libraries. - * @param libraries the list of {@link IProject} to fill. - */ - private void fillProjectDependenciesList(ProjectState projectState, - ArrayList<IProject> libraries) { - for (LibraryState libState : projectState.getLibraries()) { - ProjectState libProjectState = libState.getProjectState(); - - // only care if the LibraryState has a resolved ProjectState - if (libProjectState != null) { - // try not to add duplicate. This can happen if a project depends on 2 different - // libraries that both depend on the same one. - IProject libProject = libProjectState.getProject(); - if (libraries.contains(libProject) == false) { - libraries.add(libProject); - } - - // process the libraries of this library too. - fillProjectDependenciesList(libProjectState, libraries); - } - } - } - - /** - * Sets up a path variable for a given project. - * The name of the variable is based on the name of the project. However some valid character - * for project names can be invalid for variable paths. - * {@link #getLibraryVariableName(String)} return the name of the variable based on the - * project name. - * - * @param libProject the project - * - * @see IPathVariableManager - * @see #getLibraryVariableName(String) - */ - private void setupLibraryProject(IProject libProject) { - // if needed add a path var for this library - IPathVariableManager pathVarMgr = - ResourcesPlugin.getWorkspace().getPathVariableManager(); - IPath libPath = libProject.getLocation(); - - final String varName = getLibraryVariableName(libProject.getName()); - - if (libPath.equals(pathVarMgr.getValue(varName)) == false) { - try { - pathVarMgr.setValue(varName, libPath); - } catch (CoreException e) { - AdtPlugin.logAndPrintError(e, "Library Project", - "Unable to set linked path var '%1$s' for library %2$s: %3$s", //$NON-NLS-1$ - varName, libPath.toOSString(), e.getMessage()); - } - } - } - - - /** - * Deletes the path variable that was setup for the given project. - * @param project the project - * @see #disposeLibraryProject(String) - */ - private void disposeLibraryProject(IProject project) { - disposeLibraryProject(project.getName()); - } - - /** - * Deletes the path variable that was setup for the given project name. - * The name of the variable is based on the name of the project. However some valid character - * for project names can be invalid for variable paths. - * {@link #getLibraryVariableName(String)} return the name of the variable based on the - * project name. - * @param projectName the name of the project, unmodified. - */ - private void disposeLibraryProject(String projectName) { - IPathVariableManager pathVarMgr = - ResourcesPlugin.getWorkspace().getPathVariableManager(); - - final String varName = getLibraryVariableName(projectName); - - // remove the value by setting the value to null. - try { - pathVarMgr.setValue(varName, null /*path*/); - } catch (CoreException e) { - String message = String.format("Unable to remove linked path var '%1$s'", //$NON-NLS-1$ - varName); - AdtPlugin.log(e, message); - } - } - - /** - * Returns a valid path variable name based on the name of a library project. - * @param name the name of the library project. - */ - private String getLibraryVariableName(String name) { - /* - * From the javadoc of IPathVariableManager: - * A path variable is a pair of non-null elements (name,value) where name is a - * case-sensitive string (containing only letters, digits and the underscore character, - * and not starting with a digit), and value is an absolute IPath object. - */ - - // the variable name is made by: - // - prepending _android_ (this ensure there's no digit at the start) - // - removing all unsupported characters. - // - append the hashcode of the original name. This should help reduce collisions. - String validName = name.replaceAll("[^0-9a-zA-Z]+", "_"); //$NON-NLS-1$ //$NON-NLS-2$ - - //ensure the valid is not negative as - is not a valid char - long hash = name.hashCode() & 0x00000000ffffffffL; - return "_android_" + validName + "_" + Long.toString(hash, 16) ; //$NON-NLS-1$ //$NON-NLS-2$ - } - - /** - * Update the library links for a project - * - * This does the follow: - * - add/remove the library projects to the main projects dynamic reference list. This is used - * by the builders to receive resource change deltas for library projects and figure out what - * needs to be recompiled/recreated. - * - create new {@link IClasspathEntry} of type {@link IClasspathEntry#CPE_SOURCE} for each - * source folder for each new library project. - * - remove the {@link IClasspathEntry} of type {@link IClasspathEntry#CPE_SOURCE} for each - * source folder for each removed library project. - * - If {@link LinkUpdateBundle#mCleanupCPE} is set to true, all CPE created by ADT that cannot - * be resolved are removed. This should only be used when the project is opened. - * - * <strong>This must not be called directly. Instead the {@link LinkUpdateBundle} must - * be run through a job with {@link #queueLinkUpdateBundle(LinkUpdateBundle)}.</strong> - * - * @param bundle The {@link LinkUpdateBundle} action bundle that contains all the parameters - * necessary to execute the action. - * @param monitor an {@link IProgressMonitor}. - * @return an {@link IStatus} with the status of the action. - */ - private IStatus updateLibraryLinks(LinkUpdateBundle bundle, IProgressMonitor monitor) { - if (bundle.mProject.isOpen() == false) { - return Status.OK_STATUS; - } - try { - // add the library to the list of dynamic references. This is necessary to receive - // notifications that the library content changed in the builders. - IProjectDescription projectDescription = bundle.mProject.getDescription(); - IProject[] refs = projectDescription.getDynamicReferences(); - - if (refs.length > 0) { - ArrayList<IProject> list = new ArrayList<IProject>(Arrays.asList(refs)); - - // remove a previous library if needed (in case of a rename) - if (bundle.mDeletedLibraryPath != null) { - // since project basically have only one segment that matter, - // just check the names - removeFromList(list, bundle.mDeletedLibraryPath.lastSegment()); - } - - if (bundle.mRemovedLibraryProjects != null) { - for (IProject removedProject : bundle.mRemovedLibraryProjects) { - removeFromList(list, removedProject.getName()); - } - } - - // add the new ones if they don't exist - if (bundle.mNewLibraryProjects != null) { - for (IProject newProject : bundle.mNewLibraryProjects) { - if (list.contains(newProject) == false) { - list.add(newProject); - } - } - } - - // set the changed list - projectDescription.setDynamicReferences( - list.toArray(new IProject[list.size()])); - } else { - if (bundle.mNewLibraryProjects != null) { - projectDescription.setDynamicReferences(bundle.mNewLibraryProjects); - } - } - - // get the current classpath entries for the project to add the new source - // folders. - IJavaProject javaProject = JavaCore.create(bundle.mProject); - IClasspathEntry[] entries = javaProject.getRawClasspath(); - ArrayList<IClasspathEntry> classpathEntries = new ArrayList<IClasspathEntry>( - Arrays.asList(entries)); - - IWorkspaceRoot wsRoot = ResourcesPlugin.getWorkspace().getRoot(); - - // loop on the classpath entries and look for CPE_SOURCE entries that - // are linked folders, then record them for comparison later as we add the new - // ones. - ArrayList<IClasspathEntry> cpeToRemove = new ArrayList<IClasspathEntry>(); - for (IClasspathEntry classpathEntry : classpathEntries) { - if (classpathEntry.getEntryKind() == IClasspathEntry.CPE_SOURCE) { - IPath path = classpathEntry.getPath(); - IResource linkedRes = wsRoot.findMember(path); - if (linkedRes != null && linkedRes.isLinked() && - CREATOR_ADT.equals(ProjectHelper.loadStringProperty( - linkedRes, PROP_CREATOR))) { - - // add always to list if we're doing clean-up - if (bundle.mCleanupCPE) { - cpeToRemove.add(classpathEntry); - } else { - String libName = ProjectHelper.loadStringProperty(linkedRes, - PROP_LIBRARY_NAME); - if (libName != null && isRemovedLibrary(bundle, libName)) { - cpeToRemove.add(classpathEntry); - } - } - } - } - } - - // loop on the projects to add. - if (bundle.mNewLibraryProjects != null) { - for (IProject library : bundle.mNewLibraryProjects) { - if (library.isOpen() == false) { - continue; - } - final String libName = library.getName(); - final String varName = getLibraryVariableName(libName); - - // get the list of source folders for the library. - List<IPath> sourceFolderPaths = BaseProjectHelper.getSourceClasspaths(library); - - // loop on all the source folder, ignoring FD_GEN and add them - // as linked folder - for (IPath sourceFolderPath : sourceFolderPaths) { - IResource sourceFolder = wsRoot.findMember(sourceFolderPath); - if (sourceFolder == null || sourceFolder.isLinked()) { - continue; - } - - IPath relativePath = sourceFolder.getProjectRelativePath(); - if (SdkConstants.FD_GEN_SOURCES.equals(relativePath.toString())) { - continue; - } - - // create the linked path - IPath linkedPath = new Path(varName).append(relativePath); - - // look for an existing CPE that has the same linked path and that was - // going to be removed. - IClasspathEntry match = findClasspathEntryMatch(cpeToRemove, linkedPath, - null); - - if (match == null) { - // no match, create one - // get a string version, to make up the linked folder name - String srcFolderName = relativePath.toString().replace( - "/", //$NON-NLS-1$ - "_"); //$NON-NLS-1$ - - // folder name - String folderName = libName + "_" + srcFolderName; //$NON-NLS-1$ - - // create a linked resource for the library using the path var. - IFolder libSrc = bundle.mProject.getFolder(folderName); - IPath libSrcPath = libSrc.getFullPath(); - - // check if there's a CPE that would conflict, in which case it needs to - // be removed (this can happen for existing CPE that don't match an open - // project) - match = findClasspathEntryMatch(classpathEntries, null/*rawPath*/, - libSrcPath); - if (match != null) { - classpathEntries.remove(match); - } - - // the path of the linked resource is based on the path variable - // representing the library project, followed by the source folder name. - libSrc.createLink(linkedPath, IResource.REPLACE, monitor); - - // set some persistent properties on it to know that it was - // created by ADT. - ProjectHelper.saveStringProperty(libSrc, PROP_CREATOR, CREATOR_ADT); - ProjectHelper.saveResourceProperty(libSrc, PROP_LIBRARY, library); - ProjectHelper.saveStringProperty(libSrc, PROP_LIBRARY_NAME, - library.getName()); - - // add the source folder to the classpath entries - classpathEntries.add(JavaCore.newSourceEntry(libSrcPath)); - } else { - // there's a valid match, do nothing, but remove the match from - // the list of previously existing CPE. - cpeToRemove.remove(match); - } - } - } - } - - // remove the CPE that should be removed. - classpathEntries.removeAll(cpeToRemove); - - // set the new list - javaProject.setRawClasspath( - classpathEntries.toArray(new IClasspathEntry[classpathEntries.size()]), - monitor); - - // and delete the folders of the CPE that were removed (must be done after) - for (IClasspathEntry cpe : cpeToRemove) { - IResource res = wsRoot.findMember(cpe.getPath()); - res.delete(true, monitor); - } - - return Status.OK_STATUS; - } catch (CoreException e) { - AdtPlugin.logAndPrintError(e, bundle.mProject.getName(), - "Failed to create library links: %1$s", //$NON-NLS-1$ - e.getMessage()); - return e.getStatus(); - } - } - - private boolean isRemovedLibrary(LinkUpdateBundle bundle, String libName) { - if (bundle.mDeletedLibraryPath != null && - libName.equals(bundle.mDeletedLibraryPath.lastSegment())) { - return true; - } - - if (bundle.mRemovedLibraryProjects != null) { - for (IProject removedProject : bundle.mRemovedLibraryProjects) { - if (libName.equals(removedProject.getName())) { - return true; - } - } - } - - return false; - } - - /** - * Computes the library difference based on a previous list and a current state, and creates - * a {@link LinkUpdateBundle} action to update the given project. - * @param project The current project state - * @param oldLibraries the list of old libraries. Typically the result of - * {@link ProjectState#getFullLibraryProjects()} before the ProjectState is updated. - * @return null if there no action to take, or a {@link LinkUpdateBundle} object to run. - */ - private LinkUpdateBundle getLinkBundle(ProjectState project, List<IProject> oldLibraries) { - // get the new full list of projects - List<IProject> newLibraries = project.getFullLibraryProjects(); - - // and build the real difference. A list of new projects and a list of - // removed project. - // This is not the same as the added/removed libraries because libraries - // could be indirect dependencies through several different direct - // dependencies so it's easier to compare the full lists before and after - // the reload. - - List<IProject> addedLibs = new ArrayList<IProject>(); - List<IProject> removedLibs = new ArrayList<IProject>(); - - // first get the list of new projects. - for (IProject newLibrary : newLibraries) { - boolean found = false; - for (IProject oldLibrary : oldLibraries) { - if (newLibrary.equals(oldLibrary)) { - found = true; - break; - } - } - - // if it was not found in the old libraries, it's really new - if (found == false) { - addedLibs.add(newLibrary); - } - } - - // now the list of removed projects. - for (IProject oldLibrary : oldLibraries) { - boolean found = false; - for (IProject newLibrary : newLibraries) { - if (newLibrary.equals(oldLibrary)) { - found = true; - break; - } - } - - // if it was not found in the new libraries, it's really been removed - if (found == false) { - removedLibs.add(oldLibrary); - } - } - - if (addedLibs.size() > 0 || removedLibs.size() > 0) { - LinkUpdateBundle bundle = new LinkUpdateBundle(); - bundle.mProject = project.getProject(); - bundle.mNewLibraryProjects = - addedLibs.toArray(new IProject[addedLibs.size()]); - bundle.mRemovedLibraryProjects = - removedLibs.toArray(new IProject[removedLibs.size()]); - return bundle; - } - - return null; - } - - /** - * Removes a project from a list based on its name. - * @param projects the list of projects. - * @param name the name of the project to remove. - */ - private void removeFromList(List<IProject> projects, String name) { - final int count = projects.size(); - for (int i = 0 ; i < count ; i++) { - // since project basically have only one segment that matter, - // just check the names - if (projects.get(i).getName().equals(name)) { - projects.remove(i); - return; - } - } - } - - /** - * Returns a {@link IClasspathEntry} from the given list whose linked path match the given path. - * @param cpeList a list of {@link IClasspathEntry} of {@link IClasspathEntry#getEntryKind()} - * {@link IClasspathEntry#CPE_SOURCE} whose {@link IClasspathEntry#getPath()} - * points to a linked folder. - * @param rawPath the raw path to compare to. Can be null if <var>path</var> is used instead. - * @param path the path to compare to. Can be null if <var>rawPath</var> is used instead. - * @return the matching IClasspathEntry or null. - */ - private IClasspathEntry findClasspathEntryMatch(ArrayList<IClasspathEntry> cpeList, - IPath rawPath, IPath path) { - IWorkspaceRoot wsRoot = ResourcesPlugin.getWorkspace().getRoot(); - for (IClasspathEntry cpe : cpeList) { - IPath cpePath = cpe.getPath(); - // test the normal path of the resource. - if (path != null && path.equals(cpePath)) { - return cpe; - } - - IResource res = wsRoot.findMember(cpePath); - // getRawLocation returns the path that the linked folder points to. - if (rawPath != null && res.getRawLocation().equals(rawPath)) { - return cpe; - } - - } - return null; - } + }; /** * Updates all existing projects with a given list of new/updated libraries. @@ -1697,65 +1111,33 @@ public final class Sdk { * library project, and if they do, they are linked together. * @param libraries the list of new/updated library projects. */ - private void updateProjectsWithNewLibraries(List<ProjectState> libraries) { - if (libraries.size() == 0) { + private void updateParentProjects() { + if (mModifiedChildProjects.size() == 0) { return; } - ArrayList<ProjectState> updatedLibraries = new ArrayList<ProjectState>(); - synchronized (sLock) { - // for each projects, look for projects that depend on it, and update them. - // Once they are updated (meaning ProjectState#needs() has been called on them), - // we add them to the list so that can be updated as well. - for (ProjectState projectState : sProjectStateMap.values()) { - // record the current library dependencies - List<IProject> oldLibraries = projectState.getFullLibraryProjects(); - - boolean needLibraryDependenciesUpdated = false; - for (ProjectState library : libraries) { - // Normally we would only need to test if ProjectState#needs returns non null, - // meaning the link between the project and the library has not been - // done yet. - // However what matters here is that the library is a dependency, - // period. If the library project was updated, then we redo the link, - // with all indirect dependencies (which *have* changed, since this is - // what this method is all about.) - // We still need to call ProjectState#needs to make the link in case it's not - // been done yet (which can happen if the library project was just opened). - if (projectState != library) { - // call needs in case this new library was just opened, and the link needs - // to be done - LibraryState libState = projectState.needs(library); - if (libState == null && projectState.dependsOn(library)) { - // ProjectState.needs only returns true if the library was needed. - // but we also need to check the case where the project depends on - // the library but the link was already done. - needLibraryDependenciesUpdated = true; - } - } + ArrayList<ProjectState> childProjects = new ArrayList<ProjectState>(mModifiedChildProjects); + mModifiedChildProjects.clear(); + synchronized (LOCK) { + // for each project for which we must update its parent, we loop on the parent + // projects and adds them to the list of modified projects. If they are themselves + // libraries, we add them too. + for (ProjectState state : childProjects) { + if (DEBUG) { + System.out.println(">>> Updating parents of " + state.getProject().getName()); } - - if (needLibraryDependenciesUpdated) { - projectState.updateFullLibraryList(); + List<ProjectState> parents = state.getParentProjects(); + for (ProjectState parent : parents) { + markProject(parent, parent.isLibrary()); } - - LinkUpdateBundle bundle = getLinkBundle(projectState, oldLibraries); - if (bundle != null) { - queueLinkUpdateBundle(bundle); - - // if this updated project is a library, add it to the list, so that - // projects depending on it get updated too. - if (projectState.isLibrary() && - updatedLibraries.contains(projectState) == false) { - updatedLibraries.add(projectState); - } + if (DEBUG) { + System.out.println("<<<"); } } } - // done, but there may be updated projects that were libraries, so we need to do the same - // for this libraries, to update the project there were depending on. - updateProjectsWithNewLibraries(updatedLibraries); + // done, but there may be parents that are also libraries. Need to update their parents. + updateParentProjects(); } } diff --git a/files/ant/lib_rules.xml b/files/ant/lib_rules.xml deleted file mode 100644 index 42a1e48bc..000000000 --- a/files/ant/lib_rules.xml +++ /dev/null @@ -1,186 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<project name="android_rules" default="debug"> - - <!-- - This rules file is meant to be imported by the custom Ant task: - com.android.ant.SetupTask - - The following properties are put in place by the importing task: - android.jar, android.aidl, aapt, aidl, and dx - - Additionnaly, the task sets up the following classpath reference: - android.target.classpath - This is used by the compiler task as the boot classpath. - --> - - <!-- Custom tasks --> - <taskdef name="aapt" - classname="com.android.ant.AaptExecLoopTask" - classpathref="android.antlibs" /> - - <taskdef name="aidl" - classname="com.android.ant.AidlExecTask" - classpathref="android.antlibs" /> - - <taskdef name="xpath" - classname="com.android.ant.XPathTask" - classpathref="android.antlibs" /> - - <taskdef name="if" - classname="com.android.ant.IfElseTask" - classpathref="android.antlibs" /> - - <!-- Properties --> - - <property name="android.tools.dir" location="${sdk.dir}/tools" /> - <!-- Name of the application package extracted from manifest file --> - <xpath input="AndroidManifest.xml" expression="/manifest/@package" - output="manifest.package" /> - <xpath input="AndroidManifest.xml" expression="/manifest/application/@android:hasCode" - output="manifest.hasCode" default="true"/> - - <!-- Input directories --> - <property name="source.dir" value="src" /> - <property name="source.absolute.dir" location="${source.dir}" /> - <property name="gen.dir" value="gen" /> - <property name="gen.absolute.dir" location="${gen.dir}" /> - <property name="resource.dir" value="res" /> - <property name="resource.absolute.dir" location="${resource.dir}" /> - <property name="asset.dir" value="assets" /> - <property name="asset.absolute.dir" location="${asset.dir}" /> - - <!-- Directory for the third party java libraries --> - <property name="external.libs.dir" value="libs" /> - <property name="external.libs.absolute.dir" location="${external.libs.dir}" /> - <!-- Directory for the native libraries --> - <property name="native.libs.dir" value="libs" /> - <property name="native.libs.absolute.dir" location="${native.libs.dir}" /> - - <!-- Output directories --> - <property name="out.dir" value="bin" /> - <property name="out.absolute.dir" location="${out.dir}" /> - <property name="out.classes.dir" value="${out.absolute.dir}/classes" /> - <property name="out.classes.absolute.dir" location="${out.classes.dir}" /> - - <!-- compilation options --> - <property name="java.encoding" value="UTF-8" /> - <property name="java.target" value="1.5" /> - <property name="java.source" value="1.5" /> - - <!-- Verbosity --> - <property name="verbose" value="false" /> - <!-- This is needed by emma as it uses multilevel verbosity instead of simple 'true' or 'false' - The property 'verbosity' is not user configurable and depends exclusively on 'verbose' - value.--> - <condition property="verbosity" value="verbose" else="quiet"> - <istrue value="${verbose}" /> - </condition> - - <!-- Tools --> - <condition property="exe" value=".exe" else=""><os family="windows" /></condition> - - <!-- Emma configuration --> - <property name="emma.dir" value="${sdk.dir}/tools/lib" /> - <path id="emma.lib"> - <pathelement location="${emma.dir}/emma.jar" /> - <pathelement location="${emma.dir}/emma_ant.jar" /> - </path> - <taskdef resource="emma_ant.properties" classpathref="emma.lib" /> - <!-- End of emma configuration --> - - <!-- Rules --> - - <!-- Creates the output directories if they don't exist yet. --> - <target name="-dirs"> - <echo>Creating output directories if needed...</echo> - <mkdir dir="${resource.absolute.dir}" /> - <mkdir dir="${external.libs.absolute.dir}" /> - <mkdir dir="${gen.absolute.dir}" /> - <mkdir dir="${out.absolute.dir}" /> - <mkdir dir="${out.classes.absolute.dir}" /> - </target> - - <!-- empty default pre-build target. Create a similar target in - your build.xml and it'll be called instead of this one. --> - <target name="-pre-build"/> - - <!-- Generates the R.java file for this project's resources. --> - <target name="-resource-src" depends="-dirs, -pre-build"> - <echo>Generating R.java / Manifest.java from the resources...</echo> - <aapt executable="${aapt}" - command="package" - verbose="${verbose}" - manifest="AndroidManifest.xml" - androidjar="${android.jar}" - rfolder="${gen.absolute.dir}"> - <res path="${resource.absolute.dir}" /> - </aapt> - </target> - - <!-- Generates java classes from .aidl files. --> - <target name="-aidl" depends="-dirs"> - <if condition="${manifest.hasCode}"> - <then> - <echo>Compiling aidl files into Java classes...</echo> - <aidl executable="${aidl}" framework="${android.aidl}" - genFolder="${gen.absolute.dir}"> - <source path="${source.absolute.dir}"/> - <source refid="android.libraries.src"/> - </aidl> - </then> - <else> - <echo>hasCode = false. Skipping...</echo> - </else> - </if> - </target> - - <!-- empty default pre-compile target. Create a similar target in - your build.xml and it'll be called instead of this one. --> - <target name="-pre-compile"/> - - <!-- Compiles this project's .java files into .class files. --> - <target name="compile" depends="-resource-src, -aidl, -pre-compile" - description="Compiles project's .java files into .class files"> - <!-- If android rules are used for a test project, its classpath should include - tested project's location --> - <condition property="extensible.classpath" - value="${tested.project.absolute.dir}/bin/classes" else="."> - <isset property="tested.project.absolute.dir" /> - </condition> - <condition property="extensible.libs.classpath" - value="${tested.project.absolute.dir}/libs" - else="./libs"> - <isset property="tested.project.absolute.dir" /> - </condition> - <javac encoding="${java.encoding}" - source="${java.source}" target="${java.target}" - debug="true" extdirs="" - destdir="${out.classes.absolute.dir}" - bootclasspathref="android.target.classpath" - verbose="${verbose}" - classpath="${extensible.classpath}" - classpathref="android.libraries.jars"> - <src path="${source.absolute.dir}" /> - <src path="${gen.absolute.dir}" /> - <src refid="android.libraries.src" /> - <classpath> - <fileset dir="${external.libs.absolute.dir}" includes="*.jar" /> - <fileset dir="${extensible.libs.classpath}" includes="*.jar" /> - </classpath> - </javac> - </target> - - <target name="clean" description="Removes output files created by other targets."> - <delete dir="${out.absolute.dir}" verbose="${verbose}" /> - <delete dir="${gen.absolute.dir}" verbose="${verbose}" /> - </target> - - <target name="help"> - <!-- displays starts at col 13 - |13 80| --> - <echo>Android Ant Build. Available targets:</echo> - <echo> help: Displays this help.</echo> - <echo> clean: Removes output files created by other targets.</echo> - <echo> compile: Compiles project's .java files into .class files.</echo> - </target> -</project> diff --git a/files/ant/main_rules.xml b/files/ant/main_rules.xml index 8404eb85c..ca4cfc992 100644 --- a/files/ant/main_rules.xml +++ b/files/ant/main_rules.xml @@ -1,136 +1,146 @@ <?xml version="1.0" encoding="UTF-8"?> -<project name="android_rules" default="debug"> +<project name="" default="debug"> <!-- - This rules file is meant to be imported by the custom Ant task: - com.android.ant.SetupTask + This build file is imported by the project build file. It contains + all the targets and tasks necessary to build Android projects, be they + regular projects, library projects, or test projects. + + At the beginning of the file is a list of properties that can be overridden + by adding them to your build.properties (properties are immutable, so their + first definition sticks and is never changed). + + Follows: + - custom task definitions, + - more properties (do not override those unless the whole build system is modified). + - macros used throughout the build, + - base build targets, + - debug-specific build targets, + - release-specific build targets, + - instrument-specific build targets, + - test project-specific build targets, + - install targets, + - help target + --> - The following properties are put in place by the importing task: - android.jar, android.aidl, aapt, aidl, and dx + <!-- ********** Overrideable Properties ********** --> - Additionnaly, the task sets up the following classpath reference: - android.target.classpath - This is used by the compiler task as the boot classpath. - --> + <!-- You can override these values in your build.xml or build.properties. + Overriding any other properties may result in broken build. --> + + <!-- Tells adb which device to target. You can change this from the command line + by invoking "ant -Dadb.device.arg=-d" for device "ant -Dadb.device.arg=-e" for + the emulator. --> + <property name="adb.device.arg" value="" /> + + <!-- fileset exclude patterns (space separated) to prevent + files inside src/ from being packaged. --> + <property name="android.package.excludes" value="" /> + + <!-- set some properties used for filtering/override. If those weren't defined + before, then this will create them with empty values, which are then ignored + by the custom tasks receiving them. --> + <property name="version.code" value="" /> + <property name="version.name" value="" /> + <property name="aapt.resource.filter" value="" /> + <property name="filter.abi" value="" /> + + <!-- compilation options --> + <property name="java.encoding" value="UTF-8" /> + <property name="java.target" value="1.5" /> + <property name="java.source" value="1.5" /> + + <!-- Verbosity --> + <property name="verbose" value="false" /> + + <!-- ********** Custom Tasks ********** --> + + <!-- jar file from where the tasks are loaded --> + <path id="android.antlibs"> + <pathelement path="${sdk.dir}/tools/lib/anttasks.jar" /> + </path> <!-- Custom tasks --> + <taskdef name="setup" + classname="com.android.ant.NewSetupTask" + classpathref="android.antlibs" /> + <taskdef name="aapt" - classname="com.android.ant.AaptExecLoopTask" - classpathref="android.antlibs" /> + classname="com.android.ant.AaptExecLoopTask" + classpathref="android.antlibs" /> <taskdef name="aidl" - classname="com.android.ant.AidlExecTask" - classpathref="android.antlibs" /> + classname="com.android.ant.AidlExecTask" + classpathref="android.antlibs" /> <taskdef name="renderscript" - classname="com.android.ant.RenderScriptTask" - classpathref="android.antlibs" /> + classname="com.android.ant.RenderScriptTask" + classpathref="android.antlibs" /> <taskdef name="apkbuilder" - classname="com.android.ant.ApkBuilderTask" - classpathref="android.antlibs" /> + classname="com.android.ant.ApkBuilderTask" + classpathref="android.antlibs" /> <taskdef name="xpath" - classname="com.android.ant.XPathTask" - classpathref="android.antlibs" /> + classname="com.android.ant.XPathTask" + classpathref="android.antlibs" /> <taskdef name="if" - classname="com.android.ant.IfElseTask" - classpathref="android.antlibs" /> + classname="com.android.ant.IfElseTask" + classpathref="android.antlibs" /> - <!-- Properties --> + <!-- Emma configuration --> + <property name="emma.dir" value="${sdk.dir}/tools/lib" /> + <path id="emma.lib"> + <pathelement location="${emma.dir}/emma.jar" /> + <pathelement location="${emma.dir}/emma_ant.jar" /> + </path> + <taskdef resource="emma_ant.properties" classpathref="emma.lib" /> + <!-- End of emma configuration --> - <!-- Tells adb which device to target. You can change this from the command line - by invoking "ant -Dadb.device.arg=-d" for device "ant -Dadb.device.arg=-e" for - the emulator. --> - <property name="adb.device.arg" value="" /> - <property name="android.tools.dir" location="${sdk.dir}/tools" /> - <property name="android.platform.tools.dir" location="${sdk.dir}/platform-tools" /> - <!-- Name of the application package extracted from manifest file --> - <xpath input="AndroidManifest.xml" expression="/manifest/@package" - output="manifest.package" /> - <!-- Value of the hasCode attribute (Application node) extracted from manifest file --> - <xpath input="AndroidManifest.xml" expression="/manifest/application/@android:hasCode" - output="manifest.hasCode" default="true"/> + <!-- ********** Other Properties ********** --> + <!-- overriding these properties may break the build + unless the whole file is updated --> <!-- Input directories --> <property name="source.dir" value="src" /> <property name="source.absolute.dir" location="${source.dir}" /> - <property name="gen.dir" value="gen" /> - <property name="gen.absolute.dir" location="${gen.dir}" /> - <property name="resource.dir" value="res" /> - <property name="resource.absolute.dir" location="${resource.dir}" /> - <property name="asset.dir" value="assets" /> - <property name="asset.absolute.dir" location="${asset.dir}" /> - - <!-- Directory for the third party java libraries --> + <property name="gen.absolute.dir" location="gen" /> + <property name="resource.absolute.dir" location="res" /> + <property name="asset.absolute.dir" location="assets" /> <property name="jar.libs.dir" value="libs" /> <property name="jar.libs.absolute.dir" location="${jar.libs.dir}" /> - <!-- create a path with all the jar files, from the main project and the - libraries --> - <path id="jar.libs.ref"> - <fileset dir="${jar.libs.absolute.dir}" includes="*.jar" /> - <path refid="project.libraries.jars" /> - </path> - - <!-- Directory for the native libraries --> - <property name="native.libs.dir" value="libs" /> - <property name="native.libs.absolute.dir" location="${native.libs.dir}" /> + <property name="native.libs.absolute.dir" location="libs" /> <!-- Output directories --> <property name="out.dir" value="bin" /> <property name="out.absolute.dir" location="${out.dir}" /> - <property name="out.classes.dir" value="${out.absolute.dir}/classes" /> - <property name="out.classes.absolute.dir" location="${out.classes.dir}" /> - <property name="out.resource.dir" location="${out.dir}/res" /> - <property name="out.resource.absolute.dir" location="${out.resource.dir}" /> - + <property name="out.classes.absolute.dir" location="${out.dir}/classes" /> + <property name="out.res.absolute.dir" location="${out.dir}/res" /> + + <!-- tools location --> + <property name="android.tools.dir" location="${sdk.dir}/tools" /> + <property name="android.platform.tools.dir" location="${sdk.dir}/platform-tools" /> + <condition property="exe" value=".exe" else=""><os family="windows" /></condition> + <condition property="bat" value=".bat" else=""><os family="windows" /></condition> + <property name="adb" location="${android.platform.tools.dir}/adb${exe}" /> + <property name="zipalign" location="${android.tools.dir}/zipalign${exe}" /> + <property name="aidl" location="${android.platform.tools.dir}/aidl${exe}" /> + <property name="aapt" location="${android.platform.tools.dir}/aapt${exe}" /> + <property name="dx" location="${android.platform.tools.dir}/dx${bat}" /> + <!-- renderscript location is set by NewSetupTask since we have a choice of + several executables based on minSdkVersion --> + <!-- Intermediate files --> <property name="dex.file.name" value="classes.dex" /> - <property name="intermediate.dex.file" - location="${out.absolute.dir}/${dex.file.name}" /> - <property name="resource.package.file.name" - value="${ant.project.name}.ap_" /> - - <!-- The final package file to generate - These can be overridden by setting them earlier to - different values --> - <property name="out.debug.unaligned.file" - location="${out.absolute.dir}/${ant.project.name}-debug-unaligned.apk" /> - <property name="out.debug.file" - location="${out.absolute.dir}/${ant.project.name}-debug.apk" /> - - <property name="out.unsigned.file.name" - value="${ant.project.name}-unsigned.apk" /> - <property name="out.unsigned.file" - location="${out.absolute.dir}/${out.unsigned.file.name}" /> - - <property name="out.unaligned.file.name" - value="${ant.project.name}-unaligned.apk" /> - <property name="out.unaligned.file" - location="${out.absolute.dir}/${out.unaligned.file.name}" /> - - <property name="out.release.file.name" - value="${ant.project.name}-release.apk" /> - <property name="out.release.file" - location="${out.absolute.dir}/${out.release.file.name}" /> + <property name="intermediate.dex.file" location="${out.absolute.dir}/${dex.file.name}" /> + <property name="resource.package.file.name" value="${ant.project.name}.ap_" /> - <!-- set some properties used for filtering/override. If those weren't defined - before, then this will create them with empty values, which are then ignored - by the custom tasks receiving them. --> - <property name="version.code" value="" /> - <property name="version.name" value="" /> - <property name="aapt.resource.filter" value="" /> - <property name="filter.abi" value="" /> + <!-- Build property file --> + <property name="out.build.prop.file" location="${out.absolute.dir}/build.prop" /> - <!-- compilation options --> - <property name="java.encoding" value="UTF-8" /> - <property name="java.target" value="1.5" /> - <property name="java.source" value="1.5" /> - <!-- Verbosity --> - <property name="verbose" value="false" /> <!-- This is needed by emma as it uses multilevel verbosity instead of simple 'true' or 'false' The property 'verbosity' is not user configurable and depends exclusively on 'verbose' value.--> @@ -166,21 +176,54 @@ <!-- properties for packaging --> <property name="build.packaging.nocrunch" value="true" /> - <!-- Tools --> - <condition property="exe" value=".exe" else=""><os family="windows" /></condition> - <property name="adb" location="${android.platform.tools.dir}/adb${exe}" /> - <property name="zipalign" location="${android.tools.dir}/zipalign${exe}" /> + <!-- Name of the application package extracted from manifest file --> + <xpath input="AndroidManifest.xml" expression="/manifest/@package" + output="manifest.package" /> - <!-- Emma configuration --> - <property name="emma.dir" value="${sdk.dir}/tools/lib" /> - <path id="emma.lib"> - <pathelement location="${emma.dir}/emma.jar" /> - <pathelement location="${emma.dir}/emma_ant.jar" /> - </path> - <taskdef resource="emma_ant.properties" classpathref="emma.lib" /> - <!-- End of emma configuration --> - <!-- Macros --> + <!-- ********** Macros ********** --> + + <!-- macro to do a task on if project.is.library is false. + elseText attribute is displayed otherwise --> + <macrodef name="do-only-if-not-library"> + <attribute name="elseText" /> + <element name="task-to-do" implicit="yes" /> + <sequential> + <if condition="${project.is.library}"> + <else> + <task-to-do /> + </else> + <then> + <echo>@{elseText}</echo> + </then> + </if> + </sequential> + </macrodef> + + <!-- macro to do a task on if manifest.hasCode is true. + elseText attribute is displayed otherwise --> + <macrodef name="do-only-if-manifest-hasCode"> + <attribute name="elseText" default=""/> + <element name="task-to-do" implicit="yes" /> + <sequential> + <if condition="${manifest.hasCode}"> + <then> + <task-to-do /> + </then> + <else> + <if> + <condition> + <length string="@{elseText}" trim="true" when="greater" length="0" /> + </condition> + <then> + <echo>@{elseText}</echo> + </then> + </if> + </else> + </if> + </sequential> + </macrodef> + <!-- Configurable macro, which allows to pass as parameters output directory, output dex filename and external libraries to dex (optional) --> @@ -221,7 +264,7 @@ <!-- This is macro that enable passing variable list of external jar files to ApkBuilder Example of use: - <package-helper output.filepath="/path/to/foo.apk"> + <package-helper> <extra-jars> <jarfolder path="my_jars" /> <jarfile path="foo/bar.jar" /> @@ -229,23 +272,20 @@ </extra-jars> </package-helper> --> <macrodef name="package-helper"> - <attribute name="output.filepath" /> <element name="extra-jars" optional="yes" /> <sequential> <apkbuilder outfolder="${out.absolute.dir}" resourcefile="${resource.package.file.name}" - apkfilepath="@{output.filepath}" - debugpackaging="${build.packaging.debug}" - debugsigning="${build.signing.debug}" + apkfilepath="${out.packaged.file}" + debugpackaging="${build.is.packaging.debug}" + debugsigning="${build.is.signing.debug}" abifilter="${filter.abi}" verbose="${verbose}" hascode="${manifest.hasCode}"> <dex path="${intermediate.dex.file}"/> <sourcefolder path="${source.absolute.dir}"/> - <sourcefolder refid="project.libraries.src"/> - <jarfolder path="${jar.libs.absolute.dir}" /> - <jarfolder refid="project.libraries.libs" /> + <jarfile refid="jar.libs.ref" /> <nativefolder path="${native.libs.absolute.dir}" /> <nativefolder refid="project.libraries.libs" /> <extra-jars/> @@ -270,94 +310,245 @@ </sequential> </macrodef> - <!-- This is macro used only for sharing code among two targets, -install and - -install-with-emma which do exactly the same but differ in dependencies --> - <macrodef name="install-helper"> + <macrodef name="run-tests-helper"> + <attribute name="emma.enabled" default="false" /> + <element name="extra-instrument-args" optional="yes" /> <sequential> - <echo>Installing ${out.debug.file} onto default emulator or device...</echo> + <echo>Running tests ...</echo> <exec executable="${adb}" failonerror="true"> <arg line="${adb.device.arg}" /> - <arg value="install" /> - <arg value="-r" /> - <arg path="${out.debug.file}" /> + <arg value="shell" /> + <arg value="am" /> + <arg value="instrument" /> + <arg value="-w" /> + <arg value="-e" /> + <arg value="coverage" /> + <arg value="@{emma.enabled}" /> + <extra-instrument-args /> + <arg value="${manifest.package}/${test.runner}" /> </exec> </sequential> </macrodef> - <!-- Rules --> + <macrodef name="record-build-info"> + <attribute name="key" default="false" /> + <attribute name="value" default="false" /> + <sequential> + <propertyfile file="${out.build.prop.file}" comment="Last build type"> + <entry key="@{key}" value="@{value}"/> + </propertyfile> + </sequential> + </macrodef> - <!-- Creates the output directories if they don't exist yet. --> - <target name="-dirs"> - <echo>Creating output directories if needed...</echo> - <mkdir dir="${resource.absolute.dir}" /> - <mkdir dir="${jar.libs.absolute.dir}" /> - <mkdir dir="${out.absolute.dir}" /> - <if condition="${manifest.hasCode}"> + <macrodef name="uninstall-helper"> + <attribute name="app.package" default="false" /> + <sequential> + <echo>Uninstalling @{app.package} from the default emulator or device...</echo> + <exec executable="${adb}" failonerror="true"> + <arg line="${adb.device.arg}" /> + <arg value="uninstall" /> + <arg value="@{app.package}" /> + </exec> + </sequential> + </macrodef> + + <!-- ********** Build Targets ********** --> + + <!-- this target simply force running -setup making + the project info be read. To be used as + ant all clean + to clean the main project as well as the libraries and tested project --> + <target name="all" depends="-setup"/> + + <!-- clean target --> + <target name="clean" description="Removes output files created by other targets."> + <delete dir="${out.absolute.dir}" verbose="${verbose}" /> + <delete dir="${gen.absolute.dir}" verbose="${verbose}" /> + + <!-- if we know about a tested project or libraries, we clean them too. This + will only work if the target 'all' was called first --> + <if condition="${project.is.test}"> <then> - <mkdir dir="${gen.absolute.dir}" /> - <mkdir dir="${out.classes.absolute.dir}" /> + <property name="tested.project.absolute.dir" location="${tested.project.dir}" /> + <subant failonerror="true"> + <fileset dir="${tested.project.absolute.dir}" includes="build.xml" /> + <target name="all" /> + <target name="clean" /> + </subant> + </then> + </if> + + <if> + <condition> + <isreference refid="project.libraries" /> + </condition> + <then> + <subant + buildpathref="project.libraries" + antfile="build.xml" + failonerror="true"> + <target name="all" /> + <target name="clean" /> + </subant> </then> </if> </target> - <!-- empty default pre-build target. Create a similar target in - your build.xml and it'll be called instead of this one. --> - <target name="-pre-build"/> + <!-- generic setup --> + <target name="-setup"> + <echo>Gathering info for ${ant.project.name}...</echo> + <!-- load project properties, resolve Android target, library dependencies + and set some properties with the results. + All property names are passed as parameters ending in -Out --> + <setup + projectTypeOut="android.project.type" + androidJarFileOut="android.jar" + androidAidlFileOut="android.aidl" + renderScriptExeOut="renderscript" + renderScriptIncludeDirOut="android.rs" + bootclasspathrefOut="android.target.classpath" + projectLibrariesRootOut="project.libraries" + projectLibrariesJarsOut="project.libraries.jars" + projectLibrariesResOut="project.libraries.res" + projectLibrariesPackageOut="project.libraries.package" + projectLibrariesLibsOut="project.libraries.libs" + /> + + <!-- sets a few boolean based on android.project.type + to make the if task easier --> + <condition property="project.is.library" else="false"> + <equals arg1="${android.project.type}" arg2="library" /> + </condition> + <condition property="project.is.test" else="false"> + <equals arg1="${android.project.type}" arg2="test" /> + </condition> + </target> - <!-- Generates the R.java file for this project's resources. --> - <target name="-resource-src" depends="-dirs"> - <if condition="${manifest.hasCode}"> + <!-- Pre build setup --> + <target name="-build-setup" depends="-setup"> + + <!-- read the previous build mode --> + <property file="${out.build.prop.file}" /> + <!-- if empty the prop won't be set, so set it to the current target + to provide a default value equal to the current build --> + <property name="build.last.target" value="${build.target}" /> + <!-- also set the default value for whether the build is instrumented --> + <property name="build.last.is.instrumented" value="${build.is.instrumented}" /> + + <!-- compile the libraries if any --> + <if> + <condition> + <isreference refid="project.libraries" /> + </condition> <then> - <echo>Generating R.java / Manifest.java from the resources...</echo> - <aapt executable="${aapt}" - command="package" - verbose="${verbose}" - manifest="AndroidManifest.xml" - androidjar="${android.jar}" - rfolder="${gen.absolute.dir}"> - <res path="${resource.absolute.dir}" /> - </aapt> + <echo>Building Libraries</echo> + <subant + buildpathref="project.libraries" + antfile="build.xml" + target="${build.target}" + failonerror="true"/> + <echo></echo> + <echo>############################################</echo> + <echo>**** Back to project ${ant.project.name} ****</echo> + <echo>############################################</echo> </then> - <else> - <echo>hasCode = false. Skipping...</echo> - </else> </if> - </target> - <!-- Generates java classes from .aidl files. --> - <target name="-aidl" depends="-dirs"> - <if condition="${manifest.hasCode}"> + <!-- compile the main project if this is a test project --> + <if condition="${project.is.test}"> <then> - <echo>Compiling aidl files into Java classes...</echo> - <aidl executable="${aidl}" framework="${android.aidl}" - genFolder="${gen.absolute.dir}"> - <source path="${source.absolute.dir}"/> - <source refid="project.libraries.src"/> - </aidl> + <property name="tested.project.absolute.dir" location="${tested.project.dir}" /> + + <!-- figure out which target must be used to build the tested project. + If emma is enabled, then use 'instrument' otherwise, use 'debug' --> + <condition property="tested.project.target" value="instrument" else="debug"> + <isset property="emma.enabled" /> + </condition> + + <echo>Building tested project at ${tested.project.absolute.dir}</echo> + <subant target="${tested.project.target}" failonerror="true"> + <fileset dir="${tested.project.absolute.dir}" includes="build.xml" /> + </subant> + <echo></echo> + <echo>############################################</echo> + <echo>**** Back to project ${ant.project.name} ****</echo> + <echo>############################################</echo> </then> - <else> - <echo>hasCode = false. Skipping...</echo> - </else> </if> - </target> - <!-- Compiles RenderScript files into Java and bytecode. --> - <target name="-renderscript" depends="-dirs"> - <if condition="${manifest.hasCode}"> + <!-- Value of the hasCode attribute (Application node) extracted from manifest file --> + <xpath input="AndroidManifest.xml" expression="/manifest/application/@android:hasCode" + output="manifest.hasCode" default="true"/> + + <!-- create a path with all the jar files, from the main project and the + libraries --> + <path id="jar.libs.ref"> + <fileset dir="${jar.libs.absolute.dir}" includes="*.jar" /> + <path refid="project.libraries.jars" /> + </path> + + <!-- special case for instrumented: if the previous build was + instrumented but not this one, clear out the compiled code --> + <if> + <condition> + <and> + <istrue value="${build.last.is.instrumented}" /> + <isfalse value="${build.is.instrumented}" /> + </and> + </condition> <then> - <echo>Compiling RenderScript files into Java classes and RenderScript bytecode...</echo> - <renderscript executable="${renderscript}" - framework="${android.rs}" - genFolder="${gen.absolute.dir}" - resFolder="${resource.absolute.dir}/raw"> - <source path="${source.absolute.dir}"/> - <source refid="project.libraries.src"/> - </renderscript> + <echo>Switching from instrumented to non-instrumented build.</echo> + <echo>Deleting previous compilation output:</echo> + <delete dir="${out.classes.absolute.dir}" verbose="${verbose}" /> </then> - <else> - <echo>hasCode = false. Skipping...</echo> - </else> </if> + + <echo>Creating output directories if needed...</echo> + <mkdir dir="${resource.absolute.dir}" /> + <mkdir dir="${jar.libs.absolute.dir}" /> + <mkdir dir="${out.absolute.dir}" /> + <mkdir dir="${out.res.absolute.dir}" /> + <do-only-if-manifest-hasCode> + <mkdir dir="${gen.absolute.dir}" /> + <mkdir dir="${out.classes.absolute.dir}" /> + </do-only-if-manifest-hasCode> + </target> + + <!-- empty default pre-build target. Create a similar target in + your build.xml and it'll be called instead of this one. --> + <target name="-pre-build"/> + + <!-- Code Generation: compile resources (aapt -> R.java), aidl, renderscript --> + <target name="-code-gen"> + <do-only-if-manifest-hasCode + elseText="hasCode = false. Skipping aidl/renderscript/R.java"> + <echo>Compiling aidl files into Java classes...</echo> + <aidl executable="${aidl}" framework="${android.aidl}" + genFolder="${gen.absolute.dir}"> + <source path="${source.absolute.dir}"/> + </aidl> + + <!-- renderscript generates resources so it must be called before aapt --> + <echo>Compiling RenderScript files into Java classes and RenderScript bytecode...</echo> + <renderscript executable="${renderscript}" + framework="${android.rs}" + genFolder="${gen.absolute.dir}" + resFolder="${resource.absolute.dir}/raw"> + <source path="${source.absolute.dir}"/> + </renderscript> + + <echo>Generating R.java / Manifest.java from the resources...</echo> + <aapt executable="${aapt}" + command="package" + verbose="${verbose}" + manifest="AndroidManifest.xml" + androidjar="${android.jar}" + rfolder="${gen.absolute.dir}" + projectLibrariesResName="project.libraries.res" + projectLibrariesPackageName="project.libraries.package"> + <res path="${resource.absolute.dir}" /> + </aapt> + </do-only-if-manifest-hasCode> </target> <!-- empty default pre-compile target. Create a similar target in @@ -365,42 +556,71 @@ <target name="-pre-compile"/> <!-- Compiles this project's .java files into .class files. --> - <target name="compile" depends="-pre-build, -aidl, -renderscript, -resource-src, -pre-compile" - description="Compiles project's .java files into .class files"> - <if condition="${manifest.hasCode}"> - <then> - <!-- If android rules are used for a test project, its classpath should include - tested project's location --> - <condition property="extensible.classpath" - value="${tested.project.absolute.dir}/bin/classes" - else="."> - <isset property="tested.project.absolute.dir" /> - </condition> - <condition property="extensible.libs.classpath" - value="${tested.project.absolute.dir}/libs" - else="${jar.libs.dir}"> - <isset property="tested.project.absolute.dir" /> - </condition> - <javac encoding="${java.encoding}" - source="${java.source}" target="${java.target}" - debug="true" extdirs="" - destdir="${out.classes.absolute.dir}" - bootclasspathref="android.target.classpath" - verbose="${verbose}" - classpath="${extensible.classpath}" - classpathref="jar.libs.ref"> - <src path="${source.absolute.dir}" /> - <src path="${gen.absolute.dir}" /> - <src refid="project.libraries.src" /> - <classpath> - <fileset dir="${extensible.libs.classpath}" includes="*.jar" /> - </classpath> - </javac> - </then> - <else> - <echo>hasCode = false. Skipping...</echo> - </else> - </if> + <target name="-compile" depends="-build-setup, -pre-build, -code-gen, -pre-compile"> + <do-only-if-manifest-hasCode elseText="hasCode = false. Skipping..."> + <!-- If android rules are used for a test project, its classpath should include + tested project's location --> + <condition property="extensible.classpath" + value="${tested.project.absolute.dir}/bin/classes" + else="."> + <isset property="tested.project.absolute.dir" /> + </condition> + <condition property="extensible.libs.classpath" + value="${tested.project.absolute.dir}/${jar.libs.dir}" + else="${jar.libs.dir}"> + <isset property="tested.project.absolute.dir" /> + </condition> + <javac encoding="${java.encoding}" + source="${java.source}" target="${java.target}" + debug="true" extdirs="" + destdir="${out.classes.absolute.dir}" + bootclasspathref="android.target.classpath" + verbose="${verbose}" + classpath="${extensible.classpath}" + classpathref="jar.libs.ref"> + <src path="${source.absolute.dir}" /> + <src path="${gen.absolute.dir}" /> + <classpath> + <fileset dir="${extensible.libs.classpath}" includes="*.jar" /> + </classpath> + </javac> + <!-- if the project is a library then we generate a jar file --> + <if condition="${project.is.library}"> + <then> + <echo>Creating library output jar file...</echo> + <property name="out.library.jar.file" location="${out.absolute.dir}/classes.jar" /> + <if> + <condition> + <length string="${android.package.excludes}" trim="true" when="greater" length="0" /> + </condition> + <then> + <echo>Custom jar packaging exclusion: ${android.package.excludes}</echo> + </then> + </if> + <jar destfile="${out.library.jar.file}"> + <fileset dir="${out.classes.absolute.dir}" excludes="**/R.class **/R$*.class"/> + <fileset dir="${source.absolute.dir}" excludes="**/*.java ${android.package.excludes}" /> + </jar> + </then> + </if> + + <!-- if the project is instrumented, intrument the classes --> + <if condition="${build.is.instrumented}"> + <then> + <echo>Instrumenting classes from ${out.absolute.dir}/classes...</echo> + <!-- It only instruments class files, not any external libs --> + <emma enabled="true"> + <instr verbosity="${verbosity}" + mode="overwrite" + instrpath="${out.absolute.dir}/classes" + outdir="${out.absolute.dir}/classes"> + </instr> + <!-- TODO: exclusion filters on R*.class and allowing custom exclusion from + user defined file --> + </emma> + </then> + </if> + </do-only-if-manifest-hasCode> </target> <!-- empty default post-compile target. Create a similar target in @@ -418,13 +638,12 @@ -debug-obfuscation-check Obfuscation should not happen. Set the same property to false. -obfuscate - ** Make sure unless="do.not.compile" is used in the target definition ** check if the property set in -debug/release-obfuscation-check is set to true. If true: Perform obfuscation Set property out.dex.input.absolute.dir to be the output of the obfuscation --> - <target name="-obfuscate" unless="do.not.compile"> + <target name="-obfuscate"> <if condition="${proguard.enabled}"> <then> <property name="obfuscate.absolute.dir" location="${out.absolute.dir}/proguard" /> @@ -469,7 +688,7 @@ <mkdir dir="${obfuscate.absolute.dir}" /> <delete file="${preobfuscate.jar.file}"/> <delete file="${obfuscated.jar.file}"/> - <jar basedir="${out.classes.dir}" + <jar basedir="${out.classes.absolute.dir}" destfile="${preobfuscate.jar.file}" /> <proguard> @${proguard.config} @@ -486,28 +705,44 @@ </target> <!-- Converts this project's .class files into .dex files --> - <target name="-dex" depends="compile, -post-compile, -obfuscate" - unless="do.not.compile"> - <if condition="${manifest.hasCode}"> - <then> - <dex-helper /> - </then> - <else> - <echo>hasCode = false. Skipping...</echo> - </else> - </if> + <target name="-dex" depends="-compile, -post-compile, -obfuscate"> + <do-only-if-manifest-hasCode elseText="hasCode = false. Skipping..."> + <!-- only convert to dalvik bytecode is *not* a library --> + <do-only-if-not-library elseText="Library project: do not convert bytecode..." > + <!-- special case for instrumented builds: need to use no-locals and need + to pass in the emma jar. --> + <if condition="${build.is.instrumented}"> + <then> + <dex-helper> + <extra-parameters> + <arg value="--no-locals" /> + </extra-parameters> + <external-libs> + <fileset file="${emma.dir}/emma_device.jar" /> + </external-libs> + </dex-helper> + </then> + <else> + <dex-helper /> + </else> + </if> + </do-only-if-not-library> + </do-only-if-manifest-hasCode> </target> <!-- Updates the pre-processed PNG cache --> <target name="-crunch"> - <exec executable="${aapt}"> - <arg value="crunch" /> - <arg value="-v" /> - <arg value="-S" /> - <arg path="${resource.absolute.dir}" /> - <arg value="-C" /> - <arg path="${out.resource.absolute.dir}" /> - </exec> + <!-- only crunch if *not* a library project --> + <do-only-if-not-library elseText="Library project: do not optimize PNGs..." > + <exec executable="${aapt}" taskName="crunch"> + <arg value="crunch" /> + <arg value="-v" /> + <arg value="-S" /> + <arg path="${resource.absolute.dir}" /> + <arg value="-C" /> + <arg path="${out.res.absolute.dir}" /> + </exec> + </do-only-if-not-library> </target> <!-- Puts the project's resources into the output package file @@ -516,43 +751,53 @@ declared in default.properties. --> <target name="-package-resources" depends="-crunch"> - <echo>Packaging resources</echo> - <aapt executable="${aapt}" - command="package" - versioncode="${version.code}" - versionname="${version.name}" - debug="${build.packaging.debug}" - manifest="AndroidManifest.xml" - assets="${asset.absolute.dir}" - androidjar="${android.jar}" - apkfolder="${out.absolute.dir}" - nocrunch="${build.packaging.nocrunch}" - resourcefilename="${resource.package.file.name}" - resourcefilter="${aapt.resource.filter}"> - <res path="${out.resource.absolute.dir}" /> - <res path="${resource.absolute.dir}" /> - <!-- <nocompress /> forces no compression on any files in assets or res/raw --> - <!-- <nocompress extension="xml" /> forces no compression on specific file extensions in assets and res/raw --> - </aapt> + <!-- only package resources if *not* a library project --> + <do-only-if-not-library elseText="Library project: do not package apk..." > + <aapt executable="${aapt}" + command="package" + versioncode="${version.code}" + versionname="${version.name}" + debug="${build.is.packaging.debug}" + manifest="AndroidManifest.xml" + assets="${asset.absolute.dir}" + androidjar="${android.jar}" + apkfolder="${out.absolute.dir}" + nocrunch="${build.packaging.nocrunch}" + resourcefilename="${resource.package.file.name}" + resourcefilter="${aapt.resource.filter}" + projectLibrariesResName="project.libraries.res" + projectLibrariesPackageName="project.libraries.package" + previousBuildType="${build.last.target}" + buildType="${build.target}"> + <res path="${out.res.absolute.dir}" /> + <res path="${resource.absolute.dir}" /> + <!-- <nocompress /> forces no compression on any files in assets or res/raw --> + <!-- <nocompress extension="xml" /> forces no compression on specific file extensions in assets and res/raw --> + </aapt> + </do-only-if-not-library> </target> - <!-- Packages the application and sign it with a debug key. --> - <target name="-package-debug-sign" depends="-dex, -package-resources"> - <package-helper - output.filepath="${out.debug.unaligned.file}" /> - </target> - - <!-- Packages the application without signing it. --> - <target name="-package-release" depends="-dex, -package-resources"> - <package-helper - output.filepath="${out.unsigned.file}"/> + <!-- Packages the application. --> + <target name="-package" depends="-dex, -package-resources"> + <!-- only package apk if *not* a library project --> + <do-only-if-not-library elseText="Library project: do not package apk..." > + <if condition="${build.is.instrumented}"> + <then> + <package-helper> + <extra-jars> + <!-- Injected from external file --> + <jarfile path="${emma.dir}/emma_device.jar" /> + </extra-jars> + </package-helper> + </then> + <else> + <package-helper /> + </else> + </if> + </do-only-if-not-library> </target> - <target name="-compile-tested-if-test" if="tested.project.dir" unless="do.not.compile.again"> - <subant target="compile"> - <fileset dir="${tested.project.absolute.dir}" includes="build.xml" /> - </subant> - </target> + <!-- ********** Debug specific targets ********** --> <target name="-debug-obfuscation-check"> <!-- proguard is never enabled in debug mode --> @@ -560,27 +805,38 @@ </target> <target name="-set-debug-mode" depends="-debug-obfuscation-check"> - <!-- property only set in debug mode. - Useful for if/unless attributes in target node - when using Ant before 1.8 --> - <property name="build.mode.debug" value="true"/> + <property name="out.packaged.file" location="${out.absolute.dir}/${ant.project.name}-debug-unaligned.apk" /> + <property name="out.final.file" location="${out.absolute.dir}/${ant.project.name}-debug.apk" /> + + <!-- record the current build target --> + <property name="build.target" value="debug" /> + + <property name="build.is.instrumented" value="false" /> <!-- whether the build is a debug build. always set. --> - <property name="build.packaging.debug" value="true" /> + <property name="build.is.packaging.debug" value="true" /> <!-- signing mode: debug --> - <property name="build.signing.debug" value="true" /> + <property name="build.is.signing.debug" value="true" /> </target> - <!-- Builds debug output package, provided all the necessary files are already dexed --> - <target name="debug" depends="-set-debug-mode, -compile-tested-if-test, -package-debug-sign" + <!-- Builds debug output package --> + <target name="debug" depends="-set-debug-mode, -package" description="Builds the application and signs it with a debug key."> - <zipalign-helper in.package="${out.debug.unaligned.file}" - out.package="${out.debug.file}" /> - <echo>Debug Package: ${out.debug.file}</echo> + <!-- only create apk if *not* a library project --> + <do-only-if-not-library elseText="Library project: do not create apk..." > + <sequential> + <zipalign-helper in.package="${out.packaged.file}" out.package="${out.final.file}" /> + <echo>Debug Package: ${out.final.file}</echo> + </sequential> + </do-only-if-not-library> + <record-build-info key="build.last.target" value="${build.target}" /> + <record-build-info key="build.last.is.instrumented" value="${build.is.instrumented}" /> </target> + <!-- ********** Release specific targets ********** --> + <!-- called through target 'release'. Only executed if the keystore and key alias are known but not their password. --> <target name="-release-prompt-for-password" if="has.keystore" unless="has.password"> @@ -596,15 +852,22 @@ <!-- called through target 'release'. Only executed if there's no keystore/key alias set --> <target name="-release-nosign" unless="has.keystore"> - <echo>No key.store and key.alias properties found in build.properties.</echo> - <echo>Please sign ${out.unsigned.file} manually</echo> - <echo>and run zipalign from the Android SDK tools.</echo> + <!-- no release builds for library project --> + <do-only-if-not-library elseText="" > + <sequential> + <echo>No key.store and key.alias properties found in build.properties.</echo> + <echo>Please sign ${out.packaged.file} manually</echo> + <echo>and run zipalign from the Android SDK tools.</echo> + </sequential> + </do-only-if-not-library> + <record-build-info key="build.last.target" value="${build.target}" /> + <record-build-info key="build.last.is.instrumented" value="${build.is.instrumented}" /> </target> <target name="-release-obfuscation-check"> <condition property="proguard.enabled" value="true" else="false"> <and> - <isset property="build.mode.release" /> + <isset property="build.is.mode.release" /> <isset property="proguard.config" /> </and> </condition> @@ -618,16 +881,23 @@ </target> <target name="-set-release-mode"> + <property name="out.packaged.file" location="${out.absolute.dir}/${ant.project.name}-release-unsigned.apk" /> + <property name="out.final.file" location="${out.absolute.dir}/${ant.project.name}-release.apk" /> + + <!-- record the current build target --> + <property name="build.target" value="release" /> + + <property name="build.is.instrumented" value="false" /> + <!-- release mode is only valid if the manifest does not explicitly - set debuggable to true. default is false. - We actually store build.packaging.debug, not build.release --> + set debuggable to true. default is false. --> <xpath input="AndroidManifest.xml" expression="/manifest/application/@android:debuggable" - output="build.packaging.debug" default="false"/> + output="build.is.packaging.debug" default="false"/> <!-- signing mode: release --> - <property name="build.signing.debug" value="false" /> + <property name="build.is.signing.debug" value="false" /> - <if condition="${build.packaging.debug}"> + <if condition="${build.is.packaging.debug}"> <then> <echo>*************************************************</echo> <echo>**** Android Manifest has debuggable=true ****</echo> @@ -638,7 +908,7 @@ <!-- property only set in release mode. Useful for if/unless attributes in target node when using Ant before 1.8 --> - <property name="build.mode.release" value="true"/> + <property name="build.is.mode.release" value="true"/> </else> </if> </target> @@ -647,111 +917,257 @@ only if release-sign is true (set in -release-check, called by -release-no-sign)--> <target name="release" - depends="-set-release-mode, -release-obfuscation-check, -package-release, -release-prompt-for-password, -release-nosign" + depends="-set-release-mode, -release-obfuscation-check, -package, -release-prompt-for-password, -release-nosign" if="has.keystore" description="Builds the application. The generated apk file must be signed before it is published."> - <!-- Signs the APK --> - <echo>Signing final apk...</echo> - <signjar - jar="${out.unsigned.file}" - signedjar="${out.unaligned.file}" - keystore="${key.store}" - storepass="${key.store.password}" - alias="${key.alias}" - keypass="${key.alias.password}" - verbose="${verbose}" /> - - <!-- Zip aligns the APK --> - <zipalign-helper in.package="${out.unaligned.file}" - out.package="${out.release.file}" /> - <echo>Release Package: ${out.release.file}</echo> - </target> - <target name="install" depends="debug" - description="Installs/reinstalls the debug package onto a running - emulator or device. If the application was previously installed, - the signatures must match." > - <install-helper /> + <!-- only create apk if *not* a library project --> + <do-only-if-not-library elseText="Library project: do not create apk..." > + <sequential> + <property name="out.unaligned.file" location="${out.absolute.dir}/${ant.project.name}-release-unaligned.apk" /> + + <!-- Signs the APK --> + <echo>Signing final apk...</echo> + <signjar + jar="${out.packaged.file}" + signedjar="${out.unaligned.file}" + keystore="${key.store}" + storepass="${key.store.password}" + alias="${key.alias}" + keypass="${key.alias.password}" + verbose="${verbose}" /> + + <!-- Zip aligns the APK --> + <zipalign-helper in.package="${out.unaligned.file}" + out.package="${out.final.file}" /> + <echo>Release Package: ${out.final.file}</echo> + </sequential> + </do-only-if-not-library> + <record-build-info key="build.last.target" value="${build.target}" /> + <record-build-info key="build.last.is.instrumented" value="${build.is.instrumented}" /> </target> - <target name="-uninstall-check"> - <condition property="uninstall.run"> - <isset property="manifest.package" /> - </condition> - </target> + <!-- ********** Instrumented specific targets ********** --> + + <!-- These targets are specific for the project under test when it + gets compiled by the test projects in a way that will make it + support emma code coverage --> + + <target name="-set-instrumented-mode"> + <property name="out.packaged.file" location="${out.absolute.dir}/${ant.project.name}-instrumented-unaligned.apk" /> + <property name="out.final.file" location="${out.absolute.dir}/${ant.project.name}-instrumented.apk" /> - <target name="-uninstall-error" depends="-uninstall-check" unless="uninstall.run"> - <echo>Unable to run 'ant uninstall', manifest.package property is not defined. - </echo> + <!-- whether the build is an instrumented build. --> + <property name="build.is.instrumented" value="true" /> </target> - <!-- Uninstalls the package from the default emulator/device --> - <target name="uninstall" depends="-uninstall-error" if="uninstall.run" - description="Uninstalls the application from a running emulator or device."> - <echo>Uninstalling ${manifest.package} from the default emulator or device...</echo> - <exec executable="${adb}" failonerror="true"> - <arg line="${adb.device.arg}" /> - <arg value="uninstall" /> - <arg value="${manifest.package}" /> - </exec> + <!-- Builds instrumented output package --> + <target name="instrument" depends="-set-instrumented-mode, debug" + description="Builds an instrumented packaged."> + <!-- only create apk if *not* a library project --> + <do-only-if-not-library elseText="Library project: do not create apk..." > + <sequential> + <zipalign-helper in.package="${out.packaged.file}" out.package="${out.final.file}" /> + <echo>Instrumented Package: ${out.final.file}</echo> + </sequential> + </do-only-if-not-library> + <record-build-info key="build.last.target" value="${build.target}" /> + <record-build-info key="build.last.is.instrumented" value="${build.is.instrumented}" /> </target> - <target name="clean" description="Removes output files created by other targets."> - <delete dir="${out.absolute.dir}" verbose="${verbose}" /> - <delete dir="${gen.absolute.dir}" verbose="${verbose}" /> + <!-- ********** Test project specific targets ********** --> + + <!-- enable code coverage --> + <target name="emma"> + <property name="emma.enabled" value="true" /> </target> - <!-- Targets for code-coverage measurement purposes, invoked from external file --> - - <!-- Emma-instruments tested project classes (compiles the tested project if necessary) - and writes instrumented classes to ${instrumentation.absolute.dir}/classes --> - <target name="-emma-instrument" depends="compile"> - <echo>Instrumenting classes from ${out.absolute.dir}/classes...</echo> - <!-- It only instruments class files, not any external libs --> - <emma enabled="true"> - <instr verbosity="${verbosity}" - mode="overwrite" - instrpath="${out.absolute.dir}/classes" - outdir="${out.absolute.dir}/classes"> - </instr> - <!-- TODO: exclusion filters on R*.class and allowing custom exclusion from - user defined file --> - </emma> + <!-- fails if the project is not a test project --> + <target name="-test-project-check"> + <!-- can't use project.is.test since the setup target is not run --> + <if> + <condition> + <isset property="tested.project.dir" /> + </condition> + <else> + <fail message="Project is not a test project." /> + </else> + </if> </target> - <target name="-dex-instrumented" depends="-emma-instrument"> - <dex-helper> - <extra-parameters> - <arg value="--no-locals" /> - </extra-parameters> - <external-libs> - <fileset file="${emma.dir}/emma_device.jar" /> - </external-libs> - </dex-helper> + <!-- Installs the tested project. This make sure to install the proper package based on + the value of emma.enabled --> + <target name="-install-tested-project" depends="-test-project-check"> + <!-- figure out which tested package to install based on emma.enabled --> + <condition property="tested.project.install.target" value="installi" else="installd"> + <isset property="emma.enabled" /> + </condition> + <subant target="${tested.project.install.target}" failonerror="true"> + <fileset dir="${tested.project.absolute.dir}" includes="build.xml" /> + </subant> </target> - <!-- Invoked from external files for code coverage purposes --> - <target name="-package-with-emma" depends="-dex-instrumented, -package-resources"> - <package-helper - output.filepath="${out.debug.unaligned.file}"> - <extra-jars> - <!-- Injected from external file --> - <jarfile path="${emma.dir}/emma_device.jar" /> - </extra-jars> - </package-helper> + <target name="test" depends="-test-project-check" + description="Runs tests from the package defined in test.package property"> + + <property name="tested.project.absolute.dir" location="${tested.project.dir}" /> + + <property name="test.runner" value="android.test.InstrumentationTestRunner" /> + + <!-- Application package of the tested project extracted from its manifest file --> + <xpath input="${tested.project.absolute.dir}/AndroidManifest.xml" + expression="/manifest/@package" output="tested.manifest.package" /> + + <property name="emma.dump.file" + value="/sdcard/${tested.manifest.package}_coverage.ec" /> + + <if condition="${emma.enabled}"> + <then> + <fail message="code coverage is not currently supported. coming soon"/> + <run-tests-helper emma.enabled="true"> + <extra-instrument-args> + <arg value="-e" /> + <arg value="coverageFile" /> + <arg value="${emma.dump.file}" /> + </extra-instrument-args> + </run-tests-helper> + <echo>Downloading coverage file into project directory...</echo> + <exec executable="${adb}" failonerror="true"> + <arg line="${adb.device.arg}" /> + <arg value="pull" /> + <arg value="${emma.dump.file}" /> + <arg value="coverage.ec" /> + </exec> + <echo>Extracting coverage report...</echo> + <emma> + <report sourcepath="${tested.project.absolute.dir}/${source.dir}" + verbosity="${verbosity}"> + <!-- TODO: report.dir or something like should be introduced if necessary --> + <infileset dir="."> + <include name="coverage.ec" /> + <include name="coverage.em" /> + </infileset> + <!-- TODO: reports in other, indicated by user formats --> + <html outfile="coverage.html" /> + </report> + </emma> + <echo>Cleaning up temporary files...</echo> + <delete file="coverage.ec" /> + <delete file="coverage.em" /> + <echo>Saving the report file in ${basedir}/coverage/coverage.html</echo> + </then> + <else> + <run-tests-helper /> + </else> + </if> </target> - <target name="-debug-with-emma" depends="-set-debug-mode, -package-with-emma"> - <zipalign-helper in.package="${out.debug.unaligned.file}" - out.package="${out.debug.file}" /> + + <!-- ********** Install/uninstall specific targets ********** --> + + <target name="install" + description="Installs the newly build package. Must be used in conjunction with a build target + (debug/release/instrument). If the application was previously installed, the application + is reinstalled if the signature matches." > + <!-- only do install if *not* a library project --> + <do-only-if-not-library elseText="Library project: nothing to install!" > + <if> + <condition> + <isset property="out.final.file" /> + </condition> + <then> + <if> + <condition> + <resourceexists> + <file file="${out.final.file}"/> + </resourceexists> + </condition> + <then> + <echo>Installing ${out.final.file} onto default emulator or device...</echo> + <exec executable="${adb}" failonerror="true"> + <arg line="${adb.device.arg}" /> + <arg value="install" /> + <arg value="-r" /> + <arg path="${out.final.file}" /> + </exec> + </then> + <else> + <fail message="File {out.final.file} does not exist." /> + </else> + </if> + </then> + <else> + <echo>Install file not specified.</echo> + <echo></echo> + <echo>'ant install' now requires the build target to be specified as well.</echo> + <echo></echo> + <echo></echo> + <echo> ant debug install</echo> + <echo> ant release install</echo> + <echo> ant instrument install</echo> + <echo>This will build the given package and install it.</echo> + <echo></echo> + <echo>Alternatively, you can use</echo> + <echo> ant installd</echo> + <echo> ant installr</echo> + <echo> ant installi</echo> + <echo> ant installt</echo> + <echo>to only install an existing package (this will not rebuild the package.)</echo> + <fail /> + </else> + </if> + </do-only-if-not-library> </target> - <target name="-install-with-emma" depends="-debug-with-emma"> - <install-helper /> + <target name="installd" depends="-set-debug-mode, install" + description="Installs (only) the debug package." /> + <target name="installr" depends="-set-release-mode, install" + description="Installs (only) the release package." /> + <target name="installi" depends="-set-instrumented-mode, install" + description="Installs (only) the instrumented package." /> + <target name="installt" depends="-install-tested-project, installd" + description="Installs (only) the test and tested packages." /> + + + <!-- Uninstalls the package from the default emulator/device --> + <target name="uninstall" + description="Uninstalls the application from a running emulator or device."> + <if> + <condition> + <isset property="manifest.package" /> + </condition> + <then> + <uninstall-helper app.package="${manifest.package}" /> + </then> + <else> + <echo>Could not find application package in manifest. Cannot run 'adb uninstall'.</echo> + </else> + </if> + + <if condition="${project.is.test}"> + <then> + <property name="tested.project.absolute.dir" location="${tested.project.dir}" /> + + <!-- Application package of the tested project extracted from its manifest file --> + <xpath input="${tested.project.absolute.dir}/AndroidManifest.xml" + expression="/manifest/@package" output="tested.manifest.package" /> + <if> + <condition> + <isset property="tested.manifest.package" /> + </condition> + <then> + <uninstall-helper app.package="${tested.manifest.package}" /> + </then> + <else> + <echo>Could not find tested application package in manifest. Cannot run 'adb uninstall'.</echo> + </else> + </if> + </then> + </if> + </target> - <!-- End of targets for code-coverage measurement purposes --> <target name="help"> <!-- displays starts at col 13 @@ -759,14 +1175,29 @@ <echo>Android Ant Build. Available targets:</echo> <echo> help: Displays this help.</echo> <echo> clean: Removes output files created by other targets.</echo> - <echo> compile: Compiles project's .java files into .class files.</echo> + <echo> The 'all' target can be used to clean dependencies</echo> + <echo> (tested projects and libraries)at the same time</echo> + <echo> using: 'ant all clean'</echo> <echo> debug: Builds the application and signs it with a debug key.</echo> <echo> release: Builds the application. The generated apk file must be</echo> <echo> signed before it is published.</echo> - <echo> install: Installs/reinstalls the debug package onto a running</echo> - <echo> emulator or device.</echo> + <echo> instrument:Builds an instrumented package and signs it with a</echo> + <echo> debug key.</echo> + <echo> test: Runs the tests. Project must be a test project and</echo> + <echo> must have been built. Typical usage would be:</echo> + <echo> ant [emma] debug installt test</echo> + <echo> emma: Transiently enables code coverage for subsequent</echo> + <echo> targets.</echo> + <echo> install: Installs the newly build package. Must either be used</echo> + <echo> in conjunction with a build target (debug/release/</echo> + <echo> instrument) or with the proper suffix indicating</echo> + <echo> which package to install (see below).</echo> <echo> If the application was previously installed, the</echo> - <echo> signatures must match.</echo> + <echo> application is reinstalled if the signature matches.</echo> + <echo> installd: Installs (only) the debug package.</echo> + <echo> installr: Installs (only) the release package.</echo> + <echo> installi: Installs (only) the instrumented package.</echo> + <echo> installt: Installs (only) the test and tested packages.</echo> <echo> uninstall: Uninstalls the application from a running emulator or</echo> <echo> device.</echo> </target> diff --git a/files/ant/pre_setup.xml b/files/ant/pre_setup.xml index eee449a1e..1c8610943 100644 --- a/files/ant/pre_setup.xml +++ b/files/ant/pre_setup.xml @@ -1,14 +1,6 @@ <?xml version="1.0" encoding="UTF-8"?> <project name="imported" basedir="."> - <!-- Custom Android task to deal with the project target, and import the - proper rules. - This requires ant 1.6.0 or above. --> - <path id="android.antlibs"> - <pathelement path="${sdk.dir}/tools/lib/anttasks.jar" /> - </path> + <fail message="Your build.xml file is outdated. Delete it and regenerate it with 'android update project'"/> - <taskdef name="setup" - classname="com.android.ant.SetupTask" - classpathref="android.antlibs" /> </project> diff --git a/files/ant/test_rules.xml b/files/ant/test_rules.xml deleted file mode 100644 index 955fba4ca..000000000 --- a/files/ant/test_rules.xml +++ /dev/null @@ -1,106 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<project name="android_test_rules" default="run-tests"> - - <import file="main_rules.xml" /> - - <property name="tested.project.absolute.dir" location="${tested.project.dir}" /> - <property name="instrumentation.dir" value="instrumented" /> - <property name="instrumentation.absolute.dir" location="${instrumentation.dir}" /> - - <property name="test.runner" value="android.test.InstrumentationTestRunner" /> - <!-- Application package of the tested project extracted from its manifest file --> - <xpath input="${tested.project.absolute.dir}/AndroidManifest.xml" - expression="/manifest/@package" output="tested.manifest.package" /> - - <!-- TODO: make it more configurable in the next CL's - now it is default for auto-generated - project --> - <property name="emma.dump.file" - value="/data/data/${tested.manifest.package}/files/coverage.ec" /> - - <macrodef name="run-tests-helper"> - <attribute name="emma.enabled" default="false" /> - <element name="extra-instrument-args" optional="yes" /> - <sequential> - <echo>Running tests ...</echo> - <exec executable="${adb}" failonerror="true"> - <arg line="${adb.device.arg}" /> - <arg value="shell" /> - <arg value="am" /> - <arg value="instrument" /> - <arg value="-w" /> - <arg value="-e" /> - <arg value="coverage" /> - <arg value="@{emma.enabled}" /> - <extra-instrument-args /> - <arg value="${manifest.package}/${test.runner}" /> - </exec> - </sequential> - </macrodef> - - <!-- Invoking this target sets the value of extensible.classpath, which is being added to javac - classpath in target 'compile' (android_rules.xml) --> - <target name="-set-coverage-classpath"> - <property name="extensible.classpath" - location="${instrumentation.absolute.dir}/classes" /> - </target> - - <!-- Ensures that tested project is installed on the device before we run the tests. - Used for ordinary tests, without coverage measurement --> - <target name="-install-tested-project"> - <property name="do.not.compile.again" value="true" /> - <subant target="install"> - <fileset dir="${tested.project.absolute.dir}" includes="build.xml" /> - </subant> - </target> - - <target name="run-tests" depends="-install-tested-project, install" - description="Runs tests from the package defined in test.package property"> - <run-tests-helper /> - </target> - - <target name="-install-instrumented"> - <property name="do.not.compile.again" value="true" /> - <subant target="-install-with-emma"> - <property name="out.absolute.dir" value="${instrumentation.absolute.dir}" /> - <fileset dir="${tested.project.absolute.dir}" includes="build.xml" /> - </subant> - </target> - - <target name="coverage" depends="-set-coverage-classpath, -install-instrumented, install" - description="Runs the tests against the instrumented code and generates - code coverage report"> - <run-tests-helper emma.enabled="true"> - <extra-instrument-args> - <arg value="-e" /> - <arg value="coverageFile" /> - <arg value="${emma.dump.file}" /> - </extra-instrument-args> - </run-tests-helper> - <echo>Downloading coverage file into project directory...</echo> - <exec executable="${adb}" failonerror="true"> - <arg line="${adb.device.arg}" /> - <arg value="pull" /> - <arg value="${emma.dump.file}" /> - <arg value="coverage.ec" /> - </exec> - <echo>Extracting coverage report...</echo> - <emma> - <report sourcepath="${tested.project.absolute.dir}/${source.dir}" - verbosity="${verbosity}"> - <!-- TODO: report.dir or something like should be introduced if necessary --> - <infileset dir="."> - <include name="coverage.ec" /> - <include name="coverage.em" /> - </infileset> - <!-- TODO: reports in other, indicated by user formats --> - <html outfile="coverage.html" /> - </report> - </emma> - <echo>Cleaning up temporary files...</echo> - <delete dir="${instrumentation.absolute.dir}" /> - <delete file="coverage.ec" /> - <delete file="coverage.em" /> - <echo>Saving the report file in ${basedir}/coverage/coverage.html</echo> - </target> - -</project> diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/IAndroidTarget.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/IAndroidTarget.java index 44c46ee98..9c369edf6 100644 --- a/sdkmanager/libs/sdklib/src/com/android/sdklib/IAndroidTarget.java +++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/IAndroidTarget.java @@ -61,9 +61,13 @@ public interface IAndroidTarget extends Comparable<IAndroidTarget> { public final static int SOURCES = 18; /** OS Path to the target specific docs */ public final static int DOCS = 19; - /** OS Path to the target's version of the aapt tool. */ + /** OS Path to the target's version of the aapt tool. + * This is deprecated as aapt is now in the platform tools and not in the platform. */ + @Deprecated public final static int AAPT = 20; - /** OS Path to the target's version of the aidl tool. */ + /** OS Path to the target's version of the aidl tool. + * This is deprecated as aidl is now in the platform tools and not in the platform. */ + @Deprecated public final static int AIDL = 21; /** OS Path to the target's version of the dx too.<br> * This is deprecated as dx is now in the platform tools and not in the platform. */ diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/SdkConstants.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/SdkConstants.java index 713e8a844..181e7ecad 100644 --- a/sdkmanager/libs/sdklib/src/com/android/sdklib/SdkConstants.java +++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/SdkConstants.java @@ -50,6 +50,8 @@ public final class SdkConstants { /** An SDK Project's AndroidManifest.xml file */ public static final String FN_ANDROID_MANIFEST_XML= "AndroidManifest.xml"; + /** pre-dex jar filename. i.e. "classes.jar" */ + public final static String FN_CLASSES_JAR = "classes.jar"; //$NON-NLS-1$ /** Dex filename inside the APK. i.e. "classes.dex" */ public final static String FN_APK_CLASSES_DEX = "classes.dex"; //$NON-NLS-1$ diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/build/ApkBuilder.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/build/ApkBuilder.java index 2148ccf0b..da1790ef9 100644 --- a/sdkmanager/libs/sdklib/src/com/android/sdklib/build/ApkBuilder.java +++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/build/ApkBuilder.java @@ -47,7 +47,7 @@ import java.util.regex.Pattern; * - Native libraries from the project or its library. * */ -public final class ApkBuilder { +public final class ApkBuilder implements IArchiveBuilder { private final static Pattern PATTERN_NATIVELIB_EXT = Pattern.compile("^.+\\.so$", Pattern.CASE_INSENSITIVE); diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/build/IArchiveBuilder.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/build/IArchiveBuilder.java new file mode 100644 index 000000000..e2230e9ee --- /dev/null +++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/build/IArchiveBuilder.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0 + * + * 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.sdklib.build; + +import java.io.File; + +public interface IArchiveBuilder { + + /** + * Adds a file to the archive at a given path + * @param file the file to add + * @param archivePath the path of the file inside the APK archive. + * @throws ApkCreationException if an error occurred + * @throws SealedApkException if the APK is already sealed. + * @throws DuplicateFileException if a file conflicts with another already added to the APK + * at the same location inside the APK archive. + */ + void addFile(File file, String archivePath) throws ApkCreationException, + SealedApkException, DuplicateFileException; + +} diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/project/ProjectCreator.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/project/ProjectCreator.java index 8db958789..e2874b924 100644 --- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/project/ProjectCreator.java +++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/project/ProjectCreator.java @@ -603,20 +603,19 @@ public class ProjectCreator { // Build.xml: create if not present or no <androidinit/> in it File buildXml = new File(projectFolder, SdkConstants.FN_BUILD_XML); boolean needsBuildXml = projectName != null || !buildXml.exists(); + if (!needsBuildXml) { - // Look for for a classname="com.android.ant.SetupTask" attribute - needsBuildXml = !checkFileContainsRegexp(buildXml, - "classname=\"com.android.ant.SetupTask\""); //$NON-NLS-1$ - } - if (!needsBuildXml) { - // Note that "<setup" must be followed by either a whitespace, a "/" (for the - // XML /> closing tag) or an end-of-line. This way we know the XML tag is really this - // one and later we will be able to use an "androidinit2" tag or such as necessary. - needsBuildXml = !checkFileContainsRegexp(buildXml, "<setup(?:\\s|/|$)"); //$NON-NLS-1$ - } - if (needsBuildXml) { - if (buildXml.exists()) { - println("File %1$s is too old and needs to be updated.", SdkConstants.FN_BUILD_XML); + // we are looking for version-tag: followed by either an integer or "custom". + if (checkFileContainsRegexp(buildXml, "version-tag:\\s*custom")) { //$NON-NLS-1$ + println("File %1$s is custom and will not be overriden.", + SdkConstants.FN_BUILD_XML); + } else { + // TODO: look for the version value and update if too old. + if (!checkFileContainsRegexp(buildXml, "version-tag:\\s*(\\d*)")) { //$NON-NLS-1$ + needsBuildXml = true; + println("File %1$s is too old and needs to be updated.", + SdkConstants.FN_BUILD_XML); + } } } diff --git a/templates/build.template b/templates/build.template index d975bdc4b..3dd734d85 100644 --- a/templates/build.template +++ b/templates/build.template @@ -32,9 +32,11 @@ application and should be checked into Version Control Systems. --> <property file="default.properties" /> - - <!-- Required pre-setup import --> - <import file="${sdk.dir}/tools/ant/pre_setup.xml" /> + <!-- quick check on sdk.dir --> + <fail + message="sdk.dir is missing. Make sure to generate local.properties using 'android update project'" + unless="sdk.dir" + /> <!-- extension targets. Uncomment the ones where you want to do custom work @@ -45,35 +47,32 @@ <target name="-pre-compile"> </target> - [This is typically used for code obfuscation. - Compiled code location: ${out.classes.absolute.dir} - If this is not done in place, override ${out.dex.input.absolute.dir}] + /* This is typically used for code obfuscation. + Compiled code location: ${out.classes.absolute.dir} + If this is not done in place, override ${out.dex.input.absolute.dir} */ <target name="-post-compile"> </target> --> - <!-- Execute the Android Setup task that will setup some properties - specific to the target, and import the build rules files. - - The rules file is imported from - <SDK>/tools/ant/ - Depending on the project type it can be either: - - main_rules.xml - - lib_rules.xml - - test_rules.xml + <!-- Import the actual build file. To customize existing targets, there are two options: - Customize only one target: - copy/paste the target into this file, *before* the - <setup> task. + <import> task. - customize it to your needs. - - Customize the whole script. + - Customize the whole content of build.xml - copy/paste the content of the rules files (minus the top node) - into this file, *after* the <setup> task - - disable the import of the rules by changing the setup task - below to <setup import="false" />. + into this file, replacing the <import> task. - customize to your needs. + + *********************** + ****** IMPORTANT ****** + *********************** + In all case you should replace the following version tag value with "custom" + in order to avoid having your file be overridden. --> - <setup /> + <!-- version-tag: 1 --> + <import file="${sdk.dir}/tools/ant/build.xml" /> </project> diff --git a/testapps/basicLib/.classpath b/testapps/basicLib/.classpath index 609aa00eb..765e549ef 100644 --- a/testapps/basicLib/.classpath +++ b/testapps/basicLib/.classpath @@ -3,5 +3,6 @@ <classpathentry kind="src" path="src"/> <classpathentry kind="src" path="gen"/> <classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/> + <classpathentry kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/> <classpathentry kind="output" path="bin"/> </classpath> diff --git a/testapps/basicLibWithJar/.classpath b/testapps/basicLibWithJar/.classpath index cc48f32eb..08a60d47b 100644 --- a/testapps/basicLibWithJar/.classpath +++ b/testapps/basicLibWithJar/.classpath @@ -3,6 +3,7 @@ <classpathentry kind="src" path="src"/> <classpathentry kind="src" path="gen"/> <classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/> + <classpathentry kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/> <classpathentry kind="lib" path="libs/basicJar.jar"/> <classpathentry kind="output" path="bin"/> </classpath> diff --git a/testapps/basicProject/.classpath b/testapps/basicProject/.classpath index 609aa00eb..765e549ef 100644 --- a/testapps/basicProject/.classpath +++ b/testapps/basicProject/.classpath @@ -3,5 +3,6 @@ <classpathentry kind="src" path="src"/> <classpathentry kind="src" path="gen"/> <classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/> + <classpathentry kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/> <classpathentry kind="output" path="bin"/> </classpath> diff --git a/testapps/basicProjectWithAidl/.classpath b/testapps/basicProjectWithAidl/.classpath index 609aa00eb..765e549ef 100644 --- a/testapps/basicProjectWithAidl/.classpath +++ b/testapps/basicProjectWithAidl/.classpath @@ -3,5 +3,6 @@ <classpathentry kind="src" path="src"/> <classpathentry kind="src" path="gen"/> <classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/> + <classpathentry kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/> <classpathentry kind="output" path="bin"/> </classpath> diff --git a/testapps/basicProjectWithJar/.classpath b/testapps/basicProjectWithJar/.classpath index cc48f32eb..08a60d47b 100644 --- a/testapps/basicProjectWithJar/.classpath +++ b/testapps/basicProjectWithJar/.classpath @@ -3,6 +3,7 @@ <classpathentry kind="src" path="src"/> <classpathentry kind="src" path="gen"/> <classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/> + <classpathentry kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/> <classpathentry kind="lib" path="libs/basicJar.jar"/> <classpathentry kind="output" path="bin"/> </classpath> diff --git a/testapps/basicProjectWithJava/.classpath b/testapps/basicProjectWithJava/.classpath index 475139c71..61fad04fa 100644 --- a/testapps/basicProjectWithJava/.classpath +++ b/testapps/basicProjectWithJava/.classpath @@ -3,6 +3,7 @@ <classpathentry kind="src" path="src"/> <classpathentry kind="src" path="gen"/> <classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/> + <classpathentry kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/> <classpathentry combineaccessrules="false" kind="src" path="/basicJavaProject"/> <classpathentry kind="output" path="bin"/> </classpath> diff --git a/testapps/basicProjectWithJavaFolder/.classpath b/testapps/basicProjectWithJavaFolder/.classpath index 2fc51f47d..5ae731ada 100644 --- a/testapps/basicProjectWithJavaFolder/.classpath +++ b/testapps/basicProjectWithJavaFolder/.classpath @@ -3,6 +3,7 @@ <classpathentry kind="src" path="src"/> <classpathentry kind="src" path="gen"/> <classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/> + <classpathentry kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/> <classpathentry kind="lib" path="/basicJavaProject/bin"/> <classpathentry kind="output" path="bin"/> </classpath> diff --git a/testapps/basicProjectWithJavaFolder/.project b/testapps/basicProjectWithJavaFolder/.project index 5ff450522..e65e06a4b 100644 --- a/testapps/basicProjectWithJavaFolder/.project +++ b/testapps/basicProjectWithJavaFolder/.project @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="UTF-8"?> <projectDescription> - <name>basicProjectWithJava</name> + <name>basicProjectWithJavaFolder</name> <comment></comment> <projects> </projects> diff --git a/testapps/basicProjectWithLib/.classpath b/testapps/basicProjectWithLib/.classpath index a4a3dc5e1..29e2115e1 100644 --- a/testapps/basicProjectWithLib/.classpath +++ b/testapps/basicProjectWithLib/.classpath @@ -3,8 +3,7 @@ <classpathentry kind="src" path="src"/> <classpathentry kind="src" path="gen"/> <classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/> + <classpathentry kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/> <classpathentry kind="lib" path="/basicLibWithJar/libs/basicJar.jar"/> - <classpathentry kind="src" path="basicLib_src"/> - <classpathentry kind="src" path="basicLibWithJar_src"/> <classpathentry kind="output" path="bin"/> </classpath> diff --git a/testapps/basicProjectWithLib/.project b/testapps/basicProjectWithLib/.project index f6514f19d..4b74bbda0 100644 --- a/testapps/basicProjectWithLib/.project +++ b/testapps/basicProjectWithLib/.project @@ -30,16 +30,4 @@ <nature>com.android.ide.eclipse.adt.AndroidNature</nature> <nature>org.eclipse.jdt.core.javanature</nature> </natures> - <linkedResources> - <link> - <name>basicLibWithJar_src</name> - <type>2</type> - <locationURI>_android_basicLibWithJar_aa4247fe/src</locationURI> - </link> - <link> - <name>basicLib_src</name> - <type>2</type> - <locationURI>_android_basicLib_99a2bf77/src</locationURI> - </link> - </linkedResources> </projectDescription> |