IronPython Class Factory for MSBuild 4.0 Part 1
June 2010
See also:
Overview
Before Visual Studio 2010, i.e. under MSBuild 3.5 and earlier, custom tasks needed to be hosted in a separate assembly. MSBuild 4.0 brings with it the
ability to code tasks inline, where the code comprising the task is written within the
project file itself. As I mentioned in my 'MSBuild 4.0
Custom Task Walkabout', language support is currently somewhat limited, with C# and Visual Basic (VB.net) being the most likely choices. Someone at
Microsoft began an unofficial project that provided support for inline tasks written in PowerShell, the source code for which can be downloaded from
here, while another
page includes usage
details. I'm no Python (or IronPython) expert
and I wondered how difficult it would be to create a similiar Task Factory for IronPython. After reviewing the source code for the PowerShell
project and doing some research online, the answer to the preceding question turned out to be 'not too difficult', at least not for a simple prototype.
I haven't exercised 99% of the methods etc. available within IronPython/Python during testing but so far, so good. And even though it is technically a Task Factory
for IronPython, that includes support for almost everything within 'standard' Python (meaning CPython). In other words, you don't need to use IronPython
specific code for the inline task that references PythonClassFactory.dll, but you certainly can. Note that the most recent release of IronPython, 2.6, targets
CPython 2.6. Python syntax should match that version - print 'Hello World', not the 3.0
print('Hello World').
The Approach
I created a new C# project in Visual Studio 2010 named PythonClassFactory (yeah, probably should have named it IronPythonClassFactory but it is a little too
late now). After that:
- grabbed the five .dll's at the root of my 'IronPython 2.6 for .NET 4.0' installation
- IronPython.dll
- IronPython.Modules.dll
- Microsoft.Dynamic.dll
- Microsoft.Scripting.Debugging.dll
- Microsoft.Scripting.dll
and copied those to a \_dlls directory at a sibling level to the PythonClassFactory folder holding the .csproj
- from the .csproj added a Reference to each of those dll's, and since they will be needed at build runtime, confirmed 'Copy Local' on each was set to True
- also added (.NET) References to
- Microsoft.Build
- Microsoft.Build.Engine
- Microsoft.Build.Framework
- Microsoft.Build.Tasks.v4.0
- Microsoft.Build.Utilities.v4.0
- created a PythonClassFactory class in the owenG.PyFactory namespace, implemented the Microsoft.Build.Framework.ITaskFactory
Interface, added a using directive for Microsoft.Build.Framework
- created a PythonTask class, inheriting from Microsoft.Build.Utilities.Task and overriding
Execute(). Implemented Microsoft.Build.Framework.IGeneratedTask (only those members
that were also defined in PowerShell project) and IDisposable. Added a bunch of using statements.
- maybe other stuff I forgot about?
Once project compiled, shipped it out. Or actually spent a bunch of time testing the factory on the msbuild command line and also debugging by launching the dll
against msbuild.exe. The first draft of the source code is available for
download - NOTE: No claim of suitability, guarantee, etc., files provided "as-is"/"as-are".
The Environment
Next task, as it were, was to implement the Task Factory in a TFS 2010 Team Build environment and test. For this I used the same project and
local directory structure as discussed in my
earlier MSBuild walkabout:
Also, the workflow I used was a copy of the build process template from that article, renamed to PythonProcessTemplate.xaml and modified a bit. Logically
enough, the new build definition I set up (PythonBuildDef1) was set to use this as its process template. Since it was for testing only, PythonBuildDef1 built
$/AllInOne/Main/CSEFModelFirst.sln, which only had a single C# project -> $/AllInOne/Main/CSEFModelFirst/CSEFModelFirst.csproj. The PythonClassFactory.dll,
along with the five .dll's from IronPython
installation, were checked into source control under the $AllInOne/BuildProcessTemplates/CustomTasks/_compiled project. The PythonTargets.targets file that
held the msbuild implementation was stored in .../CustomTasks/targets. The container Project element in PythonTargets.targets is simple and contains
only two attributes:
.targets
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
...
</Project>
The Workflow
As mentioned above, the .xaml workflow for this example is same as was used in my earlier article. I simply renamed the custom MSBuild Activity to
'MSBuild_PythonTargets' and updated several of the related Properties:
The first important property is CommandLineArguments, which aggregates several MSBuild property values that will be passed on to the project file.
The different properties are going to be consumed by separate targets, the details of which will be discussed later. Note that the final argument,
outputDirectory, is set by Team Build and represents the folder to which the compiled binaries will be output
on the build machine:
LogFile, in combination with LogFileDropLocation, together define the path to which the log for this MSBuild Activity will be written. For these
builds that will mean something like ...drops\PythonBuildDef1\PythonBuildDef1_20100602.2\logs\myPythonTargetsLog.log.
The path in Project points to the MSBuild project file that will be run by this Activity. At build time this will be something like
...\AllInOne\PythonBuildDef1\Sources\AllInOne\BuildProcessTemplates\CustomTasks\_targets\PythonTargets.targets.
The PythonTargets.targets file defines four Targets (PyTarget1, PyTarget2 etc.) and they are listed in the Targets property. Each will
examined in turn.
Next, first example...