Posts tagged 'NuGet'

09

May 2011

Adding Tab-Expansion to Andrew Nurse's PSGet (NuGet Powershell Module)

If you subscribe to my blog or follow me on Twitter you'll probably know I'm quite a fan of NuGet. Recently I was playing around with Andrew Nurse's PSGet module that wraps NuGet.exe for PowerShell (which, by the way, is an awesome idea, and should be added as built-in functionality!). I decided a good way to learn a little more about PowerShell would be to try and add Tab Expansion to PSGet, similar to the functionality in the Visual Studio Package Manager Console.

Note: I forked Andrew Nurse's PSGet project to make these changes, though I'll see if he's interested in pulling them back into his, and updating the NuGet package. That way people will have a single source for PowerShell/NuGet goodness. Until then, if you want my Tab Expansion, you can grab the module from here.

Disclaimer: I've only been using PowerShell for around a week, so I'm sure there are much better ways to do what I've done. I did what I could with the (little) knowledge I have and some Googling. If you can see better ways of doing things, please let me know so I can learn, and update my code (send me a pull request on Bitbucket if you want).

Show me the code!

Tab Expansion in PowerShell works via the TabExpansion method. This function is called and provided with the current line in addition to the last word typed. I couldn't find a way to hook TabExpansion for just the commands we're interested in, so I had to hijack the whole lot. This doesn't seem to be a problem, as falling back to the default behaviour seemed pretty simple by keeping a copy of the function:

# Copy the current Tab Expansion function so we can fall back to it if we're not supposed to handle a command
Copy Function:\TabExpansion Function:\OriginalTabExpansion

function TabExpansion([string] $line, [string] $lastword)
{
    # Only run for "Get-PSPackage" or "Install-PSPackage"
    if (somecondition)
    {
        # Do cool NuGet stuff here
    }
    # Otherwise, fall back to default TabExpansion function
    else
    {
        OriginalTabExpansion $line $lastword
    }
}
Export-ModuleMember -Function TabExpansion

We need our NuGet code to run only for two specific commands, for the first parameter. The easiest way seemed to be to check that $line is equal to one of our commands followed by the $lastword. This probably isn't the best way, but it seems to do the job for the obvious cases.

# Only run for "Get-PSPackage" or "Install-PSPackage" where we're typing the first argument
if ($line -eq "Get-PSPackage $lastword" -or $line -eq "Install-PSPackage $lastword")
{
    # ...
}

Inside this block, we need to query the OData feed for NuGet packages. Rather than hard-coding the URI, we're supposed to go through a Microsoft fwlink (https://go.microsoft.com/fwlink/?LinkID=206669), but because we can't append parameters to the end, we need to resolve this redirect. I found some code in the NuGet issue tracker written by David Ebbo to do just this. I converted it to PowerShell and added a $defaultSource variable:

# Follows redirects and returns the final URI. Used to support fwlink as package source.
function GetRedirectedUri($uri)
{
    $req = [System.Net.HttpWebRequest] [System.Net.WebRequest]::Create($uri)
    $req.AllowAutoRedirect = $FALSE; # Don't follow redirects, to save bandwidth/roundtrip
    $resp = $req.GetResponse()
    if ($resp.StatusCode -eq [System.Net.HttpStatusCode]::Redirect)
    {
        $resp.Headers["Location"]
    }
    else
    {
        $uri;
    }
}
# Set the default NuGet source
$defaultSource = GetRedirectedUri("https://go.microsoft.com/fwlink/?LinkID=206669")

With this done, we just needed to call the feed with some search parameters and parse the results. Luckily, parsing OData is pretty trivial, and with Fiddler and Visual Studio, it was easy to find the syntax for searching for packages with IDs that start with a given string :)

The final step was to pipe through Get-Unique because the OData feed returns multiple entries for packages with multiple versions (but the ID is the same):

# Construct a URI to fetch the top 30 matching packages, sorted by download count
$uri = "$($defaultSource)Packages()?`$filter=startswith(tolower(Id),'$($lastword)')&`$orderby=DownloadCount%20desc,Id&$`skip=0&`$top=30"

# Fetch the data, return as XML
$wc = New-Object Net.WebClient
$data = [xml]$wc.DownloadString($uri)

# Pipe result through Get-Unique because there could be multiple versions with the same ID
$(
    # Loop, just grabbing the ID from Properties
    foreach ($entry in $data.feed.entry)
    {
        $entry.properties.Id
    }
) | Get-Unique

And viola, Tab Expansion! Now when you type "Install-PSPackage x" or "Get-PSPackage x" you can use tab to cycle through the matching packages (sorted by download count, in an attempt to give the most likely result first).

The full module is is up on Bitbucket, but maybe with a little tidying up we can get Andrew Nurse to pull the functionality into his repository and update PSGet on NuGet.

07

May 2011

