Java tutorial
/* * Copyright (C) 2012 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.idegen; import com.google.common.base.Objects; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Sets; import com.google.common.io.Files; import java.io.File; import java.io.IOException; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.logging.Logger; /** * Module constructed from a make file. * * TODO: read the make file and understand included source dirs in addition to searching * sub-directories. Make files can include sources that are not sub-directories. For example, the * framework module includes sources from: * * external/libphonenumber/java/src * * to provide: * * com.android.i18n.phonenumbers.PhoneNumberUtil; */ public class Module { private static final Logger logger = Logger.getLogger(Module.class.getName()); public static final String REL_OUT_APP_DIR = "out/target/common/obj/APPS"; private static final String IML_TEMPLATE_FILE_NAME = "module-template.iml"; private static final String[] AUTO_DEPENDENCIES = new String[] { "framework", "libcore" }; private static final String[] DIRS_WITH_AUTO_DEPENDENCIES = new String[] { "packages", "vendor", "frameworks/ex", "frameworks/opt", "frameworks/support" }; /** * All possible attributes for the make file. */ protected enum Key { LOCAL_STATIC_JAVA_LIBRARIES, LOCAL_JAVA_LIBRARIES, LOCAL_SRC_FILES } private ModuleCache moduleCache = ModuleCache.getInstance(); private File imlFile; private Set<String> allDependencies = Sets.newHashSet(); // direct + indirect private Set<File> allDependentImlFiles = Sets.newHashSet(); private File makeFile; private File moduleRoot; private HashSet<File> sourceFiles = Sets.newHashSet(); // Module dependencies come from LOCAL_STATIC_JAVA_LIBRARIES or LOCAL_JAVA_LIBRARIES Set<String> explicitModuleNameDependencies = Sets.newHashSet(); // Implicit module dependencies come from src files that fall outside the module root directory. // For example, if packages/apps/Contacts includes src files from packages/apps/ContactsCommon, // that is an implicit module dependency. It's not a module dependency from the build // perspective but it needs to be a separate module in intellij so that the src files can be // shared by multiple intellij modules. Set<File> implicitModulePathDependencies = Sets.newHashSet(); String relativeIntermediatesDir; MakeFileParser makeFileParser; boolean parseMakeFileForSource; public Module(File moduleDir) throws IOException { this(moduleDir, true); } public Module(File moduleDir, boolean parseMakeFileForSource) throws IOException { this.moduleRoot = Preconditions.checkNotNull(moduleDir); this.makeFile = new File(moduleDir, "Android.mk"); this.relativeIntermediatesDir = calculateRelativePartToRepoRoot() + REL_OUT_APP_DIR + File.separatorChar + getName() + "_intermediates" + File.separator + "src"; this.parseMakeFileForSource = parseMakeFileForSource; // TODO: auto-detect when framework dependency is needed instead of using coded list. for (String dir : DIRS_WITH_AUTO_DEPENDENCIES) { // length + 2 to account for slash boolean isDir = makeFile.getCanonicalPath().startsWith(DirectorySearch.getRepoRoot() + "/" + dir); if (isDir) { Collections.addAll(this.explicitModuleNameDependencies, AUTO_DEPENDENCIES); } } makeFileParser = new MakeFileParser(makeFile); } private String calculateRelativePartToRepoRoot() throws IOException { String rel = moduleRoot.getCanonicalPath() .substring(DirectorySearch.getRepoRoot().getCanonicalPath().length()); int count = 0; // Count the number of slashes to determine how far back to go. for (int i = 0; i < rel.length(); i++) { if (rel.charAt(i) == '/') { count++; } } StringBuilder sb = new StringBuilder(); for (int i = 0; i < count; i++) { sb.append("../"); } return sb.toString(); } public void build() throws IOException { makeFileParser.parse(); buildDependencyList(); buildDependentModules(); logger.info("Done building module " + moduleRoot); logger.info(toString()); } public File getDir() { return moduleRoot; } public String getName() { return moduleRoot.getName(); } private List<String> getRelativeIntermediatesDirs() throws IOException { return Lists.newArrayList(relativeIntermediatesDir); } private ImmutableList<File> getSourceDirs() { return ImmutableList.copyOf(sourceFiles); } private ImmutableList<File> getExcludeDirs() { return DirectorySearch.findExcludeDirs(makeFile); } private boolean isAndroidModule() { File manifest = new File(moduleRoot, "AndroidManifest.xml"); return manifest.exists(); } private void findSourceFilesAndImplicitDependencies() throws IOException { Iterable<String> values = makeFileParser.getValues(Key.LOCAL_SRC_FILES.name()); if (values != null) { for (String value : values) { File src = new File(moduleRoot, value); // value may contain garbage at this point due to relaxed make file parsing. // filter by existing file. if (src.exists()) { // Look for directories outside the current module directory. if (value.contains("..")) { // Find the closest Android make file. File moduleRoot = DirectorySearch.findModuleRoot(src); implicitModulePathDependencies.add(moduleRoot); } else { if (parseMakeFileForSource) { // Check if source files are subdirectories of generic parent src // directories. If so, no need to add since they are already included. boolean alreadyIncluded = false; for (String parentDir : DirectorySearch.SOURCE_DIRS) { if (value.startsWith(parentDir)) { alreadyIncluded = true; break; } } if (!alreadyIncluded) { sourceFiles.add(src); } } } } } } sourceFiles.addAll(DirectorySearch.findSourceDirs(moduleRoot)); } private void buildDependencyList() throws IOException { parseDirectDependencies(Key.LOCAL_STATIC_JAVA_LIBRARIES); parseDirectDependencies(Key.LOCAL_JAVA_LIBRARIES); findSourceFilesAndImplicitDependencies(); } private void parseDirectDependencies(Key key) { Iterable<String> names = makeFileParser.getValues(key.name()); if (names != null) { for (String dependency : names) { explicitModuleNameDependencies.add(dependency); } } } public void buildImlFile() throws IOException { String imlTemplate = Files.toString(new File(DirectorySearch.findTemplateDir(), IML_TEMPLATE_FILE_NAME), IntellijProject.CHARSET); String facetXml = ""; if (isAndroidModule()) { facetXml = buildAndroidFacet(); } imlTemplate = imlTemplate.replace("@FACETS@", facetXml); String moduleDir = getDir().getCanonicalPath(); StringBuilder sourceDirectories = new StringBuilder(); sourceDirectories.append(" <content url=\"file://$MODULE_DIR$\">\n"); ImmutableList<File> srcDirs = getSourceDirs(); for (File src : srcDirs) { String relative = src.getCanonicalPath().substring(moduleDir.length()); boolean isTestSource = false; // This covers directories like .../test[s]/... if (relative.matches(".*/tests?/.*")) { isTestSource = true; } sourceDirectories.append(" <sourceFolder url=\"file://$MODULE_DIR$").append(relative) .append("\" isTestSource=\"").append(isTestSource).append("\" />\n"); } ImmutableList<File> excludeDirs = getExcludeDirs(); for (File src : excludeDirs) { String relative = src.getCanonicalPath().substring(moduleDir.length()); sourceDirectories.append(" <excludeFolder url=\"file://$MODULE_DIR$").append(relative) .append("\"/>\n"); } sourceDirectories.append(" </content>\n"); // Intermediates. sourceDirectories.append(buildIntermediates()); imlTemplate = imlTemplate.replace("@SOURCES@", sourceDirectories.toString()); StringBuilder moduleDependencies = new StringBuilder(); for (String dependency : getAllDependencies()) { Module module = moduleCache.getAndCacheByDir(new File(dependency)); moduleDependencies.append(" <orderEntry type=\"module\" module-name=\"").append(module.getName()) .append("\" />\n"); } imlTemplate = imlTemplate.replace("@MODULE_DEPENDENCIES@", moduleDependencies.toString()); imlFile = new File(moduleDir, getName() + ".iml"); logger.info("Creating " + imlFile.getCanonicalPath()); Files.write(imlTemplate, imlFile, IntellijProject.CHARSET); } protected String buildIntermediates() throws IOException { StringBuilder sb = new StringBuilder(); for (String intermediatesDir : getRelativeIntermediatesDirs()) { sb.append(" <content url=\"file://$MODULE_DIR$/").append(intermediatesDir).append("\">\n"); sb.append(" <sourceFolder url=\"file://$MODULE_DIR$/").append(intermediatesDir) .append("\" isTestSource=\"false\" />\n"); sb.append(" </content>\n"); } return sb.toString(); } private void buildDependentModules() throws IOException { Set<String> moduleNameDependencies = explicitModuleNameDependencies; String[] copy = moduleNameDependencies.toArray(new String[moduleNameDependencies.size()]); for (String dependency : copy) { logger.info("Building dependency " + dependency); Module child = moduleCache.getAndCacheByName(dependency); if (child == null) { moduleNameDependencies.remove(dependency); } else { allDependencies.add(child.getDir().getCanonicalPath()); //allDependencies.addAll(child.getAllDependencies()); //logger.info("Adding iml " + child.getName() + " " + child.getImlFile()); allDependentImlFiles.add(child.getImlFile()); //allDependentImlFiles.addAll(child.getAllDependentImlFiles()); } } // Don't include self. The current module may have been brought in by framework // dependencies which will create a circular reference. allDependencies.remove(this.getDir().getCanonicalPath()); allDependentImlFiles.remove(this.getImlFile()); // TODO: add implicit dependencies. Convert all modules to be based on directory. for (File dependency : implicitModulePathDependencies) { Module child = moduleCache.getAndCacheByDir(dependency); if (child != null) { allDependencies.add(child.getDir().getCanonicalPath()); //allDependencies.addAll(child.getAllDependencies()); //logger.info("Adding iml " + child.getName() + " " + child.getImlFile()); allDependentImlFiles.add(child.getImlFile()); //allDependentImlFiles.addAll(child.getAllDependentImlFiles()); } } } public File getImlFile() { return imlFile; } public Set<String> getAllDependencies() { return allDependencies; } public Set<File> getAllDependentImlFiles() { return allDependentImlFiles; } private String buildAndroidFacet() throws IOException { // Not sure how to handle android facet for multi-module since there could be more than // one intermediates directory. String dir = getRelativeIntermediatesDirs().get(0); String xml = "" + " <component name=\"FacetManager\">\n" + " <facet type=\"android\" name=\"Android\">\n" + " <configuration>\n" + " <option name=\"GEN_FOLDER_RELATIVE_PATH_APT\" value=\"" + dir + "\" />\n" + " <option name=\"GEN_FOLDER_RELATIVE_PATH_AIDL\" value=\"" + dir + "\" />\n" + " <option name=\"MANIFEST_FILE_RELATIVE_PATH\" value=\"" + "/AndroidManifest.xml\" />\n" + " <option name=\"RES_FOLDER_RELATIVE_PATH\" value=\"/res\" />\n" + " <option name=\"ASSETS_FOLDER_RELATIVE_PATH\" value=\"/assets\" />\n" + " <option name=\"LIBS_FOLDER_RELATIVE_PATH\" value=\"/libs\" />\n" + " <option name=\"REGENERATE_R_JAVA\" value=\"true\" />\n" + " <option name=\"REGENERATE_JAVA_BY_AIDL\" value=\"true\" />\n" + " <option name=\"USE_CUSTOM_APK_RESOURCE_FOLDER\" value=\"false\" />\n" + " <option name=\"CUSTOM_APK_RESOURCE_FOLDER\" value=\"\" />\n" + " <option name=\"USE_CUSTOM_COMPILER_MANIFEST\" value=\"false\" />\n" + " <option name=\"CUSTOM_COMPILER_MANIFEST\" value=\"\" />\n" + " <option name=\"APK_PATH\" value=\"\" />\n" + " <option name=\"LIBRARY_PROJECT\" value=\"false\" />\n" + " <option name=\"RUN_PROCESS_RESOURCES_MAVEN_TASK\" value=\"true\" />\n" + " <option name=\"GENERATE_UNSIGNED_APK\" value=\"false\" />\n" + " </configuration>\n" + " </facet>\n" + " </component>\n"; return xml; } @Override public int hashCode() { return Objects.hashCode(getName()); } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null || getClass() != obj.getClass()) { return false; } Module other = (Module) obj; return Objects.equal(getName(), other.getName()); } @Override public String toString() { return Objects.toStringHelper(this).add("name", getName()).add("allDependencies", allDependencies) .add("iml files", allDependentImlFiles).add("imlFile", imlFile) .add("makeFileParser", makeFileParser) .add("explicitModuleNameDependencies", Iterables.toString(explicitModuleNameDependencies)) .add("implicitModulePathDependencies", Iterables.toString(implicitModulePathDependencies)) .toString(); } }