blob: 5614add0133b81c41ca5462fb1883a6aa4779ee8 [file] [log] [blame]
/*
* Copyright 2018 The Bazel Authors. 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 io.bazel.kotlin.builder.tasks.jvm
import com.google.devtools.build.lib.view.proto.Deps
import java.nio.file.Path
import java.nio.file.Paths
import java.util.*
import java.util.function.Predicate
import java.util.stream.Stream
class JdepsParser private constructor(private val filename: String, private val isImplicit: Predicate<String>) {
private val packageSuffix: String = " ($filename)"
private val depMap = HashMap<String, Deps.Dependency.Builder>()
private val packages = HashSet<String>()
private var mode = Mode.COLLECT_DEPS
private fun consumeJarLine(classJarPath: String, kind: Deps.Dependency.Kind) {
val path = Paths.get(classJarPath)
// ignore absolute files, -- jdk jar paths etc.
// only process jar files
if (!(path.isAbsolute || !classJarPath.endsWith(".jar"))) {
val entry = depMap.computeIfAbsent(classJarPath) {
val depBuilder = Deps.Dependency.newBuilder()
depBuilder.path = classJarPath
depBuilder.kind = kind
if (isImplicit.test(classJarPath)) {
depBuilder.kind = Deps.Dependency.Kind.IMPLICIT
}
depBuilder
}
// don't flip an implicit dep.
if (entry.kind != Deps.Dependency.Kind.IMPLICIT) {
entry.kind = kind
}
}
}
private enum class Mode {
COLLECT_DEPS,
DETERMINE_JDK,
COLLECT_PACKAGES_JDK8,
COLLECT_PACKAGES_JDK9
}
// maybe simplify this by tokenizing on whitespace and arrows.
private fun processLine(line: String) {
val trimmedLine = line.trim { it <= ' ' }
when (mode) {
Mode.COLLECT_DEPS -> if (!line.startsWith(" ")) {
val parts = line.split(" -> ".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
if (parts.size == 2) {
if (parts[0] != filename) {
throw RuntimeException("should only get dependencies for dep: $filename")
}
consumeJarLine(parts[1], Deps.Dependency.Kind.EXPLICIT)
}
} else {
mode = Mode.DETERMINE_JDK
processLine(line)
}
Mode.DETERMINE_JDK -> {
mode = Mode.COLLECT_PACKAGES_JDK8
if (!line.endsWith(packageSuffix)) {
mode = Mode.COLLECT_PACKAGES_JDK9
}
processLine(line)
}
Mode.COLLECT_PACKAGES_JDK8 -> when {
trimmedLine.endsWith(packageSuffix) -> packages.add(trimmedLine.substring(0, trimmedLine.length - packageSuffix.length))
trimmedLine.startsWith("-> ") -> {
// ignore package detail lines, in the jdk8 format these start with arrows.
}
else -> throw RuntimeException("unexpected line while collecting packages: $line")
}
Mode.COLLECT_PACKAGES_JDK9 -> {
val pkg = trimmedLine.split("\\s+".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
packages.add(pkg[0])
}
}
}
companion object {
fun pathSuffixMatchingPredicate(directory: Path, vararg jars: String): Predicate<String> {
val suffixes = jars.map { directory.resolve(it).toString() }
return Predicate { jar ->
for (implicitJarsEnding in suffixes) {
if (jar.endsWith(implicitJarsEnding)) {
return@Predicate true
}
}
false
}
}
fun parse(
label: String,
classJar: String,
classPath: String,
jdepLines: List<String>,
isImplicit: Predicate<String>
): Deps.Dependencies {
val filename = Paths.get(classJar).fileName.toString()
val jdepsParser = JdepsParser(filename, isImplicit)
Stream.of(*classPath.split(":".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray())
.forEach { x -> jdepsParser.consumeJarLine(x, Deps.Dependency.Kind.UNUSED) }
jdepLines.forEach { jdepsParser.processLine(it) }
val rootBuilder = Deps.Dependencies.newBuilder()
rootBuilder.success = false
rootBuilder.ruleLabel = label
rootBuilder.addAllContainedPackage(jdepsParser.packages)
jdepsParser.depMap.values.forEach { b -> rootBuilder.addDependency(b.build()) }
rootBuilder.success = true
return rootBuilder.build()
}
}
}