From 8b8fa5cff20f994047555b1c7af2bd566cbd5c15 Mon Sep 17 00:00:00 2001 From: Jonathan Pryor Date: Wed, 7 Jun 2017 15:05:16 -0400 Subject: [PATCH] [xabuild] Support using `msbuild` from mono 4.8+ `tools/scripts/xabuild` is an MSBuild wrapper to build Xamarin.Android apps from the command-line, using the built in-tree binaries; see commit e20863ea. `xabuild` originally invoked `xbuild`; commit 6805d204 extended that to support building with `msbuild` by setting the `MSBUILD` environment variable: MSBUILD=msbuild .../tools/scripts/xabuild SomeProject.csproj This use worked with Mono 4.4 and 4.6, but broke -- unnoticed -- when running under Mono 4.8 and later. When attempting to use `msbuild` from Mono 4.8 and later, the build will fail, cryptically: xamarin-android/bin/Debug/lib/xbuild/Xamarin/Android/Xamarin.Android.Common.targets(1007,2): error MSB3191: Unable to create directory "/Debug/". Access to the path "/Debug/" is denied. The directory `/Debug/` is mentioned because the `$(BaseIntermediateOutputPath)` MSBuild property is not being set. *Normally* we'd be creating e.g. `obj/Debug`, but without `$(BaseIntermediateOutputPath)`, this becomes `/Debug`, and everything breaks. `$(BaseIntermediateOutputPath)` isn't set, in turn, because `Microsoft.Common.props` isn't being imported. `Microsoft.Common.props` isn't being imported because `xabuild` overrides the `$MSBuildExtensionsPath` environment variable, and MSBuild is trying to import `$(MSBuildExtensionsPath)\...\Microsoft.Common.props`, which doesn't exist when we've overridden `$MSBuildExtensionsPath` to use our in-tree build location. *A* fix for this *could* be *not overriding `$MSBuildExtensionsPath`* when using `msbuild`. There is one problem with this, though: not overriding `$MSBuildExtensionsPath` means that the `.targets` files in e.g. `bin/Debug/lib/xbuild/Xamarin/Android` *will not be used*. Instead, the *system* files are used, which is undesirable. Unfortunatley, this means we need to continue overriding `$MSBuildExtensionsPath`, *while* allowing e.g. `$(MSBuildExtensionsPath)\...\Microsoft.Common.props` to be found. Square this circle by *symlinking* everything in the *system* `$MSBuildExtensionsPath` directory into our `$prefix/lib/xbuild`. Symlinking all these files raises *another* issue: we *don't* want all of these symlinks, *or their targets*, to be added to the `oss-xamarin.android*.zip` and `Xamarin.Android*.vsix` files. Support this by creating a `$prefix/lib/xbuild/.__sys_links.txt` file, which contains `zip -x` patterns (hence the `/*` suffix for directories), one per line, and update the `make package-oss` target to look for and exclude the contents of these files so that we don't "pollute" the `oss-xamarin.android*.zip` file with unnecessary contents. Similarly, update the `create-vsix` project so that instead of including `$(LibDir)xbuild\**\*.*`, it instead only includes the contents of `$(LibDir)\xbuild\Novell\**\*.*` and `$(LibDir)\xbuild\Xamarin\**\*.*`. Finally, some cleanups: * Only set `$XBUILD_FRAMEWORK_FOLDERS_PATH` when using `xbuild`. * Additionally, specify `$(TargetFrameworkRootPath)` on `msbuild`, as that MSBuild property can actually be overridden w/ `msbuild`. * Always specify `$(MonoAndroidBinDirectory)` and `$(MonoAndroidToolsDirectory)`, as this mirrors the Windows usage instructions. * Don't *require* that `$ANDROID_NDK_PATH` and `$ANDROID_SDK_PATH` be set prior to executing `xabuild`, *or* that we be running in-tree (to use `Paths.targets`). The NDK, in particular, isn't always required, so requiring that it be set is annoying. The build process will error out appropriately if required directories cannot be found. --- build-tools/create-vsix/create-vsix.targets | 14 +- build-tools/scripts/BuildEverything.mk | 12 +- tools/scripts/xabuild | 165 ++++++++++++++++---- 3 files changed, 154 insertions(+), 37 deletions(-) diff --git a/build-tools/create-vsix/create-vsix.targets b/build-tools/create-vsix/create-vsix.targets index 08d87a556c4..b46ac5bd0cc 100644 --- a/build-tools/create-vsix/create-vsix.targets +++ b/build-tools/create-vsix/create-vsix.targets @@ -26,18 +26,16 @@ - - - - - %(RecursiveDir) - + - Xamarin/Android + Novell/%(RecursiveDir) + - Xamarin/Android/lib/host-win + Xamarin/%(RecursiveDir) + + Xamarin/%(RecursiveDir) diff --git a/build-tools/scripts/BuildEverything.mk b/build-tools/scripts/BuildEverything.mk index 8192b691c5a..44bf6482081 100644 --- a/build-tools/scripts/BuildEverything.mk +++ b/build-tools/scripts/BuildEverything.mk @@ -156,6 +156,16 @@ package-oss $(ZIP_OUTPUT): if [ -d bin/Release/bin ] ; then cp tools/scripts/xabuild bin/Release/bin ; fi if [ ! -d $(ZIP_OUTPUT_BASENAME) ] ; then mkdir $(ZIP_OUTPUT_BASENAME) ; fi if [ ! -L $(ZIP_OUTPUT_BASENAME)/bin ] ; then ln -s ../bin $(ZIP_OUTPUT_BASENAME) ; fi + _exclude_list=".__exclude_list.txt"; \ + ls -1d $(_BUNDLE_ZIPS_EXCLUDE) > "$$_exclude_list" 2>/dev/null ; \ + for c in $(CONFIGURATIONS) ; do \ + _sl="$(ZIP_OUTPUT_BASENAME)/bin/$$c/lib/xbuild/.__sys_links.txt"; \ + if [ ! -f "$$_sl" ]; then continue; fi; \ + for f in `cat $$_sl` ; do \ + echo "$(ZIP_OUTPUT_BASENAME)/bin/$$c/lib/xbuild/$$f" >> "$$_exclude_list"; \ + done; \ + done; \ zip -r "$(ZIP_OUTPUT)" \ `ls -1d $(_BUNDLE_ZIPS_INCLUDE) 2>/dev/null` \ - --exclude `ls -1d $(_BUNDLE_ZIPS_EXCLUDE) 2>/dev/null` + "-x@$$_exclude_list" + -rm ".__exclude_list.txt" diff --git a/tools/scripts/xabuild b/tools/scripts/xabuild index ed8172607d2..106e0311cb9 100755 --- a/tools/scripts/xabuild +++ b/tools/scripts/xabuild @@ -1,26 +1,83 @@ #!/bin/bash -e +# +# MSBuild wrapper to build Xamarin.Android apps from the command-line, +# without pesky IDE interference. +# +# Supported MSBuild targets and properties: +# https://developer.xamarin.com/guides/android/under_the_hood/build_process/ +# +# Overridable environment variables: +# ANDROID_NDK_PATH: +# Path to Android NDK. +# Defaults to `$(AndroidNdkDirectory)` in ../../Configuration.props. +# ANDROID_SDK_PATH: +# Path to Android SDK. +# Defaults to `$(AndroidNdkDirectory)` in ../../Configuration.props. +# CONFIGURATION: +# For in-source-tree invocations, the `bin/$(CONFIGURATION)` directory to +# use as the installation prefix. If not specified, defaults to `Debug`. +# MSBUILD: +# MSBuild engine to use. Defaults to `xbuild`, assumed to be in `$PATH` +# TARGETS_DIR: +# The MSBuild `$(MSBuildExtensionsPath)` root location. +# Defaults to `$prefix/lib/xbuild`. +# +# Examples: +# To create a .apk for the HelloWorld sample: +# tools/scripts/xabuild /t:SignAndroidPackage samples/HelloWorld/HelloWorld.csproj +# +# To explicitly use `msbuild` for builds: +# MSBUILD=msbuild tools/scripts/xabuild /t:SignAndroidPackage samples/HelloWorld/HelloWorld.csproj +# +# To explicitly use `xbuild` for builds: +# MSBUILD=xbuild tools/scripts/xabuild /t:SignAndroidPackage samples/HelloWorld/HelloWorld.csproj +# + name=$(basename "$0") +prefix="$(cd `dirname "$0"` && pwd)" -if [ -z "$CONFIGURATION" ] ; then - CONFIGURATION=Debug +if [ -z "$MSBUILD" ] ; then + MSBUILD=xbuild fi -xabuilddir="$(cd `dirname "$0"` && pwd)" - -if [[ "$xabuilddir" == */tools/scripts ]] ; then - topdir="$xabuilddir/../../bin/$CONFIGURATION" - Paths_targets="$xabuilddir/../../build-tools/scripts/Paths.targets" -elif [[ "$xabuilddir" == */bin ]] ; then - topdir="$xabuilddir/.." +if [[ "$prefix" == */tools/scripts ]] ; then + Paths_targets="$prefix/../../build-tools/scripts/Paths.targets" + for c in "$CONFIGURATION" Debug Release ; do + if [ -z "$c" ]; then + continue + fi + if [ -d "$prefix/../../bin/$c" ]; then + real_prefix="$prefix/../../bin/$c" + break + fi + done + if [ -z "$real_prefix" ]; then + (>&2 echo "$name: Could not determine Xamarin.Android prefix.") + exit 1 + fi + prefix="$real_prefix" +elif [[ "$prefix" == */bin ]] ; then + prefix="$prefix/.." else - (>&2 echo "$name: Could not determine location to Xamarin.Android locations.") + (>&2 echo "$name: Could not determine Xamarin.Android prefix.") exit 1 fi -if [ -z "$MSBUILD" ] ; then - MSBUILD=xbuild +for t in "$TARGETS_DIR" "$prefix/lib/mono/xbuild" "$prefix/lib/xbuild" ; do + if [ -z "$t" -o ! -d "$t" ]; then + continue + fi + TARGETS_DIR="$t" + break +done + +if [ ! -d "$TARGETS_DIR" ]; then + (>&2 echo "$name: Could not determine Xamarin.Android targets path.") fi +export TARGETS_DIR +export MONO_ANDROID_PATH="$prefix" + if [ -z "$ANDROID_NDK_PATH" -a -f "$Paths_targets" ] ; then ANDROID_NDK_PATH=$($MSBUILD /nologo /v:minimal /t:GetAndroidNdkFullPath "$Paths_targets") @@ -34,23 +91,75 @@ if [ -z "$ANDROID_SDK_PATH" -a -f "$Paths_targets" ] ; then export ANDROID_SDK_PATH fi -if [ -z "$ANDROID_NDK_PATH" ] ; then - >&2 echo "$name: Could not determine Android NDK path. Please export the \$ANDROID_NDK_PATH environment variable." - exit 1 -fi +declare -a XABUILD_FLAGS -if [ -z "$ANDROID_SDK_PATH" ] ; then - >&2 echo "$name: Could not determine Android SDK path. Please export the \$ANDROID_SDK_PATH environment variable." - exit 1 +XABUILD_FLAGS=( + /p:MonoAndroidBinDirectory="$prefix/bin" + /p:MonoAndroidToolsDirectory="$prefix/lib/mandroid" + /p:MonoDroidInstallDirectory="$MONO_ANDROID_PATH" +) + +if [ -n "$ANDROID_NDK_PATH" ] ; then + XABUILD_FLAGS+=(/p:AndroidNdkDirectory="$ANDROID_NDK_PATH") +fi +if [ -n "$ANDROID_SDK_PATH" ] ; then + XABUILD_FLAGS+=(/p:AndroidSdkDirectory="$ANDROID_SDK_PATH") fi -export TARGETS_DIR="$topdir/lib/xbuild" export MSBuildExtensionsPath="$TARGETS_DIR" -export MONO_ANDROID_PATH="$topdir" -export XBUILD_FRAMEWORK_FOLDERS_PATH="$topdir/lib/xbuild-frameworks" - -exec $MSBUILD /p:Configuration=$CONFIGURATION $MSBUILD_OPTIONS \ - /p:AndroidNdkDirectory="$ANDROID_NDK_PATH" \ - /p:AndroidSdkDirectory="$ANDROID_SDK_PATH" \ - /p:MonoDroidInstallDirectory="$MONO_ANDROID_PATH" \ - "$@" + +case "$MSBUILD" in + *msbuild*) + XABUILD_FLAGS+=(/p:TargetFrameworkRootPath="$TARGETS_DIR-frameworks") + ;; + *xbuild*) + export XBUILD_FRAMEWORK_FOLDERS_PATH="$TARGETS_DIR-frameworks" + ;; +esac + +function GetXbuildDir() +{ + read -r -d '' get_xbuild_dir_cmd <<-'EOF' || true + using System.IO; + var corlib_loc = typeof (int).Assembly.Location; + // e.g. /Library/Frameworks/Mono.framework/Versions/5.2.0/lib/mono/4.5 + var corlib_dir = Path.GetDirectoryName (corlib_loc); + // e.g. /Library/Frameworks/Mono.framework/Versions/5.2.0/lib/mono/xbuild + var xbuild_dir = Path.Combine (corlib_dir, "..", "xbuild"); + print (Path.GetFullPath (xbuild_dir)); + EOF + echo "$get_xbuild_dir_cmd" | csharp +} + +function ConfigureLocalXbuild() +{ + if [ -d "$prefix/lib/mono" ]; then + # System installation, e.g. Linux? + return 0 + fi + xbuild_dir=`GetXbuildDir` + local sys_entry=`ls -1 "$xbuild_dir" | head -1` + if [ -d "$TARGETS_DIR/$sys_entry" ] ; then + # already configured; bail + return 0 + fi + local sys_links="$TARGETS_DIR/.__sys_links.txt" + echo ".__sys_links.txt" > "$sys_links" + local e + for e in "$xbuild_dir"/* ; do + local b=`basename "$e"` + if [ -e "$TARGETS_DIR/$b" -o -f "$TARGETS_DIR/$b" -o -L "$TARGETS_DIR/$b" ]; then + rm "$TARGETS_DIR/$b" + fi + ln -s "$e" "$TARGETS_DIR" + if [ -d "$e" ]; then + echo "$b"'/*' >> "$sys_links" + else + echo $b >> "$sys_links" + fi + done +} + +ConfigureLocalXbuild + +exec $MSBUILD "${XABUILD_FLAGS[@]}" "$@"