📜 ⬆️ ⬇️

Dynamic creation of a build plan for analyzing .NET project files using FxCop


It so happened that a year ago, I had to write a build plan using ant. It was intended for our small web project, was executed on Hudson and had to produce: compile, run NUnit tests, calculate% of code coverage for tests, search for duplicate code and identify major stylistic inconsistencies in the code. But this is an introduction, and then we'll talk about writing a build plan for analyzing project files using FxCop.

So! Go!


')

Introductory



As usual, I broke the build plan into several parts:

  1. dbdeploy.build.xml - is responsible for creating a test database and rolling up scripts that appear
  2. fxcop.build.xml - is responsible for starting the analysis and processing by FxCop of the project files and building a report on the problems found
  3. main.build.xml - here the main actions are performed on filling configs, automatic search for sln files for their assembly
  4. ncover.build.xml - in this part a report is built, the code coverage of the tests
  5. simian.build.xml - and here you are building a report about duplication in the code
  6. tests.build.xml - well, here we search for all NUnit tests in the project folder and start them


This modular construction makes it easy to exclude separate parts, divided by specific responsibility. We are with you, we have to consider the device fxcop.build.xml file.

Let's get started



At first I tried to transfer the list of prepared paths to the analyzed files via the command line, but as practice has shown, this exercise is dreary and long. And it is not very reliable, as with the expansion of the project, you will need to update the list of files for analysis. Then I began to look for ways to dynamically generate a list of files and transfer FxCop through Ant. Since the analyzed files were not small, it was necessary to automatically search for the necessary files and transfer them to FxCop. Having rummaged on the Internet and having read a manual on the Ant-Contrib and Ant commands, I found, that is necessary. It was the subant team that made it possible to achieve the goal. But more on that below!

Implementation


Consider the device file. It contains several tasks:



Next, we consider the basic Apache Ant commands for solving the problem.

basename - allows you to get the file name with the extension of the full path.
loadfile - allows you to load certain data from a file. In this case, the task is used to parse the proj file of the .NET project.
subant - allows you to perform a task from another build of the file, in this case from dynamically generated for FxCop
propertyregex - allows you to sample data using a specified regular expression on the input line.
if - allows you to add logic to the build file that depends on logical expressions.

Now you can proceed to the consideration of each of the tasks separately.

write-head-part


<target name="write-head-part"> <echo file="${dynafile.path}\${dynafilename}">&lt;?xml version="1.0" encoding="UTF-8"?&gt; &lt;project name="dyna-fxcop-build" default="run-fx-cop-report-creation" basedir="."&gt; &lt;target name="run-fx-cop-report-creation"&gt; &lt;exec executable="${fxcop.path}\FxCopCmd.exe" failonerror="false"&gt;</echo> </target> 


In this task, a standard build-plan header is written to a file located along the path ${dynafile.path}\${dynafilename} , using &lt; &gt; &lt; &gt; , to escape < > characters. As well as, the exec task is written to transfer the required parameters to the FxCop application. In this way, passing parameters, using arg , you can solve the problem of a long list of paths to the analyzed files.

