最近几天学习了一下maven,把一些理解和参考链接记录如下。

起因是android开发中用到一个插件grpc-kotlin-gen,它在build过程中要调用外部程序protoc,并指定自身为其插件,生成与该proto适配的kotlin代码
但是在windows上运行时却报错,提示

  • What went wrong:
    Execution failed for task ‘:app:generateDebugProto’.
    > protoc: stdout: . stderr: --grpckotlin_out: protoc-gen-grpckotlin: %1 不是有效的 Win32 应用程序。

看起来有点没头没脑,转到控制台里手动敲gradle命令并带上--debug --info参数,可以看到实际调用的命令行为:(已格式化)

C:\Users\abc\.gradle\caches\modules-2\files-2.1\com.google.protobuf\protoc\3.7.1\bac579ccd9d95e3d7e25c514b209648e5482faff\protoc-3.7.1-windows-x86_64.exe, 

-ID:\code\pn\app\src\main\proto,
-ID:\code\pn\app\build\extracted-protos\main,
-ID:\code\pn\app\build\extracted-protos\debug,
-ID:\code\pn\app\build\extracted-include-protos\debug, 

--plugin=protoc-gen-grpc=C:\Users\abc\.gradle\caches\modules-2\files-2.1\io.grpc\protoc-gen-grpc-java\1.22.1\e0f304c1f3a7892543de91a3b6d7be4f0409257f\protoc-gen-grpc-java-1.22.1-windows-x86_64.exe, 
--grpc_out=lite:D:\code\pn\app\build/generated/source/proto/debug/grpc, 

--plugin=protoc-gen-grpckotlin=C:\Users\abc\.gradle\caches\modules-2\files-2.1\io.rouz\grpc-kotlin-gen\0.1.1\77a0bd0727af5048ce423297991ca33509743af8\grpc-kotlin-gen-0.1.1-jdk8.jar,
--grpckotlin_out=D:\code\pn\app\build/generated/source/proto/debug/grpckotlin,

--plugin=protoc-gen-javalite=C:\Users\abc\.gradle\caches\modules-2\files-2.1\com.google.protobuf\protoc-gen-javalite\3.0.0\ee55df776bde9c094b8d96fe3bf224f36e6f31af\protoc-gen-javalite-3.0.0-windows-x86_64.exe,
--javalite_out=D:\code\pn\app\build/generated/source/proto/debug/javalite, 

D:\code\pn\app\src\main\proto\user.proto

出问题的是红色这一条,这个--plugin参数的值本身又是一个kv pair,其中key是插件名,value是可执行程序路径,这里指向一个jar文件,而jar一般来说是不能直接运行的。

** 那为什么在macos上就可以呢? **
检查这个jar文件,发现其头部居然是一段嵌入的shell代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#!/bin/bash
#
# . ____ _ __ _ _
# /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
# ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
# \\/ ___)| |_)| | | | | || (_| | ) ) ) )
# ' |____| .__|_| |_|_| |_\__, | / / / /
# =========|_|==============|___/=/_/_/_/
# :: Spring Boot Startup Script ::
#

### BEGIN INIT INFO
# Provides: grpc-kotlin-gen
# Required-Start: $remote_fs $syslog $network
# Required-Stop: $remote_fs $syslog $network
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: grpc-kotlin-gen
# Description: Root project for consistent Maven conventions.
# chkconfig: 2345 99 01
### END INIT INFO

搜索得知这是一种叫executable jar的技术,也就是在原始jar头部塞了一段shell脚本,通过自解压的方式来运行后面的jar本体
然而这种形式利用是unix系统才有的Shebang技术(也就是script header),在windows系统上显然是无法运行的,因此也就导致我不能在windows上build该project。

对比另外两个protoc插件,发现它们都提供了exe形式的命令行工具。说明问题出在grpc-kotlin-gen自身上:它没有生成exe类型的工具。

为此下载该插件的源码看了下,发现这是一个maven(pom)工程

为了研究如何修改使之生成exe格式的工具,于是一边看代码一边搜教程,先学习一下maven和pom的概念。

pom的含义

  • 即:project object model,也就是把构建一个工程需要的信息建模成对象,然后以xml的方式表现出来
  • 一个pom.xml(及所在目录)就代表了一个maven工程,其内有约定的目录结构,如src/main、src/test、target等

https://maven.apache.org/guides/introduction/introduction-to-the-standard-directory-layout.html

maven的(多重)含义:

  • 是一个命令行工具的名称,实际命令为mvn,所有的build/test/deploy都可通过它运行
  • 是一种工程组织方式,通过pom.xml来定义表示工程信息
  • 是一种仓库类型,通过mvn构建的库可以集中上传到所谓maven仓库里,其它工程可以通过配置依赖项自动下载仓库里的东西(可以是lib、res或exe等任何资源)

