You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 07:21:26