Gradle构建jar时MANIFEST.MF属性顺序与build.gradle配置不一致
运行环境
- Java 1.8
- Gradle 7
问题描述
在build.gradle中添加如下配置:
plugins { id 'application' id "com.nocwriter.runsql" version "1.0.3" id 'idea' } ext { distributeVersionNumber = "1.2.5" distributeOsFamily = "win" distributeVersionType = "Lite" vendorName = "SomeCompany" vendorURL = "www.some_host.com" projectShortName = "myApp" projectFullName = "My Application" javaMainClass = "${group}.Main" xml2SqlConverterMainClass = "${group}.dict.XML2SQLConverter" sqlStrategy = "${group}.db.SQLiteStrategy" buildResources = "${buildDir}/resources" buildResourcesMain = "${buildResources}/main" buildResourcesDbSqlitePath = "${buildResourcesMain}/db/sqlite" distArchiveFileName = "${projectShortName}-${version}-${distributeVersionType}-${distributeOsFamily}" } jar { exclude("db") exclude("scripts") archiveFileName = "${projectShortName}.jar" manifest { attributes( "Manifest-Version": "1.0", "Created-By": "Gradle ${gradle.gradleVersion}", "Built-By": System.properties["user.name"], "Built-On": new java.text.SimpleDateFormat("dd.MM.yyyy'T'HH:mm:ss").format(new Date()), //"Build-Revision": versioning.info.commit, "Build-Jdk": "${System.properties["java.version"]} (${System.properties["java.vendor"]} ${System.properties["java.vm.version"]})", "Build-OS": "${System.properties["os.name"]} ${System.properties["os.arch"]} ${System.properties["os.version"]}", "Implementation-Title": "${projectFullName}", "Implementation-Version": "${distributeVersionNumber}-${distributeVersionType}", "Implementation-Vendor": "$vendorName", "Implementation-Vendor-URL": "$vendorURL", "Sealed": "true", "Project-Type": "application", "Main-Class": "$javaMainClass", "SplashScreen-Image": "images/splash.png", "Class-Path": configurations.runtimeClasspath.collect { it.getName() }.join(" ")) } }
执行如下命令构建应用分发包:
gradle distZip
命令可正常生成应用分发包,但生成的应用jar包内MANIFEST.MF文件内容字段顺序与配置中定义的顺序不一致,实际生成内容如下:
Manifest-Version: 1.0 Implementation-Title: My Application Implementation-Version: 1.2.5-Lite Built-By: alexei Sealed: true Class-Path: ssp-0.5.7.jar log4j-1.2.17.jar jcalendar-1.4.jar sqlite-jd bc-3.36.0.3.jar commons-net-3.6.jar Built-On: 26.06.2022T13:30:25 Project-Type: application Implementation-Vendor: Some Company Implementation-Vendor-URL: www.some_host.com Main-Class: com.mycompany.myapp.Main Build-OS: Windows 7 amd64 6.1 Created-By: Gradle 7.3 Build-Jdk: 1.8.0_231 (Oracle Corporation 25.231-b11) SplashScreen-Image: images/splash.png
原因说明
JAR官方规范没有对MANIFEST.MF的属性顺序做强制要求,只要属性值正确就不会影响程序运行。
Gradle 7处理Manifest属性时,底层依赖java.util.jar.Attributes类,该类继承自HashMap,本身不保留键值对的插入顺序。直接向attributes()方法传入批量定义的Map参数时,属性会按照HashMap的哈希排序规则输出,和代码里的书写顺序无关。
解决方法
使用**保留插入顺序的LinkedHashMap**传入属性列表,或者按需要的顺序逐个调用attributes()方法传入单个属性,即可让生成的MANIFEST.MF字段顺序和定义顺序完全一致。
方法1:使用LinkedHashMap批量传入属性
修改jar配置中的manifest块如下:
jar { exclude("db") exclude("scripts") archiveFileName = "${projectShortName}.jar" manifest { // 初始化LinkedHashMap保留属性插入顺序 def orderedManifestAttrs = new LinkedHashMap<String, Object>() orderedManifestAttrs["Manifest-Version"] = "1.0" orderedManifestAttrs["Created-By"] = "Gradle ${gradle.gradleVersion}" orderedManifestAttrs["Built-By"] = System.properties["user.name"] orderedManifestAttrs["Built-On"] = new java.text.SimpleDateFormat("dd.MM.yyyy'T'HH:mm:ss").format(new Date()) orderedManifestAttrs["Build-Jdk"] = "${System.properties["java.version"]} (${System.properties["java.vendor"]} ${System.properties["java.vm.version"]})" orderedManifestAttrs["Build-OS"] = "${System.properties["os.name"]} ${System.properties["os.arch"]} ${System.properties["os.version"]}" orderedManifestAttrs["Implementation-Title"] = projectFullName orderedManifestAttrs["Implementation-Version"] = "${distributeVersionNumber}-${distributeVersionType}" orderedManifestAttrs["Implementation-Vendor"] = vendorName orderedManifestAttrs["Implementation-Vendor-URL"] = vendorURL orderedManifestAttrs["Sealed"] = "true" orderedManifestAttrs["Project-Type"] = "application" orderedManifestAttrs["Main-Class"] = javaMainClass orderedManifestAttrs["SplashScreen-Image"] = "images/splash.png" orderedManifestAttrs["Class-Path"] = configurations.runtimeClasspath.collect { it.getName() }.join(" ") attributes(orderedManifestAttrs) } }
方法2:逐个传入属性
按需要的顺序逐行调用attributes()方法传入单个键值对,效果和LinkedHashMap方案一致:
jar { exclude("db") exclude("scripts") archiveFileName = "${projectShortName}.jar" manifest { attributes "Manifest-Version": "1.0" attributes "Created-By": "Gradle ${gradle.gradleVersion}" attributes "Built-By": System.properties["user.name"] attributes "Built-On": new java.text.SimpleDateFormat("dd.MM.yyyy'T'HH:mm:ss").format(new Date()) attributes "Build-Jdk": "${System.properties["java.version"]} (${System.properties["java.vendor"]} ${System.properties["java.vm.version"]})" attributes "Build-OS": "${System.properties["os.name"]} ${System.properties["os.arch"]} ${System.properties["os.version"]}" attributes "Implementation-Title": projectFullName attributes "Implementation-Version": "${distributeVersionNumber}-${distributeVersionType}" attributes "Implementation-Vendor": vendorName attributes "Implementation-Vendor-URL": vendorURL attributes "Sealed": "true" attributes "Project-Type": "application" attributes "Main-Class": javaMainClass attributes "SplashScreen-Image": "images/splash.png" attributes "Class-Path": configurations.runtimeClasspath.collect { it.getName() }.join(" ") } }
修改配置后执行gradle clean distZip重新构建,生成的MANIFEST.MF字段就会严格按照定义的顺序排列。
内容的提问来源于stack exchange,提问作者Alexei
相关产品推荐
相关产品推荐

