Skip to content
This repository has been archived by the owner on Aug 5, 2024. It is now read-only.

Commit

Permalink
[feature] Add Android resources to the project model
Browse files Browse the repository at this point in the history
  • Loading branch information
gottagofaster236 authored and Space Team committed Jan 29, 2024
1 parent 4fbfc17 commit 33760f0
Show file tree
Hide file tree
Showing 17 changed files with 243 additions and 86 deletions.
32 changes: 26 additions & 6 deletions aspects/rules/android/android_info.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,41 @@ load("//aspects:utils/utils.bzl", "create_proto", "create_struct", "file_locatio

ANDROID_SDK_TOOLCHAIN_TYPE = "@bazel_tools//tools/android:sdk_toolchain_type"

def extract_android_sdk_info(target, ctx, dep_targets, **kwargs):
def extract_android_info(target, ctx, dep_targets, **kwargs):
if ANDROID_SDK_TOOLCHAIN_TYPE not in ctx.toolchains:
return None, None
android_sdk_toolchain = ctx.toolchains[ANDROID_SDK_TOOLCHAIN_TYPE]

if android_sdk_toolchain == None:
return None, None
android_sdk_info = android_sdk_toolchain.android_sdk_info

android_jar = android_sdk_info.android_jar
android_jar = file_location(android_sdk_info.android_jar)
if android_jar == None:
return None, None

android_sdk_info_proto = create_struct(
android_jar = file_location(android_jar),
manifest = None
if AndroidIdeInfo in target:
android_ide_info = target[AndroidIdeInfo]
manifest = file_location(target[AndroidIdeInfo].manifest)

resources = []
if hasattr(ctx.rule.attr, "resource_files"):
for resource in ctx.rule.attr.resource_files:
for resource_file in resource.files.to_list():
resources.append(file_location(resource_file))

kotlin_target_id = None
if ctx.rule.kind == "android_library" and str(target.label).endswith("_base") and not ctx.rule.attr.srcs:
# This is a hack to detect the android_library target produced by kt_android_library.
# It creates an android_library target that ends with _base and a kt_jvm_library target that ends with _kt.
# Read more here: https://github.com/bazelbuild/rules_kotlin/blob/master/kotlin/internal/jvm/android.bzl
kotlin_target_id = str(target.label)[:-5] + "_kt"

android_target_info_proto = create_struct(
android_jar = android_jar,
manifest = manifest,
resources = resources,
kotlin_target_id = kotlin_target_id,
)

return create_proto(target, ctx, android_sdk_info_proto, "android_sdk_info"), None
return create_proto(target, ctx, android_target_info_proto, "android_target_info"), None
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ object BazelFlag {
@JvmStatic fun testOutputAll(): String =
arg("test_output", "all")

@JvmStatic fun experimentalGoogleLegacyApi(): String =
flag("experimental_google_legacy_api")

private fun arg(name: String, value: String) =
String.format("--%s=%s", name, value)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1021,14 +1021,15 @@ object BazelBspSampleRepoTest : BazelBspTestBaseScenario() {
}

override fun expectedWorkspaceBuildTargetsResult(): WorkspaceBuildTargetsResult {
val architecturePart = if (System.getProperty("os.arch") == "aarch64") "_aarch64" else ""
val javaHome = "file://\$BAZEL_OUTPUT_BASE_PATH/external/remotejdk11_\$OS${architecturePart}/"
val jvmBuildTarget = JvmBuildTarget().also {
val architecturePart = if (System.getProperty("os.arch") == "aarch64") "_aarch64" else ""
it.javaHome = "file://\$BAZEL_OUTPUT_BASE_PATH/external/remotejdk11_\$OS${architecturePart}/"
it.javaHome = javaHome
it.javaVersion = "11"
}

val jvmBuildTargetWithFlag = JvmBuildTarget().also {
it.javaHome = "file://\$BAZEL_OUTPUT_BASE_PATH/external/remotejdk11_\$OS/"
it.javaHome = javaHome
it.javaVersion = "8"
}

Expand Down
19 changes: 19 additions & 0 deletions protocol/src/main/kotlin/org/jetbrains/bsp/AndroidBuildTarget.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package org.jetbrains.bsp

import ch.epfl.scala.bsp4j.JvmBuildTarget
import com.google.gson.annotations.JsonAdapter
import org.eclipse.lsp4j.jsonrpc.json.adapters.EnumTypeAdapter
import java.net.URI

@JsonAdapter(EnumTypeAdapter.Factory::class)
public enum class AndroidTargetType(public val value: Int) {
APP(1),
LIBRARY(2),
TEST(3),
}

public data class AndroidBuildTarget(
val androidJar: URI,
val androidTargetType: AndroidTargetType,
var jvmBuildTarget: JvmBuildTarget? = null,
)
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import ch.epfl.scala.bsp4j.PythonBuildTarget
import ch.epfl.scala.bsp4j.ScalaBuildTarget
import com.google.gson.Gson
import com.google.gson.JsonObject
import org.jetbrains.bsp.AndroidBuildTarget
import org.jetbrains.bsp.KotlinBuildTarget

private inline fun <reified Data> extractData(target: BuildTarget, kind: String): Data? =
Expand All @@ -24,10 +25,14 @@ public fun extractPythonBuildTarget(target: BuildTarget): PythonBuildTarget? =
public fun extractScalaBuildTarget(target: BuildTarget): ScalaBuildTarget? =
extractData(target, BuildTargetDataKind.SCALA)

public fun extractAndroidBuildTarget(target: BuildTarget): AndroidBuildTarget? =
extractData(target, "android")

public fun extractKotlinBuildTarget(target: BuildTarget): KotlinBuildTarget? =
extractData(target, "kotlin")

public fun extractJvmBuildTarget(target: BuildTarget): JvmBuildTarget? =
extractData(target, BuildTargetDataKind.JVM)
?: extractAndroidBuildTarget(target)?.jvmBuildTarget
?: extractKotlinBuildTarget(target)?.jvmBuildTarget
?: extractScalaBuildTarget(target)?.jvmBuildTarget
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import org.jetbrains.bsp.bazel.bazelrunner.params.BazelFlag.aspect
import org.jetbrains.bsp.bazel.bazelrunner.params.BazelFlag.buildManualTests
import org.jetbrains.bsp.bazel.bazelrunner.params.BazelFlag.color
import org.jetbrains.bsp.bazel.bazelrunner.params.BazelFlag.curses
import org.jetbrains.bsp.bazel.bazelrunner.params.BazelFlag.experimentalGoogleLegacyApi
import org.jetbrains.bsp.bazel.bazelrunner.params.BazelFlag.keepGoing
import org.jetbrains.bsp.bazel.bazelrunner.params.BazelFlag.outputGroups
import org.jetbrains.bsp.bazel.bazelrunner.params.BazelFlag.repositoryOverride
Expand Down Expand Up @@ -62,7 +63,8 @@ class BazelBspAspectsManager(
keepGoing(),
color(true),
buildManualTests(),
curses(false)
curses(false),
experimentalGoogleLegacyApi(),
),
null,
// Setting `CARGO_BAZEL_REPIN=1` updates `cargo_lockfile`
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
package org.jetbrains.bsp.bazel.server.bsp.managers

import java.nio.file.Paths
import java.util.Properties
import kotlin.io.path.writeText
import org.apache.velocity.app.VelocityEngine
import org.jetbrains.bsp.bazel.bazelrunner.BazelRelease
import org.jetbrains.bsp.bazel.commons.Constants
import org.jetbrains.bsp.bazel.server.bsp.utils.InternalAspectsResolver
import java.nio.file.Paths
import java.util.Properties
import kotlin.io.path.writeText

enum class Language(private val fileName: String, val ruleNames: List<String>, val functions: List<String>, val isTemplate: Boolean) {
Java("//aspects:rules/java/java_info.bzl", listOf(), listOf("extract_java_toolchain", "extract_java_runtime"), false),
Expand All @@ -16,7 +16,7 @@ enum class Language(private val fileName: String, val ruleNames: List<String>, v
Cpp("//aspects:rules/cpp/cpp_info.bzl", listOf("rules_cc"), listOf("extract_cpp_info"), false),
Kotlin("//aspects:rules/kt/kt_info.bzl", listOf("io_bazel_rules_kotlin", "rules_kotlin"), listOf("extract_kotlin_info"), true),
Rust("//aspects:rules/rust/rust_info.bzl", listOf("rules_rust"), listOf("extract_rust_crate_info"), false),
Android("//aspects:rules/android/android_info.bzl", listOf(), listOf("extract_android_sdk_info"), false);
Android("//aspects:rules/android/android_info.bzl", listOf(), listOf("extract_android_info"), false);

fun toLoadStatement(): String =
this.functions.joinToString(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import org.jetbrains.bsp.bazel.server.sync.ProjectStorage
import org.jetbrains.bsp.bazel.server.sync.TargetInfoReader
import org.jetbrains.bsp.bazel.server.sync.TargetKindResolver
import org.jetbrains.bsp.bazel.server.sync.languages.LanguagePluginsService
import org.jetbrains.bsp.bazel.server.sync.languages.android.AndroidLanguagePlugin
import org.jetbrains.bsp.bazel.server.sync.languages.cpp.CppLanguagePlugin
import org.jetbrains.bsp.bazel.server.sync.languages.java.JavaLanguagePlugin
import org.jetbrains.bsp.bazel.server.sync.languages.java.JdkResolver
Expand Down Expand Up @@ -72,8 +73,16 @@ class ServerContainer internal constructor(
val thriftLanguagePlugin = ThriftLanguagePlugin(bazelPathsResolver)
val pythonLanguagePlugin = PythonLanguagePlugin(bazelPathsResolver)
val rustLanguagePlugin = RustLanguagePlugin(bazelPathsResolver)
val androidLanguagePlugin = AndroidLanguagePlugin(javaLanguagePlugin, bazelPathsResolver)
val languagePluginsService = LanguagePluginsService(
scalaLanguagePlugin, javaLanguagePlugin, cppLanguagePlugin, kotlinLanguagePlugin, thriftLanguagePlugin, pythonLanguagePlugin, rustLanguagePlugin
scalaLanguagePlugin,
javaLanguagePlugin,
cppLanguagePlugin,
kotlinLanguagePlugin,
thriftLanguagePlugin,
pythonLanguagePlugin,
rustLanguagePlugin,
androidLanguagePlugin
)
val targetKindResolver = TargetKindResolver()
val bazelProjectMapper =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,13 @@ package org.jetbrains.bsp.bazel.server.sync

import com.google.common.hash.Hashing
import com.google.devtools.build.lib.view.proto.Deps
import java.net.URI
import java.nio.charset.StandardCharsets
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths
import kotlin.io.path.exists
import kotlin.io.path.name
import kotlin.io.path.notExists
import kotlinx.coroutines.flow.asFlow
import kotlinx.coroutines.flow.filterNot
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.toList
import kotlinx.coroutines.runBlocking
import org.jetbrains.bsp.bazel.bazelrunner.BazelInfo
import org.jetbrains.bsp.bazel.info.BspTargetInfo.Dependency
import org.jetbrains.bsp.bazel.info.BspTargetInfo.FileLocation
import org.jetbrains.bsp.bazel.info.BspTargetInfo.TargetInfo
import org.jetbrains.bsp.bazel.logger.BspClientLogger
Expand All @@ -31,6 +24,14 @@ import org.jetbrains.bsp.bazel.server.sync.model.Project
import org.jetbrains.bsp.bazel.server.sync.model.SourceSet
import org.jetbrains.bsp.bazel.server.sync.model.Tag
import org.jetbrains.bsp.bazel.workspacecontext.WorkspaceContext
import java.net.URI
import java.nio.charset.StandardCharsets
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths
import kotlin.io.path.exists
import kotlin.io.path.name
import kotlin.io.path.notExists

class BazelProjectMapper(
private val languagePluginsService: LanguagePluginsService,
Expand Down Expand Up @@ -70,15 +71,11 @@ class BazelProjectMapper(
val scalaLibrariesMapper = measure("Create scala libraries") {
calculateScalaLibrariesMapper(targetsToImport)
}
val androidSdkLibrariesMapper = measure("Create Android SDK libraries") {
calculateAndroidSdkLibrariesMapper(targetsToImport)
}
val librariesFromDeps = measure("Merge libraries from deps") {
concatenateMaps(
annotationProcessorLibraries,
kotlinStdlibsMapper,
scalaLibrariesMapper,
androidSdkLibrariesMapper,
)
}
val librariesFromDepsAndTargets = measure("Libraries from targets and deps") {
Expand Down Expand Up @@ -197,32 +194,6 @@ class BazelProjectMapper(
?.compilerJars
?.toSet().orEmpty()

private fun calculateAndroidSdkLibrariesMapper(targetsToImport: Sequence<TargetInfo>): Map<String, List<Library>> {
val projectLevelAndroidSdkLibraries = calculateProjectLevelAndroidSdkLibraries(targetsToImport) ?: return emptyMap()
val androidTargetsIds = targetsToImport.filter { it.hasAndroidSdkInfo() }.map { it.id }
return androidTargetsIds.associateWith { listOf(projectLevelAndroidSdkLibraries) }
}

private fun calculateProjectLevelAndroidSdkLibraries(targetsToImport: Sequence<TargetInfo>): Library? {
val androidSdkLibrariesJars = calculateProjectLevelAndroidSdkLibrariesJars(targetsToImport)

return if (androidSdkLibrariesJars.isNotEmpty()) {
Library(
label = "android_sdk_libraries",
outputs = androidSdkLibrariesJars,
sources = emptySet(),
dependencies = emptyList(),
)
} else null
}

private fun calculateProjectLevelAndroidSdkLibrariesJars(targetsToImport: Sequence<TargetInfo>): Set<URI> =
targetsToImport
.filter { it.hasAndroidSdkInfo() }
.map { it.androidSdkInfo.androidJar }
.map { bazelPathsResolver.resolve(it).toUri() }
.toSet()

/**
* In some cases, the jar dependencies of a target might be injected by bazel or rules and not are not
* available via `deps` field of a target. For this reason, we read JavaOutputInfo's jdeps file and
Expand Down Expand Up @@ -308,7 +279,7 @@ class BazelProjectMapper(

private fun targetSupportsJdeps(targetInfo: TargetInfo): Boolean {
val languages = inferLanguages(targetInfo)
return setOf(Language.JAVA, Language.KOTLIN, Language.SCALA).containsAll(languages)
return setOf(Language.JAVA, Language.KOTLIN, Language.SCALA, Language.ANDROID).containsAll(languages)
}

private fun syntheticLabel(lib: String): String {
Expand Down Expand Up @@ -389,6 +360,8 @@ class BazelProjectMapper(
"rust_test",
"rust_doc",
"rust_doc_test",
"android_library",
"android_binary",
)
)

Expand Down Expand Up @@ -446,16 +419,25 @@ class BazelProjectMapper(
}

private fun resolveDirectDependencies(target: TargetInfo): List<Label> =
target.dependenciesList.map { Label(it.id) }

private fun inferLanguages(target: TargetInfo): Set<Language> =
if (target.sourcesList.isEmpty()) {
Language.all().filter { isBinaryTargetOfLanguage(target.kind, it) }.toHashSet()
} else {
target.sourcesList.flatMap { source: FileLocation ->
Language.all().filter { isLanguageFile(source, it) }
}.toHashSet()
}
target.dependenciesList.map { Label(it.id) } + resolveAndroidDependencies(target)

private fun resolveAndroidDependencies(target: TargetInfo): List<Label> {
if (!target.hasAndroidTargetInfo()) return emptyList()
val kotlinTargetId = target.androidTargetInfo.kotlinTargetId
if (kotlinTargetId.isEmpty()) return emptyList()
return listOf(Label(kotlinTargetId))
}

private fun inferLanguages(target: TargetInfo): Set<Language> {
val languagesForTarget = Language.all().filter { isBinaryTargetOfLanguage(target.kind, it) }.toHashSet()
val languagesForSources = target.sourcesList.flatMap { source: FileLocation ->
Language.all().filter { isLanguageFile(source, it) }
}.toHashSet()
val languagesForDependencies = target.dependenciesList.flatMap { dependency ->
Language.all().filter { isDependencyOfLanguage(dependency, it) }
}.toHashSet()
return languagesForTarget + languagesForSources + languagesForDependencies
}

private fun isLanguageFile(file: FileLocation, language: Language): Boolean =
language.extensions.any {
Expand All @@ -465,6 +447,9 @@ class BazelProjectMapper(
private fun isBinaryTargetOfLanguage(kind: String, language: Language): Boolean =
language.binaryTargets.contains(kind)

private fun isDependencyOfLanguage(dependency: Dependency, language: Language): Boolean =
language.dependencyRegex?.matches(dependency.id) == true

private fun resolveSourceSet(target: TargetInfo, languagePlugin: LanguagePlugin<*>): SourceSet {
val sources = target.sourcesList.toSet()
.map(bazelPathsResolver::resolve)
Expand All @@ -483,7 +468,22 @@ class BazelProjectMapper(
}

private fun resolveResources(target: TargetInfo): Set<URI> =
bazelPathsResolver.resolveUris(target.resourcesList).toSet()
bazelPathsResolver.resolveUris(target.resourcesList).toSet() + resolveAndroidResources(target)

private fun resolveAndroidResources(target: TargetInfo): Set<URI> {
if (!target.hasAndroidTargetInfo()) return emptySet()
val androidTargetInfo = target.androidTargetInfo

if (!androidTargetInfo.hasManifest()) return emptySet()

if (target.kind == "android_binary") {
// This is a hack because the Android plugin wants a folder as opposed to the manifest file itself
return setOf(bazelPathsResolver.resolve(target.androidTargetInfo.manifest).parent.toUri())
}

return bazelPathsResolver
.resolveUris(listOf(target.androidTargetInfo.manifest) + target.androidTargetInfo.resourcesList).toSet()
}

private fun buildReverseSourceMapping(modules: Sequence<Module>): Map<URI, Label> =
modules.flatMap(::buildReverseSourceMappingForModule).toMap()
Expand Down
Loading

0 comments on commit 33760f0

Please sign in to comment.