1*90c8c64dSAndroid Build Coastguard Worker /* 2*90c8c64dSAndroid Build Coastguard Worker * Copyright (C) 2008 The Android Open Source Project 3*90c8c64dSAndroid Build Coastguard Worker * 4*90c8c64dSAndroid Build Coastguard Worker * Licensed under the Apache License, Version 2.0 (the "License"); 5*90c8c64dSAndroid Build Coastguard Worker * you may not use this file except in compliance with the License. 6*90c8c64dSAndroid Build Coastguard Worker * You may obtain a copy of the License at 7*90c8c64dSAndroid Build Coastguard Worker * 8*90c8c64dSAndroid Build Coastguard Worker * http://www.apache.org/licenses/LICENSE-2.0 9*90c8c64dSAndroid Build Coastguard Worker * 10*90c8c64dSAndroid Build Coastguard Worker * Unless required by applicable law or agreed to in writing, software 11*90c8c64dSAndroid Build Coastguard Worker * distributed under the License is distributed on an "AS IS" BASIS, 12*90c8c64dSAndroid Build Coastguard Worker * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13*90c8c64dSAndroid Build Coastguard Worker * See the License for the specific language governing permissions and 14*90c8c64dSAndroid Build Coastguard Worker * limitations under the License. 15*90c8c64dSAndroid Build Coastguard Worker */ 16*90c8c64dSAndroid Build Coastguard Worker 17*90c8c64dSAndroid Build Coastguard Worker import java.io.File; 18*90c8c64dSAndroid Build Coastguard Worker import java.io.IOException; 19*90c8c64dSAndroid Build Coastguard Worker import java.io.BufferedReader; 20*90c8c64dSAndroid Build Coastguard Worker import java.io.FileReader; 21*90c8c64dSAndroid Build Coastguard Worker import java.nio.file.Files; 22*90c8c64dSAndroid Build Coastguard Worker import java.nio.file.Path; 23*90c8c64dSAndroid Build Coastguard Worker import java.util.ArrayList; 24*90c8c64dSAndroid Build Coastguard Worker import java.util.Collection; 25*90c8c64dSAndroid Build Coastguard Worker import java.util.Collections; 26*90c8c64dSAndroid Build Coastguard Worker import java.util.List; 27*90c8c64dSAndroid Build Coastguard Worker import java.util.Set; 28*90c8c64dSAndroid Build Coastguard Worker import java.util.TreeSet; 29*90c8c64dSAndroid Build Coastguard Worker import java.util.SortedSet; 30*90c8c64dSAndroid Build Coastguard Worker import java.util.regex.Pattern; 31*90c8c64dSAndroid Build Coastguard Worker 32*90c8c64dSAndroid Build Coastguard Worker /** 33*90c8c64dSAndroid Build Coastguard Worker * Immutable representation of an IDE configuration. Assumes that the current 34*90c8c64dSAndroid Build Coastguard Worker * directory is the project's root directory. 35*90c8c64dSAndroid Build Coastguard Worker */ 36*90c8c64dSAndroid Build Coastguard Worker public class Configuration { 37*90c8c64dSAndroid Build Coastguard Worker 38*90c8c64dSAndroid Build Coastguard Worker /** Java source tree roots. */ 39*90c8c64dSAndroid Build Coastguard Worker public final SortedSet<File> sourceRoots; 40*90c8c64dSAndroid Build Coastguard Worker 41*90c8c64dSAndroid Build Coastguard Worker /** Found .jar files (that weren't excluded). */ 42*90c8c64dSAndroid Build Coastguard Worker public final List<File> jarFiles; 43*90c8c64dSAndroid Build Coastguard Worker 44*90c8c64dSAndroid Build Coastguard Worker /** Excluded directories which may or may not be under a source root. */ 45*90c8c64dSAndroid Build Coastguard Worker public final SortedSet<File> excludedDirs; 46*90c8c64dSAndroid Build Coastguard Worker 47*90c8c64dSAndroid Build Coastguard Worker /** The root directory for this tool. */ 48*90c8c64dSAndroid Build Coastguard Worker public final File toolDirectory; 49*90c8c64dSAndroid Build Coastguard Worker 50*90c8c64dSAndroid Build Coastguard Worker /** File name used for excluded path files. */ 51*90c8c64dSAndroid Build Coastguard Worker private static final String EXCLUDED_PATHS = "excluded-paths"; 52*90c8c64dSAndroid Build Coastguard Worker 53*90c8c64dSAndroid Build Coastguard Worker /** The vendor directory. */ 54*90c8c64dSAndroid Build Coastguard Worker private static final String VENDOR_PATH = "./vendor/"; 55*90c8c64dSAndroid Build Coastguard Worker 56*90c8c64dSAndroid Build Coastguard Worker /** 57*90c8c64dSAndroid Build Coastguard Worker * Constructs a Configuration by traversing the directory tree, looking 58*90c8c64dSAndroid Build Coastguard Worker * for .java and .jar files and identifying source roots. 59*90c8c64dSAndroid Build Coastguard Worker */ Configuration()60*90c8c64dSAndroid Build Coastguard Worker public Configuration() throws IOException { 61*90c8c64dSAndroid Build Coastguard Worker this.toolDirectory = new File("development/tools/idegen"); 62*90c8c64dSAndroid Build Coastguard Worker if (!toolDirectory.isDirectory()) { 63*90c8c64dSAndroid Build Coastguard Worker // The wrapper script should have already verified this. 64*90c8c64dSAndroid Build Coastguard Worker throw new AssertionError("Not in root directory."); 65*90c8c64dSAndroid Build Coastguard Worker } 66*90c8c64dSAndroid Build Coastguard Worker 67*90c8c64dSAndroid Build Coastguard Worker Stopwatch stopwatch = new Stopwatch(); 68*90c8c64dSAndroid Build Coastguard Worker 69*90c8c64dSAndroid Build Coastguard Worker Excludes excludes = readExcludes(); 70*90c8c64dSAndroid Build Coastguard Worker 71*90c8c64dSAndroid Build Coastguard Worker stopwatch.reset("Read excludes"); 72*90c8c64dSAndroid Build Coastguard Worker 73*90c8c64dSAndroid Build Coastguard Worker List<File> jarFiles = new ArrayList<File>(500); 74*90c8c64dSAndroid Build Coastguard Worker SortedSet<File> excludedDirs = new TreeSet<File>(); 75*90c8c64dSAndroid Build Coastguard Worker SortedSet<File> sourceRoots = new TreeSet<File>(); 76*90c8c64dSAndroid Build Coastguard Worker 77*90c8c64dSAndroid Build Coastguard Worker traverse(new File("."), sourceRoots, jarFiles, excludedDirs, excludes); 78*90c8c64dSAndroid Build Coastguard Worker 79*90c8c64dSAndroid Build Coastguard Worker stopwatch.reset("Traversed tree"); 80*90c8c64dSAndroid Build Coastguard Worker 81*90c8c64dSAndroid Build Coastguard Worker Log.debug(sourceRoots.size() + " source roots"); 82*90c8c64dSAndroid Build Coastguard Worker Log.debug(jarFiles.size() + " jar files"); 83*90c8c64dSAndroid Build Coastguard Worker Log.debug(excludedDirs.size() + " excluded dirs"); 84*90c8c64dSAndroid Build Coastguard Worker 85*90c8c64dSAndroid Build Coastguard Worker this.sourceRoots = Collections.unmodifiableSortedSet(sourceRoots); 86*90c8c64dSAndroid Build Coastguard Worker this.jarFiles = Collections.unmodifiableList(jarFiles); 87*90c8c64dSAndroid Build Coastguard Worker this.excludedDirs = Collections.unmodifiableSortedSet(excludedDirs); 88*90c8c64dSAndroid Build Coastguard Worker } 89*90c8c64dSAndroid Build Coastguard Worker 90*90c8c64dSAndroid Build Coastguard Worker /** 91*90c8c64dSAndroid Build Coastguard Worker * Reads excluded path files. 92*90c8c64dSAndroid Build Coastguard Worker */ readExcludes()93*90c8c64dSAndroid Build Coastguard Worker private Excludes readExcludes() throws IOException { 94*90c8c64dSAndroid Build Coastguard Worker List<Pattern> patterns = new ArrayList<Pattern>(); 95*90c8c64dSAndroid Build Coastguard Worker 96*90c8c64dSAndroid Build Coastguard Worker File globalExcludes = new File(toolDirectory, EXCLUDED_PATHS); 97*90c8c64dSAndroid Build Coastguard Worker parseFile(globalExcludes, patterns); 98*90c8c64dSAndroid Build Coastguard Worker 99*90c8c64dSAndroid Build Coastguard Worker // Traverse all vendor-specific directories 100*90c8c64dSAndroid Build Coastguard Worker readVendorExcludes(patterns); 101*90c8c64dSAndroid Build Coastguard Worker 102*90c8c64dSAndroid Build Coastguard Worker // Look for user-specific excluded-paths file in current directory. 103*90c8c64dSAndroid Build Coastguard Worker File localExcludes = new File(EXCLUDED_PATHS); 104*90c8c64dSAndroid Build Coastguard Worker if (localExcludes.exists()) { 105*90c8c64dSAndroid Build Coastguard Worker parseFile(localExcludes, patterns); 106*90c8c64dSAndroid Build Coastguard Worker } 107*90c8c64dSAndroid Build Coastguard Worker 108*90c8c64dSAndroid Build Coastguard Worker return new Excludes(patterns); 109*90c8c64dSAndroid Build Coastguard Worker } 110*90c8c64dSAndroid Build Coastguard Worker 111*90c8c64dSAndroid Build Coastguard Worker /** 112*90c8c64dSAndroid Build Coastguard Worker * Reads vendor excluded path files. 113*90c8c64dSAndroid Build Coastguard Worker * @see #readExcludes() 114*90c8c64dSAndroid Build Coastguard Worker */ readVendorExcludes(List<Pattern> out)115*90c8c64dSAndroid Build Coastguard Worker private static void readVendorExcludes(List<Pattern> out) throws IOException { 116*90c8c64dSAndroid Build Coastguard Worker File vendorDir = new File(VENDOR_PATH); 117*90c8c64dSAndroid Build Coastguard Worker File[] vendorList; 118*90c8c64dSAndroid Build Coastguard Worker if (!vendorDir.exists() || (vendorList = vendorDir.listFiles()) == null) return; 119*90c8c64dSAndroid Build Coastguard Worker for (File vendor : vendorList) { 120*90c8c64dSAndroid Build Coastguard Worker File vendorExcludes = new File(vendor, EXCLUDED_PATHS); 121*90c8c64dSAndroid Build Coastguard Worker if (vendorExcludes.exists()) { 122*90c8c64dSAndroid Build Coastguard Worker Log.info("Read vendor excludes: " + vendorExcludes.getPath()); 123*90c8c64dSAndroid Build Coastguard Worker parseFile(vendorExcludes, out); 124*90c8c64dSAndroid Build Coastguard Worker } 125*90c8c64dSAndroid Build Coastguard Worker } 126*90c8c64dSAndroid Build Coastguard Worker } 127*90c8c64dSAndroid Build Coastguard Worker 128*90c8c64dSAndroid Build Coastguard Worker /** 129*90c8c64dSAndroid Build Coastguard Worker * Recursively finds .java source roots, .jar files, and excluded 130*90c8c64dSAndroid Build Coastguard Worker * directories. 131*90c8c64dSAndroid Build Coastguard Worker */ traverse(File directory, Set<File> sourceRoots, Collection<File> jarFiles, Collection<File> excludedDirs, Excludes excludes)132*90c8c64dSAndroid Build Coastguard Worker private static void traverse(File directory, Set<File> sourceRoots, 133*90c8c64dSAndroid Build Coastguard Worker Collection<File> jarFiles, Collection<File> excludedDirs, 134*90c8c64dSAndroid Build Coastguard Worker Excludes excludes) throws IOException { 135*90c8c64dSAndroid Build Coastguard Worker /* 136*90c8c64dSAndroid Build Coastguard Worker * Note it would be faster to stop traversing a source root as soon as 137*90c8c64dSAndroid Build Coastguard Worker * we encounter the first .java file, but it appears we have nested 138*90c8c64dSAndroid Build Coastguard Worker * source roots in our generated source directory (specifically, 139*90c8c64dSAndroid Build Coastguard Worker * R.java files and aidl .java files don't share the same source 140*90c8c64dSAndroid Build Coastguard Worker * root). 141*90c8c64dSAndroid Build Coastguard Worker */ 142*90c8c64dSAndroid Build Coastguard Worker 143*90c8c64dSAndroid Build Coastguard Worker boolean firstJavaFile = true; 144*90c8c64dSAndroid Build Coastguard Worker File[] files = directory.listFiles(); 145*90c8c64dSAndroid Build Coastguard Worker if (files == null) { 146*90c8c64dSAndroid Build Coastguard Worker return; 147*90c8c64dSAndroid Build Coastguard Worker } 148*90c8c64dSAndroid Build Coastguard Worker for (File file : files) { 149*90c8c64dSAndroid Build Coastguard Worker // Trim preceding "./" from path. 150*90c8c64dSAndroid Build Coastguard Worker String path = file.getPath().substring(2); 151*90c8c64dSAndroid Build Coastguard Worker 152*90c8c64dSAndroid Build Coastguard Worker // Skip nonexistent files/diretories, e.g. broken symlinks. 153*90c8c64dSAndroid Build Coastguard Worker if (!file.exists()) { 154*90c8c64dSAndroid Build Coastguard Worker Log.debug("Skipped nonexistent: " + path); 155*90c8c64dSAndroid Build Coastguard Worker continue; 156*90c8c64dSAndroid Build Coastguard Worker } 157*90c8c64dSAndroid Build Coastguard Worker 158*90c8c64dSAndroid Build Coastguard Worker if (Files.isSymbolicLink(file.toPath())) { 159*90c8c64dSAndroid Build Coastguard Worker Path target = Files.readSymbolicLink(file.toPath()).normalize(); 160*90c8c64dSAndroid Build Coastguard Worker if (target.startsWith("") || target.startsWith(".") 161*90c8c64dSAndroid Build Coastguard Worker || target.startsWith("..")) { 162*90c8c64dSAndroid Build Coastguard Worker // Don't recurse symbolic link that targets to parent 163*90c8c64dSAndroid Build Coastguard Worker // or current directory. 164*90c8c64dSAndroid Build Coastguard Worker Log.debug("Skipped: " + path); 165*90c8c64dSAndroid Build Coastguard Worker continue; 166*90c8c64dSAndroid Build Coastguard Worker } 167*90c8c64dSAndroid Build Coastguard Worker } 168*90c8c64dSAndroid Build Coastguard Worker 169*90c8c64dSAndroid Build Coastguard Worker if (file.isDirectory()) { 170*90c8c64dSAndroid Build Coastguard Worker // Traverse nested directories. 171*90c8c64dSAndroid Build Coastguard Worker if (excludes.exclude(path)) { 172*90c8c64dSAndroid Build Coastguard Worker // Don't recurse into excluded dirs. 173*90c8c64dSAndroid Build Coastguard Worker Log.debug("Excluding: " + path); 174*90c8c64dSAndroid Build Coastguard Worker excludedDirs.add(file); 175*90c8c64dSAndroid Build Coastguard Worker } else { 176*90c8c64dSAndroid Build Coastguard Worker traverse(file, sourceRoots, jarFiles, excludedDirs, 177*90c8c64dSAndroid Build Coastguard Worker excludes); 178*90c8c64dSAndroid Build Coastguard Worker } 179*90c8c64dSAndroid Build Coastguard Worker } else if (path.endsWith(".java")) { 180*90c8c64dSAndroid Build Coastguard Worker // Keep track of source roots for .java files. 181*90c8c64dSAndroid Build Coastguard Worker // Do not check excludes in this branch. 182*90c8c64dSAndroid Build Coastguard Worker if (firstJavaFile) { 183*90c8c64dSAndroid Build Coastguard Worker // Only parse one .java file per directory. 184*90c8c64dSAndroid Build Coastguard Worker firstJavaFile = false; 185*90c8c64dSAndroid Build Coastguard Worker 186*90c8c64dSAndroid Build Coastguard Worker File sourceRoot = rootOf(file); 187*90c8c64dSAndroid Build Coastguard Worker if (sourceRoot != null) { 188*90c8c64dSAndroid Build Coastguard Worker sourceRoots.add(sourceRoot); 189*90c8c64dSAndroid Build Coastguard Worker } 190*90c8c64dSAndroid Build Coastguard Worker } 191*90c8c64dSAndroid Build Coastguard Worker } else if (path.endsWith(".jar")) { 192*90c8c64dSAndroid Build Coastguard Worker // Keep track of .jar files. 193*90c8c64dSAndroid Build Coastguard Worker if (excludes.exclude(path)) { 194*90c8c64dSAndroid Build Coastguard Worker Log.debug("Skipped: " + file); 195*90c8c64dSAndroid Build Coastguard Worker } else { 196*90c8c64dSAndroid Build Coastguard Worker jarFiles.add(file); 197*90c8c64dSAndroid Build Coastguard Worker } 198*90c8c64dSAndroid Build Coastguard Worker } 199*90c8c64dSAndroid Build Coastguard Worker } 200*90c8c64dSAndroid Build Coastguard Worker } 201*90c8c64dSAndroid Build Coastguard Worker 202*90c8c64dSAndroid Build Coastguard Worker /** 203*90c8c64dSAndroid Build Coastguard Worker * Determines the source root for a given .java file. Returns null 204*90c8c64dSAndroid Build Coastguard Worker * if the file doesn't have a package or if the file isn't in the 205*90c8c64dSAndroid Build Coastguard Worker * correct directory structure. 206*90c8c64dSAndroid Build Coastguard Worker */ rootOf(File javaFile)207*90c8c64dSAndroid Build Coastguard Worker private static File rootOf(File javaFile) throws IOException { 208*90c8c64dSAndroid Build Coastguard Worker String packageName = parsePackageName(javaFile); 209*90c8c64dSAndroid Build Coastguard Worker if (packageName == null) { 210*90c8c64dSAndroid Build Coastguard Worker // No package. 211*90c8c64dSAndroid Build Coastguard Worker // TODO: Treat this as a source root? 212*90c8c64dSAndroid Build Coastguard Worker return null; 213*90c8c64dSAndroid Build Coastguard Worker } 214*90c8c64dSAndroid Build Coastguard Worker 215*90c8c64dSAndroid Build Coastguard Worker String packagePath = packageName.replace('.', File.separatorChar); 216*90c8c64dSAndroid Build Coastguard Worker File parent = javaFile.getParentFile(); 217*90c8c64dSAndroid Build Coastguard Worker String parentPath = parent.getPath(); 218*90c8c64dSAndroid Build Coastguard Worker if (!parentPath.endsWith(packagePath)) { 219*90c8c64dSAndroid Build Coastguard Worker // Bad dir structure. 220*90c8c64dSAndroid Build Coastguard Worker return null; 221*90c8c64dSAndroid Build Coastguard Worker } 222*90c8c64dSAndroid Build Coastguard Worker 223*90c8c64dSAndroid Build Coastguard Worker return new File(parentPath.substring( 224*90c8c64dSAndroid Build Coastguard Worker 0, parentPath.length() - packagePath.length())); 225*90c8c64dSAndroid Build Coastguard Worker } 226*90c8c64dSAndroid Build Coastguard Worker 227*90c8c64dSAndroid Build Coastguard Worker /** 228*90c8c64dSAndroid Build Coastguard Worker * Reads a Java file and parses out the package name. Returns null if none 229*90c8c64dSAndroid Build Coastguard Worker * found. 230*90c8c64dSAndroid Build Coastguard Worker */ parsePackageName(File file)231*90c8c64dSAndroid Build Coastguard Worker private static String parsePackageName(File file) throws IOException { 232*90c8c64dSAndroid Build Coastguard Worker try (BufferedReader in = new BufferedReader(new FileReader(file))) { 233*90c8c64dSAndroid Build Coastguard Worker String line; 234*90c8c64dSAndroid Build Coastguard Worker while ((line = in.readLine()) != null) { 235*90c8c64dSAndroid Build Coastguard Worker String trimmed = line.trim(); 236*90c8c64dSAndroid Build Coastguard Worker if (trimmed.startsWith("package")) { 237*90c8c64dSAndroid Build Coastguard Worker // TODO: Make this more robust. 238*90c8c64dSAndroid Build Coastguard Worker // Assumes there's only once space after "package" and the 239*90c8c64dSAndroid Build Coastguard Worker // line ends in a ";". 240*90c8c64dSAndroid Build Coastguard Worker return trimmed.substring(8, trimmed.length() - 1); 241*90c8c64dSAndroid Build Coastguard Worker } 242*90c8c64dSAndroid Build Coastguard Worker } 243*90c8c64dSAndroid Build Coastguard Worker 244*90c8c64dSAndroid Build Coastguard Worker return null; 245*90c8c64dSAndroid Build Coastguard Worker } 246*90c8c64dSAndroid Build Coastguard Worker } 247*90c8c64dSAndroid Build Coastguard Worker 248*90c8c64dSAndroid Build Coastguard Worker /** 249*90c8c64dSAndroid Build Coastguard Worker * Picks out excluded directories that are under source roots. 250*90c8c64dSAndroid Build Coastguard Worker */ excludesUnderSourceRoots()251*90c8c64dSAndroid Build Coastguard Worker public SortedSet<File> excludesUnderSourceRoots() { 252*90c8c64dSAndroid Build Coastguard Worker // TODO: Refactor this to share the similar logic in 253*90c8c64dSAndroid Build Coastguard Worker // Eclipse.constructExcluding(). 254*90c8c64dSAndroid Build Coastguard Worker SortedSet<File> picked = new TreeSet<File>(); 255*90c8c64dSAndroid Build Coastguard Worker for (File sourceRoot : sourceRoots) { 256*90c8c64dSAndroid Build Coastguard Worker String sourcePath = sourceRoot.getPath() + "/"; 257*90c8c64dSAndroid Build Coastguard Worker SortedSet<File> tailSet = excludedDirs.tailSet(sourceRoot); 258*90c8c64dSAndroid Build Coastguard Worker for (File file : tailSet) { 259*90c8c64dSAndroid Build Coastguard Worker if (file.getPath().startsWith(sourcePath)) { 260*90c8c64dSAndroid Build Coastguard Worker picked.add(file); 261*90c8c64dSAndroid Build Coastguard Worker } else { 262*90c8c64dSAndroid Build Coastguard Worker break; 263*90c8c64dSAndroid Build Coastguard Worker } 264*90c8c64dSAndroid Build Coastguard Worker } 265*90c8c64dSAndroid Build Coastguard Worker } 266*90c8c64dSAndroid Build Coastguard Worker return picked; 267*90c8c64dSAndroid Build Coastguard Worker } 268*90c8c64dSAndroid Build Coastguard Worker 269*90c8c64dSAndroid Build Coastguard Worker /** 270*90c8c64dSAndroid Build Coastguard Worker * Reads a list of regular expressions from a file, one per line, and adds 271*90c8c64dSAndroid Build Coastguard Worker * the compiled patterns to the given collection. Ignores lines starting 272*90c8c64dSAndroid Build Coastguard Worker * with '#'. 273*90c8c64dSAndroid Build Coastguard Worker * 274*90c8c64dSAndroid Build Coastguard Worker * @param file containing regular expressions, one per line 275*90c8c64dSAndroid Build Coastguard Worker * @param patterns collection to add compiled patterns from file to 276*90c8c64dSAndroid Build Coastguard Worker */ parseFile(File file, Collection<Pattern> patterns)277*90c8c64dSAndroid Build Coastguard Worker public static void parseFile(File file, Collection<Pattern> patterns) 278*90c8c64dSAndroid Build Coastguard Worker throws IOException { 279*90c8c64dSAndroid Build Coastguard Worker try (BufferedReader in = new BufferedReader(new FileReader(file))) { 280*90c8c64dSAndroid Build Coastguard Worker String line; 281*90c8c64dSAndroid Build Coastguard Worker while ((line = in.readLine()) != null) { 282*90c8c64dSAndroid Build Coastguard Worker String trimmed = line.trim(); 283*90c8c64dSAndroid Build Coastguard Worker if (trimmed.length() > 0 && !trimmed.startsWith("#")) { 284*90c8c64dSAndroid Build Coastguard Worker patterns.add(Pattern.compile(trimmed)); 285*90c8c64dSAndroid Build Coastguard Worker } 286*90c8c64dSAndroid Build Coastguard Worker } 287*90c8c64dSAndroid Build Coastguard Worker } 288*90c8c64dSAndroid Build Coastguard Worker } 289*90c8c64dSAndroid Build Coastguard Worker } 290