Setting up NuGet to Automatically Fetch Packages When Deploying to AppHarbor Without Storing Binaries in Source Control

Over the last few days I've been blogging and tweeting about using NuGet without committing the packages folder to source control. David Ebbo blogged about using a pre-build event to fetch packages at build time.

Unfortunately, this doesn't work on AppHarbor because build events are not supported. If you try, you'll find the pre-build step doesn't fire and the build fails due to the missing dependencies :(

I voted on (and tweeted about, lots) a feature request about supporting NuGet natively on AppHarbor. The request has had quite a few votes, and Friism said they'll probably implement it, but in the meantime, this meant I couldn't deploy to AppHarbor. I didn't want to add my packages folder to source control, because even once I remove it when AppHarbor supports NuGet natively, it'll still be in the history (and therefore every clone), which I'd like to avoid.

I scratched my head a little, and re-read the proposed spec for baking this functionality into NuGet/MSBuild (to be honest, I don't actually understand what there is to improve!). The mentions of MSBuild got me thinking - custom build targets!

I opened up my csproj file in Notepad, and scanned through for the "BeforeBuild" step that's included by default but commented out. I removed the comment, and moved my pre-build event into the target:

<Target Name="BeforeBuild">
<Exec Command="&quot;$(SolutionDir)Tools\NuGet&quot; install &quot;$(ProjectDir)packages.config&quot; -o &quot;$(SolutionDir)packages&quot;" />
</Target>

First I checked whether this worked locally, by deleting my packages folder and running MSBuild from the command line. Success! I added in MarkdownHelper and added some test code to a view:

@Html.Markdown("We can use **Markdown!**")

Everything worked fine locally. An "hg ci" and "hg push" later, Bitbucket notified AppHarbor, AppHarbor checked out, built, and successfully deployed!

To show it working, you can see the test page at dev.dantup.com that will form the basis of the ASP.NET MVC / Razor version of this blog, and view the source code that AppHarbor is pulling to see there's no packages folder!

So, it turned out to be quite simple to get it working. I've also notified David Ebbo to see if he'd update his post with this info for anyone reading there that's using AppHarbor! :-)

04

May 2011

Vote for Native NuGet Support on AppHarbor to save your Repositories from The Bloatmonster

I was hoping to post tonight about how awesome BitBucket, AppHarbor and NuGet all worked together. Sadly, I hit a problem with getting NuGet to fetch packages on AppHarbor. First, some background...

Binaries in distributed version control systems are evil.

The problem with binaries in DVCS is that every clone pulls an entire copy of the repository (this is good, normally). If you have ten different revisions of a 10mb binary file, then your clone will include all of these copies - 100mb in total.

David Ebbo posted about using NuGet without committing packages to source control. The idea is that when you check out, you can use NuGet.exe to fetch all the packages based on your packages.config file. This means you can keep your repository lean, but still get all the required dependencies with little effort. In fact, David suggests a pre-build event that will run NuGet automatically! Unfortunately...

AppHarbor does not support build events

This means the pre-build event that executes NuGet to fetch the packages on a clean checkout, doesn't run, and the AppHarbor build will fail due to missing dependencies.

The Fix?

The fix is for AppHarbor to either support build events (tricky, for security reasons), or support NuGet natively, and allow us to tell AppHarbor to automatically execute NuGet to fetch our packages after checking the code out, but before running MSBuild.

So, what are you waiting for? Go and vote for NuGet support on AppHarbor so you can host your ASP.NET MVC applications there (for free, if not very big!) without needing to bloat your repositories with 400MB of NHibernate ;)

27

April 2011

MarkdownHelper on NuGet, using MarkdownDeep

Last month I posted a small HtmlHelper to make transforming Markdown in an ASP.NET MVC application a little easier. Unfortunately, getting it up and running wasn't quite so easy... You had to go and download MarkdownSharp (or copy the code file from the Google Code site) and put it in your project, then copy/paste my code into a file, add the namespace to a Views/Web.config, and blah blah, you gave up already.

Not any more!

I created a package called MarkdownHelper on NuGet. It took me less time to create the package than it took to get up and running previously, but now using the package is as simple as typing the following into the Package Manager Console...

PM> Install-Package MarkdownHelper

... that's it! All done! Now in your views, you can simply type:

@Html.Markdown(Model.YourMarkdownPropertyHere)

This means you no longer have to copy/paste code around. This is mighty useful if you're using Markdown in multiple ASP.NET MVC applications.

Rather than creating another assembly to import, the helper class will be put directly into your project (inside a cryptically named "Helpers" folder), though if this isn't the done thing, I can change it easily enough.

Also worth noting that I changed from MarkdownSharp to MarkdownDeep.NET. In addition to being faster (which isn't really important unless you're transforming a lot of text), it has a Javascript version that works transforms 100% the same, which will come in handy if you're writing an editor. Currently the package imports only the .NET version, though this might change as I add functionality.