所以我认为我有一个很好的(但冗长的)解决方案适用于 Android。在撰写本文时,我使用的是 Gradle 6.7、Android 插件 4.1.0 和 AspectJ 工具 1.9.6。
问题的要点是:
- Java 由任务
compileDebugJavaWithJavac 编译
- Kotlin 由任务
compileDebugKotlin 编译
- Gradle 可以运行其中一项任务,也可以同时运行这两项任务,或者都不运行
-
compileDebugJavaWithJavac 取决于 compileDebugKotlin
- 编织 Kotlin 通常需要 Java 类。
如果您仔细观察这些要点,您会发现您不能在编译 Kotlin 的过程中进行编织,因为此时可能会缺少 Java 类。如果您这样做,您将收到如下警告:
警告:不正确的类路径:C:\Users\user\StudioProjects\myapp\app\build\intermediates\javac\debug\classes
以及诸如
之类的错误
错误:无法确定缺少类型 myapp.Foo.Bar 的修饰符
因此,更好的方法是推迟编织,直到 Java 类被编译。但是,由于您将修改文件而不是作为编译任务的一部分,因此您会丢失增量构建...此外,这种推迟的编织非常难以正确进行 - 请记住,任何编译任务都可能不会实际计划运行!
真正的解决方案是将编织包装在 Transform 中,这将产生一个带有自己的输入和输出的 Gradle 任务。这意味着您不会污染编译任务的文件,并且这些任务以及此任务将可以说是 UP-TO-DATE-able。这需要相当多的代码,但它是相当明智的!
首先,把这个放到你的项目中build.gradle.kts:
buildscript {
dependencies {
classpath("org.aspectj:aspectjtools:1.9.6")
}
}
这是从构建脚本“内部”运行编织所必需的。如果你想在一个单独的进程中运行编织,这在 Windows 上是个好主意,你需要这个 jar 的路径,你可以通过将以下内容添加到你的应用程序中来获得它build.gradle.kts:
val weaving: Configuration by configurations.creating
dependencies {
weaving("org.aspectj:aspectjtools:1.9.6")
}
最后,将 AspectJ 运行时放在类路径中(app build.gradle.kts,注意我只需要在调试版本中编织):
dependencies {
debugImplementation("org.aspectj:aspectjrt:1.9.6")
}
现在,这是我的设置。我有一个本地日志库:cats,其中包含我想要编织的方面。日志语句仅在我的项目中,而不是其他任何地方。另外,我只想在调试版本中运行这些。所以这里是“编织猫”到应用程序中的转换(应用程序的build.gradle.kts):
class TransformCats : Transform() {
override fun getName(): String = TransformCats::class.simpleName!!
override fun getInputTypes() = setOf(QualifiedContent.DefaultContentType.CLASSES)
// only look for annotations in app classes
// transformation will consume these and put woven classes in the output dir
override fun getScopes() = mutableSetOf(QualifiedContent.Scope.PROJECT)
// ...but also have the rest on our class path
// these will not be touched by the transformation
override fun getReferencedScopes() = mutableSetOf(QualifiedContent.Scope.SUB_PROJECTS,
QualifiedContent.Scope.EXTERNAL_LIBRARIES)
override fun isIncremental() = false
// only run on debug builds
override fun applyToVariant(variant: VariantInfo) = variant.isDebuggable
override fun transform(invocation: TransformInvocation) {
if (!invocation.isIncremental) {
invocation.outputProvider.deleteAll()
}
val output = invocation.outputProvider.getContentLocation(name, outputTypes,
scopes, Format.DIRECTORY)
if (output.isDirectory) FileUtils.deleteDirectoryContents(output)
FileUtils.mkdirs(output)
val input = mutableListOf<File>()
val classPath = mutableListOf<File>()
val aspectPath = mutableListOf<File>()
invocation.inputs.forEach { source ->
source.directoryInputs.forEach { dir ->
input.add(dir.file)
classPath.add(dir.file)
}
source.jarInputs.forEach { jar ->
input.add(jar.file)
classPath.add(jar.file)
}
}
invocation.referencedInputs.forEach { source ->
source.directoryInputs.forEach { dir ->
classPath.add(dir.file)
}
source.jarInputs.forEach { jar ->
classPath.add(jar.file)
if (jar.name == ":cats") aspectPath.add(jar.file)
}
}
weave(classPath, aspectPath, input, output)
}
}
android.registerTransform(TransformCats())
这是上面提到的编织代码:
// ajc gets hold of some files such as R.jar, and on Windows it leads to errors such as:
// The process cannot access the file because it is being used by another process
// to avoid these, weave in a process, which `javaexec` will helpfully launch for us.
fun weave(classPath: Iterable<File>, aspectPath: Iterable<File>, input: Iterable<File>, output: File) {
val runInAProcess = OperatingSystem.current().isWindows
val bootClassPath = android.bootClasspath
println(if (runInAProcess) ":: weaving in a process..." else ":: weaving...")
println(":: boot class path: $bootClassPath")
println(":: class path: $classPath")
println(":: aspect path: $aspectPath")
println(":: input: $input")
println(":: output: $output")
val arguments = listOf("-showWeaveInfo",
"-1.8",
"-bootclasspath", bootClassPath.asArgument,
"-classpath", classPath.asArgument,
"-aspectpath", aspectPath.asArgument,
"-inpath", input.asArgument,
"-d", output.absolutePath)
if (runInAProcess) {
javaexec {
classpath = weaving
main = "org.aspectj.tools.ajc.Main"
args = arguments
}
} else {
val handler = MessageHandler(true)
Main().run(arguments.toTypedArray(), handler)
val log = project.logger
for (message in handler.getMessages(null, true)) {
when (message.kind) {
IMessage.DEBUG -> log.debug("DEBUG " + message.message, message.thrown)
IMessage.INFO -> log.info("INFO: " + message.message, message.thrown)
IMessage.WARNING -> log.warn("WARN: " + message.message, message.thrown)
IMessage.FAIL,
IMessage.ERROR,
IMessage.ABORT -> log.error("ERROR: " + message.message, message.thrown)
}
}
}
}
val Iterable<File>.asArgument get() = joinToString(File.pathSeparator)
(Windows 部分使用weaving 配置;您可能不需要if 的任何部分)
就是这样!
编辑:从 AGP 4.2.0 开始,jar.name 不会返回任何有用的信息。目前,我使用了这个脆弱的解决方法:
if (jar.file.directoriesInsideRootProject().contains("cats")) {
aspectPath.add(jar.file)
}
fun File.directoriesInsideRootProject() = sequence {
var file = this@directoriesInsideRootProject
while (true) {
yield(file.name)
file = file.parentFile ?: break
if (file == rootProject.projectDir) break
}
}