Posted by Ancestry Team on July 3, 2013 in Continuous Delivery, DevOps

Every once in a while when building NAnt targets you will come across the desire to create a <fileset> which is defined at runtime.  Maybe you’d like to define the <fileset> as a property in a previous target or a calling target and have it be used in a subsequent task.  For example we have a shared NAnt target that runs a test suite.  It is designed to be flexible and reusable so that you can use it for a lot of different kinds of test runs (unit tests, functional tests, performance tests).  You set up a few parameters in an upstream target which is specific to an individual component, then call the common test target and it runs the tests using the parameters you set.   In pseudo-NAnt (which is a thing I just invented, patent pending), it looks like this:

<!--This is in a build file that belongs to a specific component-->
<target name="Upstream_ComponentSpecific_Test">
	<!--set up some properties for this test run-->
	<property name="TestCategory" value="UnitTest" />
	<property name="ResultsFile" value="${ResultFilePath}" />
	<!--run the tests-->
	<call target="CommonTestTarget" />
</target>

<!--This is in some shared build file that many components re-use-->
<target name="CommonTestTarget">
	<!--a shared target for running tests using parameters set as NANt properties.-->
	<!--Pretend you have a custom NAnt task called "test", this is pseudo NAnt remember?-->
	<test category="${TestCategory}" resultsfile="${ResultsFile}">
		<fileset>
		 <!-- Dangit! How do we tell this task which files have tests and which don't? Wouldn't it be cool if we could pass that fileset in as a property? -->
		</fileset>
	</test>
</target>

You can see the fatal flaw in this design (because I point it out in the comments, isn’t that nice?).  You need to tell the reusable NAnt target which files contain the tests (I’m using this example for simplicity so let’s assume you can’t use a test definition file or a test container or some other way of achieving the same end).  We ended up with many other similar cases where this would be nice, such as excluding assemblies from code coverage, excluding files from copy tasks, including non-managed assemblies into builds, and a list of other scenarios.

So we needed to find a way to tell the Test target which assembly files include the tests, but we had to do it dynamically from an upstream target.  You cannot set a <fileset> as the value of a NAnt <property>, which is probably everyone’s  first thought.  I don’t know why this doesn’t work, but at the time of this posting, it doesn’t and I’m not going to hold my breath until it does.  The solution we arrived on was to use the refid attribute of the <fileset> type, then dynamically assign files to the <fileset> with the matching id using a NAnt target whose only purpose was to define the <fileset>.  So now it looks like this:

<!--This is in a build file that belongs to a specific component-->
<!--This target is new, its purpose is to define a custom fileset and give it an id you can reference later-->
<target name="SetFilesetForUnitTests">
	<fileset id="UnitTestFileset">
		<!--Declare the files that contain the tests. Notice I put in some includes and excludes, you can do anything that the NAnt fileset supports-->
		<include name="${DeploymentDirectory}\TestAssembly1.dll" />
		<include name="${DeploymentDirectory}\TestAssembly2.dll" />
		<include name="${DeploymentDirectory}\Test.*.dll" />
		<exclude name="${DeploymentDirectory}\Test.Broken.dll" />
	</fileset>
</target>

<target name="Upstream_ComponentSpecific_Test">
	<!--set up some properties for this test run-->
	<property name="TestCategory" value="UnitTest" />
	<property name="ResultsFile" value="${ResultFilePath}" />
	<!--call the target that sets up the dynamic fileset-->
	<call target="Fileset.UnitTest.Standard" />
	<!--run the tests-->
	<call target="CommonTestTarget" />
</target>

<!--This is in some shared build file that many components re-use-->
<target name="CommonTestTarget">
	<!--a shared target for running tests using parameters set as NANt properties.-->
	<!--Pretend you have a custom NAnt task or something, this is pseudo NAnt remember?-->
	<test category="${TestCategory}" resultsfile="${ResultsFile}">
		<!--Now you can use the fileset that was set dynamically upstream-->
		<fileset refid="UnitTestFileset" />
	</test>
</target>

Et voilà a dynamic NANt fileset that you can set at runtime from an upstream target or task!

Join the Discussion

We really do appreciate your feedback, and ask that you please be respectful to other commenters and authors. Any abusive comments may be moderated. For help with a specific problem, please contact customer service.