Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Strong-name sign the assembly #1164

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from

Conversation

AArnott
Copy link
Contributor

@AArnott AArnott commented Aug 7, 2015

This including checking in the (unprotected) private key so that the library can be built and replaced by anyone, as it was before it was strong-name signed.

Fixes #212

This including checking in the (unprotected) private key so that the library can be built and replaced by anyone, as it was before it was strong-name signed.
@ethomson
Copy link
Member

ethomson commented Aug 7, 2015

I'm sorry to see this come up again, because it was so thoroughly discussed before and closed.

However I am happy about this for one reason: I can apologize publicly for being a jerk before. I'm sorry.

We are always better served when we have more contributors, not fewer. When we have more voices. When we have more opinions. Although my opinion certainly hasn't changed - that this is a bad idea - that certainly doesn't make it a "dumb" one.

I regret being rude about this.

This issue is one that I'm particularly volatile about, though: Microsoft wants an SN signed assembly, but that means that the community has to support this desire? And pay the tax on it?

Having said that, I don't see how the circumstances have changed here and I don't see how this is generally helpful for the community. The fact remains: if Microsoft wants a strong name signed assembly, Microsoft should strong name sign it for themselves.

@AArnott
Copy link
Contributor Author

AArnott commented Aug 7, 2015

@ethomson I don't mean to push this pull request in particular. But can you help me understand what the tax is that you're referring to? What tax? The pull request was trivial to create, and requires no maintenance from here on out.

@ethomson
Copy link
Member

ethomson commented Aug 7, 2015

I concede that the actual SN signing is quite trivial, both its setup and its (literal) action of signing the DLL.

If I'm a user of libgit2sharp and in the same project I also use Octokit, does that need to be SN signed?

Am I now picking up a pitchfork and trying to convince the rest of the world about the rightness of strong name signing? I have enough crazy crusades that I'm already fighting, I don't need another.

@AArnott
Copy link
Contributor Author

AArnott commented Aug 7, 2015

@ethomson No. Remember that the .NET Framework itself is strong-name signed. Yet so many projects that consume it are not. So certainly by strong-name signing libgit2sharp, you are not imposing any requirement whatsoever on your consumers.

The only requirement it imposes is that once a project is strong-name signed, it can only reference other strong-name signed assemblies. So if libgit2sharp ever wanted to take a dependency on another nuget package, for example, it would only be able to if that other package were signed. That's the unfortunate viral nature of it, and why there is pressure to strong-name sign these nuget packages, so that they are more consumable. So as long as libgit2sharp is comfortable with its current set of dependencies, or if its possible future dependencies are already strong-name signed, there's no cost to you or anyone outside of simply starting to strong name sign.

@Therzok
Copy link
Member

Therzok commented Aug 8, 2015

Can we make this so it's in a different .targets file and we only activate strong name signing on a given msbuild property (say /p:SignLibGit2Sharp="true")?

@AArnott
Copy link
Contributor Author

AArnott commented Aug 9, 2015

That sounds very doable, @Therzok. Do we need any further agreement that that will be acceptable, or should I move forward with that?

@shiftkey
Copy link
Contributor

shiftkey commented Aug 9, 2015

I'd love to see @nulltoken comment here about the proposal (he's on holiday at the moment).

@nulltoken
Copy link
Member

@AArnott Thanks for this very straightforward contribution.

As stated in #212, please give me a few days to think about it and ponder the implications.

FWIW, I've forced AppVeyor to build your PR. One can check the resulting package at https://ci.appveyor.com/project/libgit2/libgit2sharp/build/1281/job/d4lhk2kamj5m0hq5/artifacts

@shiftkey
Copy link
Contributor

cc @shana

@nulltoken
Copy link
Member

@AArnott As pointed out in #212, I'm far from being expert with regard to strong naming. As such, it would be really helpful if you could provide some guidance regarding the changes that we should apply to the build chain and the release management.

Among others, some advice about the following topics would be especially helpful:

  • What should be the recommended strategy concerning the AssemblyVersion and AssemblyFileVersion content when bumping one of the Major.Minor.Patch segment of the version?
  • How should we deal with the specific topic of NuGet pre-release packages?

