Repository: GradleUp/shadow
Pull request #1100 (2024-12-11 04:26:26)

Convert some functional test kits to Kotlin
Refs #1087.


Patch:
diff --git a/build.gradle.kts b/build.gradle.kts
index 8cdb3d17..42fbbde7 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -86,6 +86,7 @@ dependencies {
     exclude(group = "org.hamcrest")
   }
   funcTestImplementation(sourceSets.main.get().output)
+  funcTestImplementation(intiTest.output)
 
   lintChecks(libs.androidx.gradlePluginLints)
   lintChecks(libs.assertk.lint)


Test patch:
diff --git a/src/funcTest/groovy/com/github/jengelman/gradle/plugins/shadow/util/AppendableJar.groovy b/src/funcTest/groovy/com/github/jengelman/gradle/plugins/shadow/util/AppendableJar.groovy
deleted file mode 100644
index b6fad701..00000000
--- a/src/funcTest/groovy/com/github/jengelman/gradle/plugins/shadow/util/AppendableJar.groovy
+++ /dev/null
@@ -1,25 +0,0 @@
-package com.github.jengelman.gradle.plugins.shadow.util
-
-class AppendableJar {
-
-    Map<String, String> contents = [:]
-    File file
-
-    AppendableJar(File file) {
-        this.file = file
-    }
-
-    AppendableJar insertFile(String path, String content) {
-        contents[path] = content
-        return this
-    }
-
-    File write() {
-        JarBuilder builder = new JarBuilder(file.newOutputStream())
-        contents.each { path, contents ->
-            builder.withFile(path, contents)
-        }
-        builder.build()
-        return file
-    }
-}
diff --git a/src/funcTest/groovy/com/github/jengelman/gradle/plugins/shadow/util/HashUtil.java b/src/funcTest/groovy/com/github/jengelman/gradle/plugins/shadow/util/HashUtil.java
deleted file mode 100644
index b4cb8404..00000000
--- a/src/funcTest/groovy/com/github/jengelman/gradle/plugins/shadow/util/HashUtil.java
+++ /dev/null
@@ -1,93 +0,0 @@
-package com.github.jengelman.gradle.plugins.shadow.util;
-
-import org.gradle.api.UncheckedIOException;
-import org.gradle.internal.UncheckedException;
-
-import java.io.ByteArrayInputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-
-public class HashUtil {
-    public static HashValue createHash(File file, String algorithm) {
-        try {
-            return createHash(new FileInputStream(file), algorithm);
-        } catch (UncheckedIOException e) {
-            // Catch any unchecked io exceptions and add the file path for troubleshooting
-            throw new UncheckedIOException(String.format("Failed to create %s hash for file %s.", algorithm, file.getAbsolutePath()), e.getCause());
-        } catch (FileNotFoundException e) {
-            throw new UncheckedIOException(e);
-        }
-    }
-
-    private static HashValue createHash(InputStream instr, String algorithm) {
-        MessageDigest messageDigest;
-        try {
-            messageDigest = createMessageDigest(algorithm);
-            byte[] buffer = new byte[4096];
-            try {
-                while (true) {
-                    int nread = instr.read(buffer);
-                    if (nread < 0) {
-                        break;
-                    }
-                    messageDigest.update(buffer, 0, nread);
-                }
-            } finally {
-                instr.close();
-            }
-        } catch (IOException e) {
-            throw new UncheckedIOException(e);
-        }
-        return new HashValue(messageDigest.digest());
-    }
-
-    private static MessageDigest createMessageDigest(String algorithm) {
-        try {
-            return MessageDigest.getInstance(algorithm);
-        } catch (NoSuchAlgorithmException e) {
-            throw UncheckedException.throwAsUncheckedException(e);
-        }
-    }
-
-    public static HashValue sha1(byte[] bytes) {
-        return createHash(new ByteArrayInputStream(bytes), "SHA1");
-    }
-
-    public static HashValue sha1(InputStream inputStream) {
-        return createHash(inputStream, "SHA1");
-    }
-
-    public static HashValue md5(File file) {
-        return createHash(file, "MD5");
-    }
-
-    public static HashValue sha1(File file) {
-        return createHash(file, "SHA1");
-    }
-
-    public static HashValue sha256(byte[] bytes) {
-        return createHash(new ByteArrayInputStream(bytes), "SHA-256");
-    }
-
-    public static HashValue sha256(InputStream inputStream) {
-        return createHash(inputStream, "SHA-256");
-    }
-
-    public static HashValue sha256(File file) {
-        return createHash(file, "SHA-256");
-    }
-
-    public static HashValue sha512(InputStream inputStream) {
-        return createHash(inputStream, "SHA-512");
-    }
-
-    public static HashValue sha512(File file) {
-        return createHash(file, "SHA-512");
-    }
-}
-
diff --git a/src/funcTest/groovy/com/github/jengelman/gradle/plugins/shadow/util/HashValue.java b/src/funcTest/groovy/com/github/jengelman/gradle/plugins/shadow/util/HashValue.java
deleted file mode 100644
index 967afe7e..00000000
--- a/src/funcTest/groovy/com/github/jengelman/gradle/plugins/shadow/util/HashValue.java
+++ /dev/null
@@ -1,75 +0,0 @@
-package com.github.jengelman.gradle.plugins.shadow.util;
-
-import java.math.BigInteger;
-
-public class HashValue {
-    private final BigInteger digest;
-
-    public HashValue(byte[] digest) {
-        this.digest = new BigInteger(1, digest);
-    }
-
-    public HashValue(String hexString) {
-        this.digest = new BigInteger(hexString, 16);
-    }
-
-    public static HashValue parse(String inputString) {
-        if (inputString == null || inputString.isEmpty()) {
-            return null;
-        }
-        return new HashValue(parseInput(inputString));
-    }
-
-    private static String parseInput(String inputString) {
-        if (inputString == null) {
-            return null;
-        }
-        String cleaned = inputString.trim().toLowerCase();
-        int spaceIndex = cleaned.indexOf(' ');
-        if (spaceIndex != -1) {
-            String firstPart = cleaned.substring(0, spaceIndex);
-            if (firstPart.startsWith("md") || firstPart.startsWith("sha")) {
-                cleaned = cleaned.substring(cleaned.lastIndexOf(' ') + 1);
-            } else if (firstPart.endsWith(":")) {
-                cleaned = cleaned.substring(spaceIndex + 1).replace(" ", "");
-            } else {
-                cleaned = cleaned.substring(0, spaceIndex);
-            }
-        }
-        return cleaned;
-    }
-
-    public String asCompactString() {
-        return digest.toString(36);
-    }
-
-    public String asHexString() {
-        return digest.toString(16);
-    }
-
-    public byte[] asByteArray() {
-        return digest.toByteArray();
-    }
-
-    public BigInteger asBigInteger() {
-        return digest;
-    }
-
-    @Override
-    public boolean equals(Object other) {
-        if (this == other) {
-            return true;
-        }
-        if (!(other instanceof HashValue)) {
-            return false;
-        }
-
-        HashValue otherHashValue = (HashValue) other;
-        return digest.equals(otherHashValue.digest);
-    }
-
-    @Override
-    public int hashCode() {
-        return digest.hashCode();
-    }
-}
diff --git a/src/funcTest/groovy/com/github/jengelman/gradle/plugins/shadow/util/JarBuilder.groovy b/src/funcTest/groovy/com/github/jengelman/gradle/plugins/shadow/util/JarBuilder.groovy
deleted file mode 100644
index b24eb994..00000000
--- a/src/funcTest/groovy/com/github/jengelman/gradle/plugins/shadow/util/JarBuilder.groovy
+++ /dev/null
@@ -1,51 +0,0 @@
-package com.github.jengelman.gradle.plugins.shadow.util
-
-import org.codehaus.plexus.util.IOUtil
-
-import java.util.jar.JarEntry
-import java.util.jar.JarOutputStream
-
-class JarBuilder {
-
-    List<String> entries = []
-    JarOutputStream jos
-
-    JarBuilder(OutputStream os) {
-        jos = new JarOutputStream(os)
-    }
-
-    private void addDirectory(String name) {
-        if (!entries.contains(name)) {
-            if (name.lastIndexOf('/') > 0) {
-                String parent = name.substring(0, name.lastIndexOf('/'))
-                if (!entries.contains(parent)) {
-                    addDirectory(parent)
-                }
-            }
-
-            // directory entries must end in "/"
-            JarEntry entry = new JarEntry(name + "/")
-            jos.putNextEntry(entry)
-
-            entries.add(name)
-        }
-    }
-
-    JarBuilder withFile(String path, String data) {
-        def idx = path.lastIndexOf('/')
-        if (idx != -1) {
-            addDirectory(path.substring(0, idx))
-        }
-        if (!entries.contains(path)) {
-            JarEntry entry = new JarEntry(path)
-            jos.putNextEntry(entry)
-            entries << path
-            IOUtil.copy(data.bytes, jos)
-        }
-        return this
-    }
-
-    void build() {
-        jos.close()
-    }
-}
diff --git a/src/funcTest/groovy/com/github/jengelman/gradle/plugins/shadow/util/repo/AbstractModule.groovy b/src/funcTest/groovy/com/github/jengelman/gradle/plugins/shadow/util/repo/AbstractModule.groovy
deleted file mode 100644
index 8b99bf01..00000000
--- a/src/funcTest/groovy/com/github/jengelman/gradle/plugins/shadow/util/repo/AbstractModule.groovy
+++ /dev/null
@@ -1,71 +0,0 @@
-package com.github.jengelman.gradle.plugins.shadow.util.repo
-
-import com.github.jengelman.gradle.plugins.shadow.util.HashUtil
-
-abstract class AbstractModule {
-    /**
-     * @param cl A closure that is passed a writer to use to generate the content.
-     */
-    protected void publish(File file, Closure cl) {
-        def hashBefore = file.exists() ? getHash(file, "sha1") : null
-        def tmpFile = file.parentFile.resolve("${file.name}.tmp")
-
-        tmpFile.withWriter("utf-8") {
-            cl.call(it)
-        }
-
-        def hashAfter = getHash(tmpFile, "sha1")
-        if (hashAfter == hashBefore) {
-            // Already published
-            return
-        }
-
-        assert !file.exists() || file.delete()
-        assert tmpFile.renameTo(file)
-        onPublish(file)
-    }
-
-    protected void publishWithStream(File file, Closure cl) {
-        def hashBefore = file.exists() ? getHash(file, "sha1") : null
-        def tmpFile = file.parentFile.resolve("${file.name}.tmp")
-
-        tmpFile.withOutputStream {
-            cl.call(it)
-        }
-
-        def hashAfter = getHash(tmpFile, "sha1")
-        if (hashAfter == hashBefore) {
-            // Already published
-            return
-        }
-
-        assert !file.exists() || file.delete()
-        assert tmpFile.renameTo(file)
-        onPublish(file)
-    }
-
-    protected abstract onPublish(File file)
-
-    static File sha1File(File file) {
-        hashFile(file, "sha1", 40)
-    }
-
-    static File md5File(File file) {
-        hashFile(file, "md5", 32)
-    }
-
-    private static File hashFile(File file, String algorithm, int len) {
-        def hashFile = getHashFile(file, algorithm)
-        def hash = getHash(file, algorithm)
-        hashFile.text = String.format("%0${len}x", hash)
-        return hashFile
-    }
-
-    private static File getHashFile(File file, String algorithm) {
-        file.parentFile.resolve("${file.name}.${algorithm}")
-    }
-
-    private static BigInteger getHash(File file, String algorithm) {
-        HashUtil.createHash(file, algorithm.toUpperCase()).asBigInteger()
-    }
-}
diff --git a/src/funcTest/groovy/com/github/jengelman/gradle/plugins/shadow/util/repo/maven/AbstractMavenModule.groovy b/src/funcTest/groovy/com/github/jengelman/gradle/plugins/shadow/util/repo/maven/AbstractMavenModule.groovy
index aa3ec713..90dddd2f 100644
--- a/src/funcTest/groovy/com/github/jengelman/gradle/plugins/shadow/util/repo/maven/AbstractMavenModule.groovy
+++ b/src/funcTest/groovy/com/github/jengelman/gradle/plugins/shadow/util/repo/maven/AbstractMavenModule.groovy
@@ -56,8 +56,8 @@ abstract class AbstractMavenModule extends AbstractModule implements MavenModule
     }
 
     @Override
