Geeks With Blogs
Sean Carpenter Thoughts on Development

I struggled with this a while back and finally came up with a solution that works.  There are a few things that I would do differently now (I'll try to highlight them below), but I've been running this for a few months now and it produces correct output so I'm happy.

My previous experience with NAnt involved using the <solution> task which works very well.  Unfortunately, the <solution> task doesn't support the Compact Framework.  The solution I came up with is as follows: write a list of source files to compile to a file, convert any resources from .resx format to binary resources, write a list of resource files to include to a file, and finally call the command line compiler passing in these lists of files and any other options.  I'll do a quick description of each step below.

Step 1 - Get a list of source files

This is the NAnt target I use to get the list of files (this is one of those things I'd do differently now - I'd use the /recurse option to the compiler).

<target name="listFiles">
        <delete file="sourceList.txt" if="${file::exists('sourceList.txt')}" />
        <foreach item="File" property="fName">
            <in>
                <items>
                    <include name="*.vb" />
                    <include name="${subDir}/**/*.vb" />
                </items>
            </in>
            <do>
                <echo message="&quot;${fName}&quot;" append="true" file="sourceList.txt"/>    
            </do>
        </foreach>
    </target>

This deletes the file if it already exists, then uses the <foreach> task to loop through the files with a "vb" extension in the specified directory and sub-directory (contained in the subDir property) and <echo> their names to the "sourceList.txt" file.  Pretty straightforward.

Step 2 - Convert Resources

The resources step is something that completely slipped my mind at the beginning.  I was originally only building a class library (with no embedded resources) so skipping this step wasn't an issue.  It became a problem when I added a WinForms app to the solution and built it without including the resources (that didn't run so well since many control properties are stored in the resource files).  Here's the target for building the resources.

<target name="makeResources">
        <foreach item="File" property="fName">
            <in>
                <items>
                    <include name="*.resx" />
                    <include name="${subDir}/**/*.resx" />
                </items>
            </in>
            <do>
                <property name="resName" value="${path::get-file-name-without-extension(fName)}.resources" />
                <exec program="C:\NET\CFDLL\CFResGen" commandline="${fName} ${path::combine(subDir, resName)}" />
            </do>
        </foreach>
    </target>

This target uses the <foreach> task again, this time to go through all .resx files.  This time, instead of writing the file names to a file, the task uses the <exec> task to execute the CFResgen tool, which converts .resx format files to binary .resources files.  I get the filename by stripping the extension off of the .resx file and replacing it with .resources.

Step 3 - Get a list of resource files to include

This is very similar to Step 1, but here's the target for completeness' sake.

<target name="listResourceFiles">
        <delete file="sourceResource.txt" if="${file::exists('sourceResource.txt')}" />
        <foreach item="File" property="fName">
            <in>
                <items>
                    <include name="*.resources" />
                    <include name="*.png" />
                    <include name="${subDir}/**/*.resources" />
                    <include name="${subDir}/**/*.png" />
                </items>
            </in>
            <do>
                <property name="resName" value="${path::get-file-name(fName)}" />
                <echo message="/resource:&quot;${fName}&quot;,&quot;Company.Project.${subDir}.${resName}&quot;" append="true" file="sourceResource.txt"/>    
            </do>
        </foreach>
    </target>

You can see that I'm also getting all files with a .png extension (I had one image embedded as a resource).  The other difference is that when writing the information to the file, I'm writing it as "/resource:fileName, fully qualified resource name". This is the format expected by the compiler and matches the way resources are named when compiling in Visual Studio.

Step 4 - Call the compiler

The last step is to actually call the compiler, passing in the list of source files, the list of resources, plus any other options you may need.  This is another step I'd do differently - I'd use the <vbc> task instead.

<target name="compileProj">
        <mkdir dir="build\Debug\${subDir}" unless="${directory::exists('build\Debug\' + property::get-value('subDir'))}" />
        <property name="cmdLine" value="@${subDir}.rsp @sourceList.txt" />
        <property name="cmdLine" value="${cmdLine + ' @sourceResource.txt'}" if="${file::exists('sourceResource.txt')}" />
        <exec program="C:\WINNT\Microsoft.NET\Framework\v1.1.4322\vbc.exe" commandline="${cmdLine}" output="compileData_${subDir}.txt"/>
    </target>

Here, I first make a directory to put the compiled file in.  Then, I build up the command line including an options file, the source file list, and the resource file list (if it exists).  Lastly, I use the <exec> task again to execute the compiler.  The options file looks like the following:

/warnaserror
/t:winexe
/netcf
/sdkpath:c:\net\cfdll
/out:build\Debug\programName\programName.exe
/r:c:\net\cfdll\system.dll
/r:c:\net\cfdll\system.data.dll
/r:c:\net\cfdll\system.xml.dll
/r:c:\net\cfdll\system.drawing.dll
/r:c:\net\cfdll\system.windows.forms.dll
/r:c:\net\cfdll\system.windows.forms.datagrid.dll
/r:build\Debug\DataAccess\DataAccess.dll
/r:build\Debug\CustomControls\CustomControls.dll
/debug+
/optionstrict+
/imports:Microsoft.VisualBasic
/imports:System
/imports:System.Collections
/imports:System.Configuration
/imports:System.Data
/imports:System.Diagnostics
/imports:System.Drawing
/imports:System.Windows.Forms
/rootnamespace:Company.Project

 

You can see what each of these options do on MSDN.  Basically, they pass in the equivalent settings that Visual Studio uses when it compiles a project.

Wrapping Up

One final target I'll show here is the one that is actually called by NAnt.  It sets the "subDir" property and then calls the other tasks shown above.

<target name="compileMultiple">
        <property name="subDir" value="CustomControls" />
        <call target="listFiles" />
        <call target="getResources" />
        <call target="compileProj"/>
        <property name="subDir" value="DataAccess" />
        <call target="listFiles" />
        <call target="getResources" />
        <call target="compileProj"/>
        <property name="subDir" value="programName" />
        <call target="listFiles" />
        <call target="getResources" />
        <call target="compileProj"/>
    </target>

 

If anyone has any questions (or wants to see the complete files), feel free to use the Contact link on this blog.

Posted on Wednesday, October 26, 2005 12:56 PM Compact Framework | Back to top


Comments on this post: Using NAnt to build Compact Framework Projects

Comments are closed.
Comments have been closed on this topic.
Copyright © Sean Carpenter | Powered by: GeeksWithBlogs.net