Shared target values in MS Build - xamarin

Trying to replace values in the generated AppManifest.Plist (info.plist) in a post-build script (Xamarin iOS).
Minimum example:
#!/usr/bin/env bash PLIST="$buildPath/obj/iPhoneSimulator/Debug/device-builds/iphone14.5-15.4/AppManifest.plist" /usr/libexec/PlistBuddy -c "Set :CFBundleIdentifier com.test.test" "$PLIST"
where buildPath is defined as MSBuildProjectDirectory.
The problem is finding the generic path for "$buildPath/obj/iPhoneSimulator/Debug/device-builds/iphone14.5-15.4/". This path is defined in the source code for Xamarin iOS as _AppBundleManifestPath, which is a result set by the ParseDeviceSpecificBuildInformation class.
(https://github.com/xamarin/xamarin-macios/blob/08978fa4b13c540ca2916915985d62345fd80229/msbuild/Xamarin.Shared/Xamarin.Shared.targets#L1061)
How can I, if possible, access this value in my post-build script?
This is how the post-build is run:
<PropertyGroup>
<IntermediateOutputPath>obj$(Platform)$(Configuration)</IntermediateOutputPath><_AppManifest>$(IntermediateOutputPath)AppManifest.plist</_AppManifest>
<PostBuildEvent>export "buildPath=$(MSBuildProjectDirectory)";
chmod 700 $(MSBuildProjectDirectory)\local-post-build-ios.sh;
$(MSBuildProjectDirectory)\local-post-build-ios.sh;
unset buildPath
</PostBuildEvent>
</PropertyGroup>

Related

CMake: How to add a custom variable for user macro with a value that depends on build configuration?

I use CMake version 3.16.
I tried the following:
set_property(TARGET ${PLUGIN_NAME} PROPERTY VS_GLOBAL_FR_VERSION
$<$<CONFIG:Debug2017>:"2017">
$<$<CONFIG:Release2017>:"2017">
$<$<CONFIG:Debug2018>:"2018">
$<$<CONFIG:Release2018>:"2018">
$<$<CONFIG:Debug2019>:"2019">
$<$<CONFIG:Release2019>:"2019">
)
And it kind of worked...
This variable (FR_VERSION) is supposed to be used in a script that is launched after build. This is how it looks:
add_custom_command(TARGET ${PLUGIN_NAME} POST_BUILD
COMMAND echo $(FR_VERSION)
COMMENT "This command will copy files to dist"
VERBATIM
)
In Visual Studio, however, we got the following:
echo $<$<CONFIG:Debug2017>:"2017">;$<$<CONFIG:Release2017>:"2017">;$<$<CONFIG:Debug2018>:"2018">;$<$<CONFIG:Release2018>:"2018">;$<$<CONFIG:Debug2019>:"2019">;$<$<CONFIG:Release2019>:"2019">
which fails to execute with the error message:
"The syntax of the command is incorrect."
If I don't try to set a different value for different build configs like this:
set_target_properties(${MAYA_PLUGIN_NAME} PROPERTIES VS_GLOBAL_FR_MAYA_VERSION "2018")
then the post-build script is generated as expected. (But this is not acceptable to me, because I need different parameter values for different build configurations).
I would appreciate any advice at this point.
Some target properties support generator expressions, while others do not. The documentation for a property will explicitly say that generator expressions are supported. Take the COMPILE_FEATURES property, for example:
Contents of COMPILE_FEATURES may use “generator expressions” with the syntax $<...>.
The documentation for VS_GLOBAL_<variable> does not have such language. Thus, as suggested, you can put the generator expression directly in the add_custom_command() call, which is supported:
add_custom_command(TARGET ${PLUGIN_NAME} POST_BUILD
COMMAND echo $<$<CONFIG:Debug2017>:"2017">
$<$<CONFIG:Release2017>:"2017">
$<$<CONFIG:Debug2018>:"2018">
$<$<CONFIG:Release2018>:"2018">
$<$<CONFIG:Debug2019>:"2019">
$<$<CONFIG:Release2019>:"2019">
COMMENT "This command will copy files to dist"
)

How to bootstrap generated files task output with msbuild

I have a .csproj file where I declare some generated files:
<ItemGroup>
<AutoGenerated Include="generated\*.cs;generated\Models\*.cs"/>
</ItemGroup>
This is used as Outputs on a task:
<Target Name="GenerateFilesFromTemplate" BeforeTargets="BeforeBuild;BeforeRebuild"
Inputs="sometemplatefile"
Outputs="#(AutoGenerated)">
<Exec
Command="somegenerator.exe ... "
Outputs="#(AutoGenerated)"/>
</Target>
The generated/ is added .gitignore, so the generated files are not part of the source control.
My problem is that this setup does not bootstrap itself. As initially the generated folder is empty, the build skips the generate files task because it has no outputs:
GenerateFilesFromTemplate:
Skipping target "GenerateFilesFromTemplate" because it has no outputs.
I feel I'm doing this the wrong way, or I'm missing the obvious. I know I can remove the Inputs and Outputs on the Task and then it will generate the files on every build. But the generation is lengthy, and I want to avoid it if no necessary (the template file did not change). How can I make the build self-bootstrap and generate the files on first build, or when necessary?
The output items are meant to indicate known output files of a target. MSBuild then checks if all modification dates of the outputs are greater than the highest modification date of the input items to see if it can skip the target. It will also run the target if one of the outputs are missing.
If you cannot know in advance which files are generated by a target, a workaround is to produce a temporary file with a known location.
e.g. write use the WriteLinesToFile task (configured to overwrite) to update a file ($(IntermediateOutputPath)autogen.marker) and use the file as output element. It will then run the target on the first build and then only run if the input template file is newer than the marker.

code reuse for file existence test in a Visual Studio vcxproj file

I have a vcxproj file that contains explicit Windows shell commands in the NMakeBuildCommandLine section:
<NMakeBuildCommandLine Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
move file1 file2
</NMakeBuildCommandLine>
I'm using MSBuild to execute the vcxproj, either directly or via a sln file. The problem is that when file1 does not exist, the output is very unhelpful and doesn't even list the file's name:
The system cannot find the file specified.
My naive solution is to replace move file1 file2 with:
if exist file1 (move file1 file2) else (echo file1 does not exist && exit 1)
(Note that I need to write && instead of &&.)
This works, but it's error-prone because I need to type file1 three times per line and make sure they all match. file1 is only one of many files that need to be moved. Furthermore, the series of commands is virtually identical across the various build configurations.
How can I avoid repeating myself both within a command line and across build configurations? I thought that the UserMacros property group might help me, but I couldn't figure out how to write such a macro via the IDE. (Right-clicking on the project in Visual Studio doesn't show a field for entering user macros.) Nor could I find any discussion of the syntax of this section on the Internet, so I didn't know how to write macros with a text editor (which I would actually prefer).
There may be yet a better way within a vcxproj file to identify files that need to exist, so I'm open to any alternatives.
MsBuild has 'variables' like most other programming languages called properties. You declare one in a PropertyGroup element in the project file xml and then reuse it using the $(PropertyName) syntax. Example for your case:
<PropertyGroup>
<Src>/path/to/my/src</Src>
<Dst>/path/to/my/dst</Dst>
</PropertyGroup>
<NMakeBuildCommandLine>
if exist $(Src) (move $(Src) $(Dst)) else (echo $(Src) does not exist && exit 1)
</NMakeBuildCommandLine>
If you want to use the IDE, which might get tedious if you have lots of values, you can indeed use so-called UserMacros but you have to declare those in a proprty sheet. Go to View->Property Manager, right-click on your project and select 'Add new Property Sheet'. Doubleclick on it, go to 'User Macros' and add key/value pairs there. If you save everything and look in the generated files you'll see the vcxproj now Imports the propertysheet, and the propertysheet itself has a PropertyGroup just like shown above - but editable thgough the IDE.
As an alternative which might be better (less duplication, easier to automate) in the long run you can use MsBuild code for checking file existence and moving files which has the benefit you only have to write the move command once as you can have MsBuild loop over items. Those are declared in an ItemGroup. Explaining everything here is a bit out of scope but an example should make things clear:
<Target Name="BatchMove">
<ItemGroup>
<SrcFiles Include="file1">
<Dest>file2</Dest>
</SrcFiles>
<SrcFiles Include="file3">
<Dest>file4</Dest>
</SrcFiles>
</ItemGroup>
<Warning Text="Source file %(SrcFiles.Identity) does not exist" Condition="!Exists(%(SrcFiles.Identity))" />
<Move SourceFiles="%(SrcFiles.Identity)" DestinationFiles="%(SrcFiles.Dest)" Condition="Exists(%(SrcFiles.Identity))" />
</Target>
This declares 2 source files file1/file3 and their respective destination files file2/file4. If the source does not exists (using standard MsBuild Exists check) a message is shown, else it is moved to the destination. Those % characters will make the line they occur in loop over each element of the SrcFiles collection. To add more files, just add to the ItemGroup. Last step is to get this target invoked from the nmake command line which is done simply by calling msbuild on the file itself and telling it to run the target:
<NMakeBuildCommandLine>
msbuild $(MSBuildThisFile) /t:BatchMove
</NMakeBuildCommandLine>

