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