| /* |
| * 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 |
| |
| internal 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) |
| classPath.split(":".toRegex()).dropLastWhile { it.isEmpty() }.stream() |
| .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() |
| } |
| } |
| } |