#!/bin/bash -e

# This script takes a csproj as input and creates a file with a make variable
# that lists the input files for that csproj.
#
# The idea is to use this make variable as a dependency to a make rule that
# builds the csproj.
#
# The advantage of using make to determine if a csproj needs to be rebuilt is
# that it's much faster when nothing needs to be done: make takes ~0.015s to
# figure this out, msbuild takes ~1.5s. Multiply by the number of csprojs
# (currently 3: mtouch, mmp, generator) by the number of times built every day
# (dozens), and seconds adds up as years pass.
#

cd "$(dirname "$0")"

if test -z "$1"; then
	echo "Must specify the project file to process."
	exit 1
fi

# Input: the csproj file to process.
# Output: /path/to/proj.csproj.inc

PROJECT_FILE="$1"
PROJECT=$(basename -s .csproj "$PROJECT_FILE")
PROJECT_DIR=$(dirname "$PROJECT_FILE")
FRAGMENT_PATH="$2"
REFERENCES_PATH=$(pwd)/$PROJECT-references.txt

if test -z "$FRAGMENT_PATH"; then
	FRAGMENT_PATH=$PROJECT_FILE.inc
fi

if test -z "$BUILD_EXECUTABLE"; then
	if test -z "$DOTNET"; then
		echo "The DOTNET environment variable isn't set to the location of the 'dotnet' executable"
		exit 1
	fi
	BUILD_EXECUTABLE="$DOTNET build"
fi

if test -z "$BUILD_VERBOSITY"; then
	BUILD_VERBOSITY=/verbosity:diag
fi

# ProjectInspector.csproj is an MSBuild file with a target
# (WriteProjectReferences) that takes another project file as input (the
# ProjectFile variable) and writes all the project references (recursively) to
# a file (the ReferenceListPath variable).
(
cp ProjectInspector.csproj "$PROJECT_DIR"
cd "$PROJECT_DIR"
$BUILD_EXECUTABLE ProjectInspector.csproj "/t:WriteProjectReferences" "/p:ProjectFile=$PROJECT_FILE" "/p:ReferenceListPath=$REFERENCES_PATH" $BUILD_VERBOSITY /nologo
rm -f ProjectInspector.csproj
)

# Now we have a list of all the project referenced by the input project. The
# ProjectInspector.csproj also has a target (WriteInputs) that can list all
# the inputs for a project.
#
# The caveat here is that ProjectInspector.csproj must be in the same
# directory as the inspected project, because relative paths in the inspected
# project are evaluated according to the location of the main project file
# (our ProjectInspector.csproj). Relative paths can't be fixed manually
# afterwards, because relative paths with patterns (where MSBuild evaluates
# the patterns) won't come out right.
#
# So we copy ProjectInspector.csproj to the inspected project's directory and
# execute it from there.

TMPPROJ=

# Make sure we don't leave temporary files around
function delete_tmpproj
{
	rm -f "$REFERENCES_PATH"
	if test -n "$TMPPROJ"; then
		rm -f "$TMPPROJ"
	fi
}
trap delete_tmpproj EXIT
trap delete_tmpproj ERR

echo "$PROJECT_FILE" >> "$REFERENCES_PATH"

INPUT_PATHS=()
for proj in $(sort "$REFERENCES_PATH" | uniq); do
	TMPPROJ=$PROJECT_DIR/ProjectInspector.csproj
	# Copy ProjectInspector.csproj to target directory.
	cp ProjectInspector.csproj "$TMPPROJ"

	# Execute the WriteInputs target
	proj_name=$(basename "$proj")
	proj_dir=$(dirname "$proj")
	inputs_path=$PWD/$proj_name.inputs
	INPUT_PATHS+=("$inputs_path")

	(
	cd "$(dirname "$proj")"
	$BUILD_EXECUTABLE "$TMPPROJ" "/t:WriteInputs" "/p:ProjectFile=$proj" "/p:InputsPath=$inputs_path" $BUILD_VERBOSITY /nologo
	)

	# The output contains relative paths, relative to the csproj directory
	# Change those to full paths by prepending the csproj directory.
	sed -i '' "s@^@$proj_dir/@" "$inputs_path"

	# Change to Make syntax. This is horrifically difficult in MSBuild,
	# because MSBuild blindly replaces backslashes with forward slashes (i.e.
	# windows paths to unix paths...)
	sed -i '' "s_^\\(.*\\)\$_    \\1 \\\\_" "$inputs_path"

	# Clean up
	rm -f "$TMPPROJ"
done

# Create the csproj.inc with the Makefile variable

# First add a dependency for the csproj.inc so that it's rebuilt if the project itself changes.
echo "${PROJECT}.csproj.inc: ${PROJECT_FILE} $PWD/ProjectInspector.csproj" > "$FRAGMENT_PATH"
# Now create the variable with all the input files.
echo "${PROJECT//-/_}_dependencies = \\" >> "$FRAGMENT_PATH"
sort "${INPUT_PATHS[@]}" | uniq >> "$FRAGMENT_PATH"

# Simplify paths somewhat by removing the current directory
if test -z "$ABSOLUTE_PATHS"; then
	sed -i '' "s@$PROJECT_DIR/@@" "$FRAGMENT_PATH"
fi

# Cleanup
rm -f "${INPUT_PATHS[@]}"
