Skip to content
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

Fabulous...and a design question #144

Open
markolbert opened this issue Jul 11, 2020 · 2 comments
Open

Fabulous...and a design question #144

markolbert opened this issue Jul 11, 2020 · 2 comments
Labels
Discussion/Question Discussions or questions about the code

Comments

@markolbert
Copy link

Man, I wish I'd found your library before investing two months writing something that still only did about 85% of the job! Thank you.

I'm curious about a couple of things, though. My library basically parses the obj/project.assets.json and .csproj files to extract information about the compilation environment so I can set up a subsequent Roslyn compilation properly. Along the way I had to write a resolver to locate packages in the local nuget repository for all the referenced packages.

The real problem, though, was that not every needed package was referenced in the project.assets.json file. For example, calls like this:

if( File.Exists(...) )

or

var newList = oldList.Where(...).ToList();

would cause problems because the need for the System.IO.FileSystem and System.Linq assemblies were not contained/defined anywhere in the project.assets.json or .csproj files.

Buildalyzer clearly solves that problem. It correctly identified those needs, and others, on the same test project.

So if you'll pardon me for being lazy and not walking through the Buildalyzer code...what's the secret? :)

Also, on a related subject, I noticed that the required references found by Buildalyzer for that test netstandard test project include what appear to be the entire suite of assemblies defined for netstandard. My approach tried to simplify things by just loading the netstandard package itself. But that didn't work (i.e., stuff like System.IO.FileSystem weren't loaded). Is that why Buildalyzer explicitly identifies all the packages defined for netstandard?

Again, thanx for creating this truly awesome library.

@daveaglick
Copy link
Collaborator

Thanks for the coffee!

So if you'll pardon me for being lazy and not walking through the Buildalyzer code...what's the secret? :)

Prepare for lots of words...I could write about this stuff all day :)

The initial version of Buildalyzer started from somewhere similar to where you started. I would try to reverse engineer the exact set of parameters to use when calling the MSBuild APIs directly from code. This sort of worked, but it turns out MSBuild is really complicated environment-wise and a large number of projects can't be compiled from the MSBuild APIs alone. Part of the issue is that MSBuild is more than just MSBuild at this point - when a .NET project is compiled MSBuild the engine takes care of the process, but MSBuild the set of default tasks and targets in the SDK kicks in and does a lot of the work. The MSBuild API has the engine bits but none of the SDK tasks and targets. So then you get into this strange hybrid thing where you're calling the MSBuild APIs but trying to point it to the correct tasks and targets (there's more than one set - one for .NET Framework, another for .NET Core, each version of the .NET Core SDK can have different ones, etc.). Figuring that out and getting it to all work together was almost impossible.

That was the point I totally rebooted the project. It was clear using the MSBuild APIs and trying to parse project files to figure out the right properties and task/target locations wasn't going to work. So I thought, "what will work?". You know what works? Running msbuild myproject.csproj or dotnet build from the CLI. That also has the virtue of being future-proof. Buildalyzer doesn't need to know what changes the next version of the MSBuild APIs make and what special properties have to be set because we already know that running dotnet build just works. So the question is: how to fork MSBuild while still talking to it?

The secret sauce is MsBuildPipeLogger which I wrote specifically to power Buildalizer (but which seemed isolated enough to release on it's own). The reasoning is that 1) Buildalyzer needs to launch MSBuild from the CLI by forking a process, 2) an MSBuild logger has access to the information we need to understand everything about the build, and 3) we need a way to get data from a custom MSBuild logger across the process boundary to the calling application. That's what MsBuildPipeLogger does - it adds a custom logger to any MSBuild instance using a named pipe. A client can then connect to that pipe across process boundaries to receive logging events.

This is how Buildalyzer works today - it does some rudimentary exploration to figure out exactly what CLI command to run for a given project (msbuild ... vs. dotnet build for example) and what special environment variables and MSBuild properties need to be set (there's still a handful of these that I had to reverse-engineer, much to my annoyance - without them being correctly set the builds can fail). It then forks an actual MSBuild process, specifying the MsBuildPipeLogger with a named pipe on the CLI. Then it listens to that pipe for logging events in order to gather the information we need about the project. Receiving the call to csc (the C# compiler) is especially helpful because it contains all the source files and libraries that contribute to the compilation, including generated and built-in ones.

Also, on a related subject, I noticed that the required references found by Buildalyzer for that test netstandard test project include what appear to be the entire suite of assemblies defined for netstandard.

This is a result of that call to csc I just mentioned. Buildalyzer makes no distinction between framework libraries or project libraries. Some of the MSBuild targets from the SDK are responsible for figuring out what version of the framework/netstandard is being used and adding those libraries to the compilation, but by the time csc is called it's all just references. This ensures that if you call csc with the exact same set of source files and references that Buildalyzer gathers, you'd be able to compile the project in the exact same way. In a broad sense, MSBuild and project file mostly exists just to figure out that call to csc.

Does that answer your questions? I'm more than happy to continue expanding on any of this if there's a part of it that could use more detail.

@daveaglick daveaglick added the Discussion/Question Discussions or questions about the code label Jul 11, 2020
@drittich
Copy link

drittich commented Mar 9, 2021

I love software development stories like this. One day I would like to write a book of short stories about the journeys developers went on while developing great solutions. I bet there will be both some great and insights, and some great commonalities among them.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Discussion/Question Discussions or questions about the code
Projects
None yet
Development

No branches or pull requests

3 participants