#!/bin/bash -e BASE_HASH= COMP_HASH=HEAD WORKING_DIR= WHITE=$(tput setaf 7 2>/dev/null || true) BLUE=$(tput setaf 6 2>/dev/null || true) RED=$(tput setaf 9 2>/dev/null || true) CLEAR=$(tput sgr0 2>/dev/null || true) # Clone files on High Sierra, instead of copying them. Much faster. CP="cp" if df -t apfs / >/dev/null 2>&1; then CP="cp -c" fi function report_error_line () { echo "$@" if test -n "$FAILURE_FILE"; then # remove color codes when writing to failure file # shellcheck disable=SC2001 echo "$@" | sed $'s,\x1b\\[[0-9;]*[a-zA-Z],,g' >> "$FAILURE_FILE" fi } function show_help () { echo "$(basename "$0"): Compare the managed API and generate a diff for the generated code between the currently built assemblies and a specific hash." echo "Usage is: $(basename "$0") --base=[TREEISH] [options]" echo " -h, -?, --help Displays the help" echo " -b, --base=[TREEISH] The treeish to compare the currently built assemblies against." #not quite implemented yet# echo " -c, --compare=[TREEISH] Optional, if specified use this hash to build the 'after' assemblies for the comparison." echo "" printf "${BLUE} WARNING: This tool will temporarily change the current HEAD of your git checkout.${CLEAR}\\n" printf "${BLUE} WARNING: The tool will try to restore the current HEAD when done (or if cancelled), but this is not guaranteed.${CLEAR}\\n" echo "" } ORIGINAL_ARGS=("$@") FAILURE_FILE= while ! test -z "$1"; do case "$1" in --help|-\?|-h) show_help exit 0 ;; --base=*|-b=*) BASE_HASH="${1#*=}" shift ;; --base|-b) BASE_HASH="$2" shift 2 ;; --compare=*|-c=*) COMP_HASH="${1#*=}" shift ;; --compare|-c) COMP_HASH="$2" shift 2 ;; --impl-working-dir=*) WORKING_DIR="${1#*=}" shift ;; --failure-file=*) FAILURE_FILE="${1#*=}" shift ;; --failure-file) FAILURE_FILE="$2" shift 2 ;; *) echo "Error: Unknown argument: $1" exit 1 ;; esac done if test -z "$BASE_HASH"; then report_error_line "${RED}Error: It's required to specify the hash to compare against (--base=HASH).${CLEAR}" exit 1 fi ROOT_DIR=$(git rev-parse --show-toplevel) # Go to the root directory of the git repo, so that we don't run into any surprises with paths. # Also make ROOT_DIR an absolute path. cd "$ROOT_DIR" ROOT_DIR=$(pwd) # Only show colors locally. The normal "has-controlling-terminal" doesn't work, because # we always capture the output to indent it (thus the git processes never have a controlling # terminal) if test -z "$BUILD_REVISION"; then GIT_COLOR=--color=always GIT_COLOR_P="-c color.status=always" fi if [ -n "$(git status --porcelain --ignore-submodule)" ]; then report_error_line "${RED}** Error: Working directory isn't clean:${CLEAR}" git $GIT_COLOR_P status --ignore-submodule | sed 's/^/ /' | while read line; do report_error_line "$line"; done exit 1 fi echo "Comparing the changes between $WHITE$BASE_HASH$CLEAR and $WHITE$COMP_HASH$CLEAR:" git log "$BASE_HASH..$COMP_HASH" --oneline $GIT_COLOR | sed 's/^/ /' # Resolve any treeish hash value (for instance HEAD^4) to the unique (MD5) hash COMP_HASH=$(git log -1 --pretty=%H "$COMP_HASH") BASE_HASH=$(git log -1 --pretty=%H "$BASE_HASH") # We'll clone xamarin-macios again into a different directory, and build it GENERATOR_DIFF_FILE= APIDIFF_FILE= OUTPUT_DIR=$ROOT_DIR/tools/comparison OUTPUT_SRC_DIR=$OUTPUT_DIR/src function upon_exit () { if ! test -z "$GENERATOR_DIFF_FILE"; then echo "Generator diff: $GENERATOR_DIFF_FILE" fi if ! test -z "$APIDIFF_FILE"; then echo "API diff: $APIDIFF_FILE" fi if test -f "$ROOT_DIR/NuGet.config.disabled"; then mv "$ROOT_DIR/NuGet.config.disabled" "$ROOT_DIR/NuGet.config" fi # Clean up after ourselves (but leave the comparison) rm -Rf "$OUTPUT_SRC_DIR" rm -Rf "$OUTPUT_DIR/build" rm -Rf "$OUTPUT_DIR/build-new" } trap upon_exit EXIT if test -z "$CURRENT_BRANCH"; then echo "${BLUE}Current hash: ${WHITE}$(git log -1 --pretty="%h: %s")${BLUE} (detached)${CLEAR}" else echo "${BLUE}Current branch: ${WHITE}$CURRENT_BRANCH${BLUE} (${WHITE}$(git log -1 --pretty="%h: %s")${BLUE})${CLEAR}" fi echo "${BLUE}Checking out ${WHITE}$(git log -1 --pretty="%h: %s" "$BASE_HASH")${CLEAR}...${CLEAR}" rm -Rf "$OUTPUT_DIR" mkdir -p "$OUTPUT_DIR" mkdir -p "$OUTPUT_SRC_DIR" # The top-level NuGet.config interferes with the build in XmlSync (for some # reason NuGet picks up the top-level NuGet.config instead of the NuGet.config # in XmlSync - even though all documentation I've read states otherwise). mv "$ROOT_DIR/NuGet.config" "$ROOT_DIR/NuGet.config.disabled" cd "$OUTPUT_SRC_DIR" git clone https://github.com/xamarin/xamarin-macios --reference "$ROOT_DIR" cd xamarin-macios git reset --hard "$BASE_HASH" cp "$ROOT_DIR/configure.inc" . make reset make check-versions if ! ./system-dependencies.sh; then report_error_line "${RED}Error: The system requirements for the hash to compare against ($WHITE$BASE_HASH$CLEAR) are different than for the current hash. Comparison is currently not supported in this scenario.${CLEAR}" exit 1 fi if ! make all -j8; then report_error_line "${RED}Error: 'make' failed for the hash $WHITE$BASE_HASH$CLEAR.${CLEAR}" exit 1 fi if ! make install -j8; then report_error_line "${RED}Error: 'make install' failed for the hash $WHITE$BASE_HASH$CLEAR.${CLEAR}" exit 1 fi # Restore NuGet.config mv "$ROOT_DIR/NuGet.config.disabled" "$ROOT_DIR/NuGet.config" # # API diff # # First we calculate the apidiff references for the hash we're comparing against # Then we restore the original hash, and finally we calculate the api diff. # # Calculate apidiff references according to the temporary build echo "${BLUE}Updating apidiff references...${CLEAR}" if ! make update-refs -C "$ROOT_DIR/tools/apidiff" -j8 APIDIFF_DIR="$OUTPUT_DIR/apidiff" IOS_DESTDIR="$OUTPUT_SRC_DIR/xamarin-macios/_ios-build" MAC_DESTDIR="$OUTPUT_SRC_DIR/xamarin-macios/_mac-build"; then EC=$? report_error_line "${RED}Failed to update apidiff references${CLEAR}" exit "$EC" fi # # Generator diff # # We make a copy of the generated source code to compare against, # so that we can remove files we don't want to compare without # affecting that build. $CP -R "$ROOT_DIR/src/build" "$OUTPUT_DIR/build-new" $CP -R "$OUTPUT_SRC_DIR/xamarin-macios/src/build" "$OUTPUT_DIR/build" # delete files we don't care are different cd "$OUTPUT_DIR" find "$OUTPUT_DIR/build" "$OUTPUT_DIR/build-new" '(' \ -name 'compiler' -or \ -name 'bgen' -or \ -name '*.dll' -or \ -name '*.pdb' -or \ -name '*generated-sources' -or \ -name 'generated_sources' -or \ -name '*.exe' -or \ -name '*.rsp' -or \ -name 'AssemblyInfo.cs' -or \ -name 'Constants.cs' -or \ -name 'generator.csproj*' -or \ -name 'bgen.csproj.*' -or \ -name 'PublishOutputs.*.txt' -or \ -name '*.cache' \ ')' -delete mkdir -p "$OUTPUT_DIR/generator-diff" GENERATOR_DIFF_FILE="$OUTPUT_DIR/generator-diff/index.html" git diff --no-index build build-new > "$OUTPUT_DIR/generator-diff/generator.diff" || true "$ROOT_DIR/tools/diff-to-html" "$OUTPUT_DIR/generator-diff/generator.diff" "$GENERATOR_DIFF_FILE" # Now compare the current build against those references echo "${BLUE}Running apidiff...${CLEAR}" APIDIFF_FILE=$OUTPUT_DIR/apidiff/api-diff.html if ! make all-local -C "$ROOT_DIR/tools/apidiff" -j8 APIDIFF_DIR="$OUTPUT_DIR/apidiff"; then EC=$? report_error_line "${RED}Failed to run apidiff${CLEAR}" exit "$EC" fi