maven与gradle对比:

  • 都建立了有关工程信息的对象模型,maven是用pom.xml,而gradle是用build.gradle
  • 都有一套打包上传、中央仓库、依赖解决机制
  • xml是一种纯描述语言,它的Schema本身就是对象模型,而gradle是一种编程语言(groovy),它除了使用一系列预定义类型(也就是所谓的DSL)来建模之外,还可以自由灵活的使用分支、循环以及外部调用等任何可执行逻辑
  • 所以理论上gradle比maven先进多了,本没有学习maven的必要,但鉴于还有大量使用maven的遗留项目,所以了解一下还是有用的

支持多个工程的层级结构:

  • 父目录下一个pom.xml
  • 子目录对应子工程,每个子工程也有pom.xml,其中通过<parent>元素来指定父工程
  • 子工程会继承父工程的设置,但覆盖相同的设置项

maven project的基本概念

  • 源文件,默认在src里,其内又分main/test等子目录,这些名字是一种约定,虽然可以修改,但最好不要去修改,因为很多插件只接受这个默认约定。

https://maven.apache.org/guides/mini/guide-using-one-source-directory.html

  • 依赖,在pom.xml中以<dependencies>表示,依赖会从指定的仓库中下载,缓存到本地($HOME/.m2/repository)。
  • 产出(artifact),一般是jar,实际可以是任何东西,如exe、zip。
  • Id,由groupIdartifactId共同决定,其实也就是完全限定的包名。当把产出上传到仓库,或是被指定为依赖时,就是靠这个Id来识别。
  • (依赖)仓库,由<repositories>指定,其中默认的仓库(http://repo1.maven.org/maven2)不需要定义,其它的仓库都需要指定。
  • (发布)仓库,如果产出的东西最终需要发布,那么就要指定发布到哪里去,发布仓库定义在<distributionManagement>里,但是登录仓库的用户名密码则放在$HOME/.m2/settings.xml<servers>节中。
  • 插件:所有的逻辑都是由插件完成的,maven是一个将插件组合起来并按顺序执行的框架。插件自身也被上传到仓库里,在pom.xml中的<plugins>标记里定义。插件的实际功能就是向maven注册goal,也就是基本的任务单元,在命令上运行mvn xxx,就是要执行xxx任务。
  • goalphaselifecycle: goal是最基本的任务单元,多个goal组成一个phase,多个phase组成一个lifecycle。三者的名字都可以作为参数传给mvn命令直接运行,也就是执行相应的任务。
  • 如何查看有哪些lifecycle:默认只有3种lifecyle,即clean(用来清除所有生成的东西)、site(用来生成文档),default(其实就是build,也是重点关注对象)。

https://maven.apache.org/guides/introduction/introduction-to-the-lifecycle.html

  • 如何查看有哪些phase:也在上面的链接里。在defaultlifecycle中,居然有23个phase
  • 如何查看有哪些goal
      内置命令,可列出指定phase下的goal:

mvn help:describe -Dcmd=compile

外部插件 fr.jcgay.maven.plugins ,可列出当前要执行的goal:

mvn buildplan:list

回到grpc-kotlin-gen,如何修改

  • 首先,得把jar转换成exe,这个可以用launch4j,已经有相应的maven插件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<plugin>
<groupid>com.akathist.maven.plugins.launch4j</groupid>
<artifactId>launch4j-maven-plugin</artifactId>
<executions>
<execution>
<id>l4j-clui</id>
<phase>package</phase>
<goals>
<goal>launch4j</goal>
</goals>
<configuration>
<headerType>console</headerType>
<outfile>${windows_exe}</outfile>
<jar>target/grpc-kotlin-gen-${version}-jar-with-dependencies.jar</jar>
<errTitle>encc</errTitle>
<classPath>
<mainClass>io.rouz.grpc.kotlin.GrpcKotlinGenerator</mainClass>
<addDependencies>true</addDependencies>
<preCp>anything</preCp>
</classPath>
<jre>
<minVersion>1.5.0</minVersion>
<opts>
<opt>-Djava.endorsed.dirs=./endorsed</opt>
</opts>
</jre>
<versionInfo>
<fileVersion>1.2.3.4</fileVersion>
<txtFileVersion>txt file version?</txtFileVersion>
<fileDescription>a description</fileDescription>
<copyright>my copyright</copyright>
<productVersion>4.3.2.1</productVersion>
<txtProductVersion>txt product version</txtProductVersion>
<productName>E-N-C-C</productName>
<internalName>ccne</internalName>
<originalFilename>original.exe</originalFilename>
</versionInfo>
</configuration>
</execution>
</executions>
</plugin>

具体的细节没有深入去看,从文档里把模板复制过来,修改几个关键属性就好: