36 ms·
I used to contribute to a C# project aimed at Linux users[0], and one of the personally annoying aspects was requiring the .NET runtime. It's a lot like how Jav
by jmillikin 3y ago
I used to contribute to a C# project aimed at Linux users[0], and one of the personally annoying aspects was requiring the .NET runtime. It's a lot like how Java programs require a JVM, except worse because OpenJDK is a blessed implementation in a way that Mono never was.
When I saw that .NET 7 included AOT, I immediately downloaded it to play around. It's clearly not fully baked (missing macOS support), but what is there is great. AOT makes C# a direct competitor to Go for projects that don't need to wring every last cycle out of the CPU, and it doesn't have the same squeamishness about language complexity that characterizes Go.
The only part of .NET 7 that really bothers me is the compilation process. I couldn't find any compile-to-obj command with flags and such, instead there's a `dotnet` command that takes its input via a monolithic `.csproj` XML file and spits out a fully-formed binary. Trying to integrate C# into a Unix-style build pipeline (make/Bazel) doesn't seem straightforward in the current SDK.
[0] https://www.banshee-project.org/ https://www.banshee-project.org/
- fabian2k 3y agoYou don't even need native AOT for that, you can already create self-contained binaries with .NET Core. Those have fewer restrictions than native AOT, they bundle the necessary runtime.
- pjc50 3y agoThey have some annoying downsides: they unpack the runtime when you run them. This can cause problems like "virus scanner takes time looking at each .dll unpacked" and "fill up the disk they're unpacked into". If they don't fix this by version 8 I might have to build my own CLR apphost solution :(
- fabian2k 3y agoI don't think that is the case anymore. I think it also was platform-specific before and only applied to Linux. But I couldn't find the exact change with a quick search.
- pjc50 3y agoIt's not the case for AOT, but it is for "self-contained", at least as of .NET 6.
- fabian2k 3y agoIt doesn't extract onto disk, I'm not sure if it is still doing some extraction into memory.
- ripley12 3y agoI believe this got fixed in .NET 5; now only native libraries need to be extracted and stored elsewhere on disk, and many applications can get by without native dependencies. > When the app starts, the managed DLLs are extracted and loaded in memory, avoiding the extraction to a folder. https://learn.microsoft.com/en-us/dotnet/core/deploying/single-file/overview?tabs=cli#native-libraries https://learn.microsoft.com/en-us/dotnet/core/deploying/sing...
- deleted 3y ago[deleted]
- Nathanba 3y agowell nowadays you dont need to add each .cs file separately into the csproj file anymore which means you can pretty much just use the same file for your projects and the only difference are the package references. It's not that far away from a barebones folder but I really agree that the default build should allow me to omit csproj files entirely and then maybe even change the default build to always use AOT and you're precisely where golang is
- aseipp 3y agoI've been wondering how to integrate modern .NET Core into a custom build system (buck2) and was wondering similar things. There's this project I think is cool called bflat[1] that basically makes the C# compiler more like the Go compiler in the sense it's a one-shot single-use tool that can cross compile binaries natively. It's done by one of the people on the .NET Runtime team as a side project, and quite neat. I mostly use Visual Studio but I think in practice you're supposed to compile whole .dll's or assemblies all at once, which acts as the unit of compilation; I don't think the csharp compiler generates native object-files-for-every-.cs or translation unit, the kind of approach you'd expect from javac or g++. Someone please correct me if I'm wrong though! I'd like to learn more if there's a more granular/incremental unit of compilation. Since Roslyn and the compiler itself are mostly a library, maybe you could also write your own custom "csc-for-bazel" compiler or something to sort of bootstrap the whole thing and offer tighter integration... .NET 8 will support Native AOT on macOS, I think. [1] https://github.com/bflattened/bflat https://github.com/bflattened/bflat
- MarkSweep 3y agoFor more incrementalism, you can tell csc to generate reference assemblies. These assemblies contain only the public surface area of the assembly. You can use them to avoid recompiling downstream assemblies when the public API surface does not change. They are similar to ijars in Java. https://learn.microsoft.com/en-us/dotnet/standard/assembly/reference-assemblies https://learn.microsoft.com/en-us/dotnet/standard/assembly/r... I’ve been thinking a lot about what a good intervention of .NET and tools like Buck would look like. I think both Buck 1 and Bazel’s rules_dotnet directly target the csc compiler. Given how much logic is written in MSBuild files and how many steps are part of the build (analyzers, illink, ilcompiler), I think it makes sense to target the dotnet executable directly. Having a way to set arbitrary MSBuild properties and items would be nice. It would be nice if you could generate Visual Studio projects from the Buck targets. Lastly, it would be nice if these rules supported Bazel-style persistent workers. Both MSBuild and csc support a client server architecture, but rules_dotnet does not take advantage of that currently. Performance suffers accordingly.
- andygocke 3y ago.NET 8 will support macOS, and yeah I think your approach of basically re-implementing much of the SDK logic in bazel is probably your best bet for a reliable experience. At the same time, it's obvious that that's a considerable chunk of work. Also, IDEs like VS and VS Code are reliant on MSBuild APIs to construct a semantic understanding of the projects, so without MSBuild files, they won't properly function. This is an unfortunate problem, which I don't have a simple solution for.
- Rochus 3y ago> annoying aspects was requiring the .NET runtime ... OpenJDK is a blessed implementation in a way that Mono never was Which is unjustified, because Mono CLR is just a single executable less than 5 MB which you can download and run without a complicated installation process (see e.g. https://github.com/rochus-keller/Oberon/#binary-versions https://github.com/rochus-keller/Oberon/#binary-versions ). AOT compilation on the other hand is a huge and complex installation depending on a lot of stuff including LLVM, and the resulting executables are not really smaller than the CLR + mscorlib + app.
- afavour 3y agoI think the most important part of the OP's complaint is "blessed". For a very long time Mono was an entirely unofficial reimplementation of .NET for Linux. You can't blame anyone for being hesitant about relying on it.
- Rochus 3y agoHow can an implementation of a standard (ECMA-335 or ISO 23271), which is even explicitly intended by MS to be used by others (even supported by a comprehensive MS "open source" implementation to illustrate it) be "entirely unofficial"?
- coredog64 3y agoBeen a while since I’ve touched the CLR, but I have this vague recollection that while the Mono runtime is blessed, it’s also incomplete. If you want to write a portable CLI you can use Mono, but if you want to show a GUI, you’re out of luck.
- Rochus 3y ago> if you want to show a GUI, you’re out of luck. Mono included a powerful GUI framework which run on far more platforms than the .NET GUI. And Ximian/Xamarin invested a lot of effort to be as compatible as possible to all aspects of .NET (see e.g. https://www.mono-project.com/docs/about-mono/compatibility/ https://www.mono-project.com/docs/about-mono/compatibility/).
- MarkSweep 3y agoThe .NET SDK has some options to integrate NativeAOT with a larger application. You can reference libraries (static or dynamic) and object files during the build to link into the final executable. You can also configure NativeAOT to generate a static or dynamic library that you can link into another executable. https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot/interop https://learn.microsoft.com/en-us/dotnet/core/deploying/nati...
- jmillikin 3y agoNotice how that page describes how to configure the toolchain using XML. That's the thing I don't like -- instead of just building up an args list like cl.exe, I need to write my own intermediate tooling that synthesizes a .csproj and spits it out into the build sandbox before launching their magic do-everything binary. There's clearly something else underneath, the <LinkerArg> element is exactly what I would want to generate when running link.exe, but most of the configuration options are wrapped in a layer of gunk. Ideally it would be possible to invoke a command directly to convert a bunch of *.cs files into a .dll (or .a), then I could use existing logic to link it with link.exe (or ld).
- pjc50 3y agoFWIW, the relevant gunk: C:\Program Files\dotnet\sdk\7.0.100\Sdks\Microsoft.DotNet.ILCompiler\build\Microsoft.NETCore.Native.targets And C:\Program Files\dotnet\sdk\7.0.100\Sdks\Microsoft.DotNet.ILCompiler\build\Microsoft.NETCore.Native.Unix.targets (where the linker is selected) See the area around and after this chunk <CustomLinkerArg Include=""$(NativeObject)"" /> <CustomLinkerArg Include="-o "$(NativeBinary)"" Condition="'$(TargetOS)' != 'windows'" /> <CustomLinkerArg Include="/OUT:"$(NativeBinary)"" Condition="'$(TargetOS)' == 'windows'" /> <CustomLinkerArg Include="/DEF:"$(ExportsFile)"" Condition="'$(TargetOS)' == 'windows' and $(ExportsFile) != ''" /> <CustomLinkerArg Include="/LIBPATH:"%(AdditionalNativeLibraryDirectories.Identity)"" Condition="'$(TargetOS)' == 'windows' and '@(AdditionalNativeLibraryDirectories->Count())' > 0" /> <CustomLinkerArg Include="-exported_symbols_list "$(ExportsFile)"" Condition="'$(TargetOS)' == 'OSX' and $(ExportsFile) != ''" /> <CustomLinkerArg Include="-Wl,--version-script=$(ExportsFile)" Condition="'$(TargetOS)' != 'windows' and '$(TargetOS)' != 'OSX' and $(ExportsFile) != ''" /> <CustomLinkerArg Condition="Exists('$(_Win32ResFile)')" Include=""$(_Win32ResFile)"" /> <CustomLinkerArg Include="@(LinkerArg)" />
- nerdix 3y agoLoved Banshee. There was a short period of time during the mid 00s where it looked like Mono/C# had a chance to become the dominate platform for gnome apps. Banshee, F-Spot, Beagle, Docky. And some really good stuff came out of the Mono as well. Like Mono.Cecil
- pjc50 3y ago> Trying to integrate C# into a Unix-style build pipeline (make/Bazel) doesn't seem straightforward in the current SDK. Basically, don't do that. "dotnet build" invokes "msbuild" which is a build system analogous to make. If you want a remotely peaceful life, just lay out the project the way Microsoft expect you to and let it do its thing. You can invoke Roslyn "csc" from the command line, but some of the more obscure bits of tooling (e.g. building "self contained" executables) are only implemented as .dlls that are msbuild Task objects. Msbuild isn't too bad once you've got over the culture shock that (a) everything is XML rather than tab-delimited makefiles and (b) you've got a glossary of terms that maps to things you know https://learn.microsoft.com/en-us/visualstudio/msbuild/msbuild-concepts?view=vs-2022 https://learn.microsoft.com/en-us/visualstudio/msbuild/msbui... and (c) you get the binary log viewer https://msbuildlog.com/ https://msbuildlog.com/, which is a fantastic tool that gives you total observability of every detail of the build process. Of which there is a lot. Separate thought: .net AOT is still very much experimental. All sorts of things are silently incompatible with it, either in the "refuses to build" or the "crash at runtime" way. I'd only use it for small projects built from the ground up to be AOT.
- jmillikin 3y agoI'm satisfied with Bazel as a build system, and some of its functionality that is important to me (sandboxed remote execution) works best when it's not used to delegate to recursively nested sub-builds. To the extent I wish anything different about Bazel, it's to move even further away from integrated language-specific logic[0]. The idea of trying to learn some new ecosystem-specific build tool (in this case msbuild) is unappealing. Done that too many times, and I'm tired of fighting with tools written for/by people who think projects are written in a single language. [0] I hear Buck2 is a step in this direction.
- iainmerrick 3y agoI’ve been burned by using the original Buck in an app which is now a pain to maintain. It looks like Bazel is the best path for me to get free of it. I looked briefly at Buck2 but it doesn’t work yet (for my purposes, at least). Facebook have copied the old Google cliche, “you can use the old version that’s deprecated, or the new version that doesn’t work yet”. Edit to add: a million times yes to your general point! Language-specific build systems are bad.
- rubenv 3y agoFellow Banshee contributor, ex-F-spot maintainer here. Those were good days.
- sandyarmstrong 3y agoYup, there was so much excitement around the free desktop as a promising future for computing. I miss it! And I always appreciated your contributions. Now we all use unbelievably locked-down mobile devices, nobody even knows what files and folders are, and fewer kids than ever are able to learn about how computers work by messing around under the hood. Other amazing things are happening, but it's sad what gets lost.
- rubenv 3y ago100% this! Hope you're doing well Sandy, been a long time.
- tempodox 3y agoI compile F# to a stand-alone executable that runs on a Linux box without any additional .NET installs: dotnet publish /property:PublishSingleFile=true --configuration=Release --self-contained=true
- neonsunset 3y agoSelf-contained single-file assemblies do not require this. Simply specify corresponding properties in .csproj or as arguments to dotnet publish (-p:PublishSingleFile=true --self-contained -r {RID}) and for pure C# projects you will get exactly a single executable file that does not require any dependencies to run except what is expected to have on the average target system (glibc, openssl, etc.). Other than that, it seems that due to other languages, there is misconception that Native AOT is a panacea that is superior in all areas to normal single-file executables that use JIT. In fact, on average, JIT produces faster code because some optimizations are only possible with tiered and dynamic PGO compilation.
- andygocke 3y agoYou're right -- there are a lot of tradeoffs here, and one solution isn't necessarily better. What Native AOT offers is very small binaries, with very fast startup, and smaller working set. It can also be integrated as a standalone static/shared library into a different application. Self-contained apps don't require AOT compatibility and may have higher peak throughput (as you said, JITing can be faster in some cases). It's up to you and your application whether the trade-offs make sense.
- andygocke 3y ago> I couldn't find any compile-to-obj command with flags and such, instead there's a `dotnet` command that takes its input via a monolithic `.csproj` XML file and spits out a fully-formed binary FYI I'm the lead of the Native AOT team at MSFT. The key thing about .NET AOT is that it compiles the whole framework together, so right now the `dotnet` command is the top-level tool that connects all the pieces together. If you wanted to integrate with other native code, you can use `dotnet` to output a static library, which could then be linked with the rest of your native code. You're right that this can be complicated to integrate with other build tooling. It's something we can work on, but there's a bit of inherent friction around the .NET side, where it needs to find all the appropriate libraries, NuGet packages, etc, and a make/bazel world. Definitely something we can work on as we go forward.