Tuesday, 17 August 2010

OpenWrap late changes and release date

I’ve delayed slightly the tutorial on Part 2, as ready or not, I’m pushing binaries and the server online on the 26th of August. Call it a first preview. Delays are due to my really wanting some features that I didn’t think we would have for a first release, but I think it’s worth it, and is due to my frustration in having to deal with all the mechanics of building OpenWrap and it’s server itself.

Expect the second part of the guide sometime this week.

Monday, 2 August 2010

OpenWrap’s view on versioning

There has been a bunch of questions regarding the differences between CoApp and OpenWrap when it comes to versioning. If you follow the blogs and that project, you know that they are planning to install everything in the GAC whenever packages are installed, and require everything to be Authenticode signed.

OpenWrap takes the opposite view, and discourages strong-naming your assemblies. The reason is very simple: in .net, whenever you update an assembly to a newer version, as long as the assembly is not strong-named, the assembly version is blissfully ignored, which lets people swap the dll around as and when needed, when there’s a fix / update available.

The only way to not break project references when packages are updated would be to never update the AssemblyVersion in an assembly, rendering the versioning scheme useless.

The alternative is to create policy redirections as part of the deployment to the GAC of a strong-named assembly, or for the consumer to add those redirections manually in each of their projects. Either way, it’s either a huge burden on publishers (and people don’t bother), or on the consumers (and people get angry). Either way, we’re still in unhappy land.

All this versioning is not needed, for the simple reason that OpenWrap has its own versioning scheme, per package, providing you with many more features than the 1-1 coupling provided by strong-named assemblies.

And finally, last but not least… If like me you believe very strongly in xcopy-friendly development and deployment, the GAC breaks all this: you now require the assemblies to be GAC’d on your build server and locally before you can use them, as otherwise any odd version already installed on the system would take over. And there lays the major pain point, the GAC takes over from your local references and there is *nothing* you can do about it.

As the goal of OpenWrap is to enable rapid development and update of packages, strong-naming assemblies and GAC’ing them is diametrically opposite to the vision of the tool, and as such will be discouraged. I’ll even put a big fat red warning if you take a dependency on a package that has assemblies already in the GAC, offering you in one swoop the option of removing them interactively. I may even provide the option to automatically strip out strong-naming from assemblies you try to package.

Help yourself and help versioning, say no to the GAC. If you do have scenarios where you believe strong-naming is the right solution, please leave a comment and we can discuss it.

[Note: Strong-naming has nothing to do with signing. Signing assembly code with a certificate is not useful to reduce malware (I don’t see how it would), and it pauses the risk of the certificate revocation being checked before being ran. But it has nothing to do with the key signing used in strong-naming, it’s a different process.]

Adding OpenWrap to an existing project (Part 1)

I’m continuing writing up teaser posts on how to use OpenWrap. Building a new office has taken away my Sunday, so the server is still not up, and the code unfinished. A few more days and we’ll be there.

In the meantime, assuming you have OpenWrap installed, here’s how to convert an existing asp.net site (here an OpenRasta site running on top of asp.net) to OpenWrap.

From a default project…

Here’s the layout of my project.

PS C:\src\caff\git\openwrap-server> tree
Folder PATH listing for volume BOOTCAMP
Volume serial number is 60DA-D801
C:.
└───src
    └───OpenWrap.Server
        ├───App_Data
        ├───Handlers
        ├───Properties
        ├───Resources
        └───Views

First step is to add a wrap descriptor file and a version file that will be used to build the package.

PS C:\src\caff\git\openwrap-server> "Description: an openwrap server implementation based on OpenRasta." | ac openwrap-server.wrapdesc
PS C:\src\caff\git\openwrap-server> "1.0.0" | ac version

And finally, we want to create a project repository in which openwrap packages will be stored so OpenWrap can know where to put those. We can do this by creating a wraps folder.

PS C:\src\caff\git\openwrap-server> md wraps | out-null; tree
Folder PATH listing for volume BOOTCAMP
Volume serial number is 60DA-D801
C:.
├───src
│   └───OpenWrap.Server
│       ├───App_Data
│       ├───Handlers
│       ├───Properties
│       ├───Resources
│       └───Views
└───wraps

Adding OpenWrap to the project

Time to add OpenWrap to our project, which will add the necessary instruction to our wrap descriptor and all needed files.

PS C:\src\caff\git\openwrap-server> o add-wrap openwrap
# OpenWrap v1.0.0.0 ['C:\Users\sebastien.lambla\AppData\Local\OpenWrap\wraps\_cache\openwrap-1.0.0.13979169']
No wrap descriptor found, installing locally.
Project repository found.
Dependency added to descriptor.
Copying 'openwrap-1.0.0.13979169' to 'Project repository'
Expanding packages to cache...
PS C:\src\caff\git\openwrap-server> tree
Folder PATH listing for volume BOOTCAMP
Volume serial number is 60DA-D801
C:.
├───src
│   └───OpenWrap.Server
│       ├───App_Data
│       ├───Handlers
│       ├───Properties
│       ├───Resources
│       └───Views
└───wraps
    ├───openwrap
    │   ├───bin-net35
    │   ├───build
    │   └───commands
    └───_cache
        └───openwrap-1.0.0.13979169
            ├───bin-net35
            ├───build
            └───commands

Couple of interesting notes. First, you can see that the o.exe tool redirects all calls to the last version of the OpenWrap pacakge on the system.

