#!/bin/bash # Copyright (C) 2024 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only usage () { echo Usage: `basename $0` "[-h|-u] [-d doclet] [-j javadoc] [-p user] [-s sdk] [-t task] [--] prior soon"; } help () { usage cat <&2; } die () { warn "$@"; exit 1; } second () { if [ $# -lt 2 ] then die "No argument supplied for $1" elif [ -z "$2" ] then die "Empty argument passed for $1 " fi echo "$2" } PLATFORM_JAR= GERRIT_USER= TASK_NUMBER= bad () { usage >&2; die "$@"; } while [ $# -gt 0 ] do case "$1" in -u|--usage) usage; exit 0 ;; -h|--help) help; exit 0 ;; -p|--push-as) GERRIT_USER=`second "$@"`; shift 2 ;; -s|--platform-jar) PLATFORM_JAR=`second "$@"`; shift 2 ;; -t|--task|--task-number) TASK_NUMBER=`second "$@"`; shift 2 ;; --) shift; break ;; -*) bad "Unrecognised option: $1" ;; *) break ;; esac done # Check basic expectations of context: [ -f init-repository ] || \ die "I expect to be run in the top level directory of the qt5 module (see --help)." QT_SUPER_REPO="$(pwd)" DOCLET_PATH=$QT_SUPER_REPO/qtqa/scripts/api-review/src REVIEW_FILENAME=qt-java-api-signature.txt # Select revisions to compare: [ $# -eq 2 ] || bad "Expected exactly two arguments, got $#: $@" for arg do git rev-parse "$arg" -- >/dev/null || bad "Failed to parse $arg as a git ref" done PRIOR="$1" RELEASE="$2" check_file_param() { [ -n "$1" ] || die "$2 not provided (see --help)." [ -f "$1" ] || die "$2 path '$1' doesn't exist." } check_file_param "$PLATFORM_JAR" "Android Platform JAR" command -v javac &> /dev/null || die "javac command not found (set JAVA_HOME)." command -v javadoc &> /dev/null || die "javadoc command not found (set JAVA_HOME)." compile_doclet() { javac $DOCLET_PATH/org/qtproject/qt/android/JavaApiSignature.java } run_javadoc() { source_paths="$1" version="$2" output_dir="$3" shift 3 java_packages=("${@#*:}") echo $output_dir pushd $output_dir javadoc \ -doclet org.qtproject.qt.android.JavaApiSignature \ -docletpath $DOCLET_PATH \ --output ${REVIEW_FILENAME} \ --class-path $PLATFORM_JAR \ -sourcepath $source_paths \ "${java_packages[@]}" 2>&1 1>/dev/null | \ # Ignore the five-line warning starting with this is line. sed '/javadoc: warning - The old Doclet and Taglet APIs/{N;N;N;N;d;}' mv $REVIEW_FILENAME $version-$REVIEW_FILENAME popd } run_javadoc_for_all_repos() { version="$1" repos=("${@:2}") java_source_paths=$( find "${repos[@]}" -type d -path '*/src/*/org/qtproject/qt/android' -exec echo {} + | \ sed 's/\/org\/qtproject\/qt\/android[^ ]*//g' | sed 's/:$//' | tr ' ' ':' ) echo "############################################" printf "#### Generating JAVADOC for %-8s ####\n" $version echo "############################################" for index in "${!repo_packages[@]}"; do entry="${repo_packages[$index]}" repo="${entry%%:*}" packages="${entry#*:}" run_javadoc $java_source_paths $version "$QT_SUPER_REPO/$repo" $packages done echo } checkout_git_repos() { branch="$1" repos=("${@:2}") echo "Checking out $branch in Qt repositories" for repo in "${repos[@]}"; do echo "Repo: $repo" git -C "$repo" fetch git -C "$repo" checkout -q $branch done echo } commit_api_verion() { message="$1" body="$2" ticket="$3" change_id="$4" version="$5" success=0 head_commit_count="$(git rev-list --count HEAD...$version)" if [[ "$(ls -A $version-$REVIEW_FILENAME)" || $head_commit_count -eq 1 ]]; then mv $version-$REVIEW_FILENAME $REVIEW_FILENAME if [ "$(git status | grep $REVIEW_FILENAME)" ]; then echo "Committing API for $version..." git add $REVIEW_FILENAME if [[ -n $ticket ]]; then footer=$(echo -e "Task-number: $ticket\n$change_id") git commit -q -m "$message" -m "$body" -m "$footer" else git commit -q -m "$message" -m "$body" -m "$change_id" fi success="$?" else echo "No API changes to commit for $version..." git reset HEAD^ rm $REVIEW_FILENAME success="-1" fi else echo "No API files for $version!" rm $version-$REVIEW_FILENAME fi return $success } commit_diff() { repo="$1" # Checkout review branch, remove it if it already exists echo "Preparing API Review Diff for $repo" cd "$repo" review_branch_name="java_api_review_${RELEASE}" # Get previous Change-Id if a review branch was already created if [ -n "$(git branch --list | grep -w $review_branch_name)" ]; then change_ids_log=$(git log --reverse "$RELEASE..$review_branch_name" | \ grep "Change-Id: " | sed 's/^[ \t]*//') while IFS= read -r line; do change_ids+=("$line"); done <<< "$change_ids_log" fi echo "Checking out review branch..." git checkout -q $RELEASE git branch -q -D $review_branch_name git checkout -q -b $review_branch_name $RELEASE if [ -f "$REVIEW_FILENAME" ]; then rm $REVIEW_FILENAME fi module=$(basename $repo) commit_msg="WIP: [Ignore][API Base] Review $module $RELEASE Android Java API" body="Auto-generated $PRIOR baseline commit for reviewing $RELEASE, please ignore." if [[ -n "${change_ids[1]}" ]]; then prior_change_id="${change_ids[0]}" release_change_id="${change_ids[1]}" else prior_change_id="" release_change_id="${change_ids[0]}" fi commit_api_verion "$commit_msg" "$body" "" "$prior_change_id" "$PRIOR" if [ -d "$review_dir_base" ]; then rm -R $review_dir_base fi commit_msg="Review $module $RELEASE Android Java API" body="Auto-generated commit for reviewing the Android Java API of $module." commit_api_verion "$commit_msg" "$body" "$TASK_NUMBER" "$release_change_id" "$RELEASE" if [ $? -eq 0 ]; then echo "Check the API diff file under $module/$REVIEW_FILENAME" fi } do_gerrit_command() { ssh -p 29418 $GERRIT_USER@codereview.qt-project.org gerrit "$@" } push_to_gerrit() { version="$1" echo "Pushing changes to Gerrit..." git push gerrit HEAD:refs/for/$version # Set diff review patch topic do_gerrit_command set-topic $(git log -n 1 --skip=0 --pretty=format:%H) \ --topic "java_api_review_$version" } is_valid_repo_dir() { local dir="$1" [ -d "$dir" ] || return 1 # must be an existing dir [ "$(ls -A "$dir" 2>/dev/null)" ] || return 1 # must not be empty git -C "$dir" rev-parse --is-inside-work-tree >/dev/null 2>&1 || return 1 # must be a git dir return 0 } # Individual key-value assignments for the associative array repo_packages_map=( "qtbase:org.qtproject.qt.android \ org.qtproject.qt.android.bindings \ org.qtproject.qt.android.extras \ org.qtproject.qt.android.network \ org.qtproject.qt.android.networkinformation" "qtconnectivity:org.qtproject.qt.android.bluetooth \ org.qtproject.qt.android.nfc" "qtdeclarative:org.qtproject.qt.android" "qtmultimedia:org.qtproject.qt.android.multimedia" "qtpositioning:org.qtproject.qt.android.positioning" "qtspeech:org.qtproject.qt.android.speech" "qtwebview:org.qtproject.qt.android.view" ) # Get list of absolute paths for git repos repos=() repo_packages=() for entry in "${repo_packages_map[@]}"; do repo="${entry%%:*}" dir="$QT_SUPER_REPO/$repo" if is_valid_repo_dir "$dir"; then repos+=("$dir") repo_packages+=("$entry") else echo "Skipping '$dir': not a valid git repo" >&2 fi done [[ ${#repos[@]} -eq 0 ]] && die "No valid git repositories found, nothing to do here." compile_doclet # Generate API for prior version checkout_git_repos "$PRIOR" "${repos[@]}" run_javadoc_for_all_repos $PRIOR "${repos[@]}" # Generate API for release version checkout_git_repos "$RELEASE" "${repos[@]}" run_javadoc_for_all_repos $RELEASE "${repos[@]}" # Commit diffs and push for repo in "${repos[@]}"; do commit_diff $repo commit_count="$(git rev-list --count HEAD...$RELEASE)" if [[ $commit_count -gt 0 ]] && [[ -n $GERRIT_USER ]]; then if [ $commit_count -eq 2 ]; then # Restore base API commit if it exists before pushing echo "Restore base patch if exists" base_commit_change_id=$(git show --summary HEAD~1 | grep "Change-Id: " | \ sed 's/^[ \t]*//' | cut -d' ' -f2-) do_gerrit_command review --restore $base_commit_change_id fi push_to_gerrit $RELEASE if [ $commit_count -eq 2 ]; then # Abandon base API commit if it exists base_commit_commit_id=$(git log -n 1 --skip=1 --pretty=format:%H) echo "Abandoning remote commit $base_commit_commit_id..." do_gerrit_command review --abandon $base_commit_commit_id fi fi echo done