From 03656743a69b26d50823f099e885b122f55107f0 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Fri, 7 Nov 2025 15:08:21 -0300 Subject: [PATCH 1/9] Diffuse script --- scripts/diffuse.sh | 548 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 548 insertions(+) create mode 100755 scripts/diffuse.sh diff --git a/scripts/diffuse.sh b/scripts/diffuse.sh new file mode 100755 index 000000000..86df0fc50 --- /dev/null +++ b/scripts/diffuse.sh @@ -0,0 +1,548 @@ +#!/bin/bash + +# API Diff Tool +# Compares public API surface between two Git branches using Diffuse +# Usage: ./scripts/compare-api.sh --target --source + +set -euo pipefail + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Configuration +DIFFUSE_VERSION="0.3.0" +DIFFUSE_CACHE_DIR_RELATIVE=".diffuse" +DIFFUSE_URL="https://github.com/JakeWharton/diffuse/releases/download/${DIFFUSE_VERSION}/diffuse-${DIFFUSE_VERSION}.zip" + +# These will be set after repo path is determined +DIFFUSE_CACHE_DIR="" +DIFFUSE_BINARY="" + +# AAR configuration +AAR_OUTPUT_DIR="build/outputs/aar" +AAR_NAME="android-client-release.aar" +GRADLE_TASK="assembleRelease" + +# Temporary files +TEMP_DIR=$(mktemp -d) +CLONE_DIR="${TEMP_DIR}/repo" +TARGET_AAR="${TEMP_DIR}/target-branch.aar" +SOURCE_AAR="${TEMP_DIR}/source-branch.aar" + +# Script arguments +TARGET_BRANCH="" +SOURCE_BRANCH="" +MODULE_PATH="" +REPO_PATH="" + +# Cleanup function - runs on exit, error, or interrupt +cleanup() { + local exit_code=$? + + # Always cleanup, even on success + if [ -d "$TEMP_DIR" ]; then + echo -e "\n${BLUE}Cleaning up temporary files...${NC}" + rm -rf "$TEMP_DIR" + echo "Removed temporary repository clone and AAR files" + fi + + # Only show error message if there was an error + if [ $exit_code -ne 0 ]; then + echo -e "${RED}Script failed with exit code $exit_code${NC}" + fi + + exit $exit_code +} + +# Set trap for cleanup on exit +trap cleanup EXIT INT TERM + +# Print error and exit +error_exit() { + echo -e "${RED}Error: $1${NC}" >&2 + exit 1 +} + +# Print usage +usage() { + cat << EOF +Usage: $0 --target --source [--module ] + +Options: + --target Target branch/tag/commit to compare against (e.g., main, origin/main, v1.0.0) + --source Source branch/tag/commit to compare (e.g., feature/my-feature, v1.1.0) + --module Optional: Module path (defaults to root, future-proof for multi-module) + +Examples: + # Compare branches + $0 --target origin/main --source feature/my-feature + + # Compare tags + $0 --target v1.0.0 --source v1.1.0 + + # Compare tag to branch + $0 --target v1.0.0 --source main + +EOF + exit 1 +} + +# Parse command line arguments +parse_args() { + while [[ $# -gt 0 ]]; do + case $1 in + --target) + TARGET_BRANCH="$2" + shift 2 + ;; + --source) + SOURCE_BRANCH="$2" + shift 2 + ;; + --module) + MODULE_PATH="$2" + shift 2 + ;; + -h|--help) + usage + ;; + *) + error_exit "Unknown option: $1" + ;; + esac + done + + if [ -z "$TARGET_BRANCH" ] || [ -z "$SOURCE_BRANCH" ]; then + error_exit "Both --target and --source branches must be specified" + fi +} + +# Find the Gradle project root (where settings.gradle or build.gradle is located) +find_gradle_root() { + local current_dir="$PWD" + + # Check current directory first + if [ -f "settings.gradle" ] || [ -f "build.gradle" ]; then + echo "$PWD" + return 0 + fi + + # Check if we're in a subdirectory and need to go up + local check_dir="$current_dir" + while [ "$check_dir" != "/" ]; do + if [ -f "$check_dir/settings.gradle" ] || [ -f "$check_dir/build.gradle" ]; then + echo "$check_dir" + return 0 + fi + check_dir=$(dirname "$check_dir") + done + + # If no settings.gradle found, assume current directory + echo "$PWD" +} + +# Get repository path/URL +get_repo_path() { + echo -e "${BLUE}Detecting repository...${NC}" + + # Check if we're in a Git repository + if ! git rev-parse --git-dir > /dev/null 2>&1; then + error_exit "Not in a Git repository. Please run this script from within a Git repository." + fi + + # Get the repository root directory + REPO_PATH=$(git rev-parse --show-toplevel) + echo "Repository path: $REPO_PATH" + + echo -e "${GREEN}✓${NC} Repository detected" +} + +# Clone repository to temporary directory +clone_repo() { + echo -e "${BLUE}Cloning repository to temporary directory...${NC}" + + # Clone the repository (using file:// protocol for local repos) + # This creates a clean copy without affecting the original + if [ -d "$CLONE_DIR" ]; then + rm -rf "$CLONE_DIR" + fi + + echo "Cloning to: $CLONE_DIR" + # Use file:// protocol for local repository cloning + local repo_url + if [[ "$REPO_PATH" == /* ]]; then + # Absolute path - use file:// protocol + repo_url="file://$REPO_PATH" + else + # Relative path + repo_url="file://$(cd "$REPO_PATH" && pwd)" + fi + + # Clone with tags to ensure all tags are available + git clone --tags "$repo_url" "$CLONE_DIR" > /dev/null 2>&1 || error_exit "Failed to clone repository" + + # Fetch all remotes and tags from the original repo (in case of remote refs) + (cd "$CLONE_DIR" && git remote set-url origin "$repo_url" > /dev/null 2>&1 || true) + (cd "$CLONE_DIR" && git fetch --all --tags --prune > /dev/null 2>&1 || true) + + # Also fetch tags directly from the original repo to ensure we have all local tags + (cd "$CLONE_DIR" && git fetch "$repo_url" "+refs/tags/*:refs/tags/*" > /dev/null 2>&1 || true) + + echo -e "${GREEN}✓${NC} Repository cloned" +} + +# Download and setup Diffuse +setup_diffuse() { + echo -e "${BLUE}Setting up Diffuse...${NC}" + + # Use absolute path for cache directory (since we'll be working in temp dir) + if [ -z "$DIFFUSE_CACHE_DIR" ]; then + if [ -n "$REPO_PATH" ]; then + DIFFUSE_CACHE_DIR="$REPO_PATH/$DIFFUSE_CACHE_DIR_RELATIVE" + else + DIFFUSE_CACHE_DIR="$PWD/$DIFFUSE_CACHE_DIR_RELATIVE" + fi + DIFFUSE_JAR="$DIFFUSE_CACHE_DIR/diffuse-${DIFFUSE_VERSION}.jar" + DIFFUSE_BINARY="$DIFFUSE_CACHE_DIR/diffuse-${DIFFUSE_VERSION}" + fi + + # Create cache directory if it doesn't exist + mkdir -p "$DIFFUSE_CACHE_DIR" + + # Set the expected binary path + DIFFUSE_BINARY="${DIFFUSE_CACHE_DIR}/diffuse-${DIFFUSE_VERSION}/bin/diffuse" + + # Check if Diffuse is already cached and valid (binary should exist and be executable) + if [ -f "$DIFFUSE_BINARY" ] && [ -x "$DIFFUSE_BINARY" ]; then + echo -e "${GREEN}✓${NC} Diffuse ${DIFFUSE_VERSION} found in cache" + return 0 + fi + + # Check if Java is available (required for Diffuse JAR) + if ! command -v java > /dev/null 2>&1; then + error_exit "Java is required to run Diffuse. Please install Java." + fi + + echo "Downloading Diffuse ${DIFFUSE_VERSION} from GitHub..." + + # Download Diffuse ZIP (releases are distributed as ZIP files) + local diffuse_zip="${DIFFUSE_CACHE_DIR}/diffuse-${DIFFUSE_VERSION}.zip" + echo "Downloading from: $DIFFUSE_URL" + if command -v curl > /dev/null 2>&1; then + if ! curl -L -f -o "$diffuse_zip" "$DIFFUSE_URL"; then + error_exit "Failed to download Diffuse from $DIFFUSE_URL" + fi + elif command -v wget > /dev/null 2>&1; then + if ! wget -O "$diffuse_zip" "$DIFFUSE_URL"; then + error_exit "Failed to download Diffuse from $DIFFUSE_URL" + fi + else + error_exit "Neither curl nor wget found. Please install one to download Diffuse." + fi + + # Verify ZIP download was successful (ZIP should be > 100KB) + local zip_size + zip_size=$(stat -f%z "$diffuse_zip" 2>/dev/null || stat -c%s "$diffuse_zip" 2>/dev/null || echo "0") + if [ "$zip_size" -lt 100000 ]; then + error_exit "Downloaded ZIP appears corrupted (${zip_size} bytes). Expected > 100KB." + fi + echo "Downloaded ${zip_size} bytes" + + # Extract JAR from ZIP + echo "Extracting JAR from ZIP..." + if ! command -v unzip > /dev/null 2>&1; then + error_exit "unzip is required to extract Diffuse. Please install unzip." + fi + + # Extract to a temporary directory first + local extract_dir="${DIFFUSE_CACHE_DIR}/extract" + rm -rf "$extract_dir" + mkdir -p "$extract_dir" + + if ! unzip -q "$diffuse_zip" -d "$extract_dir"; then + error_exit "Failed to extract Diffuse ZIP" + fi + + # Find the executable script in the extracted contents + local extracted_bin + extracted_bin=$(find "$extract_dir" -path "*/bin/diffuse" -type f | head -1) + if [ -z "$extracted_bin" ] || [ ! -f "$extracted_bin" ]; then + error_exit "Diffuse executable not found in downloaded ZIP" + fi + + # Copy the entire diffuse directory structure to cache + local diffuse_extracted_dir + diffuse_extracted_dir=$(dirname "$(dirname "$extracted_bin")") + local diffuse_cache_extracted="${DIFFUSE_CACHE_DIR}/diffuse-${DIFFUSE_VERSION}" + + # Remove old extracted directory if it exists + rm -rf "$diffuse_cache_extracted" + + # Copy the extracted directory + cp -r "$diffuse_extracted_dir" "$diffuse_cache_extracted" + + # The binary should be at bin/diffuse relative to the extracted directory + DIFFUSE_BINARY="${diffuse_cache_extracted}/bin/diffuse" + + # Make sure it's executable + chmod +x "$DIFFUSE_BINARY" + + # Clean up temporary extraction directory and ZIP + rm -rf "$extract_dir" "$diffuse_zip" + + # Verify the binary exists and is executable + if [ ! -f "$DIFFUSE_BINARY" ] || [ ! -x "$DIFFUSE_BINARY" ]; then + error_exit "Diffuse binary not found or not executable at: $DIFFUSE_BINARY" + fi + echo "Extracted Diffuse binary: $DIFFUSE_BINARY" + + echo -e "${GREEN}✓${NC} Diffuse ${DIFFUSE_VERSION} downloaded and ready" +} + +# Run Diffuse command +run_diffuse() { + local old_aar="$1" + local new_aar="$2" + + if [ ! -f "$old_aar" ]; then + error_exit "Target AAR not found: $old_aar" + fi + + if [ ! -f "$new_aar" ]; then + error_exit "Source AAR not found: $new_aar" + fi + + echo -e "\n${BLUE}Running Diffuse comparison...${NC}" + echo "OLD: $(basename "$old_aar")" + echo "NEW: $(basename "$new_aar")" + echo "" + + # Compare AAR files directly using --aar flag + # Filter output to show only summary tables (size differences) + # Skip detailed lists of classes, methods, and fields + local temp_output + temp_output=$(mktemp) + + # Run Diffuse and capture output + if ! "$DIFFUSE_BINARY" diff --aar "$old_aar" "$new_aar" > "$temp_output" 2>&1; then + # If Diffuse fails, show the error + cat "$temp_output" + rm -f "$temp_output" + error_exit "Diffuse comparison failed" + fi + + # Filter to show only summary/statistics, skip detailed lists + awk ' + # Print everything until we hit CLASSES:, METHODS:, or FIELDS: sections + /^CLASSES:$/ { exit 0 } + /^METHODS:$/ { exit 0 } + /^FIELDS:$/ { exit 0 } + # Print all other lines (headers, size info, summary tables) + { print } + ' "$temp_output" + + # Clean up + rm -f "$temp_output" +} + + +# Build AAR for a specific branch/tag/commit +build_aar() { + local ref="$1" + local output_aar="$2" + + echo -e "\n${BLUE}Building AAR for ref: ${ref}${NC}" + + # Change to cloned repository directory + cd "$CLONE_DIR" || error_exit "Failed to change to cloned repository directory" + + # Verify ref exists (handles branches, tags, and commits) + # First try to verify directly + if ! git rev-parse --verify "$ref" > /dev/null 2>&1; then + # If that fails, try fetching the ref from origin + echo "Ref not found locally, attempting to fetch..." + git fetch origin "$ref:$ref" > /dev/null 2>&1 || true + # Also try fetching as a tag + if [[ "$ref" =~ ^[0-9] ]]; then + git fetch origin "refs/tags/$ref:refs/tags/$ref" > /dev/null 2>&1 || true + fi + # Try again + if ! git rev-parse --verify "$ref" > /dev/null 2>&1; then + # List available tags for debugging + echo -e "${YELLOW}Available tags:${NC}" + git tag | grep -E "^5\.[34]" | head -10 || git tag | tail -10 + error_exit "Ref does not exist: $ref (must be a branch, tag, or commit)" + fi + fi + + # Checkout the ref (works for branches, tags, and commits) + echo "Checking out ref: $ref" + if ! git checkout "$ref" 2>/dev/null; then + # Try with -f flag for tags that might have conflicts + if ! git checkout -f "$ref" 2>/dev/null; then + error_exit "Failed to checkout ref: $ref" + fi + fi + + # Find the Gradle project root after checkout (structure might be different) + local gradle_root + gradle_root=$(find_gradle_root) + + # Check for Gradle wrapper in the project root + local gradlew_path + if [ -f "$gradle_root/gradlew" ]; then + gradlew_path="$gradle_root/gradlew" + elif [ -f "./gradlew" ]; then + gradlew_path="./gradlew" + else + error_exit "Gradle wrapper (gradlew) not found. Expected in: $gradle_root or current directory" + fi + + # Make gradlew executable + chmod +x "$gradlew_path" 2>/dev/null || true + + # Determine the working directory for gradle + # If gradle root is different, we might need to run from there or use -p flag + local gradle_cmd + if [ "$gradle_root" != "$PWD" ]; then + # Run from gradle root, but need to specify the project + # For now, try running from current directory with the gradlew from root + gradle_cmd="$gradlew_path" + echo "Using Gradle wrapper from: $gradle_root" + else + gradle_cmd="./gradlew" + fi + + # Handle parent settings.gradle interference + # If current directory has build.gradle but no settings.gradle, and parent has settings.gradle, + # create a minimal settings.gradle to isolate this build + local temp_settings_created=false + if [ -f "build.gradle" ] && [ ! -f "settings.gradle" ]; then + local parent_settings=$(dirname "$PWD")/settings.gradle + if [ -f "$parent_settings" ]; then + # Create a minimal settings.gradle to prevent Gradle from using parent + echo "rootProject.name = 'android-client'" > "settings.gradle" + temp_settings_created=true + echo "Created temporary settings.gradle to isolate build from parent" + fi + fi + + # Build the AAR + echo "Running: $gradle_cmd $GRADLE_TASK" + if ! "$gradle_cmd" "$GRADLE_TASK" --quiet --no-daemon; then + error_exit "Failed to build AAR for ref: $ref" + fi + + # Find the AAR file - search in multiple possible locations + local aar_path="" + local possible_paths=( + "${AAR_OUTPUT_DIR}/${AAR_NAME}" + "build/outputs/aar/${AAR_NAME}" + "build/outputs/aar/*-release.aar" + "build/outputs/aar/*.aar" + ) + + # Also check relative to gradle root if different + if [ "$gradle_root" != "$PWD" ]; then + possible_paths+=( + "$gradle_root/${AAR_OUTPUT_DIR}/${AAR_NAME}" + "$gradle_root/build/outputs/aar/${AAR_NAME}" + "$gradle_root/build/outputs/aar/*-release.aar" + "$gradle_root/build/outputs/aar/*.aar" + ) + fi + + # Try to find the AAR file + for path_pattern in "${possible_paths[@]}"; do + # Handle glob patterns + if [[ "$path_pattern" == *"*"* ]]; then + # Use find to locate AAR files matching the pattern + local found_aar + found_aar=$(find . -path "$path_pattern" -type f 2>/dev/null | head -1) + if [ -n "$found_aar" ] && [ -f "$found_aar" ]; then + aar_path="$found_aar" + break + fi + else + if [ -f "$path_pattern" ]; then + aar_path="$path_pattern" + break + fi + fi + done + + # If still not found, try a broader search + if [ -z "$aar_path" ]; then + echo "Searching for AAR files in build directory..." + local found_aars + found_aars=$(find . -path "*/build/outputs/aar/*.aar" -type f 2>/dev/null) + if [ -n "$found_aars" ]; then + # Prefer release AAR if available + aar_path=$(echo "$found_aars" | grep -i release | head -1) + if [ -z "$aar_path" ]; then + # Otherwise take the first one found + aar_path=$(echo "$found_aars" | head -1) + fi + fi + fi + + if [ -z "$aar_path" ] || [ ! -f "$aar_path" ]; then + echo -e "${YELLOW}Debug: Listing build outputs...${NC}" + find . -path "*/build/outputs/aar/*" -type f 2>/dev/null | head -10 || true + error_exit "AAR not found after build. Searched in: ${possible_paths[*]}" + fi + + echo "Found AAR at: $aar_path" + + # Copy AAR to temp location + cp "$aar_path" "$output_aar" + echo -e "${GREEN}✓${NC} AAR built and copied: $(basename "$output_aar")" + + # Clean up temporary settings.gradle if we created one + # (This is cleaned up here, but also safe if script fails - temp dir will be removed) + if [ "$temp_settings_created" = true ] && [ -f "settings.gradle" ]; then + rm -f "settings.gradle" + fi +} + +# Main execution +main() { + echo -e "${GREEN}API Diff Tool${NC}" + echo "==============" + echo "" + + # Parse arguments + parse_args "$@" + + # Get repository path + get_repo_path + + # Setup Diffuse (use absolute path for cache since we'll be in temp dir) + local original_dir="$PWD" + setup_diffuse + + # Clone repository to temporary directory + clone_repo + + # Build source branch AAR first (newer version) + # This ensures we build the newer version first, which may have more dependencies + build_aar "$SOURCE_BRANCH" "$SOURCE_AAR" + + # Build target branch AAR second (older version) + build_aar "$TARGET_BRANCH" "$TARGET_AAR" + + # Run Diffuse comparison + # Note: Diffuse shows OLD -> NEW, so we pass target (old) first, then source (new) + # We can run from any directory since we use absolute paths + run_diffuse "$TARGET_AAR" "$SOURCE_AAR" + + echo -e "\n${GREEN}✓${NC} API comparison completed successfully" +} + +# Run main function +main "$@" + From 15616f0f85f156493e2b7ac48f1fdf715559eca0 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Fri, 7 Nov 2025 18:42:59 +0000 Subject: [PATCH 2/9] Create pipeline android-client-diffuse --- .../pipelines/androidclientdiffuse.yaml | 239 ++++++++++++++++++ 1 file changed, 239 insertions(+) create mode 100644 .harness/orgs/PROD/projects/Harness_Split/pipelines/androidclientdiffuse.yaml diff --git a/.harness/orgs/PROD/projects/Harness_Split/pipelines/androidclientdiffuse.yaml b/.harness/orgs/PROD/projects/Harness_Split/pipelines/androidclientdiffuse.yaml new file mode 100644 index 000000000..42f7583ae --- /dev/null +++ b/.harness/orgs/PROD/projects/Harness_Split/pipelines/androidclientdiffuse.yaml @@ -0,0 +1,239 @@ +pipeline: + name: android-client-diffuse + identifier: androidclientdiffuse + projectIdentifier: Harness_Split + orgIdentifier: PROD + tags: {} + stages: + - stage: + name: Build and Analyze + identifier: build_and_analyze + description: Clone repo, run diffuse script with PR branch parameters, and comment on PR + type: CI + spec: + cloneCodebase: true + infrastructure: + type: KubernetesDirect + spec: + connectorRef: <+input> + namespace: <+input> + automountServiceAccountToken: true + nodeSelector: {} + os: Linux + execution: + steps: + - step: + type: Run + name: Setup Environment and Extract PR Info + identifier: setup_environment + spec: + connectorRef: <+input> + image: ubuntu:22.04 + shell: Bash + envVariables: + GH_TOKEN: <+secrets.getValue("github-devops-token")> + command: | + # Install required tools + apt-get update + apt-get install -y curl jq git + + # Extract PR information from trigger payload + PR_NUMBER="<+trigger.payload.pull_request.number>" + PR_TITLE="<+trigger.payload.pull_request.title>" + PR_AUTHOR="<+trigger.payload.pull_request.user.login>" + PR_HEAD_REF="<+trigger.payload.pull_request.head.ref>" + PR_BASE_REF="<+trigger.payload.pull_request.base.ref>" + PR_HEAD_SHA="<+trigger.payload.pull_request.head.sha>" + REPO_FULL_NAME="<+trigger.payload.repository.full_name>" + + echo "=== PR Information ===" + echo "PR Number: $PR_NUMBER" + echo "PR Title: $PR_TITLE" + echo "PR Author: $PR_AUTHOR" + echo "Source Branch (HEAD): $PR_HEAD_REF" + echo "Target Branch (BASE): $PR_BASE_REF" + echo "Head SHA: $PR_HEAD_SHA" + echo "Repository: $REPO_FULL_NAME" + echo "=== End PR Info ===" + + # Check if this is a PR build + if [ -n "$PR_NUMBER" ] && [ "$PR_NUMBER" != "null" ] && [ "$PR_NUMBER" != "" ]; then + echo "✅ Detected PR build" + SOURCE_BRANCH="$PR_HEAD_REF" + TARGET_BRANCH="$PR_BASE_REF" + IS_PR_BUILD="true" + else + echo "ℹ️ Not a PR build - using fallback values" + SOURCE_BRANCH="<+codebase.branch>" + TARGET_BRANCH="main" + IS_PR_BUILD="false" + PR_NUMBER="" + fi + + echo "Final branch configuration:" + echo " Source Branch: $SOURCE_BRANCH" + echo " Target Branch: $TARGET_BRANCH" + echo " Is PR Build: $IS_PR_BUILD" + + # Export variables for subsequent steps + cat > /harness/pr_info.sh << EOF + export PR_NUMBER="$PR_NUMBER" + export PR_TITLE="$PR_TITLE" + export PR_AUTHOR="$PR_AUTHOR" + export SOURCE_BRANCH="$SOURCE_BRANCH" + export TARGET_BRANCH="$TARGET_BRANCH" + export PR_HEAD_SHA="$PR_HEAD_SHA" + export REPO_FULL_NAME="$REPO_FULL_NAME" + export IS_PR_BUILD="$IS_PR_BUILD" + EOF + + chmod +x /harness/pr_info.sh + echo "=== Exported Variables ===" + cat /harness/pr_info.sh + - step: + type: Run + name: Run Diffuse Script with Branch Parameters + identifier: run_diffuse + spec: + connectorRef: <+input> + image: ubuntu:22.04 + shell: Bash + envVariables: + GH_TOKEN: <+secrets.getValue("github-devops-token")> + command: | + # Source the PR information + source /harness/pr_info.sh + + echo "=== Running Diffuse Analysis ===" + echo "Source Branch: $SOURCE_BRANCH" + echo "Target Branch: $TARGET_BRANCH" + + # Verify the script exists + if [ ! -f "scripts/diffuse.sh" ]; then + echo "❌ Error: scripts/diffuse.sh not found" + echo "Available files in scripts/:" + ls -la scripts/ || echo "scripts/ directory not found" + exit 1 + fi + + # Make the script executable + chmod +x scripts/diffuse.sh + + # Run the diffuse script with branch parameters + echo "Executing: ./scripts/diffuse.sh --source '$SOURCE_BRANCH' --target '$TARGET_BRANCH'" + ./scripts/diffuse.sh --source "$SOURCE_BRANCH" --target "$TARGET_BRANCH" > diffuse_output.txt 2>&1 + + # Store the exit code + DIFFUSE_EXIT_CODE=$? + + # Display the output + echo "=== Diffuse Script Output ===" + cat diffuse_output.txt + echo "=== End of Output ===" + echo "Script exit code: $DIFFUSE_EXIT_CODE" + + # Save exit code and output for the comment step + echo "$DIFFUSE_EXIT_CODE" > /harness/diffuse_exit_code.txt + cp diffuse_output.txt /harness/diffuse_output.txt + + # Don't fail the step here - let the comment step handle it + exit 0 + - step: + type: Run + name: Comment on PR + identifier: comment_on_pr + spec: + connectorRef: <+input> + image: ubuntu:22.04 + shell: Bash + envVariables: + GH_TOKEN: <+secrets.getValue("github-devops-token")> + command: | + # Source PR information + source /harness/pr_info.sh + + # Check if this is a PR build + if [ "$IS_PR_BUILD" != "true" ] || [ -z "$PR_NUMBER" ]; then + echo "ℹ️ Not a PR build, skipping comment" + exit 0 + fi + + # Get the diffuse script exit code + DIFFUSE_EXIT_CODE=$(cat /harness/diffuse_exit_code.txt 2>/dev/null || echo "unknown") + + # Read the diffuse output + if [ -f /harness/diffuse_output.txt ]; then + DIFFUSE_OUTPUT=$(cat /harness/diffuse_output.txt) + else + DIFFUSE_OUTPUT="Diffuse script output not found" + fi + + # Determine status emoji and message + if [ "$DIFFUSE_EXIT_CODE" = "0" ]; then + STATUS_EMOJI="✅" + STATUS_TEXT="Success" + else + STATUS_EMOJI="❌" + STATUS_TEXT="Failed (exit code: $DIFFUSE_EXIT_CODE)" + fi + + # Create the comment body + COMMENT_BODY="## 📱 Android Diffuse Analysis Results $STATUS_EMOJI + + **Status:** $STATUS_TEXT + **Source Branch:** \`$SOURCE_BRANCH\` + **Target Branch:** \`$TARGET_BRANCH\` + **Commit:** \`$PR_HEAD_SHA\` + + ### Script Output: + \`\`\` + $DIFFUSE_OUTPUT + \`\`\` + + --- + *Generated by Harness CI Pipeline: [\`android-client-diffuse\`](<+pipeline.execution.url>)* + *Triggered by: @$PR_AUTHOR*" + + # Escape the comment body for JSON + ESCAPED_COMMENT=$(echo "$COMMENT_BODY" | jq -Rs .) + + # Post comment to GitHub + echo "Posting comment to PR #$PR_NUMBER in $REPO_FULL_NAME..." + + RESPONSE=$(curl -s -w "\n%{http_code}" -X POST \ + -H "Authorization: token $GH_TOKEN" \ + -H "Content-Type: application/json" \ + -H "Accept: application/vnd.github.v3+json" \ + -d "{\"body\": $ESCAPED_COMMENT}" \ + "https://api.github.com/repos/$REPO_FULL_NAME/issues/$PR_NUMBER/comments") + + # Extract HTTP code from response + HTTP_CODE=$(echo "$RESPONSE" | tail -n1) + RESPONSE_BODY=$(echo "$RESPONSE" | head -n -1) + + if [ "$HTTP_CODE" -ge 200 ] && [ "$HTTP_CODE" -lt 300 ]; then + echo "✅ Comment posted successfully to PR #$PR_NUMBER" + echo "Comment URL: $(echo "$RESPONSE_BODY" | jq -r '.html_url // "N/A"')" + else + echo "❌ Failed to post comment. HTTP code: $HTTP_CODE" + echo "Response: $RESPONSE_BODY" + # Don't fail the pipeline for comment failures + fi + + # Fail the pipeline if diffuse script failed + if [ "$DIFFUSE_EXIT_CODE" != "0" ] && [ "$DIFFUSE_EXIT_CODE" != "unknown" ]; then + echo "❌ Failing pipeline due to diffuse script failure (exit code: $DIFFUSE_EXIT_CODE)" + exit 1 + fi + + echo "✅ Pipeline completed successfully" + when: + stageStatus: All + caching: + enabled: false + properties: + ci: + codebase: + connectorRef: <+input> + repoName: <+input> + build: <+input> From 6bb2a790a94abcc211794fdfac6c5d9811b1f763 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Fri, 7 Nov 2025 18:44:09 +0000 Subject: [PATCH 3/9] Create inputset androidclient_PR --- .../input_sets/androidclient_PR.yaml | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 .harness/orgs/PROD/projects/Harness_Split/pipelines/androidclientdiffuse/input_sets/androidclient_PR.yaml diff --git a/.harness/orgs/PROD/projects/Harness_Split/pipelines/androidclientdiffuse/input_sets/androidclient_PR.yaml b/.harness/orgs/PROD/projects/Harness_Split/pipelines/androidclientdiffuse/input_sets/androidclient_PR.yaml new file mode 100644 index 000000000..e4901f93b --- /dev/null +++ b/.harness/orgs/PROD/projects/Harness_Split/pipelines/androidclientdiffuse/input_sets/androidclient_PR.yaml @@ -0,0 +1,43 @@ +inputSet: + name: androidclient_PR + identifier: androidclient_PR + orgIdentifier: PROD + projectIdentifier: Harness_Split + pipeline: + identifier: androidclientdiffuse + stages: + - stage: + identifier: build_and_analyze + type: CI + spec: + infrastructure: + type: KubernetesDirect + spec: + connectorRef: "" + namespace: "" + execution: + steps: + - step: + identifier: setup_environment + type: Run + spec: + connectorRef: "" + - step: + identifier: run_diffuse + type: Run + spec: + connectorRef: "" + - step: + identifier: comment_on_pr + type: Run + spec: + connectorRef: "" + properties: + ci: + codebase: + connectorRef: fmegithubrunnersci + repoName: android-client + build: + type: PR + spec: + number: <+trigger.prNumber> From 5ccad649e2c2f615c71bb560012760786dacade8 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Fri, 7 Nov 2025 18:55:19 +0000 Subject: [PATCH 4/9] Update pipeline android-client-diffuse --- .../pipelines/androidclientdiffuse.yaml | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/.harness/orgs/PROD/projects/Harness_Split/pipelines/androidclientdiffuse.yaml b/.harness/orgs/PROD/projects/Harness_Split/pipelines/androidclientdiffuse.yaml index 42f7583ae..7b8dfe808 100644 --- a/.harness/orgs/PROD/projects/Harness_Split/pipelines/androidclientdiffuse.yaml +++ b/.harness/orgs/PROD/projects/Harness_Split/pipelines/androidclientdiffuse.yaml @@ -12,14 +12,6 @@ pipeline: type: CI spec: cloneCodebase: true - infrastructure: - type: KubernetesDirect - spec: - connectorRef: <+input> - namespace: <+input> - automountServiceAccountToken: true - nodeSelector: {} - os: Linux execution: steps: - step: @@ -231,6 +223,12 @@ pipeline: stageStatus: All caching: enabled: false + platform: + os: Linux + arch: Arm64 + runtime: + type: Cloud + spec: {} properties: ci: codebase: From ce84c3919d96e0c44662b4c082026395999bdfff Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Fri, 7 Nov 2025 16:09:47 -0300 Subject: [PATCH 5/9] Update pipeline --- .../pipelines/androidclientdiffuse.yaml | 46 +++++++++++++++---- 1 file changed, 38 insertions(+), 8 deletions(-) diff --git a/.harness/orgs/PROD/projects/Harness_Split/pipelines/androidclientdiffuse.yaml b/.harness/orgs/PROD/projects/Harness_Split/pipelines/androidclientdiffuse.yaml index 7b8dfe808..ba30fb3dd 100644 --- a/.harness/orgs/PROD/projects/Harness_Split/pipelines/androidclientdiffuse.yaml +++ b/.harness/orgs/PROD/projects/Harness_Split/pipelines/androidclientdiffuse.yaml @@ -12,6 +12,19 @@ pipeline: type: CI spec: cloneCodebase: true + caching: + enabled: true + override: true + platform: + os: Linux + arch: Amd64 + runtime: + type: Cloud + spec: {} + variables: + - name: ANDROID_HOME + type: String + value: /opt/android-sdk execution: steps: - step: @@ -82,6 +95,18 @@ pipeline: chmod +x /harness/pr_info.sh echo "=== Exported Variables ===" cat /harness/pr_info.sh + - step: + type: Run + name: Install Dependencies + identifier: install_dependencies + spec: + connectorRef: <+input> + image: ubuntu:22.04 + shell: Bash + command: | + # Use the existing install-deps.sh script for consistent setup + chmod +x .harness/scripts/install-deps.sh + .harness/scripts/install-deps.sh - step: type: Run name: Run Diffuse Script with Branch Parameters @@ -93,6 +118,19 @@ pipeline: envVariables: GH_TOKEN: <+secrets.getValue("github-devops-token")> command: | + set -e + + # Set Android SDK environment variables (matching instrumented pipeline) + export ANDROID_SDK_ROOT=$ANDROID_HOME + export JAVA_HOME=$(dirname $(dirname $(readlink -f $(which java)))) + export PATH="$PATH:$ANDROID_HOME/platform-tools:$ANDROID_HOME/cmdline-tools/latest/bin" + + # Verify environment + echo "=== Environment Verification ===" + echo "JAVA_HOME: $JAVA_HOME" + echo "ANDROID_HOME: $ANDROID_HOME" + java -version + # Source the PR information source /harness/pr_info.sh @@ -221,14 +259,6 @@ pipeline: echo "✅ Pipeline completed successfully" when: stageStatus: All - caching: - enabled: false - platform: - os: Linux - arch: Arm64 - runtime: - type: Cloud - spec: {} properties: ci: codebase: From d36bd635d025e486ce0aba05dfc97d59cda1f244 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Fri, 7 Nov 2025 16:15:06 -0300 Subject: [PATCH 6/9] Pipeline fix --- .../Harness_Split/pipelines/androidclientdiffuse.yaml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.harness/orgs/PROD/projects/Harness_Split/pipelines/androidclientdiffuse.yaml b/.harness/orgs/PROD/projects/Harness_Split/pipelines/androidclientdiffuse.yaml index ba30fb3dd..56bf82d24 100644 --- a/.harness/orgs/PROD/projects/Harness_Split/pipelines/androidclientdiffuse.yaml +++ b/.harness/orgs/PROD/projects/Harness_Split/pipelines/androidclientdiffuse.yaml @@ -32,7 +32,6 @@ pipeline: name: Setup Environment and Extract PR Info identifier: setup_environment spec: - connectorRef: <+input> image: ubuntu:22.04 shell: Bash envVariables: @@ -100,7 +99,6 @@ pipeline: name: Install Dependencies identifier: install_dependencies spec: - connectorRef: <+input> image: ubuntu:22.04 shell: Bash command: | @@ -112,7 +110,6 @@ pipeline: name: Run Diffuse Script with Branch Parameters identifier: run_diffuse spec: - connectorRef: <+input> image: ubuntu:22.04 shell: Bash envVariables: @@ -173,7 +170,6 @@ pipeline: name: Comment on PR identifier: comment_on_pr spec: - connectorRef: <+input> image: ubuntu:22.04 shell: Bash envVariables: From a61f646ec4b735b96f48cab4a47509999167ce48 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Fri, 7 Nov 2025 16:29:54 -0300 Subject: [PATCH 7/9] Fix --- .harness/scripts/install-deps.sh | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/.harness/scripts/install-deps.sh b/.harness/scripts/install-deps.sh index bbe6e6795..ff67049b9 100755 --- a/.harness/scripts/install-deps.sh +++ b/.harness/scripts/install-deps.sh @@ -11,17 +11,24 @@ echo "" # ============================================ echo "=== Installing Java 17 ===" +# Detect if we need sudo (not running as root) +if [ "$EUID" -eq 0 ] || [ "$(id -u)" -eq 0 ]; then + SUDO="" +else + SUDO="sudo" +fi + # Install Java 17 echo "Installing OpenJDK 17..." -sudo apt-get update -qq -sudo apt-get install -y openjdk-17-jdk +$SUDO apt-get update -qq +$SUDO apt-get install -y openjdk-17-jdk # Set JAVA_HOME to Java 17 explicitly export JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64 # Update alternatives to use Java 17 as default -sudo update-alternatives --set java /usr/lib/jvm/java-17-openjdk-amd64/bin/java -sudo update-alternatives --set javac /usr/lib/jvm/java-17-openjdk-amd64/bin/javac +$SUDO update-alternatives --set java /usr/lib/jvm/java-17-openjdk-amd64/bin/java +$SUDO update-alternatives --set javac /usr/lib/jvm/java-17-openjdk-amd64/bin/javac echo "" echo "Java 17 installation complete:" From 811b72011508e2fbadde96e712da9602728775d6 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Fri, 7 Nov 2025 17:24:30 -0300 Subject: [PATCH 8/9] Fix unbound var --- .../Harness_Split/pipelines/androidclientdiffuse.yaml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.harness/orgs/PROD/projects/Harness_Split/pipelines/androidclientdiffuse.yaml b/.harness/orgs/PROD/projects/Harness_Split/pipelines/androidclientdiffuse.yaml index 56bf82d24..8970e5b4f 100644 --- a/.harness/orgs/PROD/projects/Harness_Split/pipelines/androidclientdiffuse.yaml +++ b/.harness/orgs/PROD/projects/Harness_Split/pipelines/androidclientdiffuse.yaml @@ -101,7 +101,12 @@ pipeline: spec: image: ubuntu:22.04 shell: Bash + envVariables: + ANDROID_HOME: <+stage.variables.ANDROID_HOME> command: | + # Export ANDROID_HOME to ensure it's available to the script + export ANDROID_HOME="$ANDROID_HOME" + # Use the existing install-deps.sh script for consistent setup chmod +x .harness/scripts/install-deps.sh .harness/scripts/install-deps.sh @@ -114,6 +119,7 @@ pipeline: shell: Bash envVariables: GH_TOKEN: <+secrets.getValue("github-devops-token")> + ANDROID_HOME: <+stage.variables.ANDROID_HOME> command: | set -e From 317fc4f82730717f5fec891561f797b76ae13be1 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Fri, 7 Nov 2025 17:34:33 -0300 Subject: [PATCH 9/9] Install curl --- .../Harness_Split/pipelines/androidclientdiffuse.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.harness/orgs/PROD/projects/Harness_Split/pipelines/androidclientdiffuse.yaml b/.harness/orgs/PROD/projects/Harness_Split/pipelines/androidclientdiffuse.yaml index 8970e5b4f..f94b1f474 100644 --- a/.harness/orgs/PROD/projects/Harness_Split/pipelines/androidclientdiffuse.yaml +++ b/.harness/orgs/PROD/projects/Harness_Split/pipelines/androidclientdiffuse.yaml @@ -104,6 +104,10 @@ pipeline: envVariables: ANDROID_HOME: <+stage.variables.ANDROID_HOME> command: | + # Install basic tools needed by install-deps.sh + apt-get update -qq + apt-get install -y curl unzip + # Export ANDROID_HOME to ensure it's available to the script export ANDROID_HOME="$ANDROID_HOME"