-
Notifications
You must be signed in to change notification settings - Fork 899
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
Conversation
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.
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. |
@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. |
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. |
@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. |
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 |
That sounds very doable, @Therzok. Do we need any further agreement that that will be acceptable, or should I move forward with that? |
I'd love to see @nulltoken comment here about the proposal (he's on holiday at the moment). |
@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 |
cc @shana |
@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:
FWIW our current NuGet packaging mechanism is entirely dealt with by our AppVeyor build (cf. https://github.com/libgit2/libgit2sharp/blob/vNext/appveyor.yml). |
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:
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... |
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? |
@nulltoken said:
There are a couple of options I can recommend (with pros and cons from my experience listed):
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 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?).
I don't think the topic of strong name signing influences NuGet pre-release package behaviors in particular. What did you have in mind?
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. |
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.
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.
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.
|
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) |
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 ( 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.
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.) |
@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.
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 😄 |
I agree with the points from both of you. In particular:
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). |
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. |
Closed via #1265 |
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