create-arguments


  <target name="create-arguments"> //        <echo message="${item.file}"/> //    ,     filename <basename property="filename" file="${item.file}"/> //   output.path   <OutputPath>   csproj <loadfile srcfile="${item.file}" property="output.path"> <filterchain> <linecontains> <contains value="&lt;OutputPath&gt;"/> </linecontains> </filterchain> </loadfile> //   <OutputType>   csproj   output.type <loadfile srcfile="${item.file}" property="output.type"> <filterchain> <linecontains> <contains value="&lt;OutputType&gt;"/> </linecontains> </filterchain> </loadfile> //   <AssemblyName>   csproj   assembly.name <loadfile srcfile="${item.file}" property="assembly.name"> <filterchain> <linecontains> <contains value="&lt;AssemblyName&gt;"/> </linecontains> </filterchain> </loadfile> //       OutputPath    output.path.info <propertyregex property="output.path.info" input="${output.path}" regexp="&lt;OutputPath&gt;(.*?)&lt;/OutputPath&gt;" select="\1" /> //       OutputType    output.type.info <propertyregex property="output.type.info" input="${output.type}" regexp="&lt;OutputType&gt;(.*?)&lt;/OutputType&gt;" select="\1" /> //       AssemblyName    assembly.name.info <propertyregex property="assembly.name.info" input="${assembly.name}" regexp="&lt;AssemblyName&gt;(.*?)&lt;/AssemblyName&gt;" select="\1" /> //       <propertyregex property="item.path" input="${item.file}" regexp="(.*)\\" select="\1" /> <echo message="output.type.info = ${output.type.info}"/> <echo message="output.path = ${output.path}"/> //         output.type.info <if> <contains string="WinExe" substring="${output.type.info}"/> <then> <property name="file.name.ext" value="${assembly.name.info}.exe"/> </then> <elseif> <contains string="Exe" substring="${output.type.info}"/> <then> <property name="file.name.ext" value="${assembly.name.info}.exe"/> </then> </elseif> <else> <property name="file.name.ext" value="${assembly.name.info}.dll"/> </else> </if> //  <arg value=""/>    value      . <echo file="${dynafile.path}\${dynafilename}" append="true"> &lt;arg value="/f:${item.path}\${output.path.info}${file.name.ext}"/&gt; </echo> </target> 


In this task, the project files are processed, with the extension csproj. From the file, the tag data is highlighted: OutputPath, OutputType and AssemblyName. This is necessary in order not to be guided by the name of the project file (as there were such project files in which the assembly name was changed). And in the dynamically created build-plan file, arg strings are written for the exec task, with the indication of the / f flag :

write-footer-part


 <target name="write-footer-part"> <echo file="${dynafile.path}\${dynafilename}" append="true"> &lt;arg value="/r:${fxcop.path}\Rules"/&gt; &lt;arg value="/o:${fxcop.report.full.path}"/&gt; &lt;/exec&gt; &lt;/target&gt; &lt;/project&gt;</echo> </target> 


This task records the final part of the dynamically formed build plan, adding the FxCop directives intended for setting the path to the folder with the / /r:${fxcop.path}\Rules rules rules and the output folder for the report / /o:${fxcop.report.full.path} . The closing tags for exec , traget and project also traget .

run-fxcop


 <target name="run-fxcop"> //   - <antcall target="write-head-part"/> //        <echo file="${dynafile.path}\${dynafilename}" append="true"> </echo> <var name="dll.names" value=""/> //  csproj    ,     ,    item.file, //  create-arguments <foreach target="create-arguments" param="item.file" inheritall="true"> <fileset dir="${basedir}" casesensitive="no"> <include name="**/*.csproj"/> //      /obj/Debug/ <exclude name="**/obj/Debug/**.*"/> </fileset> </foreach> //    - <antcall target="write-footer-part"/> //     -. <subant target="run-fx-cop-report-creation"> <fileset dir="${dynafile.path}" includes="${dynafilename}"/> </subant> </target> 


Well, the main task of the build-plan for FxCop, which produces dynamic creation, so to speak, is a sub build-plan. It is with this approach that the .NET project will be analyzed. In this task, using the write-head-part , writes a header to the file that is created on the path ${dynafile.path}\${dynafilename} . Next, the file is transferred to the mode of adding data to the end, using the echo command with the parameter append="true" .

After these actions, files are csproj , with the extension csproj using foreach . At the same time, the path to the file is written to the variable item.file, which is defined by param="item.file" . Well and in order that ant did not view the contents of obj / Debug, using the instruction /> , we bring it to ignore.

Further, with the help of write-footer-part , the final part of the dynamically generated build file is written.

And now the most interesting! Since we, in a dynamic build plan, created a task called run-fx-cop-report-creation , now we can accomplish it, using the subant task . In the parameters to the subant, specifying the path to the dynamically formed build-plan file, from which the run-fx-cop-report-creation task will be executed.

Conclusion



I hope that this material was interesting :) Thank you for your attention!

