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 if (relative.startsWith("/test")) { 238 isTestSource = true; 239 } 240 sourceDirectories.append(" <sourceFolder url=\"file://$MODULE_DIR$") 241 .append(relative).append("\" isTestSource=\"").append(isTestSource) 242 .append("\" />\n"); 243 } 244 ImmutableList<File> excludeDirs = getExcludeDirs(); 245 for (File src : excludeDirs) { 246 String relative = src.getCanonicalPath().substring(moduleDir.length()); 247 sourceDirectories.append(" <excludeFolder url=\"file://$MODULE_DIR$") 248 .append(relative).append("\"/>\n"); 249 } 250 sourceDirectories.append(" </content>\n"); 251 252 // Intermediates. 253 sourceDirectories.append(buildIntermediates()); 254 255 imlTemplate = imlTemplate.replace("@SOURCES@", sourceDirectories.toString()); 256 257 StringBuilder moduleDependencies = new StringBuilder(); 258 for (String dependency : getAllDependencies()) { 259 Module module = moduleCache.getAndCacheByDir(new File(dependency)); 260 moduleDependencies.append(" <orderEntry type=\"module\" module-name=\"") 261 .append(module.getName()).append("\" />\n"); 262 } 263 imlTemplate = imlTemplate.replace("@MODULE_DEPENDENCIES@", moduleDependencies.toString()); 264 265 imlFile = new File(moduleDir, getName() + ".iml"); 266 logger.info("Creating " + imlFile.getCanonicalPath()); 267 Files.write(imlTemplate, imlFile, IntellijProject.CHARSET); 268 } 269 buildIntermediates()270 protected String buildIntermediates() throws IOException { 271 StringBuilder sb = new StringBuilder(); 272 for (String intermediatesDir : getRelativeIntermediatesDirs()) { 273 sb.append(" <content url=\"file://$MODULE_DIR$/").append(intermediatesDir) 274 .append("\">\n"); 275 sb.append(" <sourceFolder url=\"file://$MODULE_DIR$/") 276 .append(intermediatesDir) 277 .append("\" isTestSource=\"false\" />\n"); 278 sb.append(" </content>\n"); 279 } 280 return sb.toString(); 281 } 282 buildDependentModules()283 private void buildDependentModules() throws IOException { 284 Set<String> moduleNameDependencies = explicitModuleNameDependencies; 285 286 String[] copy = moduleNameDependencies.toArray(new String[moduleNameDependencies.size()]); 287 for (String dependency : copy) { 288 logger.info("Building dependency " + dependency); 289 Module child = moduleCache.getAndCacheByName(dependency); 290 if (child == null) { 291 moduleNameDependencies.remove(dependency); 292 } else { 293 allDependencies.add(child.getDir().getCanonicalPath()); 294 //allDependencies.addAll(child.getAllDependencies()); 295 //logger.info("Adding iml " + child.getName() + " " + child.getImlFile()); 296 allDependentImlFiles.add(child.getImlFile()); 297 //allDependentImlFiles.addAll(child.getAllDependentImlFiles()); 298 } 299 } 300 // Don't include self. The current module may have been brought in by framework 301 // dependencies which will create a circular reference. 302 allDependencies.remove(this.getDir().getCanonicalPath()); 303 allDependentImlFiles.remove(this.getImlFile()); 304 305 // TODO: add implicit dependencies. Convert all modules to be based on directory. 306 for (File dependency : implicitModulePathDependencies) { 307 Module child = moduleCache.getAndCacheByDir(dependency); 308 if (child != null) { 309 allDependencies.add(child.getDir().getCanonicalPath()); 310 //allDependencies.addAll(child.getAllDependencies()); 311 //logger.info("Adding iml " + child.getName() + " " + child.getImlFile()); 312 allDependentImlFiles.add(child.getImlFile()); 313 //allDependentImlFiles.addAll(child.getAllDependentImlFiles()); 314 } 315 } 316 } 317 getImlFile()318 public File getImlFile() { 319 return imlFile; 320 } 321 getAllDependencies()322 public Set<String> getAllDependencies() { 323 return allDependencies; 324 } 325 getAllDependentImlFiles()326 public Set<File> getAllDependentImlFiles() { 327 return allDependentImlFiles; 328 } 329 buildAndroidFacet()330 private String buildAndroidFacet() throws IOException { 331 // Not sure how to handle android facet for multi-module since there could be more than 332 // one intermediates directory. 333 String dir = getRelativeIntermediatesDirs().get(0); 334 String xml = "" 335 + " <component name=\"FacetManager\">\n" 336 + " <facet type=\"android\" name=\"Android\">\n" 337 + " <configuration>\n" 338 + " <option name=\"GEN_FOLDER_RELATIVE_PATH_APT\" value=\"" + 339 dir + "\" />\n" 340 + " <option name=\"GEN_FOLDER_RELATIVE_PATH_AIDL\" value=\"" + 341 dir + "\" />\n" 342 + " <option name=\"MANIFEST_FILE_RELATIVE_PATH\" value=\"" 343 + "/AndroidManifest.xml\" />\n" 344 + " <option name=\"RES_FOLDER_RELATIVE_PATH\" value=\"/res\" />\n" 345 + " <option name=\"ASSETS_FOLDER_RELATIVE_PATH\" value=\"/assets\" />\n" 346 + " <option name=\"LIBS_FOLDER_RELATIVE_PATH\" value=\"/libs\" />\n" 347 + " <option name=\"REGENERATE_R_JAVA\" value=\"true\" />\n" 348 + " <option name=\"REGENERATE_JAVA_BY_AIDL\" value=\"true\" />\n" 349 + " <option name=\"USE_CUSTOM_APK_RESOURCE_FOLDER\" value=\"false\" />\n" 350 + " <option name=\"CUSTOM_APK_RESOURCE_FOLDER\" value=\"\" />\n" 351 + " <option name=\"USE_CUSTOM_COMPILER_MANIFEST\" value=\"false\" />\n" 352 + " <option name=\"CUSTOM_COMPILER_MANIFEST\" value=\"\" />\n" 353 + " <option name=\"APK_PATH\" value=\"\" />\n" 354 + " <option name=\"LIBRARY_PROJECT\" value=\"false\" />\n" 355 + " <option name=\"RUN_PROCESS_RESOURCES_MAVEN_TASK\" value=\"true\" />\n" 356 + " <option name=\"GENERATE_UNSIGNED_APK\" value=\"false\" />\n" 357 + " </configuration>\n" 358 + " </facet>\n" 359 + " </component>\n"; 360 return xml; 361 } 362 363 @Override hashCode()364 public int hashCode() { 365 return Objects.hashCode(getName()); 366 } 367 368 @Override equals(Object obj)369 public boolean equals(Object obj) { 370 if (this == obj) { 371 return true; 372 } 373 if (obj == null || getClass() != obj.getClass()) { 374 return false; 375 } 376 Module other = (Module) obj; 377 return Objects.equal(getName(), other.getName()); 378 } 379 380 @Override toString()381 public String toString() { 382 return Objects.toStringHelper(this) 383 .add("name", getName()) 384 .add("allDependencies", allDependencies) 385 .add("iml files", allDependentImlFiles).add("imlFile", imlFile) 386 .add("makeFileParser", makeFileParser) 387 .add("explicitModuleNameDependencies", Iterables.toString( 388 explicitModuleNameDependencies)) 389 .add("implicitModulePathDependencies", Iterables.toString( 390 implicitModulePathDependencies)) 391 .toString(); 392 } 393 } 394 395