blob: 4695ca3f64f092c7ec3ebc87a53090348882e20c [file]
/*
* 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;
if (relative.startsWith("/test")) {
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();
}
}