IDEA插件开发心得

环境搭建

可参考:

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代码。下面是这方面的一些介绍。

  1. 当 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”),放在大括号里。

  1. 赋值语句等于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核心类的隔离,为何又出现两个加载器加载的同一个类一起运行的情况呢?

Gradle 是一款功能强大的开源构建自动化工具,它融合了 Ant 的灵活性与 Maven 的依赖管理优势,并采用基于 Groovy 或 Kotlin 的声明式语言,使构建脚本更简洁、可读性更强。作为 Android 开发的官方构建工具,Gradle 也广泛应用于 Java、C++ 等多种技术栈的项目中,是现代软件开发流程的核心组件之一。 核心优势 高性能:依托增量构建、构建缓存和并行执行技术,显著提升编译和部署效率。 高度可扩展:使用 Groovy 或 Kotlin DSL 编写脚本,轻松定制复杂构建流程,支持丰富的插件生态。 强大的依赖管理:可无缝对接 Maven、Ivy 等仓库,自动化解决依赖冲突。 关于 Gradle 8.13 该版本是 8.x 系列的长期支持(LTS)版本,注重稳定性和性能优化。其主要改进包括进一步优化配置缓存、增强对 Kotlin DSL 的支持,并修复了前期版本中的多项问题,适合用于企业级生产环境。 “-bin.zip” 是什么? “-bin” 表示二进制分发版,仅包含运行 Gradle 所必需的编译文件和基础文档,无源代码。该版本体积小、下载快,是大多数开发和持续集成环境的理想选择。 安装使用简要指南 解压 gradle-8.13-bin.zip 至目标目录(如 /opt/gradle)。 设置环境变量: 配置 GRADLE_HOME 指向解压目录 将 $GRADLE_HOME/bin 加入 PATH 终端执行 gradle -v,出现版本信息即表示安装成功。 总结 gradle-8.13-bin.zipGradle 官方推荐的稳定二进制发行包,兼顾高性能与可靠性。适用于个人学习、企业项目开发和自动化构建流程,能够有效管理项目依赖、优化构建效率,是构建现代化项目的首选工具。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值