Skip to content
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
1 change: 1 addition & 0 deletions .cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"**/*.{java,md,xml,yml,yaml,json,txt,properties}"
],
"words": [
"japicmp",
"Skyflow",
"skyflow",
"skyflowapi",
Expand Down
118 changes: 118 additions & 0 deletions .github/workflows/contract-tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
name: Contract Tests

on:
pull_request:
branches:
- main
- release/*

jobs:
contract-tests:
name: Contract Tests
runs-on: ubuntu-latest

permissions:
contents: read
pull-requests: write

steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Setup Java
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '11'
cache: 'maven'

- name: Verify API surface snapshot
run: mvn -B install -pl common,v2 -am -DskipTests -Dmaven.javadoc.skip=true -Dgpg.skip=true

- name: Show API surface diff
if: failure()
run: |
echo "### API surface changes detected ###"
echo "See v2/target/japicmp/default-cli.diff for the full comparison against v2/api-report/skyflow-java.baseline.jar."
echo "If this change is intentional, run scripts/contract-snapshot-update.sh and commit the updated baseline jar."
echo ""
cat v2/target/japicmp/default-cli.diff || true

- name: Upload API surface diff on failure
if: failure()
uses: actions/upload-artifact@v4
with:
name: api-surface-diff
path: v2/target/japicmp/**
retention-days: 7

# The step above only shows a diff when the CURRENT build differs from the
# committed baseline - once someone runs contract-snapshot-update.sh and
# commits the refreshed baseline jar, that check goes green and shows nothing.
# A reviewer looking at a green PR that touches api-report/skyflow-java.baseline.jar
# (a binary file) would otherwise have no way to see WHAT was just approved as
# the new contract. These steps explicitly diff the OLD committed baseline
# (from the PR's base branch) against the NEW committed baseline (from this PR)
# and post it as a PR comment, regardless of whether the check above passed.
- name: Check if contract baseline was updated in this PR
id: baseline-diff-check
if: always() && github.event.pull_request
run: |
git fetch origin "${{ github.event.pull_request.base.ref }}" --depth=1
if git diff --name-only "origin/${{ github.event.pull_request.base.ref }}" HEAD -- v2/api-report/skyflow-java.baseline.jar | grep -q .; then
echo "changed=true" >> "$GITHUB_OUTPUT"
else
echo "changed=false" >> "$GITHUB_OUTPUT"
fi

- name: Diff old vs new contract baseline
if: always() && steps.baseline-diff-check.outputs.changed == 'true'
run: |
curl -sL -o /tmp/japicmp-cli.jar "https://repo.maven.apache.org/maven2/com/github/siom79/japicmp/japicmp/0.26.0/japicmp-0.26.0-jar-with-dependencies.jar"
mvn -q -B dependency:build-classpath -pl v2 -Dmdep.outputFile=/tmp/v2-classpath.txt -Dmaven.javadoc.skip=true -Dgpg.skip=true
git show "origin/${{ github.event.pull_request.base.ref }}:v2/api-report/skyflow-java.baseline.jar" > /tmp/old-baseline.jar

java -jar /tmp/japicmp-cli.jar \
-o /tmp/old-baseline.jar \
-n v2/api-report/skyflow-java.baseline.jar \
-a protected \
-e "com.skyflow.generated.*;com.skyflow.utils.*" \
--old-classpath "$(cat /tmp/v2-classpath.txt)" \
--new-classpath "$(cat /tmp/v2-classpath.txt)" \
-m \
--markdown > /tmp/contract-baseline-diff.md || true

cat /tmp/contract-baseline-diff.md

- name: Comment contract baseline change on PR
if: always() && steps.baseline-diff-check.outputs.changed == 'true'
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const summary = fs.readFileSync('/tmp/contract-baseline-diff.md', 'utf8');
const marker = '<!-- contract-baseline-diff -->';
const body = `${marker}\n## Contract baseline change detected\n\nThis PR updates \`v2/api-report/skyflow-java.baseline.jar\` (the approved public API contract). Here is exactly what it changes, comparing the baseline on \`${{ github.event.pull_request.base.ref }}\` against the baseline committed in this PR:\n\n${summary}`;
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
});
const existing = comments.find(c => c.body && c.body.includes(marker));
if (existing) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: existing.id,
body,
});
} else {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body,
});
}
27 changes: 27 additions & 0 deletions scripts/contract-snapshot-update.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#!/usr/bin/env bash
# Regenerates the v2 contract-testing baseline (api-report/skyflow-java.baseline.jar)
# from the CURRENT working tree and overwrites the committed snapshot.
#
# Run this after an intentional public API change to v2, review the resulting
# git diff on the jar (a new binary blob) alongside your code change, and commit
# both together. This is the only way the committed baseline should ever change -
# japicmp never pulls a published version for this comparison.
set -euo pipefail

cd "$(dirname "$0")/.."

mvn -B package -pl common,v2 -am -DskipTests -Dmaven.javadoc.skip=true -Dgpg.skip=true

SHADED_JAR=$(ls v2/target/skyflow-java-*-with-common.jar | head -n1)

if [ -z "$SHADED_JAR" ]; then
echo "Error: could not find a built v2/target/skyflow-java-*-with-common.jar. Did the build succeed?"
exit 1
fi

mkdir -p v2/api-report
cp "$SHADED_JAR" v2/api-report/skyflow-java.baseline.jar

echo "--------------------------"
echo "Updated v2/api-report/skyflow-java.baseline.jar from $SHADED_JAR"
echo "Review the diff and commit this file alongside your API change."
Binary file added v2/api-report/skyflow-java.baseline.jar
Binary file not shown.
78 changes: 78 additions & 0 deletions v2/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,84 @@
<target>9</target>
</configuration>
</plugin>
<plugin>
<!-- Produces an additional, comparison-only jar (classifier "with-common")
that merges in com.skyflow:common, same pattern flowvault already uses.
The real published skyflow-java-2.1.1.jar is untouched (shadedArtifactAttached
keeps the main artifact slim) - this attached jar exists only so japicmp can
compare an apples-to-apples "everything a v2 consumer actually gets" surface
against the old 2.1.0 baseline, which was a single self-contained jar
(predating the common/v2 split). Without this, classes that moved into
common (Credentials, ErrorCode, BearerToken, Token, etc.) would show up as
false-positive "removed" classes, since japicmp only compares what's
physically inside the two jars it's given. -->
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.6.0</version>
<executions>
<execution>
<id>shade-for-japicmp</id>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<shadedArtifactAttached>true</shadedArtifactAttached>
<shadedClassifierName>with-common</shadedClassifierName>
<artifactSet>
<includes>
<include>com.skyflow:common</include>
</includes>
</artifactSet>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>com.github.siom79.japicmp</groupId>
<artifactId>japicmp-maven-plugin</artifactId>
<version>0.26.0</version>
<configuration>
<!-- No published version is pulled for this comparison. "Old" is the
committed contract snapshot (api-report/skyflow-java.baseline.jar) -
a jar built from the last code state a human deliberately approved as
the contract, same role as skyflow-node's committed api-report/*.api.md.
Regenerate it with scripts/contract-snapshot-update.sh after an
intentional public API change, then commit the updated jar. -->
<oldVersion>
<file>
<path>${project.basedir}/api-report/skyflow-java.baseline.jar</path>
</file>
</oldVersion>
<newVersion>
<file>
<path>${project.build.directory}/${project.build.finalName}-with-common.jar</path>
</file>
</newVersion>
<parameter>
<accessModifier>protected</accessModifier>
<onlyModified>true</onlyModified>
<ignoreMissingClasses>false</ignoreMissingClasses>
<breakBuildOnBinaryIncompatibleModifications>true</breakBuildOnBinaryIncompatibleModifications>
<breakBuildOnSourceIncompatibleModifications>true</breakBuildOnSourceIncompatibleModifications>
<excludes>
<exclude>com.skyflow.generated.*</exclude>
<exclude>com.skyflow.utils.*</exclude>
</excludes>
<skipXmlReport>false</skipXmlReport>
<skipHtmlReport>false</skipHtmlReport>
</parameter>
</configuration>
<executions>
<execution>
<id>default-cli</id>
<phase>verify</phase>
<goals>
<goal>cmp</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
Loading