-    MavenModule dependsOn(String group, String artifactId, String version) {
-        this.dependencies << [groupId: group, artifactId: artifactId, version: version, type: type]
+    MavenModule dependsOn(String groupId, String artifactId, String version) {
+        this.dependencies << [groupId: groupId, artifactId: artifactId, version: version, type: type]
         return this
     }
 
@@ -111,12 +111,12 @@ abstract class AbstractMavenModule extends AbstractModule implements MavenModule
         updateRootMavenMetaData(rootMavenMetaData)
 
         if (publishesMetaDataFile()) {
-            publish(metaDataFile) { Writer writer ->
+            publishWithWriter(metaDataFile) { Writer writer ->
                 writer << getMetaDataFileContent()
             }
         }
 
-        publish(pomFile) { Writer writer ->
+        publishWithWriter(pomFile) { Writer writer ->
             def pomPackaging = packaging ?: type
             writer << """
             <project xmlns="http://maven.apache.org/POM/4.0.0">
@@ -160,7 +160,7 @@ abstract class AbstractMavenModule extends AbstractModule implements MavenModule
     private void updateRootMavenMetaData(File rootMavenMetaData) {
         def allVersions = rootMavenMetaData.exists() ? new XmlParser().parseText(rootMavenMetaData.text).versioning.versions.version*.value().flatten() : []
         allVersions << version
-        publish(rootMavenMetaData) { Writer writer ->
+        publishWithWriter(rootMavenMetaData) { Writer writer ->
             def builder = new MarkupBuilder(writer)
             builder.metadata {
                 groupId(groupId)
@@ -203,7 +203,7 @@ abstract class AbstractMavenModule extends AbstractModule implements MavenModule
         if (type == 'pom') {
             return artifactFile
         }
-        publish(artifactFile) { Writer writer ->
+        publishWithWriter(artifactFile) { Writer writer ->
             writer << "${artifactFile.name} : $artifactContent"
         }
         return artifactFile
diff --git a/src/funcTest/groovy/com/github/jengelman/gradle/plugins/shadow/util/repo/maven/MavenFileModule.groovy b/src/funcTest/groovy/com/github/jengelman/gradle/plugins/shadow/util/repo/maven/MavenFileModule.groovy
index fad253be..80d48222 100644
--- a/src/funcTest/groovy/com/github/jengelman/gradle/plugins/shadow/util/repo/maven/MavenFileModule.groovy
+++ b/src/funcTest/groovy/com/github/jengelman/gradle/plugins/shadow/util/repo/maven/MavenFileModule.groovy
@@ -1,5 +1,7 @@
 package com.github.jengelman.gradle.plugins.shadow.util.repo.maven
 
+import org.jetbrains.annotations.NotNull
+
 class MavenFileModule extends AbstractMavenModule {
     private boolean uniqueSnapshots = true
 
@@ -32,7 +34,7 @@ class MavenFileModule extends AbstractMavenModule {
     }
 
     @Override
-    protected onPublish(File file) {
+    protected void onPublish(@NotNull File file) {
         sha1File(file)
         md5File(file)
     }
diff --git a/src/funcTest/groovy/com/github/jengelman/gradle/plugins/shadow/util/repo/maven/MavenModule.groovy b/src/funcTest/groovy/com/github/jengelman/gradle/plugins/shadow/util/repo/maven/MavenModule.groovy
deleted file mode 100644
index dd74383e..00000000
--- a/src/funcTest/groovy/com/github/jengelman/gradle/plugins/shadow/util/repo/maven/MavenModule.groovy
+++ /dev/null
@@ -1,20 +0,0 @@
-package com.github.jengelman.gradle.plugins.shadow.util.repo.maven
-
-interface MavenModule {
-    /**
-     * Publishes the pom.xml plus main artifact, plus any additional artifacts for this module. Publishes only those artifacts whose content has
-     * changed since the last call to {@code #publish()}.
-     */
-    MavenModule publish()
-
-    /**
-     * Publishes the pom.xml only
-     */
-    MavenModule publishPom()
-
-    MavenModule dependsOn(String group, String artifactId, String version)
-
-    File getPomFile()
-
-    File getMetaDataFile()
-}
diff --git a/src/funcTest/groovy/com/github/jengelman/gradle/plugins/shadow/util/repo/maven/MavenRepository.groovy b/src/funcTest/groovy/com/github/jengelman/gradle/plugins/shadow/util/repo/maven/MavenRepository.groovy
deleted file mode 100644
index 614b083b..00000000
--- a/src/funcTest/groovy/com/github/jengelman/gradle/plugins/shadow/util/repo/maven/MavenRepository.groovy
+++ /dev/null
@@ -1,12 +0,0 @@
-package com.github.jengelman.gradle.plugins.shadow.util.repo.maven
-
-/**
- * A fixture for dealing with Maven repositories.
- */
-interface MavenRepository {
-    URI getUri()
-
-    MavenModule module(String groupId, String artifactId)
-
-    MavenModule module(String groupId, String artifactId, String version)
-}
diff --git a/src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/util/AppendableJar.kt b/src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/util/AppendableJar.kt
new file mode 100644
index 00000000..798a06bf
--- /dev/null
+++ b/src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/util/AppendableJar.kt
@@ -0,0 +1,20 @@
+package com.github.jengelman.gradle.plugins.shadow.util
+
+import java.io.File
+
+class AppendableJar(private val file: File) {
+  private val contents = mutableMapOf<String, String>()
+
+  fun insertFile(path: String, content: String): AppendableJar = apply {
+    contents[path] = content
+  }
+
+  fun write(): File {
+    val builder = JarBuilder(file.outputStream())
+    contents.forEach { (path, content) ->
+      builder.withFile(path, content)
+    }
+    builder.build()
+    return file
+  }
+}
diff --git a/src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/util/HashUtil.kt b/src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/util/HashUtil.kt
new file mode 100644
index 00000000..492a8a52
--- /dev/null
+++ b/src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/util/HashUtil.kt
@@ -0,0 +1,54 @@
+package com.github.jengelman.gradle.plugins.shadow.util
+import java.io.File
+import java.io.FileInputStream
+import java.io.FileNotFoundException
+import java.io.IOException
+import java.io.InputStream
+import java.io.UncheckedIOException
+import java.security.MessageDigest
+import java.security.NoSuchAlgorithmException
+import org.gradle.internal.UncheckedException
+
+object HashUtil {
+  fun createHash(file: File, algorithm: String): HashValue {
+    try {
+      return createHash(FileInputStream(file), algorithm)
+    } catch (e: UncheckedIOException) {
+      // Catch any unchecked io exceptions and add the file path for troubleshooting
+      throw UncheckedIOException(
+        "Failed to create $algorithm hash for file ${file.absolutePath}.",
+        e.cause,
+      )
+    } catch (e: FileNotFoundException) {
+      throw UncheckedIOException(e)
+    }
+  }
+
+  private fun createHash(inputStream: InputStream, algorithm: String): HashValue {
+    val messageDigest: MessageDigest
+    try {
+      messageDigest = createMessageDigest(algorithm)
+      val buffer = ByteArray(4096)
+      inputStream.use {
+        while (true) {
+          val nread = it.read(buffer)
+          if (nread < 0) {
+            break
+          }
+          messageDigest.update(buffer, 0, nread)
+        }
+      }
+    } catch (e: IOException) {
+      throw UncheckedIOException(e)
+    }
+    return HashValue(messageDigest.digest())
+  }
+
+  private fun createMessageDigest(algorithm: String): MessageDigest {
+    try {
+      return MessageDigest.getInstance(algorithm)
+    } catch (e: NoSuchAlgorithmException) {
+      throw UncheckedException.throwAsUncheckedException(e)
+    }
+  }
+}
diff --git a/src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/util/HashValue.kt b/src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/util/HashValue.kt
new file mode 100644
index 00000000..821a0953
--- /dev/null
+++ b/src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/util/HashValue.kt
@@ -0,0 +1,7 @@
+package com.github.jengelman.gradle.plugins.shadow.util
+
+import java.math.BigInteger
+
+data class HashValue(val digest: BigInteger) {
+  constructor(digest: ByteArray) : this(BigInteger(1, digest))
+}
diff --git a/src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/util/JarBuilder.kt b/src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/util/JarBuilder.kt
new file mode 100644
index 00000000..d4c81cab
--- /dev/null
+++ b/src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/util/JarBuilder.kt
@@ -0,0 +1,42 @@
+package com.github.jengelman.gradle.plugins.shadow.util
+
+import java.io.OutputStream
+import java.util.jar.JarEntry
+import java.util.jar.JarOutputStream
+
+class JarBuilder(os: OutputStream) {
+  private val entries = mutableListOf<String>()
+  private val jos = JarOutputStream(os)
+
+  private fun addDirectory(name: String) {
+    if (!entries.contains(name)) {
+      val parent = name.substringBeforeLast('/', "")
+      if (parent.isNotEmpty() && !entries.contains(parent)) {
+        addDirectory(parent)
+      }
+
+      // directory entries must end in "/"
+      val entry = JarEntry("$name/")
+      jos.putNextEntry(entry)
+      entries.add(name)
+    }
+  }
+
+  fun withFile(path: String, data: String): JarBuilder {
+    val idx = path.lastIndexOf('/')
+    if (idx != -1) {
+      addDirectory(path.substring(0, idx))
+    }
+    if (!entries.contains(path)) {
+      val entry = JarEntry(path)
+      jos.putNextEntry(entry)
+      entries.add(path)
+      data.byteInputStream().use { it.copyTo(jos) }
+    }
+    return this
+  }
+
+  fun build() {
+    jos.close()
+  }
+}
diff --git a/src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/util/repo/AbstractModule.kt b/src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/util/repo/AbstractModule.kt
new file mode 100644
index 00000000..51d740e5
--- /dev/null
+++ b/src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/util/repo/AbstractModule.kt
@@ -0,0 +1,63 @@
+package com.github.jengelman.gradle.plugins.shadow.util.repo
+
+import com.github.jengelman.gradle.plugins.shadow.util.HashUtil
+import java.io.File
+import java.io.OutputStream
+import java.io.Writer
+import java.math.BigInteger
+
+abstract class AbstractModule {
+
+  protected abstract fun onPublish(file: File)
+
+  protected fun publishWithWriter(file: File, action: (Writer) -> Unit) {
+    publishCommon(file) { it.writer().use(action) }
+  }
+
+  protected fun publishWithStream(file: File, action: (OutputStream) -> Unit) {
+    publishCommon(file) { it.outputStream().use(action) }
+  }
+
+  private fun publishCommon(file: File, action: (File) -> Unit) {
+    val hashBefore = if (file.exists()) getHash(file, "sha1") else null
+    val tempFile = file.resolveSibling("${file.name}.tmp")
+    action(tempFile)
+
+    val hashAfter = getHash(tempFile, "sha1")
+    if (hashAfter == hashBefore) {
+      // Already published
+      return
+    }
+
+    check(!file.exists() || file.delete())
+    check(tempFile.renameTo(file))
+    onPublish(file)
+  }
+
+  companion object {
+    @JvmStatic
+    fun sha1File(file: File): File {
+      return hashFile(file, "sha1", 40)
+    }
+
+    @JvmStatic
+    fun md5File(file: File): File {
+      return hashFile(file, "md5", 32)
+    }
+
+    private fun hashFile(file: File, algorithm: String, len: Int): File {
+      val hashFile = getHashFile(file, algorithm)
+      val hash = getHash(file, algorithm)
+      hashFile.writeText("$hash${len}x")
+      return hashFile
+    }
+
+    private fun getHashFile(file: File, algorithm: String): File {
+      return File(file.parentFile, "${file.name}.$algorithm")
+    }
+
+    private fun getHash(file: File, algorithm: String): BigInteger {
+      return HashUtil.createHash(file, algorithm.uppercase()).digest
+    }
+  }
+}
diff --git a/src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/util/repo/maven/MavenModule.kt b/src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/util/repo/maven/MavenModule.kt
new file mode 100644
index 00000000..b77d2588
--- /dev/null
+++ b/src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/util/repo/maven/MavenModule.kt
@@ -0,0 +1,22 @@
+package com.github.jengelman.gradle.plugins.shadow.util.repo.maven
+
+import java.io.File
+
+interface MavenModule {
+  /**
+   * Publishes the `pom.xml` plus main artifact, plus any additional artifacts for this module.
+   * Publishes only those artifacts whose content has changed since the last call to `publish()`.
+   */
+  fun publish(): MavenModule
+
+  /**
+   * Publishes the `pom.xml` only
+   */
+  fun publishPom(): MavenModule
+
+  fun dependsOn(groupId: String, artifactId: String, version: String): MavenModule
+
+  val pomFile: File
+
+  val metaDataFile: File
+}
diff --git a/src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/util/repo/maven/MavenRepository.kt b/src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/util/repo/maven/MavenRepository.kt
new file mode 100644
index 00000000..a128e07f
--- /dev/null
+++ b/src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/util/repo/maven/MavenRepository.kt
@@ -0,0 +1,14 @@
+package com.github.jengelman.gradle.plugins.shadow.util.repo.maven
+
+import java.net.URI
+
+/**
+ * A fixture for dealing with Maven repositories.
+ */
+interface MavenRepository {
+  val uri: URI
+
+  fun module(groupId: String, artifactId: String): MavenModule
+
+  fun module(groupId: String, artifactId: String, version: String): MavenModule
+}