1 /* 2 * Copyright (C) 2012 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.idegen; 18 19 import com.google.common.base.Objects; 20 import com.google.common.base.Preconditions; 21 import com.google.common.collect.ImmutableList; 22 import com.google.common.collect.Iterables; 23 import com.google.common.collect.Lists; 24 import com.google.common.collect.Sets; 25 import com.google.common.io.Files; 26 27 import java.io.File; 28 import java.io.IOException; 29 import java.util.Collections; 30 import java.util.HashSet; 31 import java.util.List; 32 import java.util.Set; 33 import java.util.logging.Logger; 34 35 /** 36 * Module constructed from a make file. 37 * 38 * TODO: read the make file and understand included source dirs in addition to searching 39 * sub-directories. Make files can include sources that are not sub-directories. For example, the 40 * framework module includes sources from: 41 * 42 * external/libphonenumber/java/src 43 * 44 * to provide: 45 * 46 * com.android.i18n.phonenumbers.PhoneNumberUtil; 47 */ 48 public class Module { 49 50 private static final Logger logger = Logger.getLogger(Module.class.getName()); 51 52 public static final String REL_OUT_APP_DIR = "out/target/common/obj/APPS"; 53 54 private static final String IML_TEMPLATE_FILE_NAME = "module-template.iml"; 55 private static final String[] AUTO_DEPENDENCIES = new String[]{"framework", "libcore"}; 56 private static final String[] DIRS_WITH_AUTO_DEPENDENCIES = new String[]{"packages", "vendor", 57 "frameworks/ex", "frameworks/opt", "frameworks/support"}; 58 59 /** 60 * All possible attributes for the make file. 61 */ 62 protected enum Key { 63 LOCAL_STATIC_JAVA_LIBRARIES, 64 LOCAL_JAVA_LIBRARIES, 65 LOCAL_SRC_FILES 66 } 67 68 private ModuleCache moduleCache = ModuleCache.getInstance(); 69 70 private File imlFile; 71 private Set<String> allDependencies = Sets.newHashSet(); // direct + indirect 72 private Set<File> allDependentImlFiles = Sets.newHashSet(); 73 74 private File makeFile; 75 private File moduleRoot; 76 private HashSet<File> sourceFiles = Sets.newHashSet(); 77 78 // Module dependencies come from LOCAL_STATIC_JAVA_LIBRARIES or LOCAL_JAVA_LIBRARIES 79 Set<String> explicitModuleNameDependencies = Sets.newHashSet(); 80 // Implicit module dependencies come from src files that fall outside the module root directory. 81 // For example, if packages/apps/Contacts includes src files from packages/apps/ContactsCommon, 82 // that is an implicit module dependency. It's not a module dependency from the build 83 // perspective but it needs to be a separate module in intellij so that the src files can be 84 // shared by multiple intellij modules. 85 Set<File> implicitModulePathDependencies = Sets.newHashSet(); 86 87 String relativeIntermediatesDir; 88 MakeFileParser makeFileParser; 89 boolean parseMakeFileForSource; 90 Module(File moduleDir)91 public Module(File moduleDir) throws IOException { 92 this(moduleDir, true); 93 } 94 Module(File moduleDir, boolean parseMakeFileForSource)95 public Module(File moduleDir, boolean parseMakeFileForSource) throws IOException { 96 this.moduleRoot = Preconditions.checkNotNull(moduleDir); 97 this.makeFile = new File(moduleDir, "Android.mk"); 98 this.relativeIntermediatesDir = calculateRelativePartToRepoRoot() + REL_OUT_APP_DIR + 99 File.separatorChar + getName() + "_intermediates" + File.separator + "src"; 100 this.parseMakeFileForSource = parseMakeFileForSource; 101 102 // TODO: auto-detect when framework dependency is needed instead of using coded list. 103 for (String dir : DIRS_WITH_AUTO_DEPENDENCIES) { 104 // length + 2 to account for slash 105 boolean isDir = makeFile.getCanonicalPath().startsWith( 106 DirectorySearch.getRepoRoot() + "/" + dir); 107 if (isDir) { 108 Collections.addAll(this.explicitModuleNameDependencies, AUTO_DEPENDENCIES); 109 } 110 } 111 112 makeFileParser = new MakeFileParser(makeFile); 113 } 114 calculateRelativePartToRepoRoot()115 private String calculateRelativePartToRepoRoot() throws IOException { 116 String rel = moduleRoot.getCanonicalPath().substring( 117 DirectorySearch.getRepoRoot().getCanonicalPath().length()); 118 int count = 0; 119 // Count the number of slashes to determine how far back to go. 120 for (int i = 0; i < rel.length(); i++) { 121 if (rel.charAt(i) == '/') { 122 count++; 123 } 124 } 125 StringBuilder sb = new StringBuilder(); 126 for (int i = 0; i < count; i++) { 127 sb.append("../"); 128 } 129 return sb.toString(); 130 } 131 build()132 public void build() throws IOException { 133 makeFileParser.parse(); 134 buildDependencyList(); 135 buildDependentModules(); 136 logger.info("Done building module " + moduleRoot); 137 logger.info(toString()); 138 } 139 getDir()140 public File getDir() { 141 return moduleRoot; 142 } 143 getName()144 public String getName() { 145 return moduleRoot.getName(); 146 } 147 getRelativeIntermediatesDirs()148 private List<String> getRelativeIntermediatesDirs() throws IOException { 149 return Lists.newArrayList(relativeIntermediatesDir); 150 } 151 getSourceDirs()152 private ImmutableList<File> getSourceDirs() { 153 return ImmutableList.copyOf(sourceFiles); 154 } 155 getExcludeDirs()156 private ImmutableList<File> getExcludeDirs() { 157 return DirectorySearch.findExcludeDirs(makeFile); 158 } 159 isAndroidModule()160 private boolean isAndroidModule() { 161 File manifest = new File(moduleRoot, "AndroidManifest.xml"); 162 return manifest.exists(); 163 } 164 findSourceFilesAndImplicitDependencies()165 private void findSourceFilesAndImplicitDependencies() throws IOException { 166 Iterable<String> values = makeFileParser.getValues(Key.LOCAL_SRC_FILES.name()); 167 if (values != null) { 168 for (String value : values) { 169 File src = new File(moduleRoot, value); 170 171 // value may contain garbage at this point due to relaxed make file parsing. 172 // filter by existing file. 173 if (src.exists()) { 174 // Look for directories outside the current module directory. 175 if (value.contains("..")) { 176 // Find the closest Android make file. 177 File moduleRoot = DirectorySearch.findModuleRoot(src); 178 implicitModulePathDependencies.add(moduleRoot); 179 } else { 180 if (parseMakeFileForSource) { 181 // Check if source files are subdirectories of generic parent src 182 // directories. If so, no need to add since they are already included. 183 boolean alreadyIncluded = false; 184 for (String parentDir : DirectorySearch.SOURCE_DIRS) { 185 if (value.startsWith(parentDir)) { 186 alreadyIncluded = true; 187 break; 188 } 189 } 190 191 if (!alreadyIncluded) { 192 sourceFiles.add(src); 193 } 194 } 195 } 196 } 197 } 198 } 199 200 sourceFiles.addAll(DirectorySearch.findSourceDirs(moduleRoot)); 201 } 202 buildDependencyList()203 private void buildDependencyList() throws IOException { 204 parseDirectDependencies(Key.LOCAL_STATIC_JAVA_LIBRARIES); 205 parseDirectDependencies(Key.LOCAL_JAVA_LIBRARIES); 206 findSourceFilesAndImplicitDependencies(); 207 } 208 parseDirectDependencies(Key key)209 private void parseDirectDependencies(Key key) { 210 Iterable<String> names = makeFileParser.getValues(key.name()); 211 if (names != null) { 212 for (String dependency : names) { 213 explicitModuleNameDependencies.add(dependency); 214 } 215 } 216 } 217 buildImlFile()218 public void buildImlFile() throws IOException { 219 String imlTemplate = Files.toString( 220 new File(DirectorySearch.findTemplateDir(), IML_TEMPLATE_FILE_NAME), 221 IntellijProject.CHARSET); 222 223 String facetXml = ""; 224 if (isAndroidModule()) { 225 facetXml = buildAndroidFacet(); 226 } 227 imlTemplate = imlTemplate.replace("@FACETS@", facetXml); 228 229 String moduleDir = getDir().getCanonicalPath(); 230 231 StringBuilder sourceDirectories = new StringBuilder(); 232 sourceDirectories.append(" <content url=\"file://$MODULE_DIR$\">\n"); 233 ImmutableList<File> srcDirs = getSourceDirs(); 234 for (File src : srcDirs) { 235 String relative = src.getCanonicalPath().substring(moduleDir.length()); 236 boolean isTestSource = false; 237 // This covers directories like .../test[s]/... 238 if (relative.matches(".*/tests?/.*")) { 239 isTestSource = true; 240 } 241 sourceDirectories.append(" <sourceFolder url=\"file://$MODULE_DIR$") 242 .append(relative).append("\" isTestSource=\"").append(isTestSource) 243 .append("\" />\n"); 244 } 245 ImmutableList<File> excludeDirs = getExcludeDirs(); 246 for (File src : excludeDirs) { 247 String relative = src.getCanonicalPath().substring(moduleDir.length()); 248 sourceDirectories.append(" <excludeFolder url=\"file://$MODULE_DIR$") 249 .append(relative).append("\"/>\n"); 250 } 251 sourceDirectories.append(" </content>\n"); 252 253 // Intermediates. 254 sourceDirectories.append(buildIntermediates()); 255 256 imlTemplate = imlTemplate.replace("@SOURCES@", sourceDirectories.toString()); 257 258 StringBuilder moduleDependencies = new StringBuilder(); 259 for (String dependency : getAllDependencies()) { 260 Module module = moduleCache.getAndCacheByDir(new File(dependency)); 261 moduleDependencies.append(" <orderEntry type=\"module\" module-name=\"") 262 .append(module.getName()).append("\" />\n"); 263 } 264 imlTemplate = imlTemplate.replace("@MODULE_DEPENDENCIES@", moduleDependencies.toString()); 265 266 imlFile = new File(moduleDir, getName() + ".iml"); 267 logger.info("Creating " + imlFile.getCanonicalPath()); 268 Files.write(imlTemplate, imlFile, IntellijProject.CHARSET); 269 } 270 buildIntermediates()271 protected String buildIntermediates() throws IOException { 272 StringBuilder sb = new StringBuilder(); 273 for (String intermediatesDir : getRelativeIntermediatesDirs()) { 274 sb.append(" <content url=\"file://$MODULE_DIR$/").append(intermediatesDir) 275 .append("\">\n"); 276 sb.append(" <sourceFolder url=\"file://$MODULE_DIR$/") 277 .append(intermediatesDir) 278 .append("\" isTestSource=\"false\" />\n"); 279 sb.append(" </content>\n"); 280 } 281 return sb.toString(); 282 } 283 buildDependentModules()284 private void buildDependentModules() throws IOException { 285 Set<String> moduleNameDependencies = explicitModuleNameDependencies; 286 287 String[] copy = moduleNameDependencies.toArray(new String[moduleNameDependencies.size()]); 288 for (String dependency : copy) { 289 logger.info("Building dependency " + dependency); 290 Module child = moduleCache.getAndCacheByName(dependency); 291 if (child == null) { 292 moduleNameDependencies.remove(dependency); 293 } else { 294 allDependencies.add(child.getDir().getCanonicalPath()); 295 //allDependencies.addAll(child.getAllDependencies()); 296 //logger.info("Adding iml " + child.getName() + " " + child.getImlFile()); 297 allDependentImlFiles.add(child.getImlFile()); 298 //allDependentImlFiles.addAll(child.getAllDependentImlFiles()); 299 } 300 } 301 // Don't include self. The current module may have been brought in by framework 302 // dependencies which will create a circular reference. 303 allDependencies.remove(this.getDir().getCanonicalPath()); 304 allDependentImlFiles.remove(this.getImlFile()); 305 306 // TODO: add implicit dependencies. Convert all modules to be based on directory. 307 for (File dependency : implicitModulePathDependencies) { 308 Module child = moduleCache.getAndCacheByDir(dependency); 309 if (child != null) { 310 allDependencies.add(child.getDir().getCanonicalPath()); 311 //allDependencies.addAll(child.getAllDependencies()); 312 //logger.info("Adding iml " + child.getName() + " " + child.getImlFile()); 313 allDependentImlFiles.add(child.getImlFile()); 314 //allDependentImlFiles.addAll(child.getAllDependentImlFiles()); 315 } 316 } 317 } 318 getImlFile()319 public File getImlFile() { 320 return imlFile; 321 } 322 getAllDependencies()323 public Set<String> getAllDependencies() { 324 return allDependencies; 325 } 326 getAllDependentImlFiles()327 public Set<File> getAllDependentImlFiles() { 328 return allDependentImlFiles; 329 } 330 buildAndroidFacet()331 private String buildAndroidFacet() throws IOException { 332 // Not sure how to handle android facet for multi-module since there could be more than 333 // one intermediates directory. 334 String dir = getRelativeIntermediatesDirs().get(0); 335 String xml = "" 336 + " <component name=\"FacetManager\">\n" 337 + " <facet type=\"android\" name=\"Android\">\n" 338 + " <configuration>\n" 339 + " <option name=\"GEN_FOLDER_RELATIVE_PATH_APT\" value=\"" + 340 dir + "\" />\n" 341 + " <option name=\"GEN_FOLDER_RELATIVE_PATH_AIDL\" value=\"" + 342 dir + "\" />\n" 343 + " <option name=\"MANIFEST_FILE_RELATIVE_PATH\" value=\"" 344 + "/AndroidManifest.xml\" />\n" 345 + " <option name=\"RES_FOLDER_RELATIVE_PATH\" value=\"/res\" />\n" 346 + " <option name=\"ASSETS_FOLDER_RELATIVE_PATH\" value=\"/assets\" />\n" 347 + " <option name=\"LIBS_FOLDER_RELATIVE_PATH\" value=\"/libs\" />\n" 348 + " <option name=\"REGENERATE_R_JAVA\" value=\"true\" />\n" 349 + " <option name=\"REGENERATE_JAVA_BY_AIDL\" value=\"true\" />\n" 350 + " <option name=\"USE_CUSTOM_APK_RESOURCE_FOLDER\" value=\"false\" />\n" 351 + " <option name=\"CUSTOM_APK_RESOURCE_FOLDER\" value=\"\" />\n" 352 + " <option name=\"USE_CUSTOM_COMPILER_MANIFEST\" value=\"false\" />\n" 353 + " <option name=\"CUSTOM_COMPILER_MANIFEST\" value=\"\" />\n" 354 + " <option name=\"APK_PATH\" value=\"\" />\n" 355 + " <option name=\"LIBRARY_PROJECT\" value=\"false\" />\n" 356 + " <option name=\"RUN_PROCESS_RESOURCES_MAVEN_TASK\" value=\"true\" />\n" 357 + " <option name=\"GENERATE_UNSIGNED_APK\" value=\"false\" />\n" 358 + " </configuration>\n" 359 + " </facet>\n" 360 + " </component>\n"; 361 return xml; 362 } 363 364 @Override hashCode()365 public int hashCode() { 366 return Objects.hashCode(getName()); 367 } 368 369 @Override equals(Object obj)370 public boolean equals(Object obj) { 371 if (this == obj) { 372 return true; 373 } 374 if (obj == null || getClass() != obj.getClass()) { 375 return false; 376 } 377 Module other = (Module) obj; 378 return Objects.equal(getName(), other.getName()); 379 } 380 381 @Override toString()382 public String toString() { 383 return Objects.toStringHelper(this) 384 .add("name", getName()) 385 .add("allDependencies", allDependencies) 386 .add("iml files", allDependentImlFiles).add("imlFile", imlFile) 387 .add("makeFileParser", makeFileParser) 388 .add("explicitModuleNameDependencies", Iterables.toString( 389 explicitModuleNameDependencies)) 390 .add("implicitModulePathDependencies", Iterables.toString( 391 implicitModulePathDependencies)) 392 .toString(); 393 } 394 } 395 396