Secondly, the wrap decriptor now contains the line depends: openwrap.

A folder structure…

Let’s examine what files are actually in this wraps folder that now exists:

 

└───wraps
    │   openwrap-1.0.0.13979169.wrap
    │
    ├───openwrap
    │   │   openwrap-1.0.0.13979169.wrapdesc
    │   │   version
    │   │
    │   ├───bin-net35
    │   │       ICSharpCode.SharpZipLib.dll
    │   │       OpenFileSystem.dll
    │   │       OpenRasta.Client.dll
    │   │       OpenWrap.dll
    │   │
    │   ├───build
    │   │       OpenWrap.Build.Tasks.dll
    │   │       OpenWrap.CSharp.targets
    │   │       OpenWrap.Resharper.dll
    │   │       OpenWrap.tasks
    │   │
    │   └───commands
    │           OpenWrap.Commands.dll
    │
    └───_cache
        └───openwrap-1.0.0.13979169
            │   openwrap-1.0.0.13979169.wrapdesc
            │   version
            │
            ├───bin-net35
            │       ICSharpCode.SharpZipLib.dll
            │       OpenFileSystem.dll
            │       OpenRasta.Client.dll
            │       OpenWrap.dll
            │
            ├───build
            │       OpenWrap.Build.Tasks.dll
            │       OpenWrap.CSharp.targets
            │       OpenWrap.Resharper.dll
            │       OpenWrap.tasks
            │
            └───commands
                    OpenWrap.Commands.dll

There are a couple of interesting things happening. First, the wrap package itself got copied over in the wraps folder. The goal here is for you to always commit those dependencies as part of your project in source-control. Second, you’ll notice that the content of this file is uncompressed in the _cache folder. This one should always be part of your ignores, as you really don’t want to be committing the zip file *and* the content.

Finally,you’ll notice there’s also an openwrap folder that has the same content as the _cache/openwrap-1.0.0.13979169 folder. Lets look more closely.

PS C:\src\caff\git\openwrap-server> ls wraps

Mode           LastWriteTime       Length Name
----           -------------       ------ ----
d----    02/08/2010    01:23   <JUNCTION> openwrap [C:\src\caff\git\openwrap-server\wraps\_cache\openwrap-1.0.0.13979169]

Indeed, this folder is not a real folder but a junction to the latest version. This linking is only done for pacakges that are anchored. So why do we have this?

In an ideal world, everything would use OpenWrap for finding stuff, and the latest version for a project would always be selected. But we live in a world full of dragons and daemons that require a bit more of a stable path. As OpenWrap hooks in MSBuild (we’ll see how in the next post), it’s important that the msbuild files are in a reasonably stable location. It’s also important that OpenWrap is available the first time you checkout your files, so a build can happen instantly.

Folder structure

You may notice that packages contain multiple folders (which are called an export in OpenWrap). The bin-Xxx are the ones in which assemblies needed for referencing in a project are contained. Each .net framework profile has its own identifiers, and the resolution algorithm uses those identifiers to decide what assembly to load. If you’re on .net 3.5, the probing will go bin-net35, bin-net30, and bin-net20, and will fail when it cannot resolve assemblies.

There are a couple more exports in the package: commands contain all the commands you can execute from the command-line (or from any host that can execute those commands, currently only the command line), and build contains all msbuild-related extensibility points (which I really hope to manage to import automatically further down the road).

Conclusion

So, there you have it, in a few seconds we installed OpenWrap in the solution. Next up, we’ll hook-up our project by changing a line in an MSBuild file.

The initial OpenWrap installation

Going down OpenWrap’s rabbit hole, the first thing you do is retrieve a fresh copy of o.exe, the Command shell. At a whopping 32k (for the debug build), you could get the initial file in 234 text messages. Lets open the file straight from the web site, and we’re greeted by installation options.

The OpenWrap shell is not installed on this machine. Do you want to:
(i) install the shell and make it available on the path?
(c) use the current executable location and make it available on the path?
(n) do nothing?

I think the options are quite self-explanatory, but let’s recap:

  • (i) will copy o.exe to ~/AppData/Local/OpenWrap/ and add this path to your PATH environment variable for the current user
  • (c) will simply add an o.exe.link file in ~/AppData/Local/OpenWrap/ and add this path to your PATH
  • (n) will do nothing this time around and run o.exe as normal.

Let’s choose the first option

OpenWrap packages not found. Attempting download.
Downloading http://wraps.openwrap.org/bootstrap [....................]
Downloading http://wraps.openwrap.org/wraps/openwrap-1.0.0.13979169.wrap [.............]
Expanding pacakge...
# OpenWrap v1.0.0.0 ['C:\Users\sebastien.lambla\AppData\Local\OpenWrap\wraps\_cache\openwrap-1.0.0.13979169']
Command was not understood. Type 'help' to get a list of supported commands.

I do hope that this is also quite self-explanatory, but just in case… When the shell tries to execute the first time, it will not find the OpenWrap packages anywhere. At that stage, it will go to the openwrap web-site and download the bootstrap packages to the correct location, which is just a list of URIs to download in a text file.

Once all this is done (and notice the wonderful typo in the output, you can check on github that this is all live code), the shell can now delegate to the openwrap code itself to parse the command. And as there was no command (remember, we started the application from a web browser), the system tells us that the command was not understood.

I tried to make it more complicated but I just didn’t have the time.