Monday, July 02, 2012

Strong-naming assemblies using Mono.Cecil

As the Microsoft FxCop libraries are inherently 32-bit (including native code as they do), developing code using them on a 64-bit platform throws up places where the modes get mixed, and an assembly that is generally AnyCPU ends up needing to load an x86 library and barfs. No real problem here, use corflags assembly /32BIT+ /Force, where you need the /Force for a strong-named assembly. And then if later you need that one strong-named... There is the standard technique of ILDasm/ILAsm to rebuild the assembly with strong-naming (as documented e.g. here), but you still end up with the corflags yellow warning in the MSBuild output about breaking the original strong-naming if you need that too.

But when the code I was working on also uses Mono.Cecil to do stuff, it was easier to silently do the whole lot in one script:

// script relative paths to the Mono.Cecil and Mono.Options assemblies
#I "..\_Tools\Mono.Cecil"
#I "..\_Tools\Mono.Options"
#r "Mono.Cecil"
#r "Mono.Cecil.Pdb.dll" // Have to put the .dll on if Mono.Cecil.Pdb is also present
#r "Mono.Options"
open System
open System.Collections.Generic
open System.IO
open System.Reflection
open Microsoft.FSharp.Text
open Mono.Cecil
open Mono.Cecil.Pdb
open Mono.Options
// Command line argument parsing preamble ---------------------------------
let (!+) (option: string * string * (string->unit)) (options:OptionSet) =
let prototype, help, action = option
options.Add(prototype, help, new System.Action<string>(action))
let Usage (intro:string) (options:OptionSet) =
Console.Error.WriteLine(intro)
options.WriteOptionDescriptions(Console.Error);
Environment.Exit(1)
let assemblyName = ref ""
let keyName = ref ""
let cor32plus = ref false
let options = new OptionSet()
|> !+ (
"k|key=",
"The strong naming key to apply",
(fun s -> keyName := s))
|> !+ (
"a|assembly=",
"The assembly to process",
(fun s -> assemblyName := s))
|> !+ (
"c|cor32",
"Do what CorFlags /32BIT+ /Force does.",
(fun x -> cor32plus := x <> null))
let rest = try
options.Parse(fsi.CommandLineArgs)
with
| :? OptionException ->
Usage "Error - usage is:" options
new List<String>()
// The meat of the script starts here ---------------------------------
// load files
let stream = new FileStream(!keyName, FileMode.Open, FileAccess.Read)
let key = new StrongNameKeyPair(stream)
let definition = AssemblyDefinition.ReadAssembly(!assemblyName)
// Do what CorFlags /32BIT+ /Force does if required
if !cor32plus then definition.MainModule.Attributes <- ModuleAttributes.Required32Bit ||| definition.MainModule.Attributes
// The headline section : strong-naming ---------------------------------
// (Re-)apply the strong name
definition.Name.HasPublicKey <- true
definition.Name.PublicKey <- key.PublicKey
let pkey = new WriterParameters()
pkey.WriteSymbols <- true
pkey.SymbolWriterProvider <- new PdbWriterProvider()
pkey.StrongNameKeyPair <- key
// Overwrite the assembly
definition.Write(!assemblyName, pkey)
view raw gistfile1.fs hosted with ❤ by GitHub

Yes, you can do that in PowerShell too, just with more fussing about the path to the Cecil assemblies because of the schizophrenic current directory model it has. And because the rest of the code in this particular project is F#, keeping the build scripts in the same language is just natural.


No comments :