From a3000197a321eed216c9a1ae08cd327ec403f9c9 Mon Sep 17 00:00:00 2001 From: Ethan Wrasman Date: Thu, 11 Sep 2025 10:51:57 -0500 Subject: [PATCH 1/5] Update versions in ci.yml to allow it to build --- .github/workflows/ci.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7550a8a..1b6b16b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,22 +15,22 @@ jobs: timeout-minutes: 30 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v5 - name: Set up JDK - uses: actions/setup-java@v2 + uses: actions/setup-java@v5 with: java-version: 8 distribution: 'temurin' - - uses: burrunan/gradle-cache-action@cbdf4342ff988d143aa7a5aeceedffafb8c74bcf #v1.10 + - uses: burrunan/gradle-cache-action@663fbad34e03c8f12b27f4999ac46e3d90f87eca # v3.0 name: Build with Gradle with: arguments: build - name: Upload Test Results if: always() - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: Test Results Linux path: '**/test-results/**/*.xml' From bf3961229526899ea4f04aa0cd7357049a6bd9e4 Mon Sep 17 00:00:00 2001 From: Ethan Wrasman Date: Thu, 11 Sep 2025 09:52:02 -0500 Subject: [PATCH 2/5] Fix range generation to have a uniform distribution for all lengths With how the termination condition was being calculated, it was weighed heavily towards the longest length when no max length is provided. A maximum length is now calculated using DFS for any finite pattern. --- .../java/com/mifmif/common/regex/Generex.java | 136 ++++++++++++++---- .../com/mifmif/common/regex/KotlinTests.kt | 108 ++++++++++++++ 2 files changed, 215 insertions(+), 29 deletions(-) diff --git a/src/main/java/com/mifmif/common/regex/Generex.java b/src/main/java/com/mifmif/common/regex/Generex.java index 239a4db..d307a74 100644 --- a/src/main/java/com/mifmif/common/regex/Generex.java +++ b/src/main/java/com/mifmif/common/regex/Generex.java @@ -60,6 +60,13 @@ public class Generex implements Iterable { private Node rootNode; private boolean isTransactionNodeBuilt; + /** + * Determined possible minimum and maximum length of a regex by traversing the + * Automaton tree using depth first search. + */ + private Integer cachedMinLength; + private Integer cachedMaxLength; + /** * The maximum length a produced string for an infinite regex if {@link #random(int, int)} hasn't been given a max * length other than {@link Integer#MAX_VALUE}. @@ -317,7 +324,9 @@ public String random() { * See {@link #random(int, int)} */ public String random(int minLength) { - return random(minLength, automaton.isFinite() ? Integer.MAX_VALUE : DEFAULT_INFINITE_MAX_LENGTH); + calculateLengthBounds(); + int actualMaxLength = isInfinite() ? DEFAULT_INFINITE_MAX_LENGTH : cachedMaxLength; + return random(minLength, actualMaxLength); } /** @@ -342,8 +351,21 @@ public String random(int minLength) { * given range. Otherwise, see the {@code minLength} and {@code maxLength} docs. */ public String random(int minLength, int maxLength) { + calculateLengthBounds(); + + // Calculate actual valid range by comparing the regex and the user defined bounds + int actualMinLength = Math.max(minLength, cachedMinLength); + int actualMaxLength = Math.min(maxLength, isInfinite() ? maxLength : cachedMaxLength); + + // Pre-select target length uniformly from valid range + int targetLength; + if (actualMinLength > actualMaxLength) { + targetLength = actualMaxLength; + } else { + targetLength = actualMinLength + random.nextInt(actualMaxLength - actualMinLength + 1); + } - String result = prepareRandom("", automaton.getInitialState(), minLength, maxLength); + String result = prepareRandom("", automaton.getInitialState(), minLength, maxLength, targetLength); // Substring in case a length of 'maxLength + 1' is returned, which is possible if a smaller string can't be produced. return result.substring(0, Math.min(maxLength, result.length())); } @@ -357,19 +379,26 @@ public String random(int minLength, int maxLength) { * @param maxLength Maximum wanted length of produced string. * @return A string built from the accumulation of previous transitions. */ - private String prepareRandom(String currentMatch, State state, int minLength, int maxLength) { + private String prepareRandom(String currentMatch, State state, int minLength, int maxLength, int targetLength) { // Return a string of length 'maxLength + 1' to indicate a dead branch. - if (currentMatch.length() > maxLength) return currentMatch; + if (currentMatch.length() > maxLength || state.getTransitions().isEmpty()) return currentMatch; - if (state.isAccept() && shouldTerminate(currentMatch.length(), minLength, maxLength)) return currentMatch; + String returnValue = null; + + if (state.isAccept()) { + // Set the current match to the value to return, just in case this would happen to be the cloest match to + // the target length. + returnValue = currentMatch; + + if (currentMatch.length() == targetLength) return currentMatch; + } // Make a copy so the original set is never modified. Set possibleTransitions = new HashSet<>(state.getTransitions()); int totalWeightedTransitions = calculateTotalWeightedTransitions(possibleTransitions); - String returnValue = currentMatch; - + // Will never start as empty due to the initial if statement in the function. while (!possibleTransitions.isEmpty()) { Transition randomTransition = pickRandomWeightedTransition(possibleTransitions, totalWeightedTransitions); @@ -378,39 +407,88 @@ private String prepareRandom(String currentMatch, State state, int minLength, in possibleTransitions.remove(randomTransition); char randomChar = (char) (random.nextInt(subTransitions) + randomTransition.getMin()); - String result = prepareRandom(currentMatch + randomChar, randomTransition.getDest(), minLength, maxLength); + String result = prepareRandom(currentMatch + randomChar, randomTransition.getDest(), minLength, maxLength, targetLength); - // Greedily return the first valid result found. - if (minLength <= result.length() && result.length() <= maxLength) return result; + // Greedily return the first valid result found that is of the wanted length.. + if (result.length() == targetLength) return result; - // Continue to search for a valid result if the result is greater than the max length, or if the result is - // less than the minimum length. In the case a result never reaches the minimum length, return the longest - // match found. - if (returnValue.length() < result.length()) returnValue = result; + returnValue = getBestMatch(result, returnValue, minLength, maxLength, targetLength); } return returnValue; } /** - * Attempts to randomly terminate regexes in a way where a uniform distribution of lengths is produced by initially - * having a low probability of termination when close to the minimum length, and linearly increasing this - * probability as a regex nears its maximum requested length. - *
- * In practice this doesn't work well when an infinitely repeating part of the regex is located in the middle with a - * non-repeating terminal ending, but still works better than a flat chance of termination regardless of the range - * of lengths requested. + * Determines if the new generation is better than the current generation. + *

