Java tutorial
/* * Copyright (C) 2013 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.wizards.exportgradle; import static com.android.SdkConstants.GRADLE_LATEST_VERSION; import static com.android.SdkConstants.GRADLE_PLUGIN_LATEST_VERSION; import static com.android.SdkConstants.GRADLE_PLUGIN_NAME; import static com.android.tools.lint.checks.GradleDetector.APP_PLUGIN_ID; import static com.android.tools.lint.checks.GradleDetector.LIB_PLUGIN_ID; import com.android.SdkConstants; import com.android.annotations.NonNull; import com.android.annotations.Nullable; import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper; import com.android.ide.eclipse.adt.internal.sdk.ProjectState; import com.android.ide.eclipse.adt.internal.sdk.Sdk; import com.android.ide.eclipse.adt.io.IFolderWrapper; import com.android.io.IAbstractFile; import com.android.sdklib.io.FileOp; import com.android.xml.AndroidManifest; import com.google.common.base.Charsets; import com.google.common.base.Joiner; import com.google.common.collect.Lists; import com.google.common.io.Closeables; import com.google.common.io.Files; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IWorkspaceRoot; 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.SubMonitor; import org.eclipse.jdt.core.IClasspathEntry; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.JavaCore; import org.eclipse.osgi.util.NLS; import org.eclipse.swt.widgets.Shell; import java.io.ByteArrayInputStream; 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.Collection; import java.util.Comparator; import java.util.List; import java.util.Properties; import java.util.Set; import java.util.TreeSet; /** * Creates build.gradle and settings.gradle files for a set of projects. * <p> * Based on {@link org.eclipse.ant.internal.ui.datatransfer.BuildFileCreator} */ public class BuildFileCreator { static final String BUILD_FILE = "build.gradle"; //$NON-NLS-1$ static final String SETTINGS_FILE = "settings.gradle"; //$NON-NLS-1$ private static final String NEWLINE = System.getProperty("line.separator"); //$NON-NLS-1$ private static final String GRADLE_WRAPPER_LOCATION = "tools/templates/gradle/wrapper"; //$NON-NLS-1$ static final String PLUGIN_CLASSPATH = "classpath '" + GRADLE_PLUGIN_NAME + GRADLE_PLUGIN_LATEST_VERSION + "'"; //$NON-NLS-1$ static final String MAVEN_REPOSITORY = "jcenter()"; //$NON-NLS-1$ private static final String[] GRADLE_WRAPPER_FILES = new String[] { "gradlew", //$NON-NLS-1$ "gradlew.bat", //$NON-NLS-1$ "gradle/wrapper/gradle-wrapper.jar", //$NON-NLS-1$ "gradle/wrapper/gradle-wrapper.properties" //$NON-NLS-1$ }; private static final Comparator<IFile> FILE_COMPARATOR = new Comparator<IFile>() { @Override public int compare(IFile o1, IFile o2) { return o1.toString().compareTo(o2.toString()); } }; private final GradleModule mModule; private final StringBuilder mBuildFile = new StringBuilder(); /** * Create buildfile for the projects. * * @param shell parent instance for dialogs * @return project names for which buildfiles were created * @throws InterruptedException thrown when user cancels task */ public static void createBuildFiles(@NonNull ProjectSetupBuilder builder, @NonNull Shell shell, @NonNull IProgressMonitor pm) { File gradleLocation = new File(Sdk.getCurrent().getSdkOsLocation(), GRADLE_WRAPPER_LOCATION); SubMonitor localmonitor = null; try { // See if we have a Gradle wrapper in the SDK templates directory. If so, we can copy // it over. boolean hasGradleWrapper = true; for (File wrapperFile : getGradleWrapperFiles(gradleLocation)) { if (!wrapperFile.exists()) { hasGradleWrapper = false; } } Collection<GradleModule> modules = builder.getModules(); boolean multiModules = modules.size() > 1; // determine files to create/change List<IFile> files = new ArrayList<IFile>(); // add the build.gradle file for all modules. for (GradleModule module : modules) { // build.gradle file IFile file = module.getProject().getFile(BuildFileCreator.BUILD_FILE); files.add(file); } // get the commonRoot for all modules. If only one module, this returns the path // of the project. IPath commonRoot = builder.getCommonRoot(); IWorkspaceRoot workspaceRoot = ResourcesPlugin.getWorkspace().getRoot(); IPath workspaceLocation = workspaceRoot.getLocation(); IPath relativePath = commonRoot.makeRelativeTo(workspaceLocation); // if makeRelativePath to returns the same path, then commonRoot is not in the // workspace. boolean rootInWorkspace = !relativePath.equals(commonRoot); // we only care if the root is a workspace project. if it's the workspace folder itself, // then the files won't be handled by the workspace. rootInWorkspace = rootInWorkspace && relativePath.segmentCount() > 0; File settingsFile = new File(commonRoot.toFile(), SETTINGS_FILE); // more than one modules -> generate settings.gradle if (multiModules && rootInWorkspace) { // Locate the settings.gradle file and add it to the changed files list IPath settingsGradle = Path.fromOSString(settingsFile.getAbsolutePath()); // different path, means commonRoot is inside the workspace, which means we have // to add settings.gradle and wrapper files to the list of files to add. IFile iFile = workspaceRoot.getFile(settingsGradle); if (iFile != null) { files.add(iFile); } } // Gradle wrapper files if (hasGradleWrapper && rootInWorkspace) { // See if there already wrapper files there and only mark nonexistent ones for // creation. for (File wrapperFile : getGradleWrapperFiles(commonRoot.toFile())) { if (!wrapperFile.exists()) { IPath path = Path.fromOSString(wrapperFile.getAbsolutePath()); IFile file = workspaceRoot.getFile(path); files.add(file); } } } ExportStatus status = new ExportStatus(); builder.setStatus(status); // Trigger checkout of changed files Set<IFile> confirmedFiles = validateEdit(files, status, shell); if (status.hasError()) { return; } // Now iterate over all the modules and generate the build files. localmonitor = SubMonitor.convert(pm, ExportMessages.PageTitle, confirmedFiles.size()); List<String> projectSettingsPath = Lists.newArrayList(); for (GradleModule currentModule : modules) { IProject moduleProject = currentModule.getProject(); IFile file = moduleProject.getFile(BuildFileCreator.BUILD_FILE); if (!confirmedFiles.contains(file)) { continue; } localmonitor.setTaskName(NLS.bind(ExportMessages.FileStatusMessage, moduleProject.getName())); ProjectState projectState = Sdk.getProjectState(moduleProject); BuildFileCreator instance = new BuildFileCreator(currentModule, shell); if (projectState != null) { // This is an Android project if (!multiModules) { instance.appendBuildScript(); } instance.appendHeader(projectState.isLibrary()); instance.appendDependencies(); instance.startAndroidTask(projectState); //instance.appendDefaultConfig(); instance.createAndroidSourceSets(); instance.finishAndroidTask(); } else { // This is a plain Java project instance.appendJavaHeader(); instance.createJavaSourceSets(); } try { // Write the build file String buildfile = instance.mBuildFile.toString(); InputStream is = new ByteArrayInputStream(buildfile.getBytes("UTF-8")); //$NON-NLS-1$ if (file.exists()) { file.setContents(is, true, true, null); } else { file.create(is, true, null); } } catch (Exception e) { status.addFileStatus(ExportStatus.FileStatus.IO_FAILURE, file.getLocation().toFile()); status.setErrorMessage(e.getMessage()); return; } if (localmonitor.isCanceled()) { return; } localmonitor.worked(1); // get the project path to add it to the settings.gradle. projectSettingsPath.add(currentModule.getPath()); } // write the settings file. if (multiModules) { try { writeGradleSettingsFile(settingsFile, projectSettingsPath); } catch (IOException e) { status.addFileStatus(ExportStatus.FileStatus.IO_FAILURE, settingsFile); status.setErrorMessage(e.getMessage()); return; } File mainBuildFile = new File(commonRoot.toFile(), BUILD_FILE); try { writeRootBuildGradle(mainBuildFile); } catch (IOException e) { status.addFileStatus(ExportStatus.FileStatus.IO_FAILURE, mainBuildFile); status.setErrorMessage(e.getMessage()); return; } } // finally write the wrapper // TODO check we can based on where it is if (hasGradleWrapper) { copyGradleWrapper(gradleLocation, commonRoot.toFile(), status); if (status.hasError()) { return; } } } finally { if (localmonitor != null && !localmonitor.isCanceled()) { localmonitor.done(); } if (pm != null) { pm.done(); } } } /** * @param GradleModule create buildfile for this project * @param shell parent instance for dialogs */ private BuildFileCreator(GradleModule module, Shell shell) { mModule = module; } /** * Return the files that comprise the Gradle wrapper as a collection of {@link File} instances. * @param root * @return */ private static List<File> getGradleWrapperFiles(File root) { List<File> files = new ArrayList<File>(GRADLE_WRAPPER_FILES.length); for (String file : GRADLE_WRAPPER_FILES) { files.add(new File(root, file)); } return files; } /** * Copy the Gradle wrapper files from one directory to another. */ private static void copyGradleWrapper(File from, File to, ExportStatus status) { for (String file : GRADLE_WRAPPER_FILES) { File dest = new File(to, file); try { File src = new File(from, file); dest.getParentFile().mkdirs(); new FileOp().copyFile(src, dest); if (src.getName().equals(GRADLE_PROPERTIES)) { updateGradleDistributionUrl(GRADLE_LATEST_VERSION, dest); } dest.setExecutable(src.canExecute()); status.addFileStatus(ExportStatus.FileStatus.OK, dest); } catch (IOException e) { status.addFileStatus(ExportStatus.FileStatus.IO_FAILURE, dest); return; } } } /** * Outputs boilerplate buildscript information common to all Gradle build files. */ private void appendBuildScript() { appendBuildScript(mBuildFile); } /** * Outputs boilerplate header information common to all Gradle build files. */ private static void appendBuildScript(StringBuilder builder) { builder.append("buildscript {\n"); //$NON-NLS-1$ builder.append(" repositories {\n"); //$NON-NLS-1$ builder.append(" " + MAVEN_REPOSITORY + "\n"); //$NON-NLS-1$ builder.append(" }\n"); //$NON-NLS-1$ builder.append(" dependencies {\n"); //$NON-NLS-1$ builder.append(" " + PLUGIN_CLASSPATH + "\n"); //$NON-NLS-1$ builder.append(" }\n"); //$NON-NLS-1$ builder.append("}\n"); //$NON-NLS-1$ } /** * Outputs boilerplate header information common to all Gradle build files. */ private void appendHeader(boolean isLibrary) { if (isLibrary) { mBuildFile.append("apply plugin: '").append(LIB_PLUGIN_ID).append("'\n"); //$NON-NLS-1$ //$NON-NLS-2$ } else { mBuildFile.append("apply plugin: '").append(APP_PLUGIN_ID).append("'\n"); //$NON-NLS-1$ //$NON-NLS-2$ } mBuildFile.append("\n"); //$NON-NLS-1$ } /** * Outputs a block which sets up library and project dependencies. */ private void appendDependencies() { mBuildFile.append("dependencies {\n"); //$NON-NLS-1$ // first the local jars. // TODO: Fix mBuildFile.append(" compile fileTree(dir: 'libs', include: '*.jar')\n"); //$NON-NLS-1$ for (GradleModule dep : mModule.getDependencies()) { mBuildFile.append(" compile project('" + dep.getPath() + "')\n"); //$NON-NLS-1$ //$NON-NLS-2$ } mBuildFile.append("}\n"); //$NON-NLS-1$ mBuildFile.append("\n"); //$NON-NLS-1$ } /** * Outputs the beginning of an Android task in the build file. */ private void startAndroidTask(ProjectState projectState) { int buildApi = projectState.getTarget().getVersion().getApiLevel(); String toolsVersion = projectState.getTarget().getBuildToolInfo().getRevision().toString(); mBuildFile.append("android {\n"); //$NON-NLS-1$ mBuildFile.append(" compileSdkVersion " + buildApi + "\n"); //$NON-NLS-1$ mBuildFile.append(" buildToolsVersion \"" + toolsVersion + "\"\n"); //$NON-NLS-1$ mBuildFile.append("\n"); //$NON-NLS-1$ try { IJavaProject javaProject = BaseProjectHelper.getJavaProject(projectState.getProject()); // otherwise we check source compatibility String source = javaProject.getOption(JavaCore.COMPILER_SOURCE, true); if (JavaCore.VERSION_1_7.equals(source)) { mBuildFile.append(" compileOptions {\n" + //$NON-NLS-1$ " sourceCompatibility JavaVersion.VERSION_1_7\n" + //$NON-NLS-1$ " targetCompatibility JavaVersion.VERSION_1_7\n" + //$NON-NLS-1$ " }\n" + //$NON-NLS-1$ "\n"); //$NON-NLS-1$ } } catch (CoreException e) { // Ignore compliance level, go with default } } /** * Outputs a sourceSets block to the Android task that locates all of the various source * subdirectories in the project. */ private void createAndroidSourceSets() { IFolderWrapper projectFolder = new IFolderWrapper(mModule.getProject()); IAbstractFile mManifestFile = AndroidManifest.getManifest(projectFolder); if (mManifestFile == null) { return; } List<String> srcDirs = new ArrayList<String>(); for (IClasspathEntry entry : mModule.getJavaProject().readRawClasspath()) { if (entry.getEntryKind() != IClasspathEntry.CPE_SOURCE || SdkConstants.FD_GEN_SOURCES.equals(entry.getPath().lastSegment())) { continue; } IPath path = entry.getPath().removeFirstSegments(1); srcDirs.add("'" + path.toOSString() + "'"); //$NON-NLS-1$ } String srcPaths = Joiner.on(",").join(srcDirs); mBuildFile.append(" sourceSets {\n"); //$NON-NLS-1$ mBuildFile.append(" main {\n"); //$NON-NLS-1$ mBuildFile.append(" manifest.srcFile '" + SdkConstants.FN_ANDROID_MANIFEST_XML + "'\n"); //$NON-NLS-1$ mBuildFile.append(" java.srcDirs = [" + srcPaths + "]\n"); //$NON-NLS-1$ mBuildFile.append(" resources.srcDirs = [" + srcPaths + "]\n"); //$NON-NLS-1$ mBuildFile.append(" aidl.srcDirs = [" + srcPaths + "]\n"); //$NON-NLS-1$ mBuildFile.append(" renderscript.srcDirs = [" + srcPaths + "]\n"); //$NON-NLS-1$ mBuildFile.append(" res.srcDirs = ['res']\n"); //$NON-NLS-1$ mBuildFile.append(" assets.srcDirs = ['assets']\n"); //$NON-NLS-1$ mBuildFile.append(" }\n"); //$NON-NLS-1$ mBuildFile.append("\n"); //$NON-NLS-1$ mBuildFile.append(" // Move the tests to tests/java, tests/res, etc...\n"); //$NON-NLS-1$ mBuildFile.append(" instrumentTest.setRoot('tests')\n"); //$NON-NLS-1$ if (srcDirs.contains("'src'")) { mBuildFile.append("\n"); //$NON-NLS-1$ mBuildFile.append(" // Move the build types to build-types/<type>\n"); //$NON-NLS-1$ mBuildFile.append( " // For instance, build-types/debug/java, build-types/debug/AndroidManifest.xml, ...\n"); //$NON-NLS-1$ mBuildFile.append( " // This moves them out of them default location under src/<type>/... which would\n"); //$NON-NLS-1$ mBuildFile.append(" // conflict with src/ being used by the main source set.\n"); //$NON-NLS-1$ mBuildFile.append(" // Adding new build types or product flavors should be accompanied\n"); //$NON-NLS-1$ mBuildFile.append(" // by a similar customization.\n"); //$NON-NLS-1$ mBuildFile.append(" debug.setRoot('build-types/debug')\n"); //$NON-NLS-1$ mBuildFile.append(" release.setRoot('build-types/release')\n"); //$NON-NLS-1$ } mBuildFile.append(" }\n"); //$NON-NLS-1$ } /** * Outputs the completion of the Android task in the build file. */ private void finishAndroidTask() { mBuildFile.append("}\n"); //$NON-NLS-1$ } /** * Outputs a boilerplate header for non-Android projects */ private void appendJavaHeader() { mBuildFile.append("apply plugin: 'java'\n"); //$NON-NLS-1$ } /** * Outputs a sourceSets block for non-Android projects to locate the source directories. */ private void createJavaSourceSets() { List<String> dirs = new ArrayList<String>(); for (IClasspathEntry entry : mModule.getJavaProject().readRawClasspath()) { if (entry.getEntryKind() != IClasspathEntry.CPE_SOURCE) { continue; } IPath path = entry.getPath().removeFirstSegments(1); dirs.add("'" + path.toOSString() + "'"); //$NON-NLS-1$ } String srcPaths = Joiner.on(",").join(dirs); mBuildFile.append("sourceSets {\n"); //$NON-NLS-1$ mBuildFile.append(" main.java.srcDirs = [" + srcPaths + "]\n"); //$NON-NLS-1$ mBuildFile.append(" main.resources.srcDirs = [" + srcPaths + "]\n"); //$NON-NLS-1$ mBuildFile.append(" test.java.srcDirs = ['tests/java']\n"); //$NON-NLS-1$ mBuildFile.append(" test.resources.srcDirs = ['tests/resources']\n"); //$NON-NLS-1$ mBuildFile.append("}\n"); //$NON-NLS-1$ } /** * Merges the new subproject dependencies into the settings.gradle file if it already exists, * and creates one if it does not. * @throws IOException */ private static void writeGradleSettingsFile(File settingsFile, List<String> projectPaths) throws IOException { StringBuilder contents = new StringBuilder(); for (String path : projectPaths) { contents.append("include '").append(path).append("'\n"); //$NON-NLS-1$ //$NON-NLS-2$ } Files.write(contents.toString(), settingsFile, Charsets.UTF_8); } private static void writeRootBuildGradle(File buildFile) throws IOException { StringBuilder sb = new StringBuilder( "// Top-level build file where you can add configuration options common to all sub-projects/modules.\n"); appendBuildScript(sb); Files.write(sb.toString(), buildFile, Charsets.UTF_8); } /** * Request write access to given files. Depending on the version control * plug-in opens a confirm checkout dialog. * * @param shell * parent instance for dialogs * @return <code>IFile</code> objects for which user confirmed checkout * @throws CoreException * thrown if project is under version control, but not connected */ static Set<IFile> validateEdit(@NonNull List<IFile> files, @NonNull ExportStatus exportStatus, @NonNull Shell shell) { Set<IFile> confirmedFiles = new TreeSet<IFile>(FILE_COMPARATOR); if (files.size() == 0) { return confirmedFiles; } IStatus status = (files.get(0)).getWorkspace().validateEdit(files.toArray(new IFile[files.size()]), shell); if (status.isMultiStatus() && status.getChildren().length > 0) { for (int i = 0; i < status.getChildren().length; i++) { IStatus statusChild = status.getChildren()[i]; if (statusChild.isOK()) { confirmedFiles.add(files.get(i)); } else { exportStatus.addFileStatus(ExportStatus.FileStatus.VCS_FAILURE, files.get(i).getLocation().toFile()); } } } else if (status.isOK()) { confirmedFiles.addAll(files); } if (status.getSeverity() == IStatus.ERROR) { // not possible to checkout files: not connected to version // control plugin or hijacked files and made read-only, so // collect error messages provided by validator and re-throw StringBuffer message = new StringBuffer(status.getPlugin() + ": " //$NON-NLS-1$ + status.getMessage() + NEWLINE); if (status.isMultiStatus()) { for (int i = 0; i < status.getChildren().length; i++) { IStatus statusChild = status.getChildren()[i]; message.append(statusChild.getMessage() + NEWLINE); } } String s = message.toString(); exportStatus.setErrorMessage(s); } return confirmedFiles; } // ------------------------------------------------------------------------------- // Fix gradle wrapper version. This code is from GradleUtil in the Studio plugin: // ------------------------------------------------------------------------------- private static final String GRADLE_PROPERTIES = "gradle-wrapper.properties"; private static final String GRADLEW_PROPERTIES_PATH = "gradle" + File.separator + "wrapper" + File.separator + GRADLE_PROPERTIES; private static final String GRADLEW_DISTRIBUTION_URL_PROPERTY_NAME = "distributionUrl"; @NonNull private static File getGradleWrapperPropertiesFilePath(@NonNull File projectRootDir) { return new File(projectRootDir, GRADLEW_PROPERTIES_PATH); } @Nullable public static File findWrapperPropertiesFile(@NonNull File projectRootDir) { File wrapperPropertiesFile = getGradleWrapperPropertiesFilePath(projectRootDir); return wrapperPropertiesFile.isFile() ? wrapperPropertiesFile : null; } private static boolean updateGradleDistributionUrl(@NonNull String gradleVersion, @NonNull File propertiesFile) throws IOException { Properties properties = loadGradleWrapperProperties(propertiesFile); String gradleDistributionUrl = getGradleDistributionUrl(gradleVersion, false); String property = properties.getProperty(GRADLEW_DISTRIBUTION_URL_PROPERTY_NAME); if (property != null && (property.equals(gradleDistributionUrl) || property.equals(getGradleDistributionUrl(gradleVersion, true)))) { return false; } properties.setProperty(GRADLEW_DISTRIBUTION_URL_PROPERTY_NAME, gradleDistributionUrl); FileOutputStream out = null; try { out = new FileOutputStream(propertiesFile); properties.store(out, null); return true; } finally { Closeables.close(out, true); } } @NonNull private static Properties loadGradleWrapperProperties(@NonNull File propertiesFile) throws IOException { Properties properties = new Properties(); FileInputStream fileInputStream = null; try { fileInputStream = new FileInputStream(propertiesFile); properties.load(fileInputStream); return properties; } finally { Closeables.close(fileInputStream, true); } } @NonNull private static String getGradleDistributionUrl(@NonNull String gradleVersion, boolean binOnly) { String suffix = binOnly ? "bin" : "all"; return String.format("https://services.gradle.org/distributions/gradle-%1$s-" + suffix + ".zip", gradleVersion); } }