Matthew Soucoup: Code Mill Minute: Setting Up Xamarin Studio To Work With TFS Online Git Source Control
Build-time code generation is a really powerful way to automate repetitive parts of your code. It can save time, reduce frustration, and eliminate a source of copy/paste bugs.
This is something I’m familiar with due to my past work on MonoDevelop’s tooling for ASP.NET, T4 and Moonlight, and designing and/or implementing similar systems for Xamarin.iOS and Xamarin.Android. However, I haven’t seen any good documentation on it, so I decided to write an article to outline the basics.
This isn’t just something for custom project types, it’s also something that you can include in NuGets, since they can include MSBuild logic.
The basic idea is to generate C# code from other files in the project, and include it in the build. This can be to generate helpers, for example CodeBehind for views (ASPX, XAML), or to process simple DSLs (T4), or any other purpose you can imagine.
MSBuild makes this pretty easy. You can simply hook a custom target before the
Compile target, and have it emit a
Compile item based on whatever input items you want. For the purposes of this guide I’m going to assume you’re comfortable with enough MSBuild to understand that – if you’re not, the MSDN docs are pretty good for the basics.
The challenge is to include the generated C# in code completion, and update it automatically.
An IDE plugin can do this fairly easily – see for example the Generator mechanism used by T4, and the
*.designer.cs file generated by the old Windows Forms and ASP.NET designers. However, doing it this way has several downsides, for example you have to check their output into source control, and they won’t update if you edit files outside the IDE. Build-time generation, as used for XAML, is a better option in most cases.
This article describes how to implement the same model used by WPF/Silverlight/Xamarin.Forms XAML.
Generating the Code
First, you need a build target that updates the generated files, emits them into the intermediate output directory, and injects them to the
ItemGroup. For the purposes of this article I’ll call it
UpdateGeneratedFiles and assume that it’s processing
ResourceFile items and emitting a file called
GeneratedCode.g.cs. In a real implementation, you should use unique names won’t conflict with other targets, items and files.
<Target Name="UpdateGeneratedFiles" DependsOnTargets="_UpdateGeneratedFiles" Condition=="'@(ResourceFile)' != ''" > <ItemGroup> <Compile Include="$(IntermediateOutputDir)GeneratedFile.g.cs" /> <FileWrites Include="$(IntermediateOutputDir)GeneratedFile.g.cs" /> </ItemGroup> </Target> <Target Name="_UpdateGeneratedFiles" Inputs="$(MSBuildProjectFile);@(ResourceFile)" Outputs="$(IntermediateOutputDir)GeneratedFile.g.cs" > <FileGenerationTask Inputs="@(ResourceFile)" Output="$(IntermediateOutputDir)GeneratedFile.g.cs" > </Target>
A quick breakdown:
UpdateGeneratedFiles target runs if you have any
ResourceFile items. It injects the generated file into the build as a
Compile item, and also injects a
FileWrites item so the file is recorded for incremental clean. It depends on the ‘real’ generation target,
_UpdateGeneratedFiles, so that the file is generated before the
UpdateGeneratedFiles target runs.
_UpdateGeneratedFiles target has
Outputs set, so that it is incremental. The target will be skipped if the output file exists is newer than all of the input files – the project file and the resource files.
The project file is included in the inputs list because its write time will change if the list of resource files changes.
_UpdateGeneratedFiles target simply runs a tasks that generates the output file from the input files.
Note that the generated file has the suffix
.g.cs. This is the convention for built-time generated files. The
.designer.cs suffix is used for user-visible files generated at design-time by the designer.
Hooking into the Build
UpdateGeneratedFiles target is added to the dependencies of the
CoreCompile target by prepending it to the
<PropertyGroup> <CoreCompileDependsOn>UpdateGeneratedFiles;$(CoreCompileDependsOn)</CoreCompileDependsOn> </PropertyGroup>
This means that whenever the the project is compiled, the generated file is generated or updated if necessary, and the injected
Compile item is injected before the compiler is called, so is passed to the compiler – though it never exists in the project file itself.
Live Update on Project Change
So how do the types from the generated file show up in code completion before the project has been compiled? This takes advantage of the way that Visual Studio initializes its in-process compiler that’s used for code completion.
When the project is loaded in Visual Studio, or when the project file is changed, Visual Studio runs the
CoreCompile target. It intercepts the call to the compiler via a host hook in the the MSBuild
Csc task and uses the file list and arguments to initialize the in-process compiler.
UpdateGeneratedFiles is a dependency of
CoreCompile, this means that the generated file is updated before the code completion system is initialized, and the injected file is passed to the code completion system.
Note that the
UpdateGeneratedFiles target has to be fast, or it will add latency to code completion availability when first loading the project or after cleaning it.
Live Update on File Change
So, the generated code is updated whenever the project changes. But what happens when the contents of the
ResourceFile files that it depends on change?
This is handled via Generator metadata on each of the
<ItemGroup> <ResourceFile Include="Foo.png"> <Generator>MSBuild:UpdateGeneratedFiles</Generator> </ResourceFile> </ItemGroup>
This takes advantage of another Visual Studio feature. Whenever the file is saved, VS runs the
UpdateGeneratedFiles target. The code completion system detects the change to the generated file and reparses it.
This metadata has to be applied to the items by the IDE (or the user). It may be possible for the build targets to apply it automatically using an
ItemDefinitionGroup but I haven’t tested whether VS respects this for
But we have another problem. What about Xamarin Studio/MonoDevelop?
Although Xamarin Studio respects
Generator metadata, it doesn’t have an in-process compiler. It doesn’t run
CoreCompile, nor does it intercept the
Csc file list, so its code completion system won’t see the generated file at all.
The solution – for now – is to add explicit support in a Xamarin Studio addin to run the
UpdateGeneratedFiles target on project load and when the resource files change, parse the generated file and inject it into the type system directly.
Migrating automatically from a designer-generation system to a build-generation system has a few implications.
You either have to force migration of the project to the new system via an IDE, or handle the old system and make the migration optional – e.g. toggled by the presence of the old files. You have to update the project templates and samples, and you have to build a migration system that removes the designer files from the project and adds Generator metadata to existing files.
This is the age of writing iOS apps for love.
Well, that’s not true for everybody. Well-established and awesome companies such as Omni, Panic, Flexibits, AgileBits, Tapbots, and The Iconfactory have a business writing iOS apps. (They do it for love, but not just for
In part 1, we looked at researching whether your brilliant idea was worth pursuing as a business venture and it all starts with seeing if the need exists. Now that you’ve validated that there is a need for the product, only then is it time to start building things. Let’s take the next step, which is to work through the basics of the economics of your idea.