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 + + 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