blob: a6ac05f7f635496623837b96dfe70764b7e7c6d8 [file] [log] [blame]
/*
* Copyright 2016 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.turbine.deps;
import com.google.common.base.Optional;
import com.google.common.base.Predicates;
import com.google.common.collect.Collections2;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.turbine.binder.Binder.BindingResult;
import com.google.turbine.binder.bound.TypeBoundClass;
import com.google.turbine.binder.bytecode.BytecodeBoundClass;
import com.google.turbine.binder.env.CompoundEnv;
import com.google.turbine.binder.env.Env;
import com.google.turbine.binder.env.SimpleEnv;
import com.google.turbine.binder.sym.ClassSymbol;
import com.google.turbine.lower.Lower.Lowered;
import com.google.turbine.proto.DepsProto;
import java.io.BufferedInputStream;
import java.io.IOError;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Set;
/** Support for Bazel jdeps dependency output. */
public class Dependencies {
/** Creates a jdeps proto for the current compilation. */
public static DepsProto.Dependencies collectDeps(
Optional<String> targetLabel,
ImmutableSet<String> bootClassPath,
BindingResult bound,
Lowered lowered) {
DepsProto.Dependencies.Builder deps = DepsProto.Dependencies.newBuilder();
Set<ClassSymbol> closure = superTypeClosure(bound, lowered);
addPackageInfos(closure, bound);
Set<String> jars = new LinkedHashSet<>();
for (ClassSymbol sym : closure) {
BytecodeBoundClass info = bound.classPathEnv().get(sym);
if (info == null) {
// the symbol wasn't loaded from the classpath
continue;
}
String jarFile = info.jarFile();
if (bootClassPath.contains(jarFile)) {
// bootclasspath deps are not tracked
continue;
}
jars.add(jarFile);
}
for (String jarFile : jars) {
deps.addDependency(
DepsProto.Dependency.newBuilder()
.setPath(jarFile)
.setKind(DepsProto.Dependency.Kind.EXPLICIT));
}
// we don't current write jdeps for failed compilations
deps.setSuccess(true);
if (targetLabel.isPresent()) {
deps.setRuleLabel(targetLabel.get());
}
return deps.build();
}
private static Set<ClassSymbol> superTypeClosure(BindingResult bound, Lowered lowered) {
Env<ClassSymbol, TypeBoundClass> env =
CompoundEnv.<ClassSymbol, TypeBoundClass>of(new SimpleEnv<>(bound.units()))
.append(bound.classPathEnv());
Set<ClassSymbol> closure = new LinkedHashSet<>();
for (ClassSymbol sym : lowered.symbols()) {
addSuperTypes(closure, env, sym);
}
return closure;
}
private static void addSuperTypes(
Set<ClassSymbol> closure, Env<ClassSymbol, TypeBoundClass> env, ClassSymbol sym) {
if (!closure.add(sym)) {
return;
}
TypeBoundClass info = env.get(sym);
if (info == null) {
return;
}
if (info.superclass() != null) {
addSuperTypes(closure, env, info.superclass());
}
for (ClassSymbol i : info.interfaces()) {
addSuperTypes(closure, env, i);
}
}
private static void addPackageInfos(Set<ClassSymbol> closure, BindingResult bound) {
Set<ClassSymbol> packages = new LinkedHashSet<>();
for (ClassSymbol sym : closure) {
int idx = sym.binaryName().lastIndexOf('/');
if (idx == -1) {
continue;
}
packages.add(new ClassSymbol(sym.binaryName().substring(0, idx) + "/package-info"));
}
for (ClassSymbol pkg : packages) {
if (bound.classPathEnv().get(pkg) != null) {
closure.add(pkg);
}
}
}
/**
* Filters a transitive classpath to contain only the entries for direct dependencies, and the
* types needed to compile those direct deps as reported by jdeps.
*
* <p>If no direct dependency information is available the full transitive classpath is returned.
*/
public static Collection<String> reduceClasspath(
ImmutableList<String> transitiveClasspath,
ImmutableMap<String, String> directJarsToTargets,
ImmutableList<String> depsArtifacts) {
if (directJarsToTargets.isEmpty()) {
// the compilation doesn't support strict deps (e.g. proto libraries)
return transitiveClasspath;
}
Set<String> reduced = new HashSet<>(directJarsToTargets.keySet());
for (String path : depsArtifacts) {
DepsProto.Dependencies.Builder deps = DepsProto.Dependencies.newBuilder();
try (InputStream is = new BufferedInputStream(Files.newInputStream(Paths.get(path)))) {
deps.mergeFrom(is);
} catch (IOException e) {
throw new IOError(e);
}
for (DepsProto.Dependency dep : deps.build().getDependencyList()) {
switch (dep.getKind()) {
case EXPLICIT:
case IMPLICIT:
reduced.add(dep.getPath());
break;
case INCOMPLETE:
case UNUSED:
break;
default:
throw new AssertionError(dep.getKind());
}
}
}
// preserve the order of entries in the transitive classpath
return Collections2.filter(transitiveClasspath, Predicates.in(reduced));
}
}