-
Notifications
You must be signed in to change notification settings - Fork 14
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add binary compatibility checks (#282)
* Initial support for binary compatibility checks This commit introduces a plugin to automate binary compatibility checks. It is based on the japicmp Gradle plugin and currently relies on the default rules, except for one which turns errors into warnings when violations are found on internal types. * Add ability to accept regressions * Add extension to configure binary compatibility checks This extension lets the user declare where the acceptance JSON file lives (defaults to the root project's dir), if binary compatibility checks are enabled at all, and finally can force to a particular baseline version. * Make all Micronaut modules use binary compatibility checks * Automatically accept changes on `@Internal` types * Bump version
- Loading branch information
Showing
15 changed files
with
681 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
24 changes: 24 additions & 0 deletions
24
src/main/groovy/io/micronaut/build/compat/AcceptanceHelper.groovy
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
package io.micronaut.build.compat | ||
|
||
class AcceptanceHelper { | ||
static String formatAcceptance(String type, String member ) { | ||
String json = """{ | ||
"type": "$type", | ||
"member": "$member", | ||
"reason": "Provide a human readable reason for the change" | ||
}""" | ||
def changeId = (type + member).replaceAll('[^a-zA-Z0-9]', '_') | ||
""". | ||
<br> | ||
<p> | ||
If you did this intentionally, please accept the change and provide an explanation: | ||
<a class="btn btn-info" role="button" data-toggle="collapse" href="#accept-${changeId}" aria-expanded="false" aria-controls="collapseExample">Accept this change</a> | ||
<div class="collapse" id="accept-${changeId}"> | ||
<div class="well"> | ||
In order to accept this change add the following to <code>accepted-api-changes.json</code>: | ||
<pre>$json</pre> | ||
</div> | ||
</div> | ||
</p>""".stripIndent() | ||
} | ||
} |
46 changes: 46 additions & 0 deletions
46
src/main/groovy/io/micronaut/build/compat/AcceptedApiChange.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
/* | ||
* Copyright 2003-2021 the original author or authors. | ||
* | ||
* 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 | ||
* | ||
* https://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 io.micronaut.build.compat; | ||
|
||
import java.io.Serializable; | ||
|
||
public class AcceptedApiChange implements Serializable { | ||
private final String type; | ||
private final String member; | ||
private final String reason; | ||
|
||
public AcceptedApiChange(String type, String member, String reason) { | ||
this.type = type; | ||
this.member = member; | ||
this.reason = reason; | ||
} | ||
|
||
public boolean matches(String type, String member) { | ||
return this.type.equals(type) && this.member.equals(member); | ||
} | ||
|
||
public String getType() { | ||
return type; | ||
} | ||
|
||
public String getMember() { | ||
return member; | ||
} | ||
|
||
public String getReason() { | ||
return reason; | ||
} | ||
} |
19 changes: 19 additions & 0 deletions
19
src/main/groovy/io/micronaut/build/compat/AcceptedApiChangesParser.groovy
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
package io.micronaut.build.compat | ||
|
||
import groovy.json.JsonSlurper | ||
import groovy.transform.CompileStatic | ||
|
||
@CompileStatic | ||
class AcceptedApiChangesParser { | ||
static List<AcceptedApiChange> parse(InputStream jsonStream) { | ||
def parser = new JsonSlurper() | ||
List<Map<String, String>> json = parser.parse(jsonStream) as List<Map<String, String>> | ||
return json.collect {map -> | ||
new AcceptedApiChange( | ||
map["type"], | ||
map["member"], | ||
map["reason"] | ||
) | ||
} | ||
} | ||
} |
77 changes: 77 additions & 0 deletions
77
src/main/groovy/io/micronaut/build/compat/AcceptedApiChangesRule.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
/* | ||
* Copyright 2003-2021 the original author or authors. | ||
* | ||
* 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 | ||
* | ||
* https://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 io.micronaut.build.compat; | ||
|
||
import me.champeau.gradle.japicmp.report.Violation; | ||
import me.champeau.gradle.japicmp.report.ViolationTransformer; | ||
import org.gradle.api.GradleException; | ||
|
||
import java.io.File; | ||
import java.io.FileInputStream; | ||
import java.io.IOException; | ||
import java.util.Collections; | ||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.Optional; | ||
import java.util.stream.Collectors; | ||
|
||
import static io.micronaut.build.compat.AcceptanceHelper.formatAcceptance; | ||
|
||
public class AcceptedApiChangesRule implements ViolationTransformer { | ||
public static final String CHANGES_FILE = "changesFile"; | ||
|
||
private final Map<String, List<AcceptedApiChange>> changes; | ||
|
||
public AcceptedApiChangesRule(Map<String, String> params) { | ||
String filePath = params.get(CHANGES_FILE); | ||
File changesFile = new File(filePath); | ||
if (changesFile.exists()) { | ||
try (FileInputStream fis = new FileInputStream(filePath)) { | ||
this.changes = AcceptedApiChangesParser.parse(fis) | ||
.stream() | ||
.collect(Collectors.groupingBy(AcceptedApiChange::getType)); | ||
} catch (IOException e) { | ||
throw new GradleException("Unable to parse accepted regressions file", e); | ||
} | ||
} else { | ||
this.changes = Collections.emptyMap(); | ||
} | ||
} | ||
|
||
@Override | ||
public Optional<Violation> transform(String type, Violation violation) { | ||
List<AcceptedApiChange> apiChanges = changes.get(type); | ||
String violationDescription = Violation.describe(violation.getMember()); | ||
if (apiChanges != null) { | ||
Optional<AcceptedApiChange> any = apiChanges.stream() | ||
.filter(c -> c.matches(type, violationDescription)) | ||
.findAny(); | ||
if (any.isPresent()) { | ||
return Optional.of(violation.acceptWithDescription(any.get().getReason())); | ||
} | ||
} | ||
switch (violation.getSeverity()) { | ||
case info: | ||
case accepted: | ||
case warning: | ||
return Optional.of(violation); | ||
default: | ||
return Optional.of(violation.withDescription( | ||
violation.getHumanExplanation() + formatAcceptance(type, violationDescription)) | ||
); | ||
} | ||
} | ||
} |
25 changes: 25 additions & 0 deletions
25
src/main/groovy/io/micronaut/build/compat/BinaryCompatibibilityExtension.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
/* | ||
* Copyright 2003-2021 the original author or authors. | ||
* | ||
* 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 | ||
* | ||
* https://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 io.micronaut.build.compat; | ||
|
||
import org.gradle.api.file.RegularFileProperty; | ||
import org.gradle.api.provider.Property; | ||
|
||
public interface BinaryCompatibibilityExtension { | ||
RegularFileProperty getAcceptedRegressionsFile(); | ||
Property<Boolean> getEnabled(); | ||
Property<String> getBaselineVersion(); | ||
} |
54 changes: 54 additions & 0 deletions
54
src/main/groovy/io/micronaut/build/compat/InternalAnnotationCollectorRule.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
/* | ||
* Copyright 2003-2021 the original author or authors. | ||
* | ||
* 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 | ||
* | ||
* https://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 io.micronaut.build.compat; | ||
|
||
import japicmp.model.JApiClass; | ||
import japicmp.model.JApiCompatibility; | ||
import japicmp.model.JApiHasAnnotations; | ||
import me.champeau.gradle.japicmp.report.AbstractContextAwareViolationRule; | ||
import me.champeau.gradle.japicmp.report.Violation; | ||
|
||
import java.util.HashSet; | ||
|
||
public class InternalAnnotationCollectorRule extends AbstractContextAwareViolationRule { | ||
public static final String INTERNAL_TYPES = "micronaut.internal.types"; | ||
|
||
@Override | ||
public Violation maybeViolation(JApiCompatibility member) { | ||
if (member instanceof JApiClass) { | ||
JApiClass jApiClass = (JApiClass) member; | ||
maybeRecord((JApiHasAnnotations) member, jApiClass.getFullyQualifiedName()); | ||
} | ||
return null; | ||
} | ||
|
||
private void maybeRecord(JApiHasAnnotations member, String name) { | ||
if (isAnnotatedWithInternal(member)) { | ||
HashSet<String> types = getContext().getUserData(INTERNAL_TYPES); | ||
if (types == null) { | ||
types = new HashSet<>(); | ||
getContext().putUserData(INTERNAL_TYPES, types); | ||
} | ||
types.add(name); | ||
} | ||
} | ||
|
||
static boolean isAnnotatedWithInternal(JApiHasAnnotations member) { | ||
return member.getAnnotations() | ||
.stream() | ||
.anyMatch(ann -> ann.getFullyQualifiedName().equals("io.micronaut.core.annotation.Internal")); | ||
} | ||
} |
49 changes: 49 additions & 0 deletions
49
src/main/groovy/io/micronaut/build/compat/InternalAnnotationPostProcessRule.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
/* | ||
* Copyright 2003-2021 the original author or authors. | ||
* | ||
* 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 | ||
* | ||
* https://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 io.micronaut.build.compat; | ||
|
||
import me.champeau.gradle.japicmp.report.PostProcessViolationsRule; | ||
import me.champeau.gradle.japicmp.report.Severity; | ||
import me.champeau.gradle.japicmp.report.Violation; | ||
import me.champeau.gradle.japicmp.report.ViolationCheckContextWithViolations; | ||
|
||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.Set; | ||
import java.util.stream.Collectors; | ||
|
||
public class InternalAnnotationPostProcessRule implements PostProcessViolationsRule { | ||
@Override | ||
public void execute(ViolationCheckContextWithViolations context) { | ||
Set<String> internalTypes = context.getUserData(InternalAnnotationCollectorRule.INTERNAL_TYPES); | ||
if (internalTypes != null) { | ||
Set<String> toBeSuppressed = context.getViolations().keySet().stream().filter(internalTypes::contains).collect(Collectors.toSet()); | ||
Set<Map.Entry<String, List<Violation>>> entries = context.getViolations().entrySet(); | ||
for (Map.Entry<String, List<Violation>> entry : entries) { | ||
if (toBeSuppressed.contains(entry.getKey())) { | ||
List<Violation> replacement = entry.getValue().stream().map(v -> { | ||
if (v.getSeverity() == Severity.error) { | ||
return v.withSeverity(Severity.warning); | ||
} | ||
return v; | ||
}).collect(Collectors.toList()); | ||
entry.getValue().clear(); | ||
entry.getValue().addAll(replacement); | ||
} | ||
} | ||
} | ||
} | ||
} |
56 changes: 56 additions & 0 deletions
56
src/main/groovy/io/micronaut/build/compat/InternalMicronautTypeRule.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
/* | ||
* Copyright 2003-2021 the original author or authors. | ||
* | ||
* 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 | ||
* | ||
* https://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 io.micronaut.build.compat; | ||
|
||
import japicmp.model.JApiHasAnnotations; | ||
import me.champeau.gradle.japicmp.report.Severity; | ||
import me.champeau.gradle.japicmp.report.Violation; | ||
import me.champeau.gradle.japicmp.report.ViolationTransformer; | ||
|
||
import java.util.Optional; | ||
|
||
import static io.micronaut.build.compat.InternalAnnotationCollectorRule.isAnnotatedWithInternal; | ||
|
||
/** | ||
* This rule turns errors on internal types into warnings. | ||
*/ | ||
public class InternalMicronautTypeRule implements ViolationTransformer { | ||
private static boolean isInternalType(String className) { | ||
return className.startsWith("io.micronaut") && className.contains(".internal."); | ||
} | ||
|
||
/** | ||
* Transforms the current violation. | ||
* | ||
* @param type the type on which the violation was found | ||
* @param violation the violation | ||
* @return a transformed violation. If the violation should be suppressed, return Optional.empty() | ||
*/ | ||
@Override | ||
public Optional<Violation> transform(String type, Violation violation) { | ||
if (isInternalType(type) && violation.getSeverity() == Severity.error) { | ||
return Optional.of(violation.withSeverity(Severity.warning)); | ||
} | ||
if (violation.getMember() instanceof JApiHasAnnotations) { | ||
JApiHasAnnotations member = (JApiHasAnnotations) violation.getMember(); | ||
if (isAnnotatedWithInternal(member)) { | ||
return Optional.of(violation.withSeverity(Severity.warning)); | ||
} | ||
} | ||
return Optional.of(violation); | ||
} | ||
|
||
} |
Oops, something went wrong.