1 /* 2 * Copyright 2016 Google Inc. All Rights Reserved. 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.google.turbine.deps; 18 19 import com.google.common.base.Predicates; 20 import com.google.common.collect.Collections2; 21 import com.google.common.collect.ImmutableList; 22 import com.google.common.collect.ImmutableSet; 23 import com.google.turbine.binder.Binder.BindingResult; 24 import com.google.turbine.binder.ClassPath; 25 import com.google.turbine.binder.bound.EnumConstantValue; 26 import com.google.turbine.binder.bound.TurbineAnnotationValue; 27 import com.google.turbine.binder.bound.TurbineClassValue; 28 import com.google.turbine.binder.bound.TypeBoundClass; 29 import com.google.turbine.binder.bound.TypeBoundClass.FieldInfo; 30 import com.google.turbine.binder.bound.TypeBoundClass.MethodInfo; 31 import com.google.turbine.binder.bytecode.BytecodeBoundClass; 32 import com.google.turbine.binder.env.CompoundEnv; 33 import com.google.turbine.binder.env.Env; 34 import com.google.turbine.binder.env.SimpleEnv; 35 import com.google.turbine.binder.sym.ClassSymbol; 36 import com.google.turbine.lower.Lower.Lowered; 37 import com.google.turbine.model.Const; 38 import com.google.turbine.proto.DepsProto; 39 import com.google.turbine.type.AnnoInfo; 40 import com.google.turbine.type.Type; 41 import java.io.BufferedInputStream; 42 import java.io.IOError; 43 import java.io.IOException; 44 import java.io.InputStream; 45 import java.nio.file.Files; 46 import java.nio.file.Paths; 47 import java.util.Collection; 48 import java.util.HashSet; 49 import java.util.LinkedHashSet; 50 import java.util.Optional; 51 import java.util.Set; 52 53 /** Support for Bazel jdeps dependency output. */ 54 public class Dependencies { 55 /** Creates a jdeps proto for the current compilation. */ collectDeps( Optional<String> targetLabel, ClassPath bootclasspath, BindingResult bound, Lowered lowered)56 public static DepsProto.Dependencies collectDeps( 57 Optional<String> targetLabel, ClassPath bootclasspath, BindingResult bound, Lowered lowered) { 58 DepsProto.Dependencies.Builder deps = DepsProto.Dependencies.newBuilder(); 59 Set<ClassSymbol> closure = superTypeClosure(bound, lowered); 60 addPackageInfos(closure, bound); 61 Set<String> jars = new LinkedHashSet<>(); 62 for (ClassSymbol sym : closure) { 63 BytecodeBoundClass info = bound.classPathEnv().get(sym); 64 if (info == null) { 65 // the symbol wasn't loaded from the classpath 66 continue; 67 } 68 String jarFile = info.jarFile(); 69 if (bootclasspath.env().get(sym) != null) { 70 // bootclasspath deps are not tracked 71 continue; 72 } 73 jars.add(jarFile); 74 } 75 for (String jarFile : jars) { 76 deps.addDependency( 77 DepsProto.Dependency.newBuilder() 78 .setPath(jarFile) 79 .setKind(DepsProto.Dependency.Kind.EXPLICIT)); 80 } 81 // we don't current write jdeps for failed compilations 82 deps.setSuccess(true); 83 if (targetLabel.isPresent()) { 84 deps.setRuleLabel(targetLabel.get()); 85 } 86 return deps.build(); 87 } 88 superTypeClosure(BindingResult bound, Lowered lowered)89 private static Set<ClassSymbol> superTypeClosure(BindingResult bound, Lowered lowered) { 90 Env<ClassSymbol, TypeBoundClass> env = 91 CompoundEnv.<ClassSymbol, TypeBoundClass>of(new SimpleEnv<>(bound.units())) 92 .append(bound.classPathEnv()); 93 Set<ClassSymbol> closure = new LinkedHashSet<>(lowered.symbols()); 94 for (ClassSymbol sym : lowered.symbols()) { 95 TypeBoundClass info = env.get(sym); 96 addAnnotations(closure, info.annotations()); 97 for (MethodInfo method : info.methods()) { 98 addAnnotations(closure, method.annotations()); 99 } 100 for (FieldInfo field : info.fields()) { 101 addAnnotations(closure, field.annotations()); 102 } 103 addSuperTypes(closure, env, info); 104 } 105 return closure; 106 } 107 addAnnotations( Set<ClassSymbol> closure, ImmutableList<AnnoInfo> annotations)108 private static void addAnnotations( 109 Set<ClassSymbol> closure, ImmutableList<AnnoInfo> annotations) { 110 for (AnnoInfo annoInfo : annotations) { 111 addAnnotation(closure, annoInfo); 112 } 113 } 114 addAnnotation(Set<ClassSymbol> closure, AnnoInfo annoInfo)115 private static void addAnnotation(Set<ClassSymbol> closure, AnnoInfo annoInfo) { 116 closure.add(annoInfo.sym()); 117 for (Const c : annoInfo.values().values()) { 118 addConst(closure, c); 119 } 120 } 121 addConst(Set<ClassSymbol> closure, Const c)122 private static void addConst(Set<ClassSymbol> closure, Const c) { 123 switch (c.kind()) { 124 case ARRAY: 125 for (Const e : ((Const.ArrayInitValue) c).elements()) { 126 addConst(closure, e); 127 } 128 break; 129 case CLASS_LITERAL: 130 Type t = ((TurbineClassValue) c).type(); 131 if (t.tyKind() == Type.TyKind.CLASS_TY) { 132 closure.add(((Type.ClassTy) t).sym()); 133 } 134 break; 135 case ENUM_CONSTANT: 136 closure.add(((EnumConstantValue) c).sym().owner()); 137 break; 138 case ANNOTATION: 139 addAnnotation(closure, ((TurbineAnnotationValue) c).info()); 140 break; 141 case PRIMITIVE: 142 // continue below 143 } 144 } 145 addSuperTypes( Set<ClassSymbol> closure, Env<ClassSymbol, TypeBoundClass> env, ClassSymbol sym)146 private static void addSuperTypes( 147 Set<ClassSymbol> closure, Env<ClassSymbol, TypeBoundClass> env, ClassSymbol sym) { 148 if (!closure.add(sym)) { 149 return; 150 } 151 TypeBoundClass info = env.get(sym); 152 if (info == null) { 153 return; 154 } 155 addSuperTypes(closure, env, info); 156 } 157 addSuperTypes( Set<ClassSymbol> closure, Env<ClassSymbol, TypeBoundClass> env, TypeBoundClass info)158 private static void addSuperTypes( 159 Set<ClassSymbol> closure, Env<ClassSymbol, TypeBoundClass> env, TypeBoundClass info) { 160 if (info.superclass() != null) { 161 addSuperTypes(closure, env, info.superclass()); 162 } 163 for (ClassSymbol i : info.interfaces()) { 164 addSuperTypes(closure, env, i); 165 } 166 } 167 addPackageInfos(Set<ClassSymbol> closure, BindingResult bound)168 private static void addPackageInfos(Set<ClassSymbol> closure, BindingResult bound) { 169 Set<ClassSymbol> packages = new LinkedHashSet<>(); 170 for (ClassSymbol sym : closure) { 171 String packageName = sym.packageName(); 172 if (packageName.isEmpty()) { 173 continue; 174 } 175 packages.add(new ClassSymbol(packageName + "/package-info")); 176 } 177 for (ClassSymbol pkg : packages) { 178 if (bound.classPathEnv().get(pkg) != null) { 179 closure.add(pkg); 180 } 181 } 182 } 183 184 /** 185 * Filters a transitive classpath to contain only the entries for direct dependencies, and the 186 * types needed to compile those direct deps as reported by jdeps. 187 * 188 * <p>If no direct dependency information is available the full transitive classpath is returned. 189 */ reduceClasspath( ImmutableList<String> transitiveClasspath, ImmutableSet<String> directJars, ImmutableList<String> depsArtifacts)190 public static Collection<String> reduceClasspath( 191 ImmutableList<String> transitiveClasspath, 192 ImmutableSet<String> directJars, 193 ImmutableList<String> depsArtifacts) { 194 if (directJars.isEmpty()) { 195 // the compilation doesn't support strict deps (e.g. proto libraries) 196 // TODO(cushon): make this a usage error 197 return transitiveClasspath; 198 } 199 Set<String> reduced = new HashSet<>(directJars); 200 for (String path : depsArtifacts) { 201 DepsProto.Dependencies.Builder deps = DepsProto.Dependencies.newBuilder(); 202 try (InputStream is = new BufferedInputStream(Files.newInputStream(Paths.get(path)))) { 203 deps.mergeFrom(is); 204 } catch (IOException e) { 205 throw new IOError(e); 206 } 207 for (DepsProto.Dependency dep : deps.build().getDependencyList()) { 208 switch (dep.getKind()) { 209 case EXPLICIT: 210 case IMPLICIT: 211 reduced.add(dep.getPath()); 212 break; 213 case INCOMPLETE: 214 case UNUSED: 215 break; 216 } 217 } 218 } 219 // preserve the order of entries in the transitive classpath 220 return Collections2.filter(transitiveClasspath, Predicates.in(reduced)); 221 } 222 } 223