From 002d9ceef3bbca644af6f27d7df3cb9b15d8978e Mon Sep 17 00:00:00 2001 From: Danno Ferrin Date: Wed, 14 Nov 2018 08:08:45 -0700 Subject: [PATCH] Update Client Version to be ethstats friendly (#258) Update value returned by web3_clientVersion to be ethstats friendly * Version part starts with a v * SNAPSHOT builds report 32 bits of the git hash * OS and architecture are sniffed out and reported * JVM version and branding are sniffed out and reported Example values (not all real): pantheon/v0.9.0-dev-f800a0b1/osx-x86_64/oracle-java-1.8 pantheon/v0.9.0-dev-27960b57/osx-x86_64/zulu-java-11 pantheon/v0.9.0/linux-arm64/openjdk-java-12 --- build.gradle | 27 +++ .../main/groovy/ProjectPropertiesFile.groovy | 33 ++- config/build.gradle | 8 +- consensus/clique/build.gradle | 8 +- consensus/common/build.gradle | 8 +- consensus/ibft/build.gradle | 8 +- consensus/ibftlegacy/build.gradle | 8 +- crypto/build.gradle | 8 +- ethereum/blockcreation/build.gradle | 8 +- ethereum/core/build.gradle | 8 +- ethereum/eth/build.gradle | 8 +- ethereum/jsonrpc/build.gradle | 8 +- ethereum/mock-p2p/build.gradle | 8 +- ethereum/p2p/build.gradle | 8 +- ethereum/rlp/build.gradle | 8 +- ethereum/trie/build.gradle | 8 +- pantheon/build.gradle | 11 +- .../tech/pegasys/pantheon/PantheonInfo.java | 20 +- .../pantheon/util/PlatformDetector.java | 199 ++++++++++++++++++ .../pegasys/pantheon/PantheonInfoTest.java | 31 +++ services/kvstore/build.gradle | 8 +- testutil/build.gradle | 8 +- util/build.gradle | 8 +- 23 files changed, 411 insertions(+), 46 deletions(-) create mode 100644 pantheon/src/main/java/tech/pegasys/pantheon/util/PlatformDetector.java create mode 100644 pantheon/src/test/java/tech/pegasys/pantheon/PantheonInfoTest.java diff --git a/build.gradle b/build.gradle index f23604c4c3..d9753f54aa 100644 --- a/build.gradle +++ b/build.gradle @@ -394,6 +394,33 @@ configure(subprojects.findAll {it.name != 'errorprone-checks'}) { } } +// Takes the version, and if -SNAPSHOT is part of it replaces SNAPSHOT +// with the git commit version. +def calculateVersion() { + String version = rootProject.version + if (version.endsWith("-SNAPSHOT")) { + version = version.replace("-SNAPSHOT", "-dev-" + getCheckedOutGitCommitHash()) + } + return version +} + +def getCheckedOutGitCommitHash() { + def gitFolder = "$projectDir/.git/" + def takeFromHash = 8 + /* + * '.git/HEAD' contains either + * in case of detached head: the currently checked out commit hash + * otherwise: a reference to a file containing the current commit hash + */ + def head = new File(gitFolder + "HEAD").text.split(":") // .git/HEAD + def isCommit = head.length == 1 // e5a7c79edabbf7dd39888442df081b1c9d8e88fd + + if(isCommit) return head[0].trim().take(takeFromHash) // e5a7c79edabb + + def refHead = new File(gitFolder + head[1].trim()) // .git/refs/heads/master + refHead.text.trim().take takeFromHash +} + apply plugin: 'net.researchgate.release' task releaseIntegrationTest(type: Test){ diff --git a/buildSrc/src/main/groovy/ProjectPropertiesFile.groovy b/buildSrc/src/main/groovy/ProjectPropertiesFile.groovy index 2656cd702d..a073f60be9 100644 --- a/buildSrc/src/main/groovy/ProjectPropertiesFile.groovy +++ b/buildSrc/src/main/groovy/ProjectPropertiesFile.groovy @@ -55,6 +55,10 @@ class ProjectPropertiesFile extends DefaultTask { properties.add(new Property(name, value, PropertyType.STRING)) } + void addVersion(String name, String value) { + properties.add(new Property(name, value, PropertyType.VERSION)) + } + @Nested List getProperties() { return properties @@ -73,9 +77,11 @@ class ProjectPropertiesFile extends DefaultTask { String[] methodDeclarations = properties.stream().map({p -> p.methodDeclaration()}).toArray() return """package ${destPackage}; +import tech.pegasys.pantheon.util.PlatformDetector; + // This file is generated via a gradle task and should not be edited directly. -public class ${filename} { -${String.join("\n ", varDeclarations)} +public final class ${filename} { +${String.join("\n", varDeclarations)} private ${filename}() {} ${String.join("\n", methodDeclarations)} @@ -84,7 +90,8 @@ ${String.join("\n", methodDeclarations)} } private enum PropertyType { - STRING("String") + STRING("String"), + VERSION("String"); private final String strVal PropertyType(String strVal) { @@ -96,7 +103,7 @@ ${String.join("\n", methodDeclarations)} } } - private static class Property { + private class Property { private final String name private final String value private final PropertyType type @@ -123,13 +130,27 @@ ${String.join("\n", methodDeclarations)} } String variableDeclaration() { - return " private static final ${type} ${name} = \"${value}\";" + def constantName = name.replaceAll("([a-z])([A-Z]+)", '$1_$2').toUpperCase() + def constantValue = value.replaceAll("([a-z])([A-Z]+)", '$1_$2').toUpperCase() + switch (type) { + case PropertyType.STRING: + return " private static final ${type} ${constantName} = \"${value}\";" + case PropertyType.VERSION: + return " private static final ${type} ${constantName} =\n" + + " ${constantValue}\n" + + " + \"/v\"\n" + + " + ${filename}.class.getPackage().getImplementationVersion()\n" + + " + \"/\"\n" + + " + PlatformDetector.getOS()\n" + + " + \"/\"\n" + + " + PlatformDetector.getVM();" + } } String methodDeclaration() { return """ public static ${type} ${name}() { - return ${name}; + return ${name.replaceAll("([a-z])([A-Z]+)", '$1_$2').toUpperCase()}; }""" } } diff --git a/config/build.gradle b/config/build.gradle index 6f53cc7b4b..5b09240423 100644 --- a/config/build.gradle +++ b/config/build.gradle @@ -16,8 +16,12 @@ apply plugin: 'java-library' jar { baseName 'pantheon-config' manifest { - attributes('Implementation-Title': baseName, - 'Implementation-Version': project.version) + attributes( + 'Specification-Title': baseName, + 'Specification-Version': project.version, + 'Implementation-Title': baseName, + 'Implementation-Version': calculateVersion() + ) } } diff --git a/consensus/clique/build.gradle b/consensus/clique/build.gradle index a0da8e6333..3cc10f900f 100644 --- a/consensus/clique/build.gradle +++ b/consensus/clique/build.gradle @@ -16,8 +16,12 @@ plugins { id 'java' } jar { baseName 'pantheon-clique' manifest { - attributes('Implementation-Title': baseName, - 'Implementation-Version': project.version) + attributes( + 'Specification-Title': baseName, + 'Specification-Version': project.version, + 'Implementation-Title': baseName, + 'Implementation-Version': calculateVersion() + ) } } diff --git a/consensus/common/build.gradle b/consensus/common/build.gradle index cb9cdd58bb..4a297ed24c 100644 --- a/consensus/common/build.gradle +++ b/consensus/common/build.gradle @@ -16,8 +16,12 @@ apply plugin: 'java-library' jar { baseName 'pantheon-consensus-common' manifest { - attributes('Implementation-Title': baseName, - 'Implementation-Version': project.version) + attributes( + 'Specification-Title': baseName, + 'Specification-Version': project.version, + 'Implementation-Title': baseName, + 'Implementation-Version': calculateVersion() + ) } } diff --git a/consensus/ibft/build.gradle b/consensus/ibft/build.gradle index 012831a001..fd3d336047 100644 --- a/consensus/ibft/build.gradle +++ b/consensus/ibft/build.gradle @@ -16,8 +16,12 @@ apply plugin: 'java-library' jar { baseName 'pantheon-ibft' manifest { - attributes('Implementation-Title': baseName, - 'Implementation-Version': project.version) + attributes( + 'Specification-Title': baseName, + 'Specification-Version': project.version, + 'Implementation-Title': baseName, + 'Implementation-Version': calculateVersion() + ) } } diff --git a/consensus/ibftlegacy/build.gradle b/consensus/ibftlegacy/build.gradle index 5e15e9228a..57d06f39f0 100644 --- a/consensus/ibftlegacy/build.gradle +++ b/consensus/ibftlegacy/build.gradle @@ -3,8 +3,12 @@ apply plugin: 'java-library' jar { baseName 'pantheon-ibftlegacy' manifest { - attributes('Implementation-Title': baseName, - 'Implementation-Version': project.version) + attributes( + 'Specification-Title': baseName, + 'Specification-Version': project.version, + 'Implementation-Title': baseName, + 'Implementation-Version': calculateVersion() + ) } } diff --git a/crypto/build.gradle b/crypto/build.gradle index 6c4e039612..7057c3a5f6 100644 --- a/crypto/build.gradle +++ b/crypto/build.gradle @@ -16,8 +16,12 @@ apply plugin: 'java-library' jar { baseName 'pantheon-crypto' manifest { - attributes('Implementation-Title': baseName, - 'Implementation-Version': project.version) + attributes( + 'Specification-Title': baseName, + 'Specification-Version': project.version, + 'Implementation-Title': baseName, + 'Implementation-Version': calculateVersion() + ) } } diff --git a/ethereum/blockcreation/build.gradle b/ethereum/blockcreation/build.gradle index 9631c9bbfe..e1136f5921 100644 --- a/ethereum/blockcreation/build.gradle +++ b/ethereum/blockcreation/build.gradle @@ -3,8 +3,12 @@ apply plugin: 'java-library' jar { baseName 'pantheon-blockcreation' manifest { - attributes('Implementation-Title': baseName, - 'Implementation-Version': project.version) + attributes( + 'Specification-Title': baseName, + 'Specification-Version': project.version, + 'Implementation-Title': baseName, + 'Implementation-Version': calculateVersion() + ) } } diff --git a/ethereum/core/build.gradle b/ethereum/core/build.gradle index 69bf6f1147..86904029d5 100644 --- a/ethereum/core/build.gradle +++ b/ethereum/core/build.gradle @@ -16,8 +16,12 @@ apply plugin: 'java-library' jar { baseName 'pantheon-core' manifest { - attributes('Implementation-Title': baseName, - 'Implementation-Version': project.version) + attributes( + 'Specification-Title': baseName, + 'Specification-Version': project.version, + 'Implementation-Title': baseName, + 'Implementation-Version': calculateVersion() + ) } } diff --git a/ethereum/eth/build.gradle b/ethereum/eth/build.gradle index 1ea34365a9..7991374ea4 100644 --- a/ethereum/eth/build.gradle +++ b/ethereum/eth/build.gradle @@ -16,8 +16,12 @@ apply plugin: 'java-library' jar { baseName 'pantheon-eth' manifest { - attributes('Implementation-Title': baseName, - 'Implementation-Version': project.version) + attributes( + 'Specification-Title': baseName, + 'Specification-Version': project.version, + 'Implementation-Title': baseName, + 'Implementation-Version': calculateVersion() + ) } } diff --git a/ethereum/jsonrpc/build.gradle b/ethereum/jsonrpc/build.gradle index 641887a3ce..5911a5ac55 100644 --- a/ethereum/jsonrpc/build.gradle +++ b/ethereum/jsonrpc/build.gradle @@ -16,8 +16,12 @@ apply plugin: 'java-library' jar { baseName 'pantheon-json-rpc' manifest { - attributes('Implementation-Title': baseName, - 'Implementation-Version': project.version) + attributes( + 'Specification-Title': baseName, + 'Specification-Version': project.version, + 'Implementation-Title': baseName, + 'Implementation-Version': calculateVersion() + ) } } diff --git a/ethereum/mock-p2p/build.gradle b/ethereum/mock-p2p/build.gradle index 991edb0dac..61bd4a5215 100644 --- a/ethereum/mock-p2p/build.gradle +++ b/ethereum/mock-p2p/build.gradle @@ -16,8 +16,12 @@ apply plugin: 'java-library' jar { baseName 'pantheon-mock-p2p' manifest { - attributes('Implementation-Title': baseName, - 'Implementation-Version': project.version) + attributes( + 'Specification-Title': baseName, + 'Specification-Version': project.version, + 'Implementation-Title': baseName, + 'Implementation-Version': calculateVersion() + ) } } diff --git a/ethereum/p2p/build.gradle b/ethereum/p2p/build.gradle index 9556c62095..9ae06a9b4d 100644 --- a/ethereum/p2p/build.gradle +++ b/ethereum/p2p/build.gradle @@ -16,8 +16,12 @@ apply plugin: 'java-library' jar { baseName 'pantheon-p2p' manifest { - attributes('Implementation-Title': baseName, - 'Implementation-Version': project.version) + attributes( + 'Specification-Title': baseName, + 'Specification-Version': project.version, + 'Implementation-Title': baseName, + 'Implementation-Version': calculateVersion() + ) } } diff --git a/ethereum/rlp/build.gradle b/ethereum/rlp/build.gradle index 3be2cd519d..e147ca7441 100644 --- a/ethereum/rlp/build.gradle +++ b/ethereum/rlp/build.gradle @@ -16,8 +16,12 @@ apply plugin: 'java-library' jar { baseName 'pantheon-ethereum-rlp' manifest { - attributes('Implementation-Title': baseName, - 'Implementation-Version': project.version) + attributes( + 'Specification-Title': baseName, + 'Specification-Version': project.version, + 'Implementation-Title': baseName, + 'Implementation-Version': calculateVersion() + ) } } diff --git a/ethereum/trie/build.gradle b/ethereum/trie/build.gradle index 9459b1d851..125c9fb25f 100644 --- a/ethereum/trie/build.gradle +++ b/ethereum/trie/build.gradle @@ -16,8 +16,12 @@ apply plugin: 'java-library' jar { baseName 'pantheon-trie' manifest { - attributes('Implementation-Title': baseName, - 'Implementation-Version': project.version) + attributes( + 'Specification-Title': baseName, + 'Specification-Version': project.version, + 'Implementation-Title': baseName, + 'Implementation-Version': calculateVersion() + ) } } diff --git a/pantheon/build.gradle b/pantheon/build.gradle index ca360dec58..902d180bb7 100644 --- a/pantheon/build.gradle +++ b/pantheon/build.gradle @@ -16,8 +16,12 @@ apply plugin: 'java-library' jar { baseName 'pantheon' manifest { - attributes('Implementation-Title': baseName, - 'Implementation-Version': project.version) + attributes( + 'Specification-Title': baseName, + 'Specification-Version': project.version, + 'Implementation-Title': baseName, + 'Implementation-Version': calculateVersion() + ) } } @@ -58,7 +62,8 @@ dependencies { task writeInfoFile(type: ProjectPropertiesFile) { destPackage = "tech.pegasys.pantheon" - addString("version", "$rootProject.name/$rootProject.version") + addString("clientIdentity", rootProject.name) + addVersion("version", "clientIdentity") } compileJava.dependsOn(writeInfoFile) diff --git a/pantheon/src/main/java/tech/pegasys/pantheon/PantheonInfo.java b/pantheon/src/main/java/tech/pegasys/pantheon/PantheonInfo.java index 7bd2481f79..7ea7af288b 100644 --- a/pantheon/src/main/java/tech/pegasys/pantheon/PantheonInfo.java +++ b/pantheon/src/main/java/tech/pegasys/pantheon/PantheonInfo.java @@ -1,12 +1,26 @@ package tech.pegasys.pantheon; +import tech.pegasys.pantheon.util.PlatformDetector; + // This file is generated via a gradle task and should not be edited directly. -public class PantheonInfo { - private static final String version = "pantheon/0.9.0-SNAPSHOT"; +public final class PantheonInfo { + private static final String CLIENT_IDENTITY = "pantheon"; + private static final String VERSION = + CLIENT_IDENTITY + + "/v" + + PantheonInfo.class.getPackage().getImplementationVersion() + + "/" + + PlatformDetector.getOS() + + "/" + + PlatformDetector.getVM(); private PantheonInfo() {} + public static String clientIdentity() { + return CLIENT_IDENTITY; + } + public static String version() { - return version; + return VERSION; } } diff --git a/pantheon/src/main/java/tech/pegasys/pantheon/util/PlatformDetector.java b/pantheon/src/main/java/tech/pegasys/pantheon/util/PlatformDetector.java new file mode 100644 index 0000000000..3458956762 --- /dev/null +++ b/pantheon/src/main/java/tech/pegasys/pantheon/util/PlatformDetector.java @@ -0,0 +1,199 @@ +/* + * Copyright 2018 ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package tech.pegasys.pantheon.util; + +import java.util.Locale; + +/** + * Detects OS and VMs. + * + *

Derived from Detector.java https://github.com/trustin/os-maven-plugin/ version 59fd029 on 21 + * Apr 2018, Copyright 2014 Trustin Heuiseung Lee. + */ +public class PlatformDetector { + + private static String _os; + private static String _osType; + private static String _vm; + + public static String getOSType() { + if (_osType == null) { + detect(); + } + return _osType; + } + + public static String getOS() { + if (_os == null) { + detect(); + } + return _os; + } + + public static String getVM() { + if (_vm == null) { + detect(); + } + return _vm; + } + + private static final String UNKNOWN = "unknown"; + + private static void detect() { + final String detectedOS = normalizeOS(normalize("os.name")); + final String detectedArch = normalizeArch(normalize("os.arch")); + final String detectedVM = normalizeVM(normalize("java.vendor"), normalize("java.vm.name")); + final String detectedJavaVersion = normalizeJavaVersion("java.specification.version"); + + _os = detectedOS + '-' + detectedArch; + _osType = detectedOS; + _vm = detectedVM + "-java-" + detectedJavaVersion; + } + + private static String normalizeOS(final String osName) { + if (osName.startsWith("aix")) { + return "aix"; + } + if (osName.startsWith("hpux")) { + return "hpux"; + } + if (osName.startsWith("os400")) { + // Avoid the names such as os4000 + if (osName.length() <= 5 || !Character.isDigit(osName.charAt(5))) { + return "os400"; + } + } + if (osName.startsWith("linux")) { + return "linux"; + } + if (osName.startsWith("macosx") || osName.startsWith("osx")) { + return "osx"; + } + if (osName.startsWith("freebsd")) { + return "freebsd"; + } + if (osName.startsWith("openbsd")) { + return "openbsd"; + } + if (osName.startsWith("netbsd")) { + return "netbsd"; + } + if (osName.startsWith("solaris") || osName.startsWith("sunos")) { + return "sunos"; + } + if (osName.startsWith("windows")) { + return "windows"; + } + + return UNKNOWN; + } + + private static String normalizeArch(final String osArch) { + if (osArch.matches("^(x8664|amd64|ia32e|em64t|x64)$")) { + return "x86_64"; + } + if (osArch.matches("^(x8632|x86|i[3-6]86|ia32|x32)$")) { + return "x86_32"; + } + if (osArch.matches("^(ia64w?|itanium64)$")) { + return "itanium_64"; + } + if ("ia64n".equals(osArch)) { + return "itanium_32"; + } + if (osArch.matches("^(sparc|sparc32)$")) { + return "sparc_32"; + } + if (osArch.matches("^(sparcv9|sparc64)$")) { + return "sparc_64"; + } + if (osArch.matches("^(arm|arm32)$")) { + return "arm_32"; + } + if ("aarch64".equals(osArch)) { + return "aarch_64"; + } + if (osArch.matches("^(mips|mips32)$")) { + return "mips_32"; + } + if (osArch.matches("^(mipsel|mips32el)$")) { + return "mipsel_32"; + } + if ("mips64".equals(osArch)) { + return "mips_64"; + } + if ("mips64el".equals(osArch)) { + return "mipsel_64"; + } + if (osArch.matches("^(ppc|ppc32)$")) { + return "ppc_32"; + } + if (osArch.matches("^(ppcle|ppc32le)$")) { + return "ppcle_32"; + } + if ("ppc64".equals(osArch)) { + return "ppc_64"; + } + if ("ppc64le".equals(osArch)) { + return "ppcle_64"; + } + if ("s390".equals(osArch)) { + return "s390_32"; + } + if ("s390x".equals(osArch)) { + return "s390_64"; + } + + return UNKNOWN; + } + + static String normalizeVM(final String javaVendor, final String javaVmName) { + if (javaVmName.contains("graalvm")) { + return "graalvm"; + } + if (javaVendor.contains("oracle")) { + if (javaVmName.contains("openjdk")) { + return "oracle_openjdk"; + } else { + return "oracle"; + } + } + if (javaVendor.contains("adoptopenjdk")) { + return "adoptopenjdk"; + } + if (javaVendor.contains("openj9")) { + return "openj9"; + } + if (javaVendor.contains("azul")) { + if (javaVmName.contains("zing")) { + return "zing"; + } else { + return "zulu"; + } + } + + return "-" + javaVendor + "-" + javaVmName; + } + + static String normalizeJavaVersion(final String javaVersion) { + // These are already normalized. + return System.getProperty(javaVersion); + } + + private static String normalize(final String value) { + if (value == null) { + return ""; + } + return System.getProperty(value).toLowerCase(Locale.US).replaceAll("[^a-z0-9]+", ""); + } +} diff --git a/pantheon/src/test/java/tech/pegasys/pantheon/PantheonInfoTest.java b/pantheon/src/test/java/tech/pegasys/pantheon/PantheonInfoTest.java new file mode 100644 index 0000000000..f94b2bd4ee --- /dev/null +++ b/pantheon/src/test/java/tech/pegasys/pantheon/PantheonInfoTest.java @@ -0,0 +1,31 @@ +/* + * Copyright 2018 ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package tech.pegasys.pantheon; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.Test; + +public final class PantheonInfoTest { + + /** + * Ethstats wants a version string like <foo>/v<bar>/<baz>/<bif>. Foo is the + * client identity (pantheon, Geth, Parity, etc). Bar is the version, in semantic version form + * (1.2.3-whatever), baz is OS and chip architecture, and bif is "compiler" - which we use as JVM + * info. + */ + @Test + public void versionStringIsEthstatsFriendly() { + assertThat(PantheonInfo.version()).matches("[^/]+/v(\\d+\\.\\d+\\.\\d+[^/]*|null)/[^/]+/[^/]+"); + } +} diff --git a/services/kvstore/build.gradle b/services/kvstore/build.gradle index 80b6a3ca1b..25daf5706b 100644 --- a/services/kvstore/build.gradle +++ b/services/kvstore/build.gradle @@ -16,8 +16,12 @@ apply plugin: 'java-library' jar { baseName 'pantheon-kvstore' manifest { - attributes('Implementation-Title': baseName, - 'Implementation-Version': project.version) + attributes( + 'Specification-Title': baseName, + 'Specification-Version': project.version, + 'Implementation-Title': baseName, + 'Implementation-Version': calculateVersion() + ) } } diff --git a/testutil/build.gradle b/testutil/build.gradle index 640372939e..2cf3769675 100644 --- a/testutil/build.gradle +++ b/testutil/build.gradle @@ -16,8 +16,12 @@ apply plugin: 'java-library' jar { baseName 'pantheon-util-test' manifest { - attributes('Implementation-Title': baseName, - 'Implementation-Version': project.version) + attributes( + 'Specification-Title': baseName, + 'Specification-Version': project.version, + 'Implementation-Title': baseName, + 'Implementation-Version': calculateVersion() + ) } } diff --git a/util/build.gradle b/util/build.gradle index 3da2740f1a..6fab3b01c4 100644 --- a/util/build.gradle +++ b/util/build.gradle @@ -16,8 +16,12 @@ apply plugin: 'java-library' jar { baseName 'pantheon-util' manifest { - attributes('Implementation-Title': baseName, - 'Implementation-Version': project.version) + attributes( + 'Specification-Title': baseName, + 'Specification-Version': project.version, + 'Implementation-Title': baseName, + 'Implementation-Version': calculateVersion() + ) } }