diff --git a/.github/workflows/javadoc.yaml b/.github/workflows/javadoc.yaml
new file mode 100644
index 000000000..034078215
--- /dev/null
+++ b/.github/workflows/javadoc.yaml
@@ -0,0 +1,292 @@
+name: JavaDoc to Documentation Portal
+on:
+ workflow_dispatch:
+ inputs:
+ branch:
+ description: 'Branch/Commit/Tag for JavaDoc'
+ required: true
+ default: 'main'
+
+permissions:
+ contents: read
+
+env:
+ JAVA_VERSION: 21
+ MAVEN_CACHE_KEY: maven-dependencies
+ MAVEN_CACHE_DIR: ~/.m2
+ DOCS_REPO: SAP/ai-sdk
+ MODULE_PATHS: |
+ core
+ orchestration
+ core-services/document-grounding
+ core-services/prompt-registry
+ foundation-models/openai
+ foundation-models/sap-rpt
+ MODULE_NAMES: |
+ AI Core client
+ Orchestration client
+ Document Grounding Client
+ Prompt Registry client
+ OpenAI client
+ SAP RPT Model Client
+
+jobs:
+
+ build:
+ runs-on: ubuntu-latest
+ defaults:
+ run:
+ shell: bash -euo pipefail {0}
+ steps:
+
+ - name: "Checkout repository"
+ uses: actions/checkout@v6
+ with:
+ fetch-depth: 1
+ ref: ${{ inputs.branch }}
+
+ - name: "Setup Java"
+ uses: actions/setup-java@v5
+ with:
+ distribution: "sapmachine"
+ java-version: ${{ env.JAVA_VERSION }}
+ cache: 'maven'
+
+ - name: "Restore Dependencies"
+ uses: actions/cache/restore@v5
+ with:
+ key: ${{ env.MAVEN_CACHE_KEY }}
+ path: ${{ env.MAVEN_CACHE_DIR }}
+
+ - name: "Build SDK"
+ run: >
+ mvn -B -ntp -Dstyle.color=never -Drelease -DskipTests -Dgpg.skip=true
+ clean install
+
+ - name: "Generate Javadocs from delomboked sources"
+ run: |
+ JAVADOC="$JAVA_HOME/bin/javadoc"
+
+ # Reconstruct the module list from the top-level env var.
+ # printf '%s' prevents <<< from adding an extra newline, which would produce a spurious empty element.
+ readarray -t module_paths < <(printf '%s' "$MODULE_PATHS")
+
+ # Build a classpath from all module JARs and their dependencies so javadoc can resolve cross-module type references.
+ classpath=""
+ for module_path in "${module_paths[@]}"; do
+ cp_file="$module_path/target/cp.txt"
+
+ # Write all compile- and provided-scope dependency JARs to cp.txt.
+ # provided-scope types (e.g. SAP Cloud SDK interfaces) must be on the classpath for javadoc to resolve them.
+ mvn -B -ntp -f "$module_path/pom.xml" \
+ dependency:build-classpath -Dmdep.outputFile=target/cp.txt -Dmdep.includeScope=provided \
+ -Dgpg.skip=true -q
+
+ # Add the module's own compiled JAR to the classpath.
+ # Exclude *-sources.jar and *-javadoc.jar produced by the release profile.
+ module_jar=$(find "$module_path/target" -maxdepth 1 -name "*.jar" ! -name "*sources*" ! -name "*javadoc*" | head -1)
+ if [[ -n "$module_jar" ]]; then
+ [[ -z "$classpath" ]] && classpath="$module_jar" || classpath+=":$module_jar"
+ fi
+
+ # Append the dependency JARs from cp.txt to the classpath.
+ if [[ -f "$cp_file" ]]; then
+ cp_content=$(cat "$cp_file")
+ if [[ -n "$cp_content" ]]; then
+ [[ -z "$classpath" ]] && classpath="$cp_content" || classpath+=":$cp_content"
+ fi
+ fi
+ done
+
+ # Runs the javadoc tool for a given source tree and writes HTML to destdir.
+ run_javadoc() {
+ local javadoc="$1"
+ local sourcepath="$2"
+ local destdir="$3"
+ local pkg_file="$4"
+ local cp="$5"
+
+ # Skip if no packages were found for this module.
+ if [[ ! -s "$pkg_file" ]]; then
+ echo "No packages found for $destdir, skipping" >&2
+ return
+ fi
+
+ mkdir -p "$destdir"
+ "$javadoc" \
+ -sourcepath "$sourcepath" \
+ -d "$destdir" \
+ -classpath "$cp" \
+ -quiet \
+ -Xdoclint:none \
+ -encoding UTF-8 \
+ -charset UTF-8 \
+ -docencoding UTF-8 \
+ @"$pkg_file"
+ }
+
+ # Generate per-module javadocs from delomboked sources (Lombok annotations expanded to plain Java).
+ for module_path in "${module_paths[@]}"; do
+ delombok_dir="$module_path/target/delombok"
+ if [[ ! -d "$delombok_dir" ]]; then
+ echo "Missing delombok sources for $module_path" >&2
+ exit 1
+ fi
+
+ # Extract unique package names from the module's Java files.
+ # -print0/-0: null-delimited to handle spaces in paths; -r: skip if no files; -h: omit filenames from output.
+ pkg_file="$module_path/target/javadoc-packages"
+ find "$delombok_dir" -name "*.java" -print0 \
+ | xargs -r -0 grep -h "^package " \
+ | awk '{print $2}' | tr -d ';' | sort -u > "$pkg_file"
+ echo "Generating javadoc for $module_path ..."
+ run_javadoc "$JAVADOC" "$delombok_dir" "$module_path/target/javadoc-delombok" "$pkg_file" "$classpath"
+ done
+
+ # Generate a combined javadoc across all modules with a unified package index.
+ combined_sourcepath=""
+ combined_pkg_file="target/javadoc-all-packages"
+ > "$combined_pkg_file"
+ for module_path in "${module_paths[@]}"; do
+ delombok_dir="$module_path/target/delombok"
+ # Colon-join all delombok dirs into a single sourcepath for javadoc.
+ [[ -z "$combined_sourcepath" ]] \
+ && combined_sourcepath="$delombok_dir" \
+ || combined_sourcepath+=":$delombok_dir"
+ # Accumulate package names from all modules into one file.
+ cat "$module_path/target/javadoc-packages" >> "$combined_pkg_file"
+ done
+ # Deduplicate packages shared across modules.
+ sort -u "$combined_pkg_file" -o "$combined_pkg_file"
+ echo "Generating combined javadoc for sdk-parent ..."
+ run_javadoc "$JAVADOC" "$combined_sourcepath" "target/javadoc-delombok" "$combined_pkg_file" "$classpath"
+
+ - name: "Assemble local-javadocs output directory"
+ run: |
+ out_dir="target/local-javadocs"
+ rm -rf "$out_dir"
+ mkdir -p "$out_dir"
+
+ # Reconstruct the module lists from the top-level env vars.
+ # printf '%s' prevents <<< from adding an extra newline, which would produce a spurious empty element.
+ readarray -t module_paths < <(printf '%s' "$MODULE_PATHS")
+ readarray -t module_names < <(printf '%s' "$MODULE_NAMES")
+
+ # Copy the combined javadoc into the 'aggregate' subdirectory.
+ if [[ ! -f "target/javadoc-delombok/index.html" ]]; then
+ echo "Missing combined javadocs at target/javadoc-delombok" >&2
+ exit 1
+ fi
+ cp -R "target/javadoc-delombok" "$out_dir/aggregate"
+
+ # Write an HTML index linking to the aggregate docs and each per-module doc.
+ index_file="$out_dir/index.html"
+ cat > "$index_file" <<'HTML'
+
+
+
+
+ Local Javadocs
+
+
+ Local Javadocs
+ Aggregate API docs (all modules)
+
+ HTML
+
+ # Iterate over modules by index to pair each path with its display name.
+ for i in "${!module_paths[@]}"; do
+ module_path="${module_paths[$i]}"
+ module_name="${module_names[$i]}"
+ # Replace '/' with '-' to use the module path as a flat directory name.
+ module_id="${module_path//\//-}"
+
+ src_dir="$module_path/target/javadoc-delombok"
+ if [[ ! -f "$src_dir/index.html" ]]; then
+ echo "Missing javadocs for $module_path at $src_dir" >&2
+ exit 1
+ fi
+
+ # Copy the module's javadoc into the output dir and add an index entry.
+ cp -R "$src_dir" "$out_dir/$module_id"
+ printf ' - %s
\n' "$module_id" "$module_name" >> "$index_file"
+ done
+
+ cat >> "$index_file" <<'HTML'
+
+
+
+ HTML
+
+ echo "Local javadocs assembled at: $out_dir/index.html"
+
+ - name: "Upload Javadocs artifact"
+ uses: actions/upload-artifact@v7
+ with:
+ name: local-javadocs
+ path: target/local-javadocs/
+ retention-days: 7
+
+ - name: "Determine Version"
+ id: determine-version
+ run: |
+ VERSION="${{ inputs.branch }}"
+ VERSION="${VERSION#rel/}"
+ MAJOR_VERSION=$(echo "$VERSION" | cut -d '.' -f 1)
+ echo "CURRENT_VERSION=$VERSION" >> $GITHUB_OUTPUT
+ echo "MAJOR_VERSION=$MAJOR_VERSION" >> $GITHUB_OUTPUT
+
+ - name: "Generate GitHub App token"
+ id: app-token
+ uses: actions/create-github-app-token@v3
+ with:
+ client-id: ${{ secrets.SAP_AI_SDK_BOT_CLIENT_ID }}
+ private-key: ${{ secrets.SAP_AI_SDK_BOT_PRIVATE_KEY }}
+ owner: SAP
+ repositories: ai-sdk
+ permission-contents: write
+ permission-pull-requests: write
+
+ - name: "Prepare Git"
+ run: |
+ git config --global user.email "cloudsdk@sap.com"
+ git config --global user.name "SAP Cloud SDK Bot"
+
+ - name: "Checkout Docs Repository"
+ uses: actions/checkout@v6
+ with:
+ repository: ${{ env.DOCS_REPO }}
+ path: .ai-sdk-docs
+ token: ${{ steps.app-token.outputs.token }}
+
+ - name: "Create JavaDoc PR"
+ id: create-javadoc-pr
+ run: |
+ TARGET_DIR=.ai-sdk-docs/static/java-api/v${{ steps.determine-version.outputs.MAJOR_VERSION }}
+
+ rm -rf "$TARGET_DIR"
+ mkdir -p "$TARGET_DIR"
+ cp -R target/local-javadocs/aggregate/. "$TARGET_DIR"
+
+ cd .ai-sdk-docs
+ git add -A .
+
+ CHANGED_FILES="$(git status -s)"
+ if [[ -z "$CHANGED_FILES" ]]; then
+ echo "[DEBUG] No changes to API docs detected, skipping Pull Request creation."
+ exit 0
+ fi
+
+ echo "Committing new JavaDocs"
+ BRANCH_NAME=java/release-docs-${{ steps.determine-version.outputs.CURRENT_VERSION }}
+ git switch --create "$BRANCH_NAME"
+ git commit -m "chore: Update JavaDocs for release ${{ steps.determine-version.outputs.CURRENT_VERSION }}"
+ git push origin "$BRANCH_NAME"
+
+ echo "Creating PR"
+ PR_TITLE="Java: Update JavaDocs for release ${{ steps.determine-version.outputs.CURRENT_VERSION }}"
+ PR_BODY="Replace the contents of v${{ steps.determine-version.outputs.MAJOR_VERSION }} API docs with the latest release of the SDK."
+ PR_URL=$(gh pr create --title "$PR_TITLE" --body "$PR_BODY" --repo "${{ env.DOCS_REPO }}")
+ echo "PR: $PR_URL" >> $GITHUB_STEP_SUMMARY
+ env:
+ GH_TOKEN: ${{ steps.app-token.outputs.token }}
diff --git a/.github/workflows/perform-release.yaml b/.github/workflows/perform-release.yaml
index d3dee5df4..90259985d 100644
--- a/.github/workflows/perform-release.yaml
+++ b/.github/workflows/perform-release.yaml
@@ -29,6 +29,7 @@ jobs:
outputs:
code-branch: ${{ steps.determine-branch-names.outputs.CODE_BRANCH_NAME }}
release-notes-branch: ${{ steps.determine-branch-names.outputs.RELEASE_NOTES_BRANCH_NAME }}
+ javadoc-branch: ${{ steps.determine-branch-names.outputs.JAVADOC_BRANCH_NAME }}
release-tag: ${{ steps.determine-branch-names.outputs.RELEASE_TAG }}
release-commit: ${{ steps.determine-branch-names.outputs.RELEASE_COMMIT }}
runs-on: ubuntu-latest
@@ -41,12 +42,14 @@ jobs:
RELEASE_TAG=rel/$RELEASE_VERSION
RELEASE_COMMIT=$(gh release view $RELEASE_TAG --repo ${{github.repository}} --json targetCommitish --jq '.targetCommitish')
RELEASE_NOTES_BRANCH_NAME=java/release-notes-$RELEASE_VERSION
+ JAVADOC_BRANCH_NAME=java/release-docs-$RELEASE_VERSION
echo "CODE_BRANCH_NAME=$CODE_BRANCH_NAME" >> $GITHUB_OUTPUT
echo "RELEASE_VERSION=$RELEASE_VERSION" >> $GITHUB_OUTPUT
echo "RELEASE_TAG=$RELEASE_TAG" >> $GITHUB_OUTPUT
echo "RELEASE_COMMIT=$RELEASE_COMMIT" >> $GITHUB_OUTPUT
echo "RELEASE_NOTES_BRANCH_NAME=$RELEASE_NOTES_BRANCH_NAME" >> $GITHUB_OUTPUT
+ echo "JAVADOC_BRANCH_NAME=$JAVADOC_BRANCH_NAME" >> $GITHUB_OUTPUT
echo -e "[DEBUG] Current GITHUB_OUTPUT:\n$(cat $GITHUB_OUTPUT)"
env:
@@ -95,6 +98,18 @@ jobs:
\"Build Cloud SDK Documentation\": [\"dependabot\"]
}
+ - name: 'Check Whether JavaDoc PR Can Be Merged'
+ if: ${{ inputs.skip-pr-merge != 'true' }}
+ uses: ./.github/actions/pr-is-mergeable
+ with:
+ pr-ref: ${{ steps.determine-branch-names.outputs.JAVADOC_BRANCH_NAME }}
+ repo: ${{ env.DOCS_REPO }}
+ token: ${{ steps.app-token.outputs.token }}
+ excluded-check-runs: |
+ {
+ \"Build Cloud SDK Documentation\": [\"dependabot\"]
+ }
+
release:
name: 'Release'
needs: [prerequisites]
@@ -163,3 +178,9 @@ jobs:
run: gh pr merge --squash "${{ needs.prerequisites.outputs.release-notes-branch }}" --delete-branch --repo "${{ env.DOCS_REPO }}"
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
+
+ - name: 'Merge JavaDoc PR'
+ if: ${{ inputs.skip-pr-merge != 'true' }}
+ run: gh pr merge --squash "${{ needs.prerequisites.outputs.javadoc-branch }}" --delete-branch --repo "${{ env.DOCS_REPO }}"
+ env:
+ GH_TOKEN: ${{ steps.app-token.outputs.token }}
diff --git a/.github/workflows/prepare-release.yaml b/.github/workflows/prepare-release.yaml
index 72a7c46e2..255ee4677 100644
--- a/.github/workflows/prepare-release.yaml
+++ b/.github/workflows/prepare-release.yaml
@@ -149,6 +149,26 @@ jobs:
env:
GH_TOKEN: ${{ github.token }}
+ create-javadoc-aggregated-pr:
+ needs: [create-release]
+ name: 'Create Aggregated JavaDoc PR on Documentation Portal'
+ runs-on: ubuntu-latest
+ permissions:
+ actions: write # needed to trigger the javadoc workflow
+ statuses: write # needed to update the commit status
+ steps:
+ - name: 'Checkout repository'
+ uses: actions/checkout@v6
+ with:
+ ref: ${{ needs.create-release.outputs.release-name }}
+ - name: 'Trigger workflow (ignore failures)'
+ uses: ./.github/actions/trigger-workflow
+ continue-on-error: true
+ with:
+ workflow: javadoc.yaml
+ workflow-ref: main
+ parameters: -f branch=${{ needs.create-release.outputs.release-name }}
+
create-release-notes-pr:
name: 'Create Release Notes PR'
needs: [bump-version, run-ci]
@@ -284,6 +304,7 @@ jobs:
PR_URL=$(gh pr create --title "feat: Release ${{ needs.bump-version.outputs.release-version }}" --body "## TODOs
- [ ] Review the changes in [the release commit]($COMMIT_URL)
- [ ] Review **and approve** the [Release Notes PR](${{ needs.create-release-notes-pr.outputs.pr-url }})
+ - [ ] Review **and approve** the [JavaDoc PR](https://github.com/SAP/ai-sdk/pulls?q=is%3Aopen+is%3Apr+Update+JavaDocs)
- [ ] Add release notes to the [Draft Release](${{ needs.create-release.outputs.release-url }}) and improve formatting
- [ ] Review **and approve** this PR
- [ ] Trigger the [Perform Release Workflow](${{ github.event.repository.html_url }}/actions/workflows/perform-release.yaml)
diff --git a/docs/release_notes.md b/docs/release_notes.md
index d0ec84d77..eabb0fd9d 100644
--- a/docs/release_notes.md
+++ b/docs/release_notes.md
@@ -16,7 +16,7 @@
### 📈 Improvements
--
+- Aggregated JavaDocs are now published on our [documentation portal](https://sap.github.io/ai-sdk/docs/java/overview-cloud-sdk-for-ai-java).
### 🐛 Fixed Issues