FWIW our current NuGet packaging mechanism is entirely dealt with by our AppVeyor build (cf. https://github.com/libgit2/libgit2sharp/blob/vNext/appveyor.yml).

@xoofx
Copy link
Contributor

xoofx commented Aug 21, 2015

If you could stay away from strong naming, that would be better... because strong naming implies somehow a strong binding that can lead to errors like this:

Unhandled Exception: System.IO.FileLoadException: Could not load file or assembly 
'LibGit2Sharp, Version=0.23.0.0, Culture=neutral, PublicKeyToken=62eea21acd6795a9' or one of
its dependencies. The located assembly's manifest definition does not match the assembly
reference. (Exception from HRESULT: 0x80131040)
at ConsoleApplication1.Program.Main(String[] args)

My main complain about strong-naming an assembly is that it forces all clients using the strong-named library to recompile their binary against the new one in case the version change (even if it is just a minor/build update that doesn't break any contract), unless they provide a binding redirect from the .config file of the application.

Suppose you develop a library AAA (not signed) compiled with a signed LibGit2Sharp and this library provides plugins that can use LibGit2Sharp. If AAA has to upgrade to a newer version of LigGit2Sharp (even minor/build), all plugins using LibGit2Sharp will be broken without recompiling them with the new version or without forcing all application using AAA to include in their .config file a binding redirect.

Also, in case you want to ILMerge/ILRepack/obfuscate your application with a signed LibGit2Sharp, It would not be possible...

@carlosmn
Copy link
Member

We are aware that it can be a pain if the releases aren't done properly, which is why we're asking for guidance in that regard. You're describing what can go wrong, but what can be done to avoid it?

@AArnott
Copy link
Contributor Author

AArnott commented Aug 22, 2015

@nulltoken said:

What should be the recommended strategy concerning the AssemblyVersion and AssemblyFileVersion content when bumping one of the Major.Minor.Patch segment of the version?

There are a couple of options I can recommend (with pros and cons from my experience listed):

  1. The .NET Framework encourages (and in the corefx repo itself follows) the policy of keeping the AssemblyVersion and AssemblyFileVersion in sync, and incrementing them in some way for every single release. This policy seems to maximize the negative of therefore requiring binding redirects in the .config files of applications that consume them. I've had several conversations with members of this team personally to try to understand why they follow this policy and while I do not yet understand or fully agree with it, they stand by their decision. What's more, I haven't heard many folks refuse to depend on .NET Framework or corefx assemblies as a result of this policy. So perhaps it's right and the best one even so.
  2. The policy I personally use and have used for years with success is to change AssemblyFileVersion for every single release (so that there's an easy way to see exactly what version it really is) but let AssemblyVersion contain a shorter form of AssemblyFileVersion to maximize compatibility without requiring binding redirects. I follow semver semantics of major.minor.patch. Patch increments are by definition fully compatible either direction (aside from whatever bug fixes were made). Minor version increments suggest new API but no breaking changes, and major version allows for breaking changes. A breaking change usually warrants folks recompiling anyway to ensure they don't get bad "missing method exceptions" at runtime. A new feature can be added safely, but not removed if the version goes backwards. So let's say you have 2.0, and you add a feature and call it 2.1. Should you keep the assembly version at 2.0? If you do, your existing folks will continue to work without binding redirects, but new folks who take a dependency on your new 2.1 feature will document in their assembly metadata that the version of the dependency on your library is simply 2.0, and thus the CLR will be willing to bind you to 2.0 if that's what some application ships with, and you'll once again get a missing method exception.
    All this is the pro and con of why the policy I follow is to set AssemblyVersion to major.minor, and AssemblyFileVersion to major.minor.patch. That way applications remain stable at runtime, and can be fixed up to work without a recompile if necessary by adding a binding redirect.

I've heard somewhere (perhaps the related issue) that libgit2sharp's API is not yet stable. If that's true, then that can play a role in deciding which policy you follow, depending on what outcome you want. Semver would suggest you either increment your major build number each time you break the API or ship every release with an -unstable-like suffix. I'm guessing you have enough customers now though that will harp on you for a "stable" nuget package so that their depending package can also be "stable", that you wouldn't want to switch to unstable. That's kind of an aside from the question of strong name signing, but it's related because of the points I make in option 2 above: if you make breaking changes, generally speaking the best thing to do is certainly change your AssemblyVersion, in order to require folks either recompile or express their intent to avoid that step by adding a binding redirect -- and thus avoid folks accidentally crashing their apps due to missing method exceptions.

Avoid keeping your AssemblyVersion the same for too long (i.e. across too many new features or breaking changes). Some libraries do this. I've heard that Newtonsoft.Json might be one of them (although I have no personal knowledge), and as a result some folks are quite upset with them because their libraries can't avoid "missing method" exceptions in various applications.

Oh, and just FYI: binding redirects are automatically added as necessary both by the NuGet Client and by MSBuild if built by .NET Framework 4.5.1 or later (the app might also have to target 4.5.1 or later too, I'm not sure -- @terrajobst might know). So in practice, requiring binding redirects lately isn't as bad as perhaps it has been in the past. So as you evaluate the above policies, you might consider that, but reality check it against your customer base (e.g. are they all using .NET 3.5 or 2.0 and thus binding redirects are still a pain?).

How should we deal with the specific topic of NuGet pre-release packages?

I don't think the topic of strong name signing influences NuGet pre-release package behaviors in particular. What did you have in mind?
In case it's relevant, I'll venture to say that I pack as much version information into NuGet pre-release package version specs as I can. My versioning policy for these and other aspects of libraries (open source and closed) is encoded in my NerdBank.GitVersioning package, in case you want to read over that. It has special handling for pre-release packages that might be interesting to you.

FWIW our current NuGet packaging mechanism is entirely dealt with by our AppVeyor build (cf. https://github.com/libgit2/libgit2sharp/blob/vNext/appveyor.yml).

I'm afraid I'm not familiar with yml configuration in appveyor so when I looked at your file, I'm afraid it was quite foreign to me. I'm not sure how that impacts any of the foregoing advice.

I hope this helps.

@shana
Copy link

shana commented Aug 24, 2015

I've found that different MS-related teams handle the issue of AssemblyVersion/AssemblyFileVersion differently - the Visual Studio team, for instance, will freeze public APIs upon release and their AssemblyVersion is frozen to match the release (but not the AssemblyFileVersion). This creates for them a tough situation where they are very weary of making APIs public, because as soon as they're a part of an official release, they can never again be touched (i.e., altered or removed) until the next one (and VS releases every 18 months, so quite a long lifetime). Other teams, like the Rx team, are shipping very broken assemblies right now because they've changed public API without changing versions. So I wouldn't take their ideas of versioning as gospel 😄

Freezing the AssemblyVersion for an extended period of time is definitely not a good idea. Like @AArnott details so well, the AssemblyVersion should be upped according to what changed. I also use Major.Minor.Patch.Build, where AssemblyFileVersion gets the full version and AssemblyVersion gets Major.Minor or Major.Minor.Patch.

  • Build == purely an incremental number and side effect of the build system (the CI ups the build number automatically as a tracking counter, for instance)
  • Patch == a change that doesn't affect a public API or public behavior (i.e., you're not breaking existing consumers by changing public methods or the logic that consumers expect), minor bug fixes
  • Minor == non-breaking additions to public API, major bug fixes - with bug fixes, it's minor if it doesn't break existing users, major if it does (even if the API is the same). There are bugs that people rely on that you want to fix (which might break your users, but you don't know how many rely on the broken behavior), and for these I follow the 99% bug compatibility rule - never break userspace except for that 1% of bugs that you really need to fix now, and even then with very careful should-I-bump-major-version-for-this analysis.
  • Major == breaking changes to API or breaking behavioral changes for consumers.

The AssemblyVersion can have as many parts of the version (including being the same as AssemblyFileVersion) depending on the stability of the API. If the API is not frozen or stable, AssemblyVersion can be upped often, with no binding redirects. That way, people know that they shouldn't trust any updates without recompiling first.

Suppose you develop a library AAA (not signed) compiled with a signed LibGit2Sharp and this library provides plugins that can use LibGit2Sharp. If AAA has to upgrade to a newer version of LigGit2Sharp (even minor/build), all plugins using LibGit2Sharp will be broken without recompiling them with the new version or without forcing all application using AAA to include in their .config file a binding redirect.

Plugins should always ship with their own desired versions of a library unless the application they're plugging into explicitly says otherwise.

So, for instance, Visual Studio ships with a third-party library that I also depend on (newtonsoft, libgit2sharp, etc). I could reference the version that's shipped in VS and use it directly, but that would make it harder for the VS team to update it if they need to (even though they will never update it once it's out, API stability being the priority for them - but they could), so even though VS has the library loaded in its process space, I ship my own version.

I will never trust that any dependency I need is going to be fulfilled by someone else in the process space, unless that dependency is official and clearly made so - that means that a library that I depend on gets updated together with the application for any breaking changes. In fact, the only reason I'm safe* knowing that my dependencies will always be available and nobody can ever break me by loading something weird in my process space is because everything is signed.

  • for a certain measure of safe

@shana
Copy link

shana commented Aug 24, 2015

How should we deal with the specific topic of NuGet pre-release packages?

In terms of AssemblyFileVersion/AssemblyVersion, nothing changes, it just represents the next bump in whatever type of change the package represents. AssemblyInformationalVersion can have a string that says it's a -alpha or -beta or -prerelease if you want, the same that goes into the nuget package version information (0.12.1-alpha1, for instance). The Build part of the version can go after the -suffix moniker, if you want to do that for ease of tracking, 0.12.1-alpha5 will always be considered a smaller version than 0.12.1.0, iirc)

@ethomson
Copy link
Member

I've found that different MS-related teams handle the issue of AssemblyVersion/AssemblyFileVersion differently - the Visual Studio team, for instance, will freeze public APIs upon release and their AssemblyVersion is frozen to match the release (but not the AssemblyFileVersion). This creates for them a tough situation where they are very weary of making APIs public, because as soon as they're a part of an official release, they can never again be touched

Presumably different teams handle things differently, and @AArnott can weigh in on what things look like on his side of the product if I'm not articulating his view of the world correctly, but we (Microsoft.TeamFoundation.*) intend to provide backcompat for the length of a product support requirement. Which is to say that despite massive refactoring in the way things work Under The Hood, you can still code against the VS 2005 (aka 8.0) API of Microsoft.TeamFoundation.VersionControl.Client and it will work against a VS 2013 DLL.

I don't know that this is a written guarantee or part of our actual support lifecycle - I think that this is just something we do (and that our management holds us to.) But that's why we're loathe to add new public APIs, because we'll have to support it for a significant portion of our careers. This is independent of assembly versioning.

Assemblies are versioned as part of our build process. Things get a new file version when they're built, which is used to determine if it's different or not (and should be updated). This doesn't really have much to do with API stability considerations.

So, for instance, Visual Studio ships with a third-party library that I also depend on (newtonsoft, libgit2sharp, etc). I could reference the version that's shipped in VS and use it directly, but that would make it harder for the VS team to update it if they need to (even though they will never update it once it's out, API stability being the priority for them - but they could), so even though VS has the library loaded in its process space, I ship my own version.

No, these are not public APIs, these APIs are things that we consider internal to Visual Studio. We are consumers of these libraries just like other people are consumers of these libraries, and we bring them along for ourselves. We are not providing support guarantees for Json.NET or LibGit2Sharp.

We reserve the right to update LibGit2Sharp between minor updates within a major version number. The assembly version and file version are completely meaningless, internal to us, and should not be relied upon for any sort of guarantee of consistency of an API. Absolutely nobody should be using these internal APIs and not expecting to get completely and totally broken in the future by updates.

(It may not seem like we do a lot of updates to LibGit2Sharp - and indeed we do not - but there are a plurality of reasons for this predicated on the long lead time of a release and the stability concerns that fall out from that. But I assure you that it has nothing to do with API stability which we would bulldoze right over you if you decided to consume this.)

Now note that the above paragraphs are really meant for the benefit of anybody following this thread and thinking "oh, Visual Studio ships LibGit2Sharp, I can just use that". Don't. Really, only people who are part of Visual Studio (eg, they ship in the box) should be using our library. (There are a few third parties that this applies to, and you will know if you're one of them.)

@shana
Copy link

shana commented Aug 24, 2015

@ethomson Yes, your way of doing versioning is bound by the way VS versions all its assemblies and by its release cycle. It definitely makes sense for VS - since you have to provide back-compatibility for pretty much everything, having versions frozen and bound to the VS version makes perfect sense. Keeping the API small is a way of making it a bit less painful to maintain, and it's nice for developers that know they're safe using those APIs. It's annoying for developers if an API is missing, because it takes years to get it :P

When I said not to take versioning strategies as gospel, it's precisely because versioning strategies depend deeply on release cycles and compatibility strategies and other factors that are pretty specific to each project. Low-level native-binding libraries with fast-moving APIs are way different from managed-only utility libraries, which are way different from libraries that extend other libraries or applications, which are different from library frameworks (like the .net class libraries) that move in completely different release cycles.

Now note that the above paragraphs are really meant for the benefit of anybody following this thread and thinking "oh, Visual Studio ships LibGit2Sharp, I can just use that". Don't. Really, only people who are part of Visual Studio (eg, they ship in the box) should be using our library. (There are a few third parties that this applies to, and you will know if you're one of them.)

This is exactly the point I was trying to make, I apologize if I wasn't clear enough. When you're doing plugins, unless the app (or main library) you're plugging into clearly states that a third-party library is a shared API, it's not meant to be shared, and any plugins should not rely on it at all, and should ship their own versions of whatever libraries they need. Otherwise, you suffer the consequences when things unexpectedly break under your feet.

PS: I'll make sure to scream at anyone that I see relying on the libgit2sharp that VS ships out of the box 😄

@AArnott
Copy link
Contributor Author

AArnott commented Aug 24, 2015

I agree with the points from both of you. In particular:

When you're doing plugins, unless the app (or main library) you're plugging into clearly states that a third-party library is a shared API, it's not meant to be shared, and any plugins should not rely on it at all, and should ship their own versions of whatever libraries they need. Otherwise, you suffer the consequences when things unexpectedly break under your feet.

This is precisely why incrementing the AssemblyVersion for any breaking change or feature increment(!) is so important. Suppose libgit2sharp with assembly version 1.1 shipped twice, where the second time it added a feature. If the old and the new assembly were both in VS's probing path, either one might get picked up, and regardless whether the caller wanted the newer one or the older one, they wouldn't know which one they got till they hit the MissingMethodException. So even though a VS plugin shipped its own libgit2sharp assembly, they still might get the one in-box if the assembly version collided. In contrast, if the second time libgit2sharp shipped it incremented the assembly version to 1.2, then the VS in-box code would get 1.1 loaded and the plugin would get 1.2 loaded (at the same time!) allowing both to run successfully.

BTW, I think loading two versions of the same assembly is only allowed when the assembly is strong-named. So consider that a bonus toward strong-naming your assembly as it lets scenarios like the above work (but if it's the tipping argument, you should verify rather than trust me since this is from vague memory).

@shana
Copy link

shana commented Aug 24, 2015

BTW, I think loading two versions of the same assembly is only allowed when the assembly is strong-named. So consider that a bonus toward strong-naming your assembly as it lets scenarios like the above work (but if it's the tipping argument, you should verify rather than trust me since this is from vague memory).

Yup, this is true. As msdn puts it, "Versioning is done only on assemblies with strong names.". Without a strong-name, the runtime won't load multiple versions of assemblies, and it's not possible to properly enforce dependency loading.

@ethomson
Copy link
Member

ethomson commented Mar 4, 2016

Closed via #1265

@ethomson ethomson closed this Mar 4, 2016
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants