Showing posts with label MSBuild. Show all posts
Showing posts with label MSBuild. Show all posts

Wednesday, March 19, 2014

Pack nuget packages locally and with TFS Build

Currently I’m working on a project at a customer where we are integrating a new ASP.NET MVC application into an existing ASP.NET WebForms application. Due the complexity of the legacy system (70+ projects) I decided to start a brand new solution for the new development and put this into a nuget package. Every time a new version of the ASP.NET MVC needs to be published, a new version of the nuget package gets installed on the existing legacy system.

To automate this, I started by adding a post-build event to the ASP.NET MVC Project that got triggered every time I was building a release build. So far, so good.

Next step was configuring the TFS Build so I could push the package to a share, this way every any of the team could install the package and I could use package restore. This way I no longer needed to check-in the package folder.

Note: Don’t use ‘enable Nuget Package Restore’ function on the solution, but enable it on ‘Tools > Options > Package Manager > General’ on this tab page you have a group box called Package Restore. Check both checkboxes. More information about this can be found on this blog by Xavier Decoster.

image

This is where the hell began. Building the project succeeded, but the build failed on packaging the nuget package. One of the issues I had was that nuget looked for was looking for the bin folder to copy the dll’s, but a TFS build doesn’t place the dll files in the bin folder after build, but to a binaries folder at solution level. A possible fix was changing the output path for a release build to ‘..\..\Binaries’ on the properties of the project file (Build tab). But this is rather a workaround then a good solution. So I looked further for a better solution.

Next I started to take a look at the nuget.targets file that gets added when you use the ‘enable Nuget Package Restore’ on the solution. I know I just told not to use this, and you shouldn’t, but this targets file also contained a task for packaging nuget packages. So the next thing I did was copying the content of the .nuget folder to my own folder and modified the NuGet.targets file.

If you thought this would solve all my problems, think again. The problem of the packaging  looking for the bin folder of the project was solved by adding –OutputDirectory “$(OutDir) “ to the pack command. Note the space after the $(OutDir). This must be present and is necessary to handle the double slashes that occurs when packaging on TFS. This results in something like 'bin\ \package.nupkg'. Not nice, but seems to work. Next problem I had was the fact I was using the “-IncludeReferencedProjects” option. This was still using the output path configured for this project instead of the binaries folder.

After some googling I found a helpfull comment on a nuget issue. (The last comment by athinton). So by adding -Properties OutputPath="$(OutDir) " to the pack statement, I solved the issue and packaging after a TFS build finally worked with the in“-IncludeReferencedProjects” option.

But … this broke the packaging inside visual studio. For making it work in VS, the -Properties OutputPath="$(OutDir) " must be removed, so that is why I’m using an MSBuild task to define all this rules.

The NuGetToolsPath must be change to the path where the nuget.exe file is located. (Note that it appears 2 times)
Extra options for packaging the nuget package can be added to the buildcommand element.

Last but not least, don’t forget to import this file in your project file.

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">$(MSBuildProjectDirectory)\..\</SolutionDir>

<!-- Property that enables building a package from a project -->
<BuildPackage Condition=" '$(BuildPackage)' == '' ">true</BuildPackage>

<!-- Download NuGet.exe if it does not already exist -->
<DownloadNuGetExe Condition=" '$(DownloadNuGetExe)' == '' ">false</DownloadNuGetExe>
</PropertyGroup>

<PropertyGroup Condition=" '$(OS)' == 'Windows_NT'">
<!-- Windows specific commands -->
<NuGetToolsPath>$([System.IO.Path]::Combine($(SolutionDir), "Nuget"))</NuGetToolsPath>
</PropertyGroup>

<PropertyGroup Condition=" '$(OS)' != 'Windows_NT'">
<!-- We need to launch nuget.exe with the mono command if we're not on windows -->
<NuGetToolsPath>$(SolutionDir)Nuget</NuGetToolsPath>
</PropertyGroup>

<PropertyGroup>
<!-- NuGet command -->
<NuGetExePath Condition=" '$(NuGetExePath)' == '' ">$(NuGetToolsPath)\NuGet.exe</NuGetExePath>

<NuGetCommand Condition=" '$(OS)' == 'Windows_NT'">"$(NuGetExePath)"</NuGetCommand>
<NuGetCommand Condition=" '$(OS)' != 'Windows_NT' ">mono --runtime=v4.0.30319 $(NuGetExePath)</NuGetCommand>

<PackageOutputDir Condition="$(PackageOutputDir) == ''">$(OutDir) </PackageOutputDir>

<OutputPath Condition="'$(BuildingInsideVisualStudio)' == 'false' " >OutputPath="$(OutDir) "</OutputPath>
<OutputPath Condition="'$(BuildingInsideVisualStudio)' == 'true' " ></OutputPath>

<NonInteractiveSwitch Condition=" '$(VisualStudioVersion)' != '' AND '$(OS)' == 'Windows_NT' ">-NonInteractive</NonInteractiveSwitch>

<!-- Commands -->
<BuildCommand>$(NuGetCommand) pack "$(ProjectPath)" -Properties "Configuration=$(Configuration);Platform=$(Platform);$(OutputPath)" $(NonInteractiveSwitch) -OutputDirectory "$(PackageOutputDir)" -IncludeReferencedProjects</BuildCommand>

<!-- Make the build depend on restore packages -->
<BuildDependsOn Condition="$(BuildPackage) == 'true'">
$(BuildDependsOn);
BuildPackage;
</BuildDependsOn>
</PropertyGroup>
<Target Name="BuildPackage">
<Exec Command="$(BuildCommand)"
Condition=" '$(OS)' != 'Windows_NT' " />

<Exec Command="$(BuildCommand)"
LogStandardErrorAsError="true"
Condition=" '$(OS)' == 'Windows_NT' " />
</Target>
</Project>




Wednesday, December 12, 2012

Build automation: Merging and minifying JavaScript files with Ajax Minifier

In my Library I have come to the point where it is gets harder and harder to maintain it. I' am currently over the 3000 lines, so it got time to split up my library in separate files and divide it in logical groups. After splitting everything up in separate files, I was looking for away to merge all these files into one file and have the option to minify it. This is also a step I wanted to make, so my lib could get more and more mature. The last thing I wanted was to integrate it with visual studio – which is my IDE that I’m using to develop the library – and be portable. With that last thing I mean I didn’t want to install something on my machine to do the magic.

After some searching I choose the Microsoft Ajax Minified. It comes in a nuget package which makes it easy to install. Next to that it integrates smoothly with visual studio using MS Build Tasks. This means it will merge and minify the files every time I build the project. There is only a little disadvantage where I will start with.

Set-up Ajax minifier

To start, the first thing we need to do after installing the nuget package, is adding the following files to your project (I have added a new folder Build to add them in.)

  • AjaxMin.dll
  • AjaxMinTask.dll
  • AjaxMinTask.targets

All these files can be found in the tools/net40 folder in the nuget package folder of the Ajax minifier

<folder where the solution is located>\packages\AjaxMin.<version>\tools\net40

The disadvantage of this way of working is that you need to copy the above files when getting the latest version of the Ajax minifier from nuget.

The last thing you need to do is editing the .csproj file so it will use the added build task. You can do this by unloading the project. (right click on the project –> Unload project)

image

Once you have done this, you can right click on your project and choose for edit <project name>.csproj

image

This will open an XML file containing information about the project. At the end of the file before the project closing tag, you must add the following line.

<Import Project="$(MSBuildProjectDirectory)\Build\AjaxMinTask.targets" />

Save this file and right click on the project again and choose for Reload Project. This will load the project again with the modifications.

Ajax minifier manifest File

Once all this is done, we can add an Ajax manifest file. In this file we can declare which files must be merged and minified. Also we declare the location where these files must be saved. Optionally we can provide some arguments to overwrite the default behavior. More information about the arguments can be found here. Note that these arguments are the same as the arguments you can provide when working with the command line tool.

The location of this manifest file doesn’t matter, but the extension needs to be “.ajaxmin”. For my library I have put this file also in the build folder where all the Ajax minifier files are located. You can also provide multiple output tag so you can create for example a minified and a non minified file.

Bellow you can find an example of an Ajax manifest file.

<?xml version="1.0" encoding="utf-8" ?>
<root> 
  <output path="Scripts\output.js">
    <arguments>-clobber -pretty</arguments>
    <input path="file1.js"/>
    <input path="file2.js"/>
  </output>
</root>

The arguments I am providing are “-clobber” to overwrite files even when they are flagged read-only, “-pretty” this provides a nice formatted JavaScript file that is readable. (not minified)


By default the output files will be added to a content folder in your project (must exist or you will get an error.) If you want to determine the path in your manifest file, then you need to change the AjaxMinTask.targets file we added in the first part. This is also an XML-file containing the build task definition.


In this file you will find a tag called “AjaxMinManifestTask”. On that element you have several attributes and the one we need is Output Folder. In my case the value of it is “$(ProjectDir)”. With this I can address the location in my manifest file starting from the root of my project. As said earlier: the only thing you need to keep in mind is that the folder structure must be present.


When all this is done, the only thing you need to do is build the project, and the files will get created.


Conclusion


Working with the Ajax Minifier gives you a lot of options for minifying and merging your JavaScript files. Next to that it can also minify CSS files. And as seen above with some work you can easily integrate it into the build of your project.