aboutsummaryrefslogtreecommitdiffstats
path: root/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/project/ProjectCreator.java
diff options
context:
space:
mode:
Diffstat (limited to 'sdkmanager/libs/sdklib/src/com/android/sdklib/internal/project/ProjectCreator.java')
-rw-r--r--sdkmanager/libs/sdklib/src/com/android/sdklib/internal/project/ProjectCreator.java1324
1 files changed, 0 insertions, 1324 deletions
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
deleted file mode 100644
index fee9472..0000000
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/project/ProjectCreator.java
+++ /dev/null
@@ -1,1324 +0,0 @@
-/*
- * Copyright (C) 2007 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.internal.project;
-
-import com.android.SdkConstants;
-import com.android.io.FileWrapper;
-import com.android.io.FolderWrapper;
-import com.android.sdklib.IAndroidTarget;
-import com.android.sdklib.SdkManager;
-import com.android.sdklib.internal.project.ProjectProperties.PropertyType;
-import com.android.utils.ILogger;
-import com.android.xml.AndroidManifest;
-import com.android.xml.AndroidXPathFactory;
-
-import org.w3c.dom.NodeList;
-import org.xml.sax.InputSource;
-
-import java.io.BufferedReader;
-import java.io.BufferedWriter;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.FileReader;
-import java.io.FileWriter;
-import java.io.IOException;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import javax.xml.xpath.XPath;
-import javax.xml.xpath.XPathConstants;
-import javax.xml.xpath.XPathExpressionException;
-import javax.xml.xpath.XPathFactory;
-
-/**
- * Creates the basic files needed to get an Android project up and running.
- *
- * @hide
- */
-public class ProjectCreator {
-
- /** Version of the build.xml. Stored in version-tag */
- private final static int MIN_BUILD_VERSION_TAG = 1;
-
- /** Package path substitution string used in template files, i.e. "PACKAGE_PATH" */
- private final static String PH_JAVA_FOLDER = "PACKAGE_PATH";
- /** Package name substitution string used in template files, i.e. "PACKAGE" */
- private final static String PH_PACKAGE = "PACKAGE";
- /** Activity name substitution string used in template files, i.e. "ACTIVITY_NAME".
- * @deprecated This is only used for older templates. For new ones see
- * {@link #PH_ACTIVITY_ENTRY_NAME}, and {@link #PH_ACTIVITY_CLASS_NAME}. */
- @Deprecated
- private final static String PH_ACTIVITY_NAME = "ACTIVITY_NAME";
- /** Activity name substitution string used in manifest templates, i.e. "ACTIVITY_ENTRY_NAME".*/
- private final static String PH_ACTIVITY_ENTRY_NAME = "ACTIVITY_ENTRY_NAME";
- /** Activity name substitution string used in class templates, i.e. "ACTIVITY_CLASS_NAME".*/
- private final static String PH_ACTIVITY_CLASS_NAME = "ACTIVITY_CLASS_NAME";
- /** Activity FQ-name substitution string used in class templates, i.e. "ACTIVITY_FQ_NAME".*/
- private final static String PH_ACTIVITY_FQ_NAME = "ACTIVITY_FQ_NAME";
- /** Original Activity class name substitution string used in class templates, i.e.
- * "ACTIVITY_TESTED_CLASS_NAME".*/
- private final static String PH_ACTIVITY_TESTED_CLASS_NAME = "ACTIVITY_TESTED_CLASS_NAME";
- /** Project name substitution string used in template files, i.e. "PROJECT_NAME". */
- private final static String PH_PROJECT_NAME = "PROJECT_NAME";
- /** Application icon substitution string used in the manifest template */
- private final static String PH_ICON = "ICON";
- /** Version tag name substitution string used in template files, i.e. "VERSION_TAG". */
- private final static String PH_VERSION_TAG = "VERSION_TAG";
-
- /** The xpath to find a project name in an Ant build file. */
- private static final String XPATH_PROJECT_NAME = "/project/@name";
-
- /** Pattern for characters accepted in a project name. Since this will be used as a
- * directory name, we're being a bit conservative on purpose: dot and space cannot be used. */
- public static final Pattern RE_PROJECT_NAME = Pattern.compile("[a-zA-Z0-9_]+");
- /** List of valid characters for a project name. Used for display purposes. */
- public final static String CHARS_PROJECT_NAME = "a-z A-Z 0-9 _";
-
- /** Pattern for characters accepted in a package name. A package is list of Java identifier
- * separated by a dot. We need to have at least one dot (e.g. a two-level package name).
- * A Java identifier cannot start by a digit. */
- public static final Pattern RE_PACKAGE_NAME =
- Pattern.compile("[a-zA-Z_][a-zA-Z0-9_]*(?:\\.[a-zA-Z_][a-zA-Z0-9_]*)+");
- /** List of valid characters for a project name. Used for display purposes. */
- public final static String CHARS_PACKAGE_NAME = "a-z A-Z 0-9 _";
-
- /** Pattern for characters accepted in an activity name, which is a Java identifier. */
- public static final Pattern RE_ACTIVITY_NAME =
- Pattern.compile("[a-zA-Z_][a-zA-Z0-9_]*");
- /** List of valid characters for a project name. Used for display purposes. */
- public final static String CHARS_ACTIVITY_NAME = "a-z A-Z 0-9 _";
-
-
- public enum OutputLevel {
- /** Silent mode. Project creation will only display errors. */
- SILENT,
- /** Normal mode. Project creation will display what's being done, display
- * error but not warnings. */
- NORMAL,
- /** Verbose mode. Project creation will display what's being done, errors and warnings. */
- VERBOSE;
- }
-
- /**
- * Exception thrown when a project creation fails, typically because a template
- * file cannot be written.
- */
- private static class ProjectCreateException extends Exception {
- /** default UID. This will not be serialized anyway. */
- private static final long serialVersionUID = 1L;
-
- @SuppressWarnings("unused")
- ProjectCreateException(String message) {
- super(message);
- }
-
- ProjectCreateException(Throwable t, String format, Object... args) {
- super(format != null ? String.format(format, args) : format, t);
- }
-
- ProjectCreateException(String format, Object... args) {
- super(String.format(format, args));
- }
- }
-
- /** The {@link OutputLevel} verbosity. */
- private final OutputLevel mLevel;
- /** Logger for errors and output. Cannot be null. */
- private final ILogger mLog;
- /** The OS path of the SDK folder. */
- private final String mSdkFolder;
- /** The {@link SdkManager} instance. */
- private final SdkManager mSdkManager;
-
- /**
- * Helper class to create android projects.
- *
- * @param sdkManager The {@link SdkManager} instance.
- * @param sdkFolder The OS path of the SDK folder.
- * @param level The {@link OutputLevel} verbosity.
- * @param log Logger for errors and output. Cannot be null.
- */
- public ProjectCreator(SdkManager sdkManager, String sdkFolder, OutputLevel level, ILogger log) {
- mSdkManager = sdkManager;
- mSdkFolder = sdkFolder;
- mLevel = level;
- mLog = log;
- }
-
- /**
- * Creates a new project.
- * <p/>
- * The caller should have already checked and sanitized the parameters.
- *
- * @param folderPath the folder of the project to create.
- * @param projectName the name of the project. The name must match the
- * {@link #RE_PROJECT_NAME} regex.
- * @param packageName the package of the project. The name must match the
- * {@link #RE_PACKAGE_NAME} regex.
- * @param activityEntry the activity of the project as it will appear in the manifest. Can be
- * null if no activity should be created. The name must match the
- * {@link #RE_ACTIVITY_NAME} regex.
- * @param target the project target.
- * @param library whether the project is a library.
- * @param pathToMainProject if non-null the project will be setup to test a main project
- * located at the given path.
- */
- public void createProject(String folderPath, String projectName,
- String packageName, String activityEntry, IAndroidTarget target, boolean library,
- String pathToMainProject) {
-
- // create project folder if it does not exist
- File projectFolder = checkNewProjectLocation(folderPath);
- if (projectFolder == null) {
- return;
- }
-
- try {
- boolean isTestProject = pathToMainProject != null;
-
- // first create the project properties.
-
- // location of the SDK goes in localProperty
- ProjectPropertiesWorkingCopy localProperties = ProjectProperties.create(folderPath,
- PropertyType.LOCAL);
- localProperties.setProperty(ProjectProperties.PROPERTY_SDK, mSdkFolder);
- localProperties.save();
-
- // target goes in default properties
- ProjectPropertiesWorkingCopy defaultProperties = ProjectProperties.create(folderPath,
- PropertyType.PROJECT);
- defaultProperties.setProperty(ProjectProperties.PROPERTY_TARGET, target.hashString());
- if (library) {
- defaultProperties.setProperty(ProjectProperties.PROPERTY_LIBRARY, "true");
- }
- defaultProperties.save();
-
- // create a build.properties file with just the application package
- ProjectPropertiesWorkingCopy buildProperties = ProjectProperties.create(folderPath,
- PropertyType.ANT);
-
- if (isTestProject) {
- buildProperties.setProperty(ProjectProperties.PROPERTY_TESTED_PROJECT,
- pathToMainProject);
- }
-
- buildProperties.save();
-
- // create the map for place-holders of values to replace in the templates
- final HashMap<String, String> keywords = new HashMap<String, String>();
-
- // create the required folders.
- // compute src folder path
- final String packagePath =
- stripString(packageName.replace(".", File.separator),
- File.separatorChar);
-
- // put this path in the place-holder map for project files that needs to list
- // files manually.
- keywords.put(PH_JAVA_FOLDER, packagePath);
- keywords.put(PH_PACKAGE, packageName);
- keywords.put(PH_VERSION_TAG, Integer.toString(MIN_BUILD_VERSION_TAG));
-
-
- // compute some activity related information
- String fqActivityName = null, activityPath = null, activityClassName = null;
- String originalActivityEntry = activityEntry;
- String originalActivityClassName = null;
- if (activityEntry != null) {
- if (isTestProject) {
- // append Test so that it doesn't collide with the main project activity.
- activityEntry += "Test";
-
- // get the classname from the original activity entry.
- int pos = originalActivityEntry.lastIndexOf('.');
- if (pos != -1) {
- originalActivityClassName = originalActivityEntry.substring(pos + 1);
- } else {
- originalActivityClassName = originalActivityEntry;
- }
- }
-
- // get the fully qualified name of the activity
- fqActivityName = AndroidManifest.combinePackageAndClassName(packageName,
- activityEntry);
-
- // get the activity path (replace the . to /)
- activityPath = stripString(fqActivityName.replace(".", File.separator),
- File.separatorChar);
-
- // remove the last segment, so that we only have the path to the activity, but
- // not the activity filename itself.
- activityPath = activityPath.substring(0,
- activityPath.lastIndexOf(File.separatorChar));
-
- // finally, get the class name for the activity
- activityClassName = fqActivityName.substring(fqActivityName.lastIndexOf('.') + 1);
- }
-
- // at this point we have the following for the activity:
- // activityEntry: this is the manifest entry. For instance .MyActivity
- // fqActivityName: full-qualified class name: com.foo.MyActivity
- // activityClassName: only the classname: MyActivity
- // originalActivityClassName: the classname of the activity being tested (if applicable)
-
- // Add whatever activity info is needed in the place-holder map.
- // Older templates only expect ACTIVITY_NAME to be the same (and unmodified for tests).
- if (target.getVersion().getApiLevel() < 4) { // legacy
- if (originalActivityEntry != null) {
- keywords.put(PH_ACTIVITY_NAME, originalActivityEntry);
- }
- } else {
- // newer templates make a difference between the manifest entries, classnames,
- // as well as the main and test classes.
- if (activityEntry != null) {
- keywords.put(PH_ACTIVITY_ENTRY_NAME, activityEntry);
- keywords.put(PH_ACTIVITY_CLASS_NAME, activityClassName);
- keywords.put(PH_ACTIVITY_FQ_NAME, fqActivityName);
- if (originalActivityClassName != null) {
- keywords.put(PH_ACTIVITY_TESTED_CLASS_NAME, originalActivityClassName);
- }
- }
- }
-
- // Take the project name from the command line if there's one
- if (projectName != null) {
- keywords.put(PH_PROJECT_NAME, projectName);
- } else {
- if (activityClassName != null) {
- // Use the activity class name as project name
- keywords.put(PH_PROJECT_NAME, activityClassName);
- } else {
- // We need a project name. Just pick up the basename of the project
- // directory.
- projectName = projectFolder.getName();
- keywords.put(PH_PROJECT_NAME, projectName);
- }
- }
-
- // create the source folder for the activity
- if (activityClassName != null) {
- String srcActivityFolderPath =
- SdkConstants.FD_SOURCES + File.separator + activityPath;
- File sourceFolder = createDirs(projectFolder, srcActivityFolderPath);
-
- String javaTemplate = isTestProject ? "java_tests_file.template"
- : "java_file.template";
- String activityFileName = activityClassName + ".java";
-
- installTargetTemplate(javaTemplate, new File(sourceFolder, activityFileName),
- keywords, target);
- } else {
- // we should at least create 'src'
- createDirs(projectFolder, SdkConstants.FD_SOURCES);
- }
-
- // create other useful folders
- File resourceFolder = createDirs(projectFolder, SdkConstants.FD_RESOURCES);
- createDirs(projectFolder, SdkConstants.FD_OUTPUT);
- createDirs(projectFolder, SdkConstants.FD_NATIVE_LIBS);
-
- if (isTestProject == false) {
- /* Make res files only for non test projects */
- File valueFolder = createDirs(resourceFolder, SdkConstants.FD_RES_VALUES);
- installTargetTemplate("strings.template", new File(valueFolder, "strings.xml"),
- keywords, target);
-
- File layoutFolder = createDirs(resourceFolder, SdkConstants.FD_RES_LAYOUT);
- installTargetTemplate("layout.template", new File(layoutFolder, "main.xml"),
- keywords, target);
-
- // create the icons
- if (installIcons(resourceFolder, target)) {
- keywords.put(PH_ICON, "android:icon=\"@drawable/ic_launcher\"");
- } else {
- keywords.put(PH_ICON, "");
- }
- }
-
- /* Make AndroidManifest.xml and build.xml files */
- String manifestTemplate = "AndroidManifest.template";
- if (isTestProject) {
- manifestTemplate = "AndroidManifest.tests.template";
- }
-
- installTargetTemplate(manifestTemplate,
- new File(projectFolder, SdkConstants.FN_ANDROID_MANIFEST_XML),
- keywords, target);
-
- installTemplate("build.template",
- new File(projectFolder, SdkConstants.FN_BUILD_XML),
- keywords);
-
- // install the proguard config file.
- installTemplate(SdkConstants.FN_PROJECT_PROGUARD_FILE,
- new File(projectFolder, SdkConstants.FN_PROJECT_PROGUARD_FILE),
- null /*keywords*/);
- } catch (Exception e) {
- mLog.error(e, null);
- }
- }
-
- private File checkNewProjectLocation(String folderPath) {
- File projectFolder = new File(folderPath);
- if (!projectFolder.exists()) {
-
- boolean created = false;
- Throwable t = null;
- try {
- created = projectFolder.mkdirs();
- } catch (Exception e) {
- t = e;
- }
-
- if (created) {
- println("Created project directory: %1$s", projectFolder);
- } else {
- mLog.error(t, "Could not create directory: %1$s", projectFolder);
- return null;
- }
- } else {
- Exception e = null;
- String error = null;
- try {
- String[] content = projectFolder.list();
- if (content == null) {
- error = "Project folder '%1$s' is not a directory.";
- } else if (content.length != 0) {
- error = "Project folder '%1$s' is not empty. Please consider using '%2$s update' instead.";
- }
- } catch (Exception e1) {
- e = e1;
- }
-
- if (e != null || error != null) {
- mLog.error(e, error, projectFolder, SdkConstants.androidCmdName());
- }
- }
- return projectFolder;
- }
-
- /**
- * Updates an existing project.
- * <p/>
- * Workflow:
- * <ul>
- * <li> Check AndroidManifest.xml is present (required)
- * <li> Check if there's a legacy properties file and convert it
- * <li> Check there's a project.properties with a target *or* --target was specified
- * <li> Update default.prop if --target was specified
- * <li> Refresh/create "sdk" in local.properties
- * <li> Build.xml: create if not present or if version-tag is found or not. version-tag:custom
- * prevent any overwrite. version-tag:[integer] will override. missing version-tag will query
- * the dev.
- * </ul>
- *
- * @param folderPath the folder of the project to update. This folder must exist.
- * @param target the project target. Can be null.
- * @param projectName The project name from --name. Can be null.
- * @param libraryPath the path to a library to add to the references. Can be null.
- * @return true if the project was successfully updated.
- */
- @SuppressWarnings("deprecation")
- public boolean updateProject(String folderPath, IAndroidTarget target, String projectName,
- String libraryPath) {
- // since this is an update, check the folder does point to a project
- FileWrapper androidManifest = checkProjectFolder(folderPath,
- SdkConstants.FN_ANDROID_MANIFEST_XML);
- if (androidManifest == null) {
- return false;
- }
-
- // get the parent folder.
- FolderWrapper projectFolder = (FolderWrapper) androidManifest.getParentFolder();
-
- boolean hasProguard = false;
-
- // Check there's a project.properties with a target *or* --target was specified
- IAndroidTarget originalTarget = null;
- boolean writeProjectProp = false;
- ProjectProperties props = ProjectProperties.load(projectFolder, PropertyType.PROJECT);
-
- if (props == null) {
- // no project.properties, try to load default.properties
- props = ProjectProperties.load(projectFolder, PropertyType.LEGACY_DEFAULT);
- writeProjectProp = true;
- }
-
- if (props != null) {
- String targetHash = props.getProperty(ProjectProperties.PROPERTY_TARGET);
- originalTarget = mSdkManager.getTargetFromHashString(targetHash);
-
- // if the project is already setup with proguard, we won't copy the proguard config.
- hasProguard = props.getProperty(ProjectProperties.PROPERTY_PROGUARD_CONFIG) != null;
- }
-
- if (originalTarget == null && target == null) {
- mLog.error(null,
- "The project either has no target set or the target is invalid.\n" +
- "Please provide a --target to the '%1$s update' command.",
- SdkConstants.androidCmdName());
- return false;
- }
-
- boolean saveProjectProps = false;
-
- ProjectPropertiesWorkingCopy propsWC = null;
-
- // Update default.prop if --target was specified
- if (target != null || writeProjectProp) {
- // we already attempted to load the file earlier, if that failed, create it.
- if (props == null) {
- propsWC = ProjectProperties.create(projectFolder, PropertyType.PROJECT);
- } else {
- propsWC = props.makeWorkingCopy(PropertyType.PROJECT);
- }
-
- // set or replace the target
- if (target != null) {
- propsWC.setProperty(ProjectProperties.PROPERTY_TARGET, target.hashString());
- }
- saveProjectProps = true;
- }
-
- if (libraryPath != null) {
- // At this point, the default properties already exists, either because they were
- // already there or because they were created with a new target
- if (propsWC == null) {
- assert props != null;
- propsWC = props.makeWorkingCopy();
- }
-
- // check the reference is valid
- File libProject = new File(libraryPath);
- String resolvedPath;
- if (libProject.isAbsolute() == false) {
- libProject = new File(projectFolder, libraryPath);
- try {
- resolvedPath = libProject.getCanonicalPath();
- } catch (IOException e) {
- mLog.error(e, "Unable to resolve path to library project: %1$s", libraryPath);
- return false;
- }
- } else {
- resolvedPath = libProject.getAbsolutePath();
- }
-
- println("Resolved location of library project to: %1$s", resolvedPath);
-
- // check the lib project exists
- if (checkProjectFolder(resolvedPath, SdkConstants.FN_ANDROID_MANIFEST_XML) == null) {
- mLog.error(null, "No Android Manifest at: %1$s", resolvedPath);
- return false;
- }
-
- // look for other references to figure out the index
- int index = 1;
- while (true) {
- String propName = ProjectProperties.PROPERTY_LIB_REF + Integer.toString(index);
- assert props != null;
- if (props == null) {
- // This should not happen yet SDK bug 20535 says it can, not sure how.
- break;
- }
- String ref = props.getProperty(propName);
- if (ref == null) {
- break;
- } else {
- index++;
- }
- }
-
- String propName = ProjectProperties.PROPERTY_LIB_REF + Integer.toString(index);
- propsWC.setProperty(propName, libraryPath);
- saveProjectProps = true;
- }
-
- // save the default props if needed.
- if (saveProjectProps) {
- try {
- assert propsWC != null;
- propsWC.save();
- if (writeProjectProp) {
- println("Updated and renamed %1$s to %2$s",
- PropertyType.LEGACY_DEFAULT.getFilename(),
- PropertyType.PROJECT.getFilename());
- } else {
- println("Updated %1$s", PropertyType.PROJECT.getFilename());
- }
- } catch (Exception e) {
- mLog.error(e, "Failed to write %1$s file in '%2$s'",
- PropertyType.PROJECT.getFilename(),
- folderPath);
- return false;
- }
-
- if (writeProjectProp) {
- // need to delete the default prop file.
- ProjectProperties.delete(projectFolder, PropertyType.LEGACY_DEFAULT);
- }
- }
-
- // Refresh/create "sdk" in local.properties
- // because the file may already exists and contain other values (like apk config),
- // we first try to load it.
- props = ProjectProperties.load(projectFolder, PropertyType.LOCAL);
- if (props == null) {
- propsWC = ProjectProperties.create(projectFolder, PropertyType.LOCAL);
- } else {
- propsWC = props.makeWorkingCopy();
- }
-
- // set or replace the sdk location.
- propsWC.setProperty(ProjectProperties.PROPERTY_SDK, mSdkFolder);
- try {
- propsWC.save();
- println("Updated %1$s", PropertyType.LOCAL.getFilename());
- } catch (Exception e) {
- mLog.error(e, "Failed to write %1$s file in '%2$s'",
- PropertyType.LOCAL.getFilename(),
- folderPath);
- return false;
- }
-
- // legacy: check if build.properties must be renamed to ant.properties.
- props = ProjectProperties.load(projectFolder, PropertyType.ANT);
- if (props == null) {
- props = ProjectProperties.load(projectFolder, PropertyType.LEGACY_BUILD);
- if (props != null) {
- try {
- // get a working copy with the new property type
- propsWC = props.makeWorkingCopy(PropertyType.ANT);
- propsWC.save();
-
- // delete the old file
- ProjectProperties.delete(projectFolder, PropertyType.LEGACY_BUILD);
-
- println("Renamed %1$s to %2$s",
- PropertyType.LEGACY_BUILD.getFilename(),
- PropertyType.ANT.getFilename());
- } catch (Exception e) {
- mLog.error(e, "Failed to write %1$s file in '%2$s'",
- PropertyType.ANT.getFilename(),
- folderPath);
- return false;
- }
- }
- }
-
- // 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 it seems there's no need for a new build.xml, look for inside the file
- // to try to detect old ones that may need updating.
- if (!needsBuildXml) {
- // we are looking for version-tag: followed by either an integer or "custom".
- if (checkFileContainsRegexp(buildXml, "version-tag:\\s*custom") != null) { //$NON-NLS-1$
- println("%1$s: Found version-tag: custom. File will not be updated.",
- SdkConstants.FN_BUILD_XML);
- } else {
- Matcher m = checkFileContainsRegexp(buildXml, "version-tag:\\s*(\\d+)"); //$NON-NLS-1$
- if (m == null) {
- println("----------\n" +
- "%1$s: Failed to find version-tag string. File must be updated.\n" +
- "In order to not erase potential customizations, the file will not be automatically regenerated.\n" +
- "If no changes have been made to the file, delete it manually and run the command again.\n" +
- "If you have made customizations to the build process, the file must be manually updated.\n" +
- "It is recommended to:\n" +
- "\t* Copy current file to a safe location.\n" +
- "\t* Delete original file.\n" +
- "\t* Run command again to generate a new file.\n" +
- "\t* Port customizations to the new file, by looking at the new rules file\n" +
- "\t located at <SDK>/tools/ant/build.xml\n" +
- "\t* Update file to contain\n" +
- "\t version-tag: custom\n" +
- "\t to prevent file from being rewritten automatically by the SDK tools.\n" +
- "----------\n",
- SdkConstants.FN_BUILD_XML);
- } else {
- String versionStr = m.group(1);
- if (versionStr != null) {
- // can't fail due to regexp above.
- int version = Integer.parseInt(versionStr);
- if (version < MIN_BUILD_VERSION_TAG) {
- println("%1$s: Found version-tag: %2$d. Expected version-tag: %3$d: file must be updated.",
- SdkConstants.FN_BUILD_XML, version, MIN_BUILD_VERSION_TAG);
- needsBuildXml = true;
- }
- }
- }
- }
- }
-
- if (needsBuildXml) {
- // create the map for place-holders of values to replace in the templates
- final HashMap<String, String> keywords = new HashMap<String, String>();
-
- // put the current version-tag value
- keywords.put(PH_VERSION_TAG, Integer.toString(MIN_BUILD_VERSION_TAG));
-
- // if there was no project name on the command line, figure one out.
- if (projectName == null) {
- // otherwise, take it from the existing build.xml if it exists already.
- if (buildXml.exists()) {
- try {
- XPathFactory factory = XPathFactory.newInstance();
- XPath xpath = factory.newXPath();
-
- projectName = xpath.evaluate(XPATH_PROJECT_NAME,
- new InputSource(new FileInputStream(buildXml)));
- } catch (XPathExpressionException e) {
- // this is ok since we're going to recreate the file.
- mLog.error(e, "Unable to find existing project name from %1$s",
- SdkConstants.FN_BUILD_XML);
- } catch (FileNotFoundException e) {
- // can't happen since we check above.
- }
- }
-
- // if the project is still null, then we find another way.
- if (projectName == null) {
- extractPackageFromManifest(androidManifest, keywords);
- if (keywords.containsKey(PH_ACTIVITY_ENTRY_NAME)) {
- String activity = keywords.get(PH_ACTIVITY_ENTRY_NAME);
- // keep only the last segment if applicable
- int pos = activity.lastIndexOf('.');
- if (pos != -1) {
- activity = activity.substring(pos + 1);
- }
-
- // Use the activity as project name
- projectName = activity;
-
- println("No project name specified, using Activity name '%1$s'.\n" +
- "If you wish to change it, edit the first line of %2$s.",
- activity, SdkConstants.FN_BUILD_XML);
- } else {
- // We need a project name. Just pick up the basename of the project
- // directory.
- File projectCanonicalFolder = projectFolder;
- try {
- projectCanonicalFolder = projectCanonicalFolder.getCanonicalFile();
- } catch (IOException e) {
- // ignore, keep going
- }
-
- // Use the folder name as project name
- projectName = projectCanonicalFolder.getName();
-
- println("No project name specified, using project folder name '%1$s'.\n" +
- "If you wish to change it, edit the first line of %2$s.",
- projectName, SdkConstants.FN_BUILD_XML);
- }
- }
- }
-
- // put the project name in the map for replacement during the template installation.
- keywords.put(PH_PROJECT_NAME, projectName);
-
- if (mLevel == OutputLevel.VERBOSE) {
- println("Regenerating %1$s with project name %2$s",
- SdkConstants.FN_BUILD_XML,
- keywords.get(PH_PROJECT_NAME));
- }
-
- try {
- installTemplate("build.template", buildXml, keywords);
- } catch (ProjectCreateException e) {
- mLog.error(e, null);
- return false;
- }
- }
-
- if (hasProguard == false) {
- try {
- installTemplate(SdkConstants.FN_PROJECT_PROGUARD_FILE,
- // Write ProGuard config files with the extension .pro which
- // is what is used in the ProGuard documentation and samples
- new File(projectFolder, SdkConstants.FN_PROJECT_PROGUARD_FILE),
- null /*placeholderMap*/);
- } catch (ProjectCreateException e) {
- mLog.error(e, null);
- return false;
- }
- }
-
- return true;
- }
-
- /**
- * Updates a test project with a new path to the main (tested) project.
- * @param folderPath the path of the test project.
- * @param pathToMainProject the path to the main project, relative to the test project.
- */
- @SuppressWarnings("deprecation")
- public void updateTestProject(final String folderPath, final String pathToMainProject,
- final SdkManager sdkManager) {
- // since this is an update, check the folder does point to a project
- if (checkProjectFolder(folderPath, SdkConstants.FN_ANDROID_MANIFEST_XML) == null) {
- return;
- }
-
- // check the path to the main project is valid.
- File mainProject = new File(pathToMainProject);
- String resolvedPath;
- if (mainProject.isAbsolute() == false) {
- mainProject = new File(folderPath, pathToMainProject);
- try {
- resolvedPath = mainProject.getCanonicalPath();
- } catch (IOException e) {
- mLog.error(e, "Unable to resolve path to main project: %1$s", pathToMainProject);
- return;
- }
- } else {
- resolvedPath = mainProject.getAbsolutePath();
- }
-
- println("Resolved location of main project to: %1$s", resolvedPath);
-
- // check the main project exists
- if (checkProjectFolder(resolvedPath, SdkConstants.FN_ANDROID_MANIFEST_XML) == null) {
- mLog.error(null, "No Android Manifest at: %1$s", resolvedPath);
- return;
- }
-
- // now get the target from the main project
- ProjectProperties projectProp = ProjectProperties.load(resolvedPath, PropertyType.PROJECT);
- if (projectProp == null) {
- // legacy support for older file name.
- projectProp = ProjectProperties.load(resolvedPath, PropertyType.LEGACY_DEFAULT);
- if (projectProp == null) {
- mLog.error(null, "No %1$s at: %2$s", PropertyType.PROJECT.getFilename(),
- resolvedPath);
- return;
- }
- }
-
- String targetHash = projectProp.getProperty(ProjectProperties.PROPERTY_TARGET);
- if (targetHash == null) {
- mLog.error(null, "%1$s in the main project has no target property.",
- PropertyType.PROJECT.getFilename());
- return;
- }
-
- IAndroidTarget target = sdkManager.getTargetFromHashString(targetHash);
- if (target == null) {
- mLog.error(null, "Main project target %1$s is not a valid target.", targetHash);
- return;
- }
-
- // update test-project does not support the --name parameter, therefore the project
- // name should generally not be passed to updateProject().
- // However if build.xml does not exist then updateProject() will recreate it. In this
- // case we will need the project name.
- // To do this, we look for the parent project name and add "test" to it.
- // If the main project does not have a project name (yet), then the default behavior
- // will be used (look for activity and then folder name)
- String projectName = null;
- XPathFactory factory = XPathFactory.newInstance();
- XPath xpath = factory.newXPath();
-
- File testBuildXml = new File(folderPath, SdkConstants.FN_BUILD_XML);
- if (testBuildXml.isFile() == false) {
- File mainBuildXml = new File(resolvedPath, SdkConstants.FN_BUILD_XML);
- if (mainBuildXml.isFile()) {
- try {
- // get the name of the main project and add Test to it.
- String mainProjectName = xpath.evaluate(XPATH_PROJECT_NAME,
- new InputSource(new FileInputStream(mainBuildXml)));
- projectName = mainProjectName + "Test";
- } catch (XPathExpressionException e) {
- // it's ok, updateProject() will figure out a name automatically.
- // We do log the error though as the build.xml file may be broken.
- mLog.warning("Failed to parse %1$s.\n" +
- "File may not be valid. Consider running 'android update project' on the main project.",
- mainBuildXml.getPath());
- } catch (FileNotFoundException e) {
- // should not happen since we check first.
- }
- }
- }
-
- // now update the project as if it's a normal project
- if (updateProject(folderPath, target, projectName, null /*libraryPath*/) == false) {
- // error message has already been displayed.
- return;
- }
-
- // add the test project specific properties.
- // At this point, we know build.prop has been renamed ant.prop
- ProjectProperties antProps = ProjectProperties.load(folderPath, PropertyType.ANT);
- ProjectPropertiesWorkingCopy antWorkingCopy;
- if (antProps == null) {
- antWorkingCopy = ProjectProperties.create(folderPath, PropertyType.ANT);
- } else {
- antWorkingCopy = antProps.makeWorkingCopy();
- }
-
- // set or replace the path to the main project
- antWorkingCopy.setProperty(ProjectProperties.PROPERTY_TESTED_PROJECT, pathToMainProject);
- try {
- antWorkingCopy.save();
- println("Updated %1$s", PropertyType.ANT.getFilename());
- } catch (Exception e) {
- mLog.error(e, "Failed to write %1$s file in '%2$s'",
- PropertyType.ANT.getFilename(),
- folderPath);
- return;
- }
- }
-
- /**
- * Checks whether the give <var>folderPath</var> is a valid project folder, and returns
- * a {@link FileWrapper} to the required file.
- * <p/>This checks that the folder exists and contains an AndroidManifest.xml file in it.
- * <p/>Any error are output using {@link #mLog}.
- * @param folderPath the folder to check
- * @param requiredFilename the file name of the file that's required.
- * @return a {@link FileWrapper} to the AndroidManifest.xml file, or null otherwise.
- */
- private FileWrapper checkProjectFolder(String folderPath, String requiredFilename) {
- // project folder must exist and be a directory, since this is an update
- FolderWrapper projectFolder = new FolderWrapper(folderPath);
- if (!projectFolder.isDirectory()) {
- mLog.error(null, "Project folder '%1$s' is not a valid directory.",
- projectFolder);
- return null;
- }
-
- // Check AndroidManifest.xml is present
- FileWrapper requireFile = new FileWrapper(projectFolder, requiredFilename);
- if (!requireFile.isFile()) {
- mLog.error(null,
- "%1$s is not a valid project (%2$s not found).",
- folderPath, requiredFilename);
- return null;
- }
-
- return requireFile;
- }
-
- /**
- * Looks for a given regex in a file and returns the matcher if any line of the input file
- * contains the requested regexp.
- *
- * @param file the file to search.
- * @param regexp the regexp to search for.
- *
- * @return a Matcher or null if the regexp is not found.
- */
- private Matcher checkFileContainsRegexp(File file, String regexp) {
- Pattern p = Pattern.compile(regexp);
-
- BufferedReader in = null;
- try {
- in = new BufferedReader(new FileReader(file));
- String line;
-
- while ((line = in.readLine()) != null) {
- Matcher m = p.matcher(line);
- if (m.find()) {
- return m;
- }
- }
-
- in.close();
- } catch (Exception e) {
- // ignore
- } finally {
- if (in != null) {
- try {
- in.close();
- } catch (IOException e) {
- // ignore
- }
- }
- }
-
- return null;
- }
-
- /**
- * Extracts a "full" package & activity name from an AndroidManifest.xml.
- * <p/>
- * The keywords dictionary is always filed the package name under the key {@link #PH_PACKAGE}.
- * If an activity name can be found, it is filed under the key {@link #PH_ACTIVITY_ENTRY_NAME}.
- * When no activity is found, this key is not created.
- *
- * @param manifestFile The AndroidManifest.xml file
- * @param outKeywords Place where to put the out parameters: package and activity names.
- * @return True if the package/activity was parsed and updated in the keyword dictionary.
- */
- private boolean extractPackageFromManifest(File manifestFile,
- Map<String, String> outKeywords) {
- try {
- XPath xpath = AndroidXPathFactory.newXPath();
-
- InputSource source = new InputSource(new FileReader(manifestFile));
- String packageName = xpath.evaluate("/manifest/@package", source);
-
- source = new InputSource(new FileReader(manifestFile));
-
- // Select the "android:name" attribute of all <activity> nodes but only if they
- // contain a sub-node <intent-filter><action> with an "android:name" attribute which
- // is 'android.intent.action.MAIN' and an <intent-filter><category> with an
- // "android:name" attribute which is 'android.intent.category.LAUNCHER'
- String expression = String.format("/manifest/application/activity" +
- "[intent-filter/action/@%1$s:name='android.intent.action.MAIN' and " +
- "intent-filter/category/@%1$s:name='android.intent.category.LAUNCHER']" +
- "/@%1$s:name", AndroidXPathFactory.DEFAULT_NS_PREFIX);
-
- NodeList activityNames = (NodeList) xpath.evaluate(expression, source,
- XPathConstants.NODESET);
-
- // If we get here, both XPath expressions were valid so we're most likely dealing
- // with an actual AndroidManifest.xml file. The nodes may not have the requested
- // attributes though, if which case we should warn.
-
- if (packageName == null || packageName.length() == 0) {
- mLog.error(null,
- "Missing <manifest package=\"...\"> in '%1$s'",
- manifestFile.getName());
- return false;
- }
-
- // Get the first activity that matched earlier. If there is no activity,
- // activityName is set to an empty string and the generated "combined" name
- // will be in the form "package." (with a dot at the end).
- String activityName = "";
- if (activityNames.getLength() > 0) {
- activityName = activityNames.item(0).getNodeValue();
- }
-
- if (mLevel == OutputLevel.VERBOSE && activityNames.getLength() > 1) {
- println("WARNING: There is more than one activity defined in '%1$s'.\n" +
- "Only the first one will be used. If this is not appropriate, you need\n" +
- "to specify one of these values manually instead:",
- manifestFile.getName());
-
- for (int i = 0; i < activityNames.getLength(); i++) {
- String name = activityNames.item(i).getNodeValue();
- name = combinePackageActivityNames(packageName, name);
- println("- %1$s", name);
- }
- }
-
- if (activityName.length() == 0) {
- mLog.warning("Missing <activity %1$s:name=\"...\"> in '%2$s'.\n" +
- "No activity will be generated.",
- AndroidXPathFactory.DEFAULT_NS_PREFIX, manifestFile.getName());
- } else {
- outKeywords.put(PH_ACTIVITY_ENTRY_NAME, activityName);
- }
-
- outKeywords.put(PH_PACKAGE, packageName);
- return true;
-
- } catch (IOException e) {
- mLog.error(e, "Failed to read %1$s", manifestFile.getName());
- } catch (XPathExpressionException e) {
- Throwable t = e.getCause();
- mLog.error(t == null ? e : t,
- "Failed to parse %1$s",
- manifestFile.getName());
- }
-
- return false;
- }
-
- private String combinePackageActivityNames(String packageName, String activityName) {
- // Activity Name can have 3 forms:
- // - ".Name" means this is a class name in the given package name.
- // The full FQCN is thus packageName + ".Name"
- // - "Name" is an older variant of the former. Full FQCN is packageName + "." + "Name"
- // - "com.blah.Name" is a full FQCN. Ignore packageName and use activityName as-is.
- // To be valid, the package name should have at least two components. This is checked
- // later during the creation of the build.xml file, so we just need to detect there's
- // a dot but not at pos==0.
-
- int pos = activityName.indexOf('.');
- if (pos == 0) {
- return packageName + activityName;
- } else if (pos > 0) {
- return activityName;
- } else {
- return packageName + "." + activityName;
- }
- }
-
- /**
- * Installs a new file that is based on a template file provided by a given target.
- * Each match of each key from the place-holder map in the template will be replaced with its
- * corresponding value in the created file.
- *
- * @param templateName the name of to the template file
- * @param destFile the path to the destination file, relative to the project
- * @param placeholderMap a map of (place-holder, value) to create the file from the template.
- * @param target the Target of the project that will be providing the template.
- * @throws ProjectCreateException
- */
- private void installTargetTemplate(String templateName, File destFile,
- Map<String, String> placeholderMap, IAndroidTarget target)
- throws ProjectCreateException {
- // query the target for its template directory
- String templateFolder = target.getPath(IAndroidTarget.TEMPLATES);
- final String sourcePath = templateFolder + File.separator + templateName;
-
- installFullPathTemplate(sourcePath, destFile, placeholderMap);
- }
-
- /**
- * Installs a new file that is based on a template file provided by the tools folder.
- * Each match of each key from the place-holder map in the template will be replaced with its
- * corresponding value in the created file.
- *
- * @param templateName the name of to the template file
- * @param destFile the path to the destination file, relative to the project
- * @param placeholderMap a map of (place-holder, value) to create the file from the template.
- * @throws ProjectCreateException
- */
- private void installTemplate(String templateName, File destFile,
- Map<String, String> placeholderMap)
- throws ProjectCreateException {
- // query the target for its template directory
- String templateFolder = mSdkFolder + File.separator + SdkConstants.OS_SDK_TOOLS_LIB_FOLDER;
- final String sourcePath = templateFolder + File.separator + templateName;
-
- installFullPathTemplate(sourcePath, destFile, placeholderMap);
- }
-
- /**
- * Installs a new file that is based on a template.
- * Each match of each key from the place-holder map in the template will be replaced with its
- * corresponding value in the created file.
- *
- * @param sourcePath the full path to the source template file
- * @param destFile the destination file
- * @param placeholderMap a map of (place-holder, value) to create the file from the template.
- * @throws ProjectCreateException
- */
- private void installFullPathTemplate(String sourcePath, File destFile,
- Map<String, String> placeholderMap) throws ProjectCreateException {
-
- boolean existed = destFile.exists();
-
- try {
- BufferedWriter out = new BufferedWriter(new FileWriter(destFile));
- BufferedReader in = new BufferedReader(new FileReader(sourcePath));
- String line;
-
- while ((line = in.readLine()) != null) {
- if (placeholderMap != null) {
- for (Map.Entry<String, String> entry : placeholderMap.entrySet()) {
- line = line.replace(entry.getKey(), entry.getValue());
- }
- }
-
- out.write(line);
- out.newLine();
- }
-
- out.close();
- in.close();
- } catch (Exception e) {
- throw new ProjectCreateException(e, "Could not access %1$s: %2$s",
- destFile, e.getMessage());
- }
-
- println("%1$s file %2$s",
- existed ? "Updated" : "Added",
- destFile);
- }
-
- /**
- * Installs the project icons.
- * @param resourceFolder the resource folder
- * @param target the target of the project.
- * @return true if any icon was installed.
- */
- private boolean installIcons(File resourceFolder, IAndroidTarget target)
- throws ProjectCreateException {
- // query the target for its template directory
- String templateFolder = target.getPath(IAndroidTarget.TEMPLATES);
-
- boolean installedIcon = false;
-
- installedIcon |= installIcon(templateFolder, "ic_launcher_xhdpi.png", resourceFolder,
- "drawable-xhdpi");
- installedIcon |= installIcon(templateFolder, "ic_launcher_hdpi.png", resourceFolder,
- "drawable-hdpi");
- installedIcon |= installIcon(templateFolder, "ic_launcher_mdpi.png", resourceFolder,
- "drawable-mdpi");
- installedIcon |= installIcon(templateFolder, "ic_launcher_ldpi.png", resourceFolder,
- "drawable-ldpi");
-
- return installedIcon;
- }
-
- /**
- * Installs an Icon in the project.
- * @return true if the icon was installed.
- */
- private boolean installIcon(String templateFolder, String iconName, File resourceFolder,
- String folderName) throws ProjectCreateException {
- File icon = new File(templateFolder, iconName);
- if (icon.exists()) {
- File drawable = createDirs(resourceFolder, folderName);
- installBinaryFile(icon, new File(drawable, "ic_launcher.png"));
- return true;
- }
-
- return false;
- }
-
- /**
- * Installs a binary file
- * @param source the source file to copy
- * @param destination the destination file to write
- * @throws ProjectCreateException
- */
- private void installBinaryFile(File source, File destination) throws ProjectCreateException {
- byte[] buffer = new byte[8192];
-
- FileInputStream fis = null;
- FileOutputStream fos = null;
- try {
- fis = new FileInputStream(source);
- fos = new FileOutputStream(destination);
-
- int read;
- while ((read = fis.read(buffer)) != -1) {
- fos.write(buffer, 0, read);
- }
-
- } catch (FileNotFoundException e) {
- // shouldn't happen since we check before.
- } catch (IOException e) {
- throw new ProjectCreateException(e, "Failed to read binary file: %1$s",
- source.getAbsolutePath());
- } finally {
- if (fis != null) {
- try {
- fis.close();
- } catch (IOException e) {
- // ignore
- }
- }
- if (fos != null) {
- try {
- fos.close();
- } catch (IOException e) {
- // ignore
- }
- }
- }
-
- }
-
- /**
- * Prints a message unless silence is enabled.
- * <p/>
- * This is just a convenience wrapper around {@link ILogger#info(String, Object...)} from
- * {@link #mLog} after testing if output level is {@link OutputLevel#VERBOSE}.
- *
- * @param format Format for String.format
- * @param args Arguments for String.format
- */
- private void println(String format, Object... args) {
- if (mLevel != OutputLevel.SILENT) {
- if (!format.endsWith("\n")) {
- format += "\n";
- }
- mLog.info(format, args);
- }
- }
-
- /**
- * Creates a new folder, along with any parent folders that do not exists.
- *
- * @param parent the parent folder
- * @param name the name of the directory to create.
- * @throws ProjectCreateException
- */
- private File createDirs(File parent, String name) throws ProjectCreateException {
- final File newFolder = new File(parent, name);
- boolean existedBefore = true;
-
- if (!newFolder.exists()) {
- if (!newFolder.mkdirs()) {
- throw new ProjectCreateException("Could not create directory: %1$s", newFolder);
- }
- existedBefore = false;
- }
-
- if (newFolder.isDirectory()) {
- if (!newFolder.canWrite()) {
- throw new ProjectCreateException("Path is not writable: %1$s", newFolder);
- }
- } else {
- throw new ProjectCreateException("Path is not a directory: %1$s", newFolder);
- }
-
- if (!existedBefore) {
- try {
- println("Created directory %1$s", newFolder.getCanonicalPath());
- } catch (IOException e) {
- throw new ProjectCreateException(
- "Could not determine canonical path of created directory", e);
- }
- }
-
- return newFolder;
- }
-
- /**
- * Strips the string of beginning and trailing characters (multiple
- * characters will be stripped, example stripString("..test...", '.')
- * results in "test";
- *
- * @param s the string to strip
- * @param strip the character to strip from beginning and end
- * @return the stripped string or the empty string if everything is stripped.
- */
- private static String stripString(String s, char strip) {
- final int sLen = s.length();
- int newStart = 0, newEnd = sLen - 1;
-
- while (newStart < sLen && s.charAt(newStart) == strip) {
- newStart++;
- }
- while (newEnd >= 0 && s.charAt(newEnd) == strip) {
- newEnd--;
- }
-
- /*
- * newEnd contains a char we want, and substring takes end as being
- * exclusive
- */
- newEnd++;
-
- if (newStart >= sLen || newEnd < 0) {
- return "";
- }
-
- return s.substring(newStart, newEnd);
- }
-}