Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a flag to allow minor updates #708

Merged
merged 1 commit into from
Jul 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ public void apply(Project project) {
task.getIgnoredModules().convention(Collections.emptySet());
task.getRejectedVersionsPerModule().convention(Collections.emptyMap());
task.getAllowMajorUpdates().convention(false);
task.getAllowMinorUpdates().convention(true);
});
tasks.register("useLatestVersions", Copy.class, task -> {
VersionCatalogUpdate dependent = updater.get();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import io.micronaut.build.catalogs.internal.VersionModel;
import org.gradle.api.DefaultTask;
import org.gradle.api.GradleException;
import org.gradle.api.artifacts.ComponentSelection;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.artifacts.ConfigurationContainer;
import org.gradle.api.artifacts.Dependency;
Expand Down Expand Up @@ -80,6 +81,9 @@ public abstract class VersionCatalogUpdate extends DefaultTask {
@Input
public abstract Property<Boolean> getAllowMajorUpdates();

@Input
public abstract Property<Boolean> getAllowMinorUpdates();

@Input
public abstract MapProperty<String, String> getRejectedVersionsPerModule();

Expand Down Expand Up @@ -134,6 +138,7 @@ private void updateCatalog(File inputCatalog, File outputCatalog, File logFile)
}
List<String> lines = Files.readAllLines(inputCatalog.toPath(), StandardCharsets.UTF_8);
boolean allowMajorUpdate = getAllowMajorUpdates().get();
boolean allowMinorUpdate = getAllowMinorUpdates().get();
VersionCatalogTomlModel model = parser.getModel();
DependencyHandler dependencies = getProject().getDependencies();
ConfigurationContainer configurations = getProject().getConfigurations();
Expand Down Expand Up @@ -181,14 +186,7 @@ private void updateCatalog(File inputCatalog, File outputCatalog, File logFile)
log.println("Rejecting version " + candidateVersion + " because of configuration. It matches regular expression: " + rejected);
}
}
if (!allowMajorUpdate) {
String major = majorVersionOf(required);
String candidateMajor = majorVersionOf(candidateVersion);
if (!major.equals(candidateMajor)) {
rules.reject("Rejecting major version " + candidateMajor);
log.println("Rejecting " + candidateModule.getModuleIdentifier() + " version " + candidateVersion + " because it's not the same major version");
}
}
maybeRejectVersionByMinorMajor(rules, allowMajorUpdate, allowMinorUpdate, required, candidateVersion, log, candidateModule);
}
}
}
Expand Down Expand Up @@ -286,6 +284,31 @@ private void updateCatalog(File inputCatalog, File outputCatalog, File logFile)
}
}

// Visible for testing
static void maybeRejectVersionByMinorMajor(ComponentSelection rules,
boolean allowMajorUpdate,
boolean allowMinorUpdate,
String currentVersion,
String candidateVersion,
PrintWriter log,
ModuleComponentIdentifier candidateModule) {
if (!allowMajorUpdate || !allowMinorUpdate) {
int major = majorVersionOf(currentVersion);
int candidateMajor = majorVersionOf(candidateVersion);
if (major != candidateMajor && !allowMajorUpdate) {
rules.reject("Rejecting major version " + candidateMajor);
log.println("Rejecting " + candidateModule.getModuleIdentifier() + " version " + candidateVersion + " because it's not the same major version");
} else if (major == candidateMajor && !allowMinorUpdate) {
int minor = minorVersionOf(currentVersion);
int candidateMinor = minorVersionOf(candidateVersion);
if (minor!=candidateMinor) {
rules.reject("Rejecting minor version " + candidateMinor);
log.println("Rejecting " + candidateModule.getModuleIdentifier() + " version " + candidateVersion + " because it's not the same minor version");
}
}
}
}

private static String requiredVersionOf(Library library) {
RichVersion version = library.getVersion().getVersion();
if (version != null) {
Expand All @@ -307,11 +330,38 @@ private static PrintWriter newPrintWriter(File file) throws FileNotFoundExceptio
return new PrintWriter(new OutputStreamWriter(new FileOutputStream(file), StandardCharsets.UTF_8));
}

private static String majorVersionOf(String version) {
static int majorVersionOf(String version) {
int idx = version.indexOf(".");
if (idx < 0) {
return safeParseInt(version);
}
return safeParseInt(version.substring(0, idx));
}

static int minorVersionOf(String version) {
int idx = version.indexOf(".");
if (idx < 0) {
return version;
return 0;
}
var bugfixIdx = version.indexOf(".", idx + 1);
if (bugfixIdx < 0) {
return safeParseInt(version.substring(idx + 1));
}
return safeParseInt(version.substring(idx + 1, bugfixIdx));
}

private static int safeParseInt(String pollutedVersion) {
int idx = 0;
while (idx < pollutedVersion.length() && Character.isDigit(pollutedVersion.charAt(idx))) {
idx++;
}
if (idx == 0) {
return 0;
}
try {
return Integer.parseInt(pollutedVersion.substring(0, idx));
} catch (NumberFormatException ex) {
return 0;
}
return version.substring(0, idx);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package io.micronaut.build.catalogs.tasks

import org.gradle.api.artifacts.ComponentSelection
import org.gradle.api.artifacts.component.ModuleComponentIdentifier
import spock.lang.Specification

import static io.micronaut.build.catalogs.tasks.VersionCatalogUpdate.maybeRejectVersionByMinorMajor

class VersionCatalogUpdateTest extends Specification {
def "test major and minor version extraction"() {
expect:
VersionCatalogUpdate.majorVersionOf(version) == expectedMajor
VersionCatalogUpdate.minorVersionOf(version) == expectedMinor

where:
version | expectedMajor | expectedMinor
"1.2.3" | 1 | 2
"1.2" | 1 | 2
"1" | 1 | 0
"1.2.3.4" | 1 | 2
"3.5-beta" | 3 | 5
"3.5-beta1" | 3 | 5
"2.1.0-rc1" | 2 | 1
"128.256.12" | 128 | 256
// not semantic versioning, edge cases to make sure
// the implementation is robust enough
"abc.def" | 0 | 0
"oh.123noes" | 0 | 123
"" | 0 | 0
"wut" | 0 | 0
}

def "tests rejection rules"() {
def componentSelection = Mock(ComponentSelection)
def log = Stub(PrintWriter)
def id = Stub(ModuleComponentIdentifier) {
getGroup() >> "io.micronaut"
getModule() >> "micronaut-core"
getVersion() >> candidateVersion
}

when:
maybeRejectVersionByMinorMajor(
componentSelection,
allowMajorUpdate,
allowMinorUpdate,
currentVersion,
candidateVersion,
log,
id
)

then:
reject * componentSelection.reject(_)

where:
allowMajorUpdate | allowMinorUpdate | currentVersion | candidateVersion | reject
false | false | '1.0.0' | '2.0.0' | 1
false | false | '1.0.0' | '1.1.0' | 1
false | false | '1.0.0' | '1.0.1' | 0
false | true | '1.0.0' | '1.1.0' | 0
false | true | '1.0.0' | '2.0.0' | 1
true | false | '1.0.0' | '2.0.0' | 0
true | false | '1.0.0' | '1.1.0' | 1
true | false | '1.0.0' | '1.0.1' | 0
true | true | '1.0.0' | '2.0.0' | 0
true | true | '1.0.0' | '1.1.0' | 0
true | true | '1.0.0' | '1.0.1' | 0
}
}
Loading