Update Info.plist values in archive

I need to be able to set a couple custom values in the Info.plist during the Xcode ARCHIVE process. This is for Xcode 6 and Xcode 7.
I already have a script in place that successfully updates these values as a post-action on the BUILD process. It works great when deploying to the simulator or to a phone from Xcode 6.
However, the Info.plist doesn't seem to be available from within the directory structures during the ARCHIVE process. After a BUILD, I can find the results under .../Build/Products in $CONFIGURATION-iphoneos and $CONFIGURATION-iphonesimulator. But after the ARCHIVE, there isn't anything there and I only find the compiled binaries under .../Build/Intermediates.
Certainly, I can see the Info.plist in the IPA itself. Yet any attempts to update and replace this file after the fact are unsuccessful; the IPA is no longer valid, I assume due to checksum changes or something.
I don't want to update these values in the source Info.plist (e.g., using a pre-action) as it will always make the source dirty every time I archive.
Figured this out. The process is nearly identical to the build -- use a post-action for the build, use a post-action for the archive -- just the path is different (all listed below) for where to find the Info.plist.
Below is my build script where I've used tokens for the "name" and the "value" to be updated in Info.plist. I just copied this script and renamed it for use with the archive post-action. Note that this script also has an example of extracting a value from Info.plist as I am deriving the web services version from the client version.
The path to the build Info.plist is either of:
"$BUILD_DIR/$CONFIGURATION-iphoneos/$PRODUCT_NAME.app/Info.plist"
"$BUILD_DIR/$CONFIGURATION-iphonesimulator/$PRODUCT_NAME.app/Info.plist"
NOTE: Both targets are being updated for a build since I've not figured out a way to identify which build it is doing.
The path to the archive Info.plist is:
"$ARCHIVE_PRODUCTS_PATH/Applications/$PRODUCT_NAME.app/Info.plist"
Build post-action:
$SRCROOT/post_build.sh <value> ~/xcode_build_$PRODUCT_NAME.out
Build script:
#!/bin/bash
# post_build.sh
#
# This script is intended for use by Xcode build process as a post-action.
# It expects the only argument is the value to be updated in Info.plist. It
# derives the WS version for the URL from the version found in Info.plist.
printf "Running $0 using scheme '$SCHEME_NAME' as '$USER'\n"
# If this is a clean operation, just leave
if [ $COPY_PHASE_STRIP == "YES" ]
then
printf "Doing a clean; exiting.\n"
exit 1
fi
# Confirm that PlistBuddy is available
PLIST_BUDDY=/usr/libexec/PlistBuddy
if ![-f "$PLIST_BUDDY"]
then
printf "Unable to access $PLIST_BUDDY\n"
exit 1
else
printf "PLIST_BUDDY=$PLIST_BUDDY\n"
fi
# Function to perform the changes
updatePlist()
{
PLIST_FILE=$1
if [ -f "$PLIST_FILE" ]
then
printf "Determing WS version...\n"
if [[ $SCHEME_NAME == *"Local"* ]]
then
WS_VER=""
else
# Determine the services version
BUILD_VER=$(${PLIST_BUDDY} -c "Print CFBundleShortVersionString" "$PLIST_FILE")
WS_VER=$(printf $BUILD_VER | sed 's/\(.*\)\..*/\1/' | sed 's/\./_/g')
fi
# Update the plist
${PLIST_BUDDY} -c "Set <name> <value>" "$PLIST_FILE"
printf "Updated plist $PLIST_FILE\n"
else
printf "Skipping -- no plist: $PLIST_FILE\n"
fi
}
# Retrieve the supplied URL
BASE_URL=$1
printf "BASE_URL=$BASE_URL\n\n"
# Record the environment settings
printenv | sort > ~/xcode_build_$PRODUCT_NAME.env
# Locate the plist in the device build
printf "Checking device build...\n"
updatePlist "$BUILD_DIR/$CONFIGURATION-iphoneos/$PRODUCT_NAME.app/Info.plist"
printf "\n"
# Locate the plist in the simulator build
printf "Checking simulator build...\n"
updatePlist "$BUILD_DIR/$CONFIGURATION-iphonesimulator/$PRODUCT_NAME.app/Info.plist"
printf "\n"

