=== modified file 'build.gradle'
--- old/build.gradle	2026-08-06 05:02:07 +0000
+++ new/build.gradle	2026-08-06 05:12:25 +0000
@@ -791,13 +791,7 @@
 // ============================================================================
 def aopLtwHome = 'com/goldencode/p2j/aspects/ltw'
 
-tasks.named('jar') {
-    dependsOn 'prepareClassesAop', 'makeManifests', 'copyDependencies',
-              'mergePostgreSQLUDFs', 'mergeMariaDBUDFs', 'mergeSqlServerUDFs',
-              'copyToolsScripts', 'fwdspiJar', 'fwdaopltwJar',
-              'createCompileClasspathJar', 'createTestsClasspathJar'
-    archiveFileName = 'p2j.jar'
-    destinationDirectory = file("${layout.buildDirectory.get()}/lib")
+def p2jJarContent = copySpec {
     from("${layout.buildDirectory.get()}/classes.aop") {
         include 'com/goldencode/**'
         include 'rules/**'
@@ -835,7 +829,6 @@
         include 'org.slf4j.spi.SLF4JServiceProvider'
         into 'META-INF/services'
     }
-    duplicatesStrategy = DuplicatesStrategy.INCLUDE
     // UDF SQL files
     from("${layout.buildDirectory.get()}") {
         include 'udf/**/udfs.sql'
@@ -846,6 +839,17 @@
     from("${layout.buildDirectory.get()}") {
         include 'tools/scripts/*.sql'
     }
+}
+
+tasks.named('jar') {
+    dependsOn 'prepareClassesAop', 'makeManifests', 'copyDependencies',
+              'mergePostgreSQLUDFs', 'mergeMariaDBUDFs', 'mergeSqlServerUDFs',
+              'copyToolsScripts', 'fwdspiJar', 'fwdaopltwJar',
+              'createCompileClasspathJar', 'createTestsClasspathJar'
+    archiveFileName = 'p2j.jar'
+    destinationDirectory = file("${layout.buildDirectory.get()}/lib")
+    with p2jJarContent
+    duplicatesStrategy = DuplicatesStrategy.INCLUDE
     // Manifest must be set at execution time because the version string is
     // computed by the buildVersion task
     doFirst {
@@ -859,6 +863,25 @@
     }
 }
 
+['Client', 'Server'].each { dist ->
+    tasks.register("p2jJar${dist}", Jar) {
+        dependsOn 'jar'
+        archiveFileName = 'p2j.jar'
+        destinationDirectory = file("${layout.buildDirectory.get()}/lib.${dist.toLowerCase()}")
+        with p2jJarContent
+        duplicatesStrategy = DuplicatesStrategy.INCLUDE
+        doFirst {
+            manifest {
+                attributes(
+                    'Implementation-Version': ant.properties['fwdver'],
+                    'Main-Class': 'com.goldencode.p2j.Version',
+                    'Class-Path': configurations.getByName("fwd${dist}").collect { it.name }.join(' ')
+                )
+            }
+        }
+    }
+}
+
 tasks.register('fwdspiJar', Jar) {
     dependsOn 'prepareClassesAop', 'makeManifests'
     archiveFileName = 'fwdspi.jar'
@@ -1081,7 +1104,8 @@
 }
 
 tasks.register('archiveServer', Zip) {
-    dependsOn 'allJars', 'buildVersion', 'distribution', 'sheet:war', 'nativeCompile'
+    dependsOn 'allJars', 'buildVersion', 'distribution', 'sheet:war', 'nativeCompile',
+              'p2jJarServer', 'verifyDistributions'
     doFirst {
         def ver = ant.properties['fwdver'] ?: 'undefined'
         def parts = ver.tokenize('_')
@@ -1094,9 +1118,13 @@
     }
     def libname = isWindows ? 'p2j.dll' : 'libp2j.so'
     destinationDirectory = file(distDir)
+    from("${layout.buildDirectory.get()}/lib.server") {
+        into 'lib'
+        include 'p2j.jar'
+    }
     from("${layout.buildDirectory.get()}/lib") {
         into 'lib'
-        include 'p2j.jar', 'fwdaopltw.jar', 'fwd_sheet.war', libname
+        include 'fwdaopltw.jar', 'fwd_sheet.war', libname
     }
     from("${layout.buildDirectory.get()}/lib") {
         into 'lib/spi'
@@ -1106,7 +1134,8 @@
 }
 
 tasks.register('archiveClient', Zip) {
-    dependsOn 'allJars', 'buildVersion', 'distribution', 'nativeCompile'
+    dependsOn 'allJars', 'buildVersion', 'distribution', 'nativeCompile',
+              'p2jJarClient', 'verifyDistributions'
     doFirst {
         def ver = ant.properties['fwdver'] ?: 'undefined'
         def parts = ver.tokenize('_')
@@ -1119,9 +1148,13 @@
     }
     def libname = isWindows ? 'p2j.dll' : 'libp2j.so'
     destinationDirectory = file(distDir)
+    from("${layout.buildDirectory.get()}/lib.client") {
+        into 'lib'
+        include 'p2j.jar'
+    }
     from("${layout.buildDirectory.get()}/lib") {
         into 'lib'
-        include 'p2j.jar', libname
+        include libname
     }
     from("${distDir}/client") { into 'lib' }
 }
@@ -1207,6 +1240,34 @@
     from("${distDir}/docs") { include 'api/**' }
 }
 
+tasks.register('verifyDistributions') {
+    dependsOn 'jar', 'p2jJarClient', 'p2jJarServer', 'distribution'
+    doLast {
+        def problems = []
+        def check = { String dist, File jarFile, File libSet ->
+            def classPath = new java.util.jar.JarFile(jarFile).withCloseable { jf ->
+                jf.manifest?.mainAttributes?.getValue('Class-Path') ?: ''
+            }
+            def available = (libSet.listFiles() ?: []).collect { it.name } as Set
+            def missing = classPath.tokenize(' ').findAll { !available.contains(it) }
+            if (missing) {
+                problems << "${dist}: ${jarFile} names ${missing.size()} JAR(s) absent from " +
+                            "${libSet}: ${missing.join(', ')}"
+            }
+        }
+        def buildLib = file("${layout.buildDirectory.get()}/lib")
+        check('convert', new File(buildLib, 'p2j.jar'), buildLib)
+        check('server', file("${layout.buildDirectory.get()}/lib.server/p2j.jar"),
+              file("${distDir}/server"))
+        check('client', file("${layout.buildDirectory.get()}/lib.client/p2j.jar"),
+              file("${distDir}/client"))
+        if (problems) {
+            throw new GradleException("The p2j.jar Class-Path manifest does not match the " +
+                                      "distribution it is shipped in:\n" + problems.join('\n'))
+        }
+    }
+}
+
 tasks.register('archiveAll') {
     dependsOn 'archiveConvert', 'archiveServer', 'archiveClient',
               'archiveNative', 'archiveSpawner', 'archiveAdmin', 'archiveDocs'
@@ -2514,3 +2575,6 @@
 tasks.named('archiveAdmin') { mustRunAfter 'clean' }
 tasks.named('archiveDocs') { mustRunAfter 'clean' }
 tasks.named('archiveAll') { mustRunAfter 'clean' }
+tasks.named('p2jJarClient') { mustRunAfter 'clean' }
+tasks.named('p2jJarServer') { mustRunAfter 'clean' }
+tasks.named('verifyDistributions') { mustRunAfter 'clean' }