+ * The new generation is better if it is within the bounds and is closer to the target length than the current + * generation. Otherwise, the current generation is better. + * + * @param newMatch the new generation to compare against the current generation. + * @param currentMatch the current generation. + * @param min minimum length of the generated string. + * @param max maximum length of the generated string. + * @param target the target length of the generated string. + * @return the best match between the new generation and the current generation. + */ + private String getBestMatch(String newMatch, String currentMatch, int min, int max, int target) { + + if (currentMatch == null) return newMatch; + if (newMatch.length() > max && currentMatch.length() > min) return currentMatch; + + boolean newInRange = newMatch.length() >= min; + boolean currentInRange = currentMatch.length() >= min && currentMatch.length() <= max; + + if (newInRange && !currentInRange) return newMatch; + if (currentInRange && !newInRange) return currentMatch; + + int currentTargetDistance = Math.abs(currentMatch.length() - target); + int newTargetDistance = Math.abs(newMatch.length() - target); + + if (newTargetDistance < currentTargetDistance) return newMatch; + return currentMatch; + } + + /** + * Calculate the possible bounds of the generated string by traversing the regex + */ + private void calculateLengthBounds() { + if (cachedMinLength != null) return; + + int[] bounds = dfsLengthBounds(automaton.getInitialState(), new HashSet<>()); + cachedMinLength = bounds[0]; + cachedMaxLength = bounds[1]; + } + + /** + * Uses a depth first search to calculate the minimum and maximum length of the regex by + * traversing through the automaton tree. *
- * It is assumed `maxLength` is not an absurdly large value, as this could allow the regex to grow extremely long, - * and that `maxLength` is greater than `minLength` + * We can use DFS because the automaton is finite (does not contain infinite loops) and + * we need to visit every state regardless to determine the longest length. * - * @param depth Size of the current string produced to match a regex. - * @param minLength Minimum wanted length of the produced string. - * @param maxLength Maximum wanted length of the produced string. - * @return Whether the current string should be returned as a match for the regex. + * @param state the current state of the automaton. + * @param visited the set of visited states. + * @return an int array containing the minimum and maximum length of the regex. */ - private boolean shouldTerminate(int depth, int minLength, int maxLength) { - return depth >= minLength && random.nextInt(maxLength - depth + 1) == 0; + private int[] dfsLengthBounds(State state, Set visited) { + if (visited.contains(state)) return new int[]{Integer.MAX_VALUE, 0}; + + int minLength = state.isAccept() ? 0 : Integer.MAX_VALUE; + int maxLength = 0; + + visited.add(state); + + for (Transition transition : state.getTransitions()) { + int[] bounds = dfsLengthBounds(transition.getDest(), visited); + if (bounds[0] != Integer.MAX_VALUE) { + minLength = Math.min(minLength, bounds[0] + 1); + } + maxLength = Math.max(maxLength, bounds[1] + 1); + } + + visited.remove(state); + return new int[]{minLength, maxLength}; } /** diff --git a/src/test/kotlin/com/mifmif/common/regex/KotlinTests.kt b/src/test/kotlin/com/mifmif/common/regex/KotlinTests.kt index a1c7f2e..444bf9d 100644 --- a/src/test/kotlin/com/mifmif/common/regex/KotlinTests.kt +++ b/src/test/kotlin/com/mifmif/common/regex/KotlinTests.kt @@ -93,6 +93,100 @@ class KotlinTests { assertThat(ratio).isLessThan(1.1) } + @Test + fun `ranges of group generate with a semi-uniform distribution`() { + val regex = "(abcde){1,5}" + + val generex = Generex(regex) + val instancesMap = HashMap() + + repeat(100_000) { + + val result = generex.random(5, 25) + instancesMap[result] = instancesMap.getOrDefault(result, 0) + 1 + + assertThat(result).matches(regex) + } + + val sortedKeys = instancesMap.keys.sortedBy { it.length } + + + // Bounds are uniformly distributed + + var maxInstances = 0 + var minInstances = Int.MAX_VALUE + + println("Bounds:") + println("\t${sortedKeys.first()}: ${instancesMap[sortedKeys.first()]}") + maxInstances = max(maxInstances, instancesMap[sortedKeys.first()]!!) + minInstances = min(minInstances, instancesMap[sortedKeys.first()]!!) + println("\t${sortedKeys.last()}: ${instancesMap[sortedKeys.last()]}") + maxInstances = max(maxInstances, instancesMap[sortedKeys.last()]!!) + minInstances = min(minInstances, instancesMap[sortedKeys.last()]!!) + + var ratio = 1.0 * maxInstances / minInstances + assertThat(ratio).isLessThan(1.1) + + + // Middle range is uniformly distributed + + maxInstances = 0 + minInstances = Int.MAX_VALUE + + println("Middle Ranges:") + for (key in sortedKeys.subList(1, 4)) { + println("\t$key: ${instancesMap[key]}") + maxInstances = max(maxInstances, instancesMap[key]!!) + minInstances = min(minInstances, instancesMap[key]!!) + } + + ratio = 1.0 * maxInstances / minInstances + assertThat(ratio).isLessThan(1.1) + + + + } + + @ParameterizedTest + @MethodSource("rangeUniformDistributionArgs") + fun `range regexes generate with uniform distributions`(regex: String) { + + val generex = Generex(regex) + val instancesMap = HashMap() + + repeat(100_000) { + + val result = generex.random() + instancesMap[result.length] = instancesMap.getOrDefault(result.length, 0) + 1 + + assertThat(result).matches(regex) + } + + var maxInstances = 0 + var minInstances = Int.MAX_VALUE + + // Assumes all possible strings have actually been produced. + for ((key, instances) in instancesMap) { + println("$key: $instances") + maxInstances = max(maxInstances, instances) + minInstances = min(minInstances, instances) + } + + val ratio = 1.0 * maxInstances / minInstances + assertThat(ratio).isLessThan(1.1) + } + + @ParameterizedTest + @MethodSource("regexExceedsColumnValue") + fun `if regex must produce longer value, return value is trimmed`( + regex: String, + targetLength: Int, + ) { + val generated = Generex(regex).random(targetLength, targetLength) + + assertThat(generated.length).isEqualTo(targetLength) + } + companion object { @JvmStatic @@ -130,6 +224,20 @@ class KotlinTests { Arguments.of("[a-ce-gr-ux-z]", 1, 1), Arguments.of("123a*", 1, 10), Arguments.of("123a*", 5, 10), + Arguments.of("a*123", 5, 20), + ) + + @JvmStatic + fun rangeUniformDistributionArgs() = Stream.of( + Arguments.of("\\d{1,5}"), + Arguments.of("\\d{1,10}"), + ) + + @JvmStatic + fun regexExceedsColumnValue() = Stream.of( + Arguments.of("(hi){3,5}", 7), + Arguments.of("aaa", 2), + Arguments.of("a{5,10}", 2), ) } } From 22a541cb060d7d6c0df8be8549aa106abe8d1791 Mon Sep 17 00:00:00 2001 From: Ethan Wrasman Date: Thu, 11 Sep 2025 10:22:28 -0500 Subject: [PATCH 3/5] Rename packages to the pkware namespace --- .../com/{mifmif/common/regex => pkware/generex}/Generex.java | 2 +- .../common/regex => pkware/generex}/GenerexIterator.java | 2 +- .../java/com/{mifmif/common/regex => pkware/generex}/Main.java | 2 +- .../java/com/{mifmif/common/regex => pkware/generex}/Node.java | 2 +- .../common/regex => pkware/generex}/GenerexIteratorTest.java | 2 +- .../regex => pkware/generex}/GenerexIteratorUnitTest.java | 2 +- .../common/regex => pkware/generex}/GenerexRandomTest.java | 2 +- .../{mifmif/common/regex => pkware/generex}/GenerexTest.java | 2 +- .../common/regex => pkware/generex}/GenerexUnitTest.java | 2 +- .../com/{mifmif/common/regex => pkware/generex}/KotlinTests.kt | 3 ++- 10 files changed, 11 insertions(+), 10 deletions(-) rename src/main/java/com/{mifmif/common/regex => pkware/generex}/Generex.java (99%) rename src/main/java/com/{mifmif/common/regex => pkware/generex}/GenerexIterator.java (99%) rename src/main/java/com/{mifmif/common/regex => pkware/generex}/Main.java (98%) rename src/main/java/com/{mifmif/common/regex => pkware/generex}/Node.java (98%) rename src/test/java/com/{mifmif/common/regex => pkware/generex}/GenerexIteratorTest.java (98%) rename src/test/java/com/{mifmif/common/regex => pkware/generex}/GenerexIteratorUnitTest.java (99%) rename src/test/java/com/{mifmif/common/regex => pkware/generex}/GenerexRandomTest.java (98%) rename src/test/java/com/{mifmif/common/regex => pkware/generex}/GenerexTest.java (99%) rename src/test/java/com/{mifmif/common/regex => pkware/generex}/GenerexUnitTest.java (99%) rename src/test/kotlin/com/{mifmif/common/regex => pkware/generex}/KotlinTests.kt (99%) diff --git a/src/main/java/com/mifmif/common/regex/Generex.java b/src/main/java/com/pkware/generex/Generex.java similarity index 99% rename from src/main/java/com/mifmif/common/regex/Generex.java rename to src/main/java/com/pkware/generex/Generex.java index d307a74..81925c3 100644 --- a/src/main/java/com/mifmif/common/regex/Generex.java +++ b/src/main/java/com/pkware/generex/Generex.java @@ -16,7 +16,7 @@ * limitations under the License. */ -package com.mifmif.common.regex; +package com.pkware.generex; import dk.brics.automaton.Automaton; import dk.brics.automaton.RegExp; diff --git a/src/main/java/com/mifmif/common/regex/GenerexIterator.java b/src/main/java/com/pkware/generex/GenerexIterator.java similarity index 99% rename from src/main/java/com/mifmif/common/regex/GenerexIterator.java rename to src/main/java/com/pkware/generex/GenerexIterator.java index d1ec1c2..c2322b7 100644 --- a/src/main/java/com/mifmif/common/regex/GenerexIterator.java +++ b/src/main/java/com/pkware/generex/GenerexIterator.java @@ -15,7 +15,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.mifmif.common.regex; +package com.pkware.generex; import dk.brics.automaton.State; import dk.brics.automaton.Transition; diff --git a/src/main/java/com/mifmif/common/regex/Main.java b/src/main/java/com/pkware/generex/Main.java similarity index 98% rename from src/main/java/com/mifmif/common/regex/Main.java rename to src/main/java/com/pkware/generex/Main.java index 45d15b0..ace75e1 100644 --- a/src/main/java/com/mifmif/common/regex/Main.java +++ b/src/main/java/com/pkware/generex/Main.java @@ -15,7 +15,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.mifmif.common.regex; +package com.pkware.generex; /** * @author y.mifrah diff --git a/src/main/java/com/mifmif/common/regex/Node.java b/src/main/java/com/pkware/generex/Node.java similarity index 98% rename from src/main/java/com/mifmif/common/regex/Node.java rename to src/main/java/com/pkware/generex/Node.java index daee4d7..d12a253 100644 --- a/src/main/java/com/mifmif/common/regex/Node.java +++ b/src/main/java/com/pkware/generex/Node.java @@ -15,7 +15,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.mifmif.common.regex; +package com.pkware.generex; import java.util.ArrayList; import java.util.List; diff --git a/src/test/java/com/mifmif/common/regex/GenerexIteratorTest.java b/src/test/java/com/pkware/generex/GenerexIteratorTest.java similarity index 98% rename from src/test/java/com/mifmif/common/regex/GenerexIteratorTest.java rename to src/test/java/com/pkware/generex/GenerexIteratorTest.java index 8dbc592..f5eee0f 100644 --- a/src/test/java/com/mifmif/common/regex/GenerexIteratorTest.java +++ b/src/test/java/com/pkware/generex/GenerexIteratorTest.java @@ -1,4 +1,4 @@ -package com.mifmif.common.regex; +package com.pkware.generex; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; diff --git a/src/test/java/com/mifmif/common/regex/GenerexIteratorUnitTest.java b/src/test/java/com/pkware/generex/GenerexIteratorUnitTest.java similarity index 99% rename from src/test/java/com/mifmif/common/regex/GenerexIteratorUnitTest.java rename to src/test/java/com/pkware/generex/GenerexIteratorUnitTest.java index ba378fe..a49bb96 100644 --- a/src/test/java/com/mifmif/common/regex/GenerexIteratorUnitTest.java +++ b/src/test/java/com/pkware/generex/GenerexIteratorUnitTest.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.mifmif.common.regex; +package com.pkware.generex; import dk.brics.automaton.Automaton; import dk.brics.automaton.State; diff --git a/src/test/java/com/mifmif/common/regex/GenerexRandomTest.java b/src/test/java/com/pkware/generex/GenerexRandomTest.java similarity index 98% rename from src/test/java/com/mifmif/common/regex/GenerexRandomTest.java rename to src/test/java/com/pkware/generex/GenerexRandomTest.java index ed514fc..1aee061 100644 --- a/src/test/java/com/mifmif/common/regex/GenerexRandomTest.java +++ b/src/test/java/com/pkware/generex/GenerexRandomTest.java @@ -1,4 +1,4 @@ -package com.mifmif.common.regex; +package com.pkware.generex; import kotlin.ranges.IntRange; import org.junit.jupiter.params.ParameterizedTest; diff --git a/src/test/java/com/mifmif/common/regex/GenerexTest.java b/src/test/java/com/pkware/generex/GenerexTest.java similarity index 99% rename from src/test/java/com/mifmif/common/regex/GenerexTest.java rename to src/test/java/com/pkware/generex/GenerexTest.java index a33c412..1b9d1e3 100644 --- a/src/test/java/com/mifmif/common/regex/GenerexTest.java +++ b/src/test/java/com/pkware/generex/GenerexTest.java @@ -1,4 +1,4 @@ -package com.mifmif.common.regex; +package com.pkware.generex; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; diff --git a/src/test/java/com/mifmif/common/regex/GenerexUnitTest.java b/src/test/java/com/pkware/generex/GenerexUnitTest.java similarity index 99% rename from src/test/java/com/mifmif/common/regex/GenerexUnitTest.java rename to src/test/java/com/pkware/generex/GenerexUnitTest.java index 020f5f7..e7ffe8c 100644 --- a/src/test/java/com/mifmif/common/regex/GenerexUnitTest.java +++ b/src/test/java/com/pkware/generex/GenerexUnitTest.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.mifmif.common.regex; +package com.pkware.generex; import dk.brics.automaton.Automaton; import org.junit.jupiter.api.Test; diff --git a/src/test/kotlin/com/mifmif/common/regex/KotlinTests.kt b/src/test/kotlin/com/pkware/generex/KotlinTests.kt similarity index 99% rename from src/test/kotlin/com/mifmif/common/regex/KotlinTests.kt rename to src/test/kotlin/com/pkware/generex/KotlinTests.kt index 444bf9d..0d6abb7 100644 --- a/src/test/kotlin/com/mifmif/common/regex/KotlinTests.kt +++ b/src/test/kotlin/com/pkware/generex/KotlinTests.kt @@ -1,4 +1,4 @@ -package com.mifmif.common.regex +package com.pkware.generex import com.google.common.truth.Truth.assertThat import org.junit.jupiter.api.Test @@ -7,6 +7,7 @@ import org.junit.jupiter.params.provider.Arguments import org.junit.jupiter.params.provider.MethodSource import org.junit.jupiter.params.provider.ValueSource import java.util.stream.Stream +import kotlin.collections.iterator import kotlin.math.max import kotlin.math.min From be0a9f0494a6d1572d77a76c0e8a3364aadb3e0a Mon Sep 17 00:00:00 2001 From: Ethan Wrasman Date: Thu, 11 Sep 2025 10:32:33 -0500 Subject: [PATCH 4/5] Update Sonatype publishing to use OSSRH Staging Repository --- .github/workflows/publish.yml | 48 +++++++++++++++++++++++++++++++++++ build.gradle.kts | 23 ++++++++++++++--- 2 files changed, 68 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/publish.yml diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..49996a8 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,48 @@ +name: Publish to Maven Central + +on: + push: + branches: + - main + +jobs: + publish: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v5 + + - name: Set up JDK + uses: actions/setup-java@v4 + with: + java-version: 11 + distribution: 'temurin' + + - name: Publish to Maven Central + run: ./gradlew publish + env: + ORG_GRADLE_PROJECT_NEXUS_USERNAME: ${{ secrets.NEXUS_USERNAME }} + ORG_GRADLE_PROJECT_NEXUS_PASSWORD: ${{ secrets.NEXUS_PASSWORD }} + ORG_GRADLE_PROJECT_SIGNING_KEY_ID: ${{ secrets.SIGNING_KEY_ID }} + ORG_GRADLE_PROJECT_SIGNING_KEY: ${{ secrets.SIGNING_KEY }} + ORG_GRADLE_PROJECT_SIGNING_PASSWORD: ${{ secrets.SIGNING_PASSWORD }} + + - name: Check version + id: version + run: | + version=$(grep "generexVersion" gradle.properties | cut -d'=' -f2 | tr -d ' ') + if [[ "$version" != *"SNAPSHOT"* ]]; then + echo "is_release=true" >> $GITHUB_OUTPUT + else + echo "is_release=false" >> $GITHUB_OUTPUT + fi + + - name: Notify Central Publisher Portal + if: steps.version.outputs.is_release == 'true' + run: | + token=$(echo -n "${{ secrets.NEXUS_USERNAME }}:${{ secrets.NEXUS_PASSWORD }}" | base64) + curl -X POST \ + --max-time 30 \ + --fail-with-body \ + -H "Authorization: Bearer $token" \ + "https://ossrh-staging-api.central.sonatype.com/manual/upload/defaultRepository/com.pkware.generex?publishing_type=automatic" diff --git a/build.gradle.kts b/build.gradle.kts index 9a1880c..79598a5 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -71,6 +71,7 @@ publishing { } repositories { maven { + name = "MavenCentral" url = uri(if (version.toString().isReleaseBuild) releaseRepositoryUrl else snapshotRepositoryUrl) credentials { username = repositoryUsername @@ -81,8 +82,15 @@ publishing { } signing { - // Signing credentials are stored locally in the user's global gradle.properties file. + // Signing credentials are stored as secrets in GitHub. // See https://docs.gradle.org/current/userguide/signing_plugin.html#sec:signatory_credentials for more information. + + useInMemoryPgpKeys( + signingKeyId, + signingKey, + signingPassword, + ) + sign(publishing.publications["mavenJava"]) } @@ -92,13 +100,13 @@ val String.isReleaseBuild val Project.releaseRepositoryUrl: String get() = properties.getOrDefault( "RELEASE_REPOSITORY_URL", - "https://oss.sonatype.org/service/local/staging/deploy/maven2" + "https://ossrh-staging-api.central.sonatype.com/service/local/staging/deploy/maven2", ).toString() val Project.snapshotRepositoryUrl: String get() = properties.getOrDefault( "SNAPSHOT_REPOSITORY_URL", - "https://oss.sonatype.org/content/repositories/snapshots" + "https://central.sonatype.com/repository/maven-snapshots/", ).toString() val Project.repositoryUsername: String @@ -107,6 +115,15 @@ val Project.repositoryUsername: String val Project.repositoryPassword: String get() = properties.getOrDefault("NEXUS_PASSWORD", "").toString() +val Project.signingKeyId: String + get() = properties.getOrDefault("SIGNING_KEY_ID", "").toString() + +val Project.signingKey: String + get() = properties.getOrDefault("SIGNING_KEY", "").toString() + +val Project.signingPassword: String + get() = properties.getOrDefault("SIGNING_PASSWORD", "").toString() + val Project.pomPackaging: String get() = properties.getOrDefault("POM_PACKAGING", "jar").toString() From 07d19eee69423c492d710f92a784ccf2267aae43 Mon Sep 17 00:00:00 2001 From: Ethan Wrasman Date: Thu, 11 Sep 2025 10:33:23 -0500 Subject: [PATCH 5/5] Release 1.2.0 --- README.md | 2 +- gradle.properties | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9b5320f..0ea73c7 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ If you use [Maven](http://maven.apache.org) you can include this library to your com.pkware.generex generex - 1.1.0 + 1.2.0 ``` diff --git a/gradle.properties b/gradle.properties index aea5f34..5ec4e56 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ kotlin.code.style=official -generexVersion=1.1.0 +generexVersion=1.2.0 POM_ARTIFACT_ID=generex POM_NAME=Generex