环境搭建
可参考:
https://github.com/yangfeng20/idea-plugin-dev-guide?tab=readme-ov-file
下面是执行上述过程中记录的一些问题。
gradle-bin下载失败
默认是从官方地址下载:
https\://services.gradle.org/distributions/gradle-8.2-bin.zip
可以改为国内镜像地址,修改gradle-wrapper.properties文件:
# old
# distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
# now
distributionUrl=https\://mirrors.cloud.tencent.com/gradle/gradle-8.13-bin.zip
org.jetbrains.intellij.gradle.plugin下载失败
报错信息:
Plugin Repositories (could not resolve plugin artifact 'org.jetbrains.intellij:org.jetbrains.intellij.gradle.plugin:1.17.3')
修改settings.gradle.kts,设置插件仓为国内镜像:
pluginManagement {
repositories {
maven("https://maven.aliyun.com/repository/gradle-plugin")
maven("https://mirrors.cloud.tencent.com/nexus/repository/maven-public/")
gradlePluginPortal()
}
}
Caused by: java.lang.NullPointerException: getHeaderField(“Location“) must not be null错误
网上的解决方法:启用离线模式运行,toggle offline mode。
但我把IDEA从2023.2.4升级到2024.2.0.1就没这个问题了。
Unable to load class ‘com.intellij.ant.InstrumentIdeaExtensions’.错误
暂时无法解决,网上说是把org.jetbrains.intellij升级到1.17就行,但我的就是1.17.3,始终不行。
后续把IDEA从2023.2.4升级到2024.2.0.1就没这个问题了。
如何查看IDEA和gradle的版本配套关系
https://www.jetbrains.com/legal/third-party-software/?product=iic&version=2023.2.4
IDEA与jdk的关系
2024.2+用jdk21,之前的用jdk17或jdk19。
避免下载gradle-8.X-src.zip
有时候,新工程会自动下载gradle src包,可启用离线模式运行:toggle offline mode来解决。
插件开发
开发具体功能的插件,取决于需求,这里不赘述。IDEA插件的gradle配置是支持java编译的,不一定非要用kotlin开发。
plugin.xml配置
插件需要配置plugin.xml,下面是一个例子:
<action id="HelloWorldAction"
class="com.lee.idea_plugin_supplier.HelloWorldAction"
text="Hello World"
description="Show a greeting notification">
<add-to-group group-id="ToolsMenu" anchor="first"/>
</action>
group-id是具体要添加到的主菜单标识,上例是Tools菜单。
单元测试
我所使用的gradle8.10,似乎只能支持junit4,支持不了junit5,一跑junit5就报错。所以只能引入junit4的依赖:
dependencies {
...
// testImplementation("org.junit.jupiter:junit-jupiter-api:5.11.4")
// testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.11.4")
// testRuntimeOnly("org.junit.platform:junit-platform-launcher:1.13.4")
testCompileOnly("junit:junit:4.13.2")
testRuntimeOnly("org.junit.vintage:junit-vintage-engine:5.12.0")
}
同时增加test任务:
tasks {
named<Test>("test") {
useJUnitPlatform()
}
...
}
识别lombok
要在build.gradle.kts里增加依赖、注解处理器和io.freefair.lombok插件:
plugins {
...
id("io.freefair.lombok") version "8.14"
}
dependencies {
...
compileOnly("org.projectlombok:lombok:1.18.36")
annotationProcessor("org.projectlombok:lombok:1.18.30")
}
若还有问题,在build.gradle.kts的tasks里增加annotationProcessorGeneratedSourcesDirectory(我这边未加也ok):
tasks {
...
withType<JavaCompile> {
...
options.annotationProcessorGeneratedSourcesDirectory =
file("$buildDir/generated/sources/annotationProcessor/java/main")
}
}
打印日志
用idea提供的日志类:
import com.intellij.openapi.diagnostic.Logger;
public class MyPlugin {
private static final Logger LOG = Logger.getInstance(MyPlugin.class);
private int runNativeCmd(String cmd, @NotNull ProgressIndicator indicator) throws Exception {
if (Strings.isNullOrEmpty(cmd)) {
LOG.warn("cmd is null,skip");
return 0;
}
...
}
日志会打印到idea.log文件里,如果是开发阶段,会打印到沙箱日志里,路径为:
<你的开发目录>/build/idea-sandbox/system/log/idea.log
如果是实际运行时,windows会打印到
C:\Users\<用户名>\AppData\Roaming\JetBrains\<product><version>\log
linux会打印到:
~/.config/JetBrains/<product><version>/log
gradle相关知识
gradle kotlin dsl
同ruby一样,kotlin有一些有趣的语法糖,从而支持像写dsl那样写kotlin代码。下面是这方面的一些介绍。
- 当 lambda 表达式是函数的最后一个参数时,需将其放在大括号里调用。比如:
plugins {
id("java")
}
这里的plugins就是一个kotlin函数,它的定义如下:
public final fun plugins(block: org.gradle.kotlin.dsl.PluginDependenciesSpecScope.() -> kotlin.Unit): kotlin.Unit { /* compiled code */ }
它只接受一个lambda参数,此处是id(“java”),放在大括号里。
- 赋值语句等于setter:
maven {
url = uri("xxx")
}
这里,url = xxx,实际上等价于调用maven.setUrl(xxx)
gradle task
一些常用的gradle task如下,它们都是Task类实例:
...
> Task :jar UP-TO-DATE
> Task :instrumentedJar UP-TO-DATE
> Task :prepareTestingSandbox UP-TO-DATE
> Task :prepareSandbox UP-TO-DATE
> Task :buildSearchableOptions UP-TO-DATE
> Task :jarSearchableOptions UP-TO-DATE
> Task :buildPlugin UP-TO-DATE
> Task :assemble UP-TO-DATE
> Task :test UP-TO-DATE
> Task :check UP-TO-DATE
> Task :build UP-TO-DATE
像buildPlugin就是对IDEA插件进行zip打包的task,buildPlugin是任务名,BuildPluginTask是其任务类型,该类型继承自Zip任务类型。要查看所有任务的名称及类型,可在build.gradle.kts里这么写:
tasks.forEach { task ->
println("${task.name} -> ${task.javaClass.simpleName}")
}
强行指定依赖版本
比如依赖路径里有commons-io的多个版本,可以在build.gradle.kts里强行指定一个版本2.16.1,这样所有依赖路径的版本都会指向2.16.1
dependencies {
...
implementation("commons-io:commons-io:2.16.1")
指定包为provided
使用compileOnly函数,等价于maven的provided:
compileOnly("org.slf4j:slf4j-api:1.7.36")
排掉某个依赖
例如排掉某个包里的slf4j依赖:
implementation("com.alibaba:easyexcel:4.0.3") {
exclude(group = "org.slf4j", module = "slf4j-api")
}
插件打包发布
idea插件最后会打包为一个zip,安装到IDEA时,会解压到IDEA的插件目录下,这个目录在windows下一般是:
C:\Users\<用户名>\AppData\Roaming\JetBrains\<product><version>\plugins
zip内只有一个lib目录,包含所有的jar包。如果不希望把三方文件放到jar包里,比如我有一个a.exe,想放到与lib平齐的bin目录,最后再统一打包为一个zip,可以这么做:
named<BuildPluginTask>("buildPlugin") {
// 添加自定义文件夹到zip文件的bin目录下
from("../../custom") {
into("bin")
}
}
如前所述, buildPlugin是一个Zip类任务,可以在配置里新增from项,额外将某个custom目录复制到zip的bin目录里。注意:这里的配置项是增量的,不影响原有的zip打包。
IDEA API
一些基本概念需要了解。
VirtualFile和PsiFile类
- VirtualFile(应用级):
- 适用于需要对文件进行低级操作的场景,如读取文件内容、重命名文件等。
- 适用于处理文件系统级别的操作,而不涉及文件的具体内容。
- PsiFile(项目级):
- 适用于需要对文件内容进行高级操作的场景,如代码分析、导航、重构等。
- 适用于处理特定编程语言的文件,如 Java 文件或 XML 文件。
总结来说,VirtualFile 更适合处理文件系统级别的操作,而 PsiFile 更适合处理文件内容的高级操作。在开发插件时,根据具体需求选择合适的文件表示方式。
开发问题
不同IDEA版本间的兼容性
这个是需要逐个版本的测试的,可在build.gradle.kts里替换版本号,加以测试:
intellij {
version.set("2023.2.4") //替换这里的版本号
type.set("IC") // Target IDE Platform
plugins.set(listOf(/* Plugin Dependencies */))
}
若不同的IDEA版本间有兼容性问题,可尝试调整三方依赖的版本来解决。
LinkageError错误
运行插件时报这样的异常:
java.lang.LinkageError: loader constraint violation: when resolving method 'org.slf4j.ILoggerFactory org.slf4j.impl.StaticLoggerBinder.getLoggerFactory()' the class loader com.intellij.ide.plugins.cl.PluginClassLoader @763963c1 of the current class, org/slf4j/LoggerFactory, and the class loader com.intellij.util.lang.PathClassLoader @2ac1fdc4 for the method's defining class, org/slf4j/impl/StaticLoggerBinder, have different Class objects for the type org/slf4j/ILoggerFactory used in the signature (org.slf4j.LoggerFactory is in unnamed module of loader com.intellij.ide.plugins.cl.PluginClassLoader @763963c1, parent loader 'bootstrap'; org.slf4j.impl.StaticLoggerBinder is in unnamed module of loader com.intellij.util.lang.PathClassLoader @2ac1fdc4)
at org.slf4j.LoggerFactory.getILoggerFactory(LoggerFactory.java:423)
at org.slf4j.LoggerFactory.getLogger(LoggerFactory.java:362)
at org.slf4j.LoggerFactory.getLogger(LoggerFactory.java:388)
at com.alibaba.excel.ExcelReader.<clinit>(ExcelReader.java:21)
当同一个类文件被不同类加载器加载到内存中生成的类实例发生交互时,就会出现“LinkageError”异常。
这里牵涉到两个类加载器:PathClassLoader和PluginClassLoader,前者用于加载IDEA核心类和JDK通用类,后者用于加载单个插件的类,以做到插件间、插件与IDEA间相互隔离。
解决方法是将我们自己插件里引入的slf4j-api包排掉(参考https://bigzuo.github.io/2017/03/19/java-LinkageError-loader-constraint-violation-error/ )但有一点我不太明白,既然PluginClassLoader的目的就是为了插件与IDEA核心类的隔离,为何又出现两个加载器加载的同一个类一起运行的情况呢?

4431

被折叠的 条评论
为什么被折叠?