Full code xml build file for FxCop
 <?xml version="1.0" encoding="UTF-8"?> <project name="fxcop-xxx-project" default="run-fxcop" basedir="."> <property name="dynafile.path" value="${basedir}"/> <property name="dynafilename" value="dynabuild.xml"/> <property name="fxcop.report.dir" value="${basedir}\FxCopReports"/> <property name="fxcop.report.full.path" value="${fxcop.report.dir}\fxcop.report.xml"/> <target name="clean-fxcop-result-folder"> <echo message="Cleaning FxCop result report dir, and dynamic xml"/> <delete> <fileset dir="${fxcop.report.dir}" includes="**/*.*"/> </delete> <delete file="${dynafile.path}\${dynafilename}" failonerror="false"/> </target> <target name="run-fxcop"> <antcall target="write-head-part"/> <echo file="${dynafile.path}\${dynafilename}" append="true"> </echo> <var name="dll.names" value=""/> <foreach target="create-arguments" param="item.file" inheritall="true"> <fileset dir="${basedir}" casesensitive="no"> <include name="**/*.csproj"/> <exclude name="**/obj/Debug/**.*"/> </fileset> </foreach> <antcall target="write-footer-part"/> <subant target="run-fx-cop-report-creation"> <fileset dir="${dynafile.path}" includes="${dynafilename}"/> </subant> </target> <target name="create-arguments"> <echo message="${item.file}"/> <basename property="filename" file="${item.file}"/> <loadfile srcfile="${item.file}" property="output.path"> <filterchain> <linecontains> <contains value="&lt;OutputPath&gt;"/> </linecontains> </filterchain> </loadfile> <loadfile srcfile="${item.file}" property="output.type"> <filterchain> <linecontains> <contains value="&lt;OutputType&gt;"/> </linecontains> </filterchain> </loadfile> <loadfile srcfile="${item.file}" property="assembly.name"> <filterchain> <linecontains> <contains value="&lt;AssemblyName&gt;"/> </linecontains> </filterchain> </loadfile> <propertyregex property="output.path.info" input="${output.path}" regexp="&lt;OutputPath&gt;(.*?)&lt;/OutputPath&gt;" select="\1" /> <propertyregex property="output.type.info" input="${output.type}" regexp="&lt;OutputType&gt;(.*?)&lt;/OutputType&gt;" select="\1" /> <propertyregex property="assembly.name.info" input="${assembly.name}" regexp="&lt;AssemblyName&gt;(.*?)&lt;/AssemblyName&gt;" select="\1" /> <propertyregex property="item.path" input="${item.file}" regexp="(.*)\\" select="\1" /> <echo message="output.type.info = ${output.type.info}"/> <echo message="output.path = ${output.path}"/> <if> <contains string="WinExe" substring="${output.type.info}"/> <then> <property name="file.name.ext" value="${assembly.name.info}.exe"/> </then> <elseif> <contains string="Exe" substring="${output.type.info}"/> <then> <property name="file.name.ext" value="${assembly.name.info}.exe"/> </then> </elseif> <else> <property name="file.name.ext" value="${assembly.name.info}.dll"/> </else> </if> <echo file="${dynafile.path}\${dynafilename}" append="true"> &lt;arg value="/f:${item.path}\${output.path.info}${file.name.ext}"/&gt; </echo> </target> <target name="write-head-part"> <echo file="${dynafile.path}\${dynafilename}">&lt;?xml version="1.0" encoding="UTF-8"?&gt; &lt;project name="dyna-fxcop-build" default="run-fx-cop-report-creation" basedir="."&gt; &lt;target name="run-fx-cop-report-creation"&gt; &lt;exec executable="${fxcop.path}\FxCopCmd.exe" failonerror="false"&gt;</echo> </target> <target name="write-footer-part"> <echo file="${dynafile.path}\${dynafilename}" append="true"> &lt;arg value="/r:${fxcop.path}\Rules"/&gt; &lt;arg value="/o:${fxcop.report.full.path}"/&gt; &lt;/exec&gt; &lt;/target&gt; &lt;/project&gt;</echo> </target> </project> 

Source: https://habr.com/ru/post/149406/


All Articles