How does F# Interactive #I command know about project path?

Here is the scenario:
Open Visual Studio. This was done in VS2010 Pro.
Open F# Interactive within Visual Studio
Open project with fsx file
Note: Project and fsx file are in E:\<directories>\fsharp-tapl\arith
Send commands to F# Interactive from fsx file
> System.Environment.CurrentDirectory;;
val it : string = "C:\Users\Eric\AppData\Local\Temp"
I was not expecting a Temp directory but it makes sense.
> #r #"arith.exe"
Examples.fsx(7,1): error FS0082: Could not resolve this reference.
Could not locate the assembly "arith.exe".
Check to make sure the assembly exists on disk.
If this reference is required by your code, you may get compilation errors.
(Code=MSB3245)
Examples.fsx(7,1): error FS0084: Assembly reference 'arith.exe' was not found
or is invalid
The #r command error shows that F# Interactive currently does not know the location of arith.exe.
> #I #"bin\Debug"
--> Added 'E:\<directories>\fsharp-tapl\arith\bin\Debug'
to library include path
So we tell F# Interactive the location of the arith.exe.
Notice that the path is NOT an absolute path but a sub-path of the project.
I have not told F# Interactive the location of the arith project
E:\<directories>\fsharp-tapl\arith
> #r #"arith.exe"
--> Referenced 'E:\<directories>\fsharp-tapl\arith\bin\Debug\arith.exe'
And F# Interactive correctly finds arith.exe reporting the correct absolute path.
> open Main
> eval "true;" ;;
true
val it : unit = ()
This confirms that arith.exe was correctly found, loaded and works.
So how did F# Interactive #I command know the project path since the current directory is of no help?
What I am really after is from within F# Interactive how does one get the path to the project, E:\<directories>\fsharp-tapl\arith.
EDIT
> printfn __SOURCE_DIRECTORY__;;
E:\<directories>\fsharp-tapl\arith
val it : unit = ()
In F# Interactive, the default directory to search is the source directory. You can query it easily using __SOURCE_DIRECTORY__.
This behaviour is very convenient to allow you to use relative paths. You often have fsx files in the same folder with fs files.
#load "Ast.fs"
#load "Core.fs"
When your refer to a relative path, F# Interactive will always use the implicit source directory as the starting point.
#I ".."
#r ... // Reference some dll in parent folder of source directory
#I ".."
#r ... // Reference some dll in that folder again
If you want to remember the old directory for next reference, you should use #cd instead:
#cd "bin"
#r ... // Reference some dll in bin
#cd "Debug"
#r ... // Reference some dll in bin/Debug

Resources