From c1e21ed1c747ea00602db966220fbafb33b811fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Garai=20M=C3=A1rton?= Date: Wed, 15 Sep 2021 15:16:08 +0200 Subject: [PATCH 1/3] Add Making Booster cross-platform blog entry --- assets/outputs.png | Bin 0 -> 22935 bytes .../20210915-making-booster-cross-platform.md | 99 ++++++++++++++++++ 2 files changed, 99 insertions(+) create mode 100644 assets/outputs.png create mode 100644 user/mgarai/20210915-making-booster-cross-platform.md diff --git a/assets/outputs.png b/assets/outputs.png new file mode 100644 index 0000000000000000000000000000000000000000..0cc25b90daa78775a4de3c0e1ffb66563bb1e082 GIT binary patch literal 22935 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 INgMk9f0N0dL;wH) literal 0 HcmV?d00001 diff --git a/user/mgarai/20210915-making-booster-cross-platform.md b/user/mgarai/20210915-making-booster-cross-platform.md new file mode 100644 index 0000000..bf11053 --- /dev/null +++ b/user/mgarai/20210915-making-booster-cross-platform.md @@ -0,0 +1,99 @@ +--- +title: "Making Booster compilation service cross-platform" +categories: "websharper,fsharp,compilation,cross-platform,linux" +abstract: "Change a console application used as a service to support cross-platform. RIDs. What they are good for. Executables across platforms. Detached child process. NamedPipe in linux. IO behavior when using nohup." +identity: "-1,-1" +--- + +## Intro + +Make use of experience from making WebSharper Booster console application (used as service) cross-platform. From Windows only to Linux compatible. + +## RIDs + +RIDs ([Runtime Identifier][runtime-identifier]) are specifiers for compiler to output operating system, version, architecture, "distro kind" specific code. + +That is more information that can be inferred from `RuntimeInformation.IsOSPlatform(OSPlatform.Windows)` or `RuntimeInformation.IsOSPlatform(OSPlatform.Linux)`. For further information check [Platform compatibility analyzer][platform-compatibility-analyzer]. For example it's a valid RID to use `linux-musl-x64`, but the `musl` part is not really version specific. It's not easy to infer it from runtime. + +## Why specify RID? + +Deploying the application itself doesn't need to be RID qualified ([.NET application publishing overview][dotnet-application-publishing-overview]). Simple `dotnet publish` produces the right `.dll` (cross-platform) and an executable, which is current platform specific. + +> Publishing an app as framework-dependent produces a cross-platform binary as a dll file, and a platform-specific executable that targets your current platform + +When cross-platform executable is needed, than the `dotnet publish` command needs a `-r` flag. Which is the RID. The `` tag in the project file specifies RID. + +If the executable expected to be of the right name: + +Windows specific +```shell +wsfscservice.exe +``` + +Linux specific +```bash +./wsfscservice +``` + +I need to specify RID. This helps finding the running service from the `Task Manager` or `ps`. + +Alternative would be using the cross-platform `.dll` with `dotnet wsfscservice.dll` but this makes hard to find the running process through all running `dotnet` instances. [Rename the running process is not possible][rename-the-running-process-is-not-possible] or require `start "wsfscservice" dotnet wsfscservice.dll` "hack" which is Windows specific. Also finding the process programatically is simpler: + +```fsharp +let runningServers = + try + Process.GetProcessesByName("wsfscservice") + |> Array.filter (fun x -> System.String.Equals(x.MainModule.FileName, fileNameOfService, System.StringComparison.OrdinalIgnoreCase)) + with + | e -> + // If the processes cannot be queried, because of insufficient rights, the Mutex in service will handle + // not running 2 instances + nLogger.Error(e, "Could not read running processes of wsfscservice.") + [||] + +``` + +Attach/reattach debugger is [easier][reattach-debugger]. + +So the choice is [framework-dependent][framework-dependent] but non `--self-contained`. `--self-contained` would make the executable bigger, and makes harder to patch the runtime around the executable. + +## `` vs `` + +Expecting that using `` instead of `` (mind the plural) changes the output structure of published nuget is wrong. When used correctly the RID specific output should be scattered around in `Debug`/`Release` folder (inside for example `win-x64`, `linux-x64`, `linux-musl-x64`). But `dotnet publish` doesn't publish for all different RIDs specified in the ``. Instead uses directly the `Debug`/`Release` folder. Just like the `dotnet build` command doesn't. + +![](/assets/outputs.png) + +For that to work we needed RIDs count `publish` command for all executables. Libraries doesn't need extra care. In our build process it looks like this (Fake specific. `DotNet.publish` calls `dotnet publish`) + +```fsharp +let publishExe (mode: BuildMode) fw input output = + for rid in [ "win-x64"; "linux-x64"; "linux-musl-x64" ] do + let outputPath = + __SOURCE_DIRECTORY__ "build" mode.ToString() output fw rid "deploy" + DotNet.publish (fun p -> + { p with + Framework = Some fw + OutputPath = Some outputPath + NoRestore = true + SelfContained = false |> Some // add --self-contained false + Runtime = rid |> Some // add -r from the list above. + Configuration = DotNet.BuildConfiguration.fromString (mode.ToString()) + }) input +... +BuildAction.Custom <| fun mode -> + publishExe mode "net5.0" "src/compiler/WebSharper.FSharp/WebSharper.FSharp.fsproj" "FSharp" + publishExe mode "net5.0" "src/compiler/WebSharper.FSharp.Service/WebSharper.FSharp.Service.fsproj" "FSharp" + publishExe mode "net5.0" "src/compiler/WebSharper.CSharp/WebSharper.CSharp.fsproj" "CSharp" +``` + +This also can be achieved by simple shell script, which iterates on the RIDs specified in the ``. If the singular `-r` parameter is not from the listed `` RIDs from the project file the `dotnet publish` will fail! + +It was a surprise that `--self-contained`'s default value is variable. If you specify RID, it's `true` if you just leave `dotnet publish` it's `false`. So to avoid [Publish self-contained][publish-self-contained], `--self-contained false` had to be added. + +[publish-self-contained]: +[framework-dependent]: +[reattach-debugger]: +[rename-the-running-process-is-not-possible]: +[dotnet-application-publishing-overview]: +[platform-compatibility-analyzer]: +[runtime-identifier]: \ No newline at end of file From 563a5a054d3bd41b7d2e894411687f136562d5c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Garai=20M=C3=A1rton?= Date: Wed, 15 Sep 2021 18:31:36 +0200 Subject: [PATCH 2/3] Finishing Making Booster Linux compatible blog --- .../20210915-making-booster-cross-platform.md | 116 ++++++++++++++++-- 1 file changed, 109 insertions(+), 7 deletions(-) diff --git a/user/mgarai/20210915-making-booster-cross-platform.md b/user/mgarai/20210915-making-booster-cross-platform.md index bf11053..b758ce4 100644 --- a/user/mgarai/20210915-making-booster-cross-platform.md +++ b/user/mgarai/20210915-making-booster-cross-platform.md @@ -1,7 +1,7 @@ --- title: "Making Booster compilation service cross-platform" categories: "websharper,fsharp,compilation,cross-platform,linux" -abstract: "Change a console application used as a service to support cross-platform. RIDs. What they are good for. Executables across platforms. Detached child process. NamedPipe in linux. IO behavior when using nohup." +abstract: "Change a console application used as a service to support cross-platform. RIDs. What they are good for. Executables across platforms. Detached child process. NamedPipe in Linux. IO behavior when using nohup." identity: "-1,-1" --- @@ -15,11 +15,11 @@ RIDs ([Runtime Identifier][runtime-identifier]) are specifiers for compiler to o That is more information that can be inferred from `RuntimeInformation.IsOSPlatform(OSPlatform.Windows)` or `RuntimeInformation.IsOSPlatform(OSPlatform.Linux)`. For further information check [Platform compatibility analyzer][platform-compatibility-analyzer]. For example it's a valid RID to use `linux-musl-x64`, but the `musl` part is not really version specific. It's not easy to infer it from runtime. -## Why specify RID? +### Why specify RID? Deploying the application itself doesn't need to be RID qualified ([.NET application publishing overview][dotnet-application-publishing-overview]). Simple `dotnet publish` produces the right `.dll` (cross-platform) and an executable, which is current platform specific. -> Publishing an app as framework-dependent produces a cross-platform binary as a dll file, and a platform-specific executable that targets your current platform +> Publishing an app as framework-dependent produces a cross-platform binary as a `.dll` file, and a platform-specific executable that targets your current platform When cross-platform executable is needed, than the `dotnet publish` command needs a `-r` flag. Which is the RID. The `` tag in the project file specifies RID. @@ -37,7 +37,30 @@ Linux specific I need to specify RID. This helps finding the running service from the `Task Manager` or `ps`. -Alternative would be using the cross-platform `.dll` with `dotnet wsfscservice.dll` but this makes hard to find the running process through all running `dotnet` instances. [Rename the running process is not possible][rename-the-running-process-is-not-possible] or require `start "wsfscservice" dotnet wsfscservice.dll` "hack" which is Windows specific. Also finding the process programatically is simpler: +### Current platform's RID? + +As mentioned before the RID have more information coded in itself than `RuntimeInformation` namespace gives us. If the execution of the application dispatched through `.targets` file definition, we can use the RID that's available there for .NET Core SDK Runtime Identifier. I found that variable while checking through SDK's code reverse engineering how `_UsingDefaultPlatformTarget` is set. For `_UsingDefaultPlatformTarget` = `true` something is used as [default RID][default-runtime-id]. + +```xml +$(NETCoreSdkRuntimeIdentifier) +``` + +Some [information][netcoresdkruntimeidentifier-information] about the `MSBuild` variable: + +> The NETCoreSdkRuntimeIdentifier MSBuild property determines the bitness of *.comhost.dll + +This is a good default value to find out the folder where platform specific `.dll` is located: + +```xml + +$(MSBuildThisFileDirectory)/../tools/net5.0/$(NETCoreSdkRuntimeIdentifier)/ +``` + +In the two cases tested `$(NETCoreSdkRuntimeIdentifier)` is substituted for `win-x64` and `linux-x64`. + +### Keeping the process's name across different platforms + +Alternative would be using the cross-platform `.dll` with `dotnet wsfscservice.dll` but this makes hard to find the running process through all running `dotnet` instances. [Rename the running process is not possible][rename-the-running-process-is-not-possible] or require `start "wsfscservice" dotnet wsfscservice.dll` "hack" which is Windows specific. Also finding the process programmatically is simpler: ```fsharp let runningServers = @@ -50,14 +73,13 @@ let runningServers = // not running 2 instances nLogger.Error(e, "Could not read running processes of wsfscservice.") [||] - ``` Attach/reattach debugger is [easier][reattach-debugger]. So the choice is [framework-dependent][framework-dependent] but non `--self-contained`. `--self-contained` would make the executable bigger, and makes harder to patch the runtime around the executable. -## `` vs `` +### `` vs `` Expecting that using `` instead of `` (mind the plural) changes the output structure of published nuget is wrong. When used correctly the RID specific output should be scattered around in `Debug`/`Release` folder (inside for example `win-x64`, `linux-x64`, `linux-musl-x64`). But `dotnet publish` doesn't publish for all different RIDs specified in the ``. Instead uses directly the `Debug`/`Release` folder. Just like the `dotnet build` command doesn't. @@ -88,12 +110,92 @@ BuildAction.Custom <| fun mode -> This also can be achieved by simple shell script, which iterates on the RIDs specified in the ``. If the singular `-r` parameter is not from the listed `` RIDs from the project file the `dotnet publish` will fail! +Important tags to the project file to be added: + +```xml +Exe +net5.0 +wsfscservice +wsfscservice +win-x64;linux-x64;linux-musl-x64 +``` + +### Self contained + It was a surprise that `--self-contained`'s default value is variable. If you specify RID, it's `true` if you just leave `dotnet publish` it's `false`. So to avoid [Publish self-contained][publish-self-contained], `--self-contained false` had to be added. + +## Running detached child processes + +When spinning off an executable the process will become the child process of the main executable. That means however someone starts a process to be background it will be closing with the parent executable. Our Console application can't function as a service. + +Starting the executable can be: + +```fsharp +// start a detached wsfscservice.exe. Platform specific. +let cmdName = if System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.Windows) then + "wsfscservice.exe" else "wsfscservice" +let cmdFullPath = (location, cmdName) |> System.IO.Path.Combine +let startInfo = ProcessStartInfo(cmdFullPath) +startInfo.CreateNoWindow <- true +startInfo.UseShellExecute <- false +startInfo.WindowStyle <- ProcessWindowStyle.Hidden +proc <- Process.Start(startInfo) +``` + +But it will be still child process. The solution is to start the process operation system specifically. Create a `.cmd` for Windows using `start` with `/b` parameter to be [background process][windows-background]. + +```shell +@echo off + +start /d %~dp0 /b wsfscservice.exe +``` + +And a `.sh` for Linux. Here [`nohup`][nohup] makes `wsfscservice` immune to hangups and `&` at the end makes it [background process][linux-background]. + +> If a command is terminated by the control operator &, the shell executes the command in the background in a subshell. The shell does not wait for the command to finish, and the return status is 0 + +```bash +#!/usr/bin/env bash + +nohup "$(dirname "$BASH_SOURCE")/wsfscservice" >/dev/null 2>&1 & +``` + +`Process.Start` can start the platform specific `.cmd`/`.sh`. No matter it returned immediately with `0` code it's sure that the background process is running. `Task Manager` shows `wsfscservice` and `ps -al` show `wsfscservice`. "Start new process, without being a child of the spawning process" question on `Stackoverflow.com` top voted answer also does [something similar][so-use-stub-exe]. + +```fsharp +let cmdName = if System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.Windows) then + "wsfscservice_start.cmd" else "wsfscservice_start.sh" +``` + +## Caveat around `nohup` + +The service processes commands in an infinite loop. This can be achieved by startup a mailbox processor with `Async.Start`, then on the main thread block the execution. At first blocking the main thread was done by `Console.ReadLine() |> ignore`. Avoiding busy wait. But `nohup`'s IO piping makes this line of code just continue. + +Another possible solution for non-busy wait is using: + +```fsharp +use locker = new AutoResetEvent(false) +locker.WaitOne() |> ignore +``` + +That solved that a code working on Windows now keeps the service running on Linux as well. + +## NamedPipes on Linux + +One thing it's worth mentioning is `NamedPipe`'s `PipeTransmissionMode.Message` is [not supported][not-supported] in the Linux runtime. Had to reimplement the `IsMessageComplete` behavior on `PipeTransmissionMode.Byte` either by pre-sending bytes count (how much byte will a full message be independent of buffer size), or sending separator byte sequence between messages. + +[not-supported]: +[so-use-stub-exe]: +[linux-background]: +[nohup]: +[windows-background]: +[netcoresdkruntimeidentifier-information]: +[default-runtime-id]: [publish-self-contained]: [framework-dependent]: [reattach-debugger]: [rename-the-running-process-is-not-possible]: [dotnet-application-publishing-overview]: [platform-compatibility-analyzer]: -[runtime-identifier]: \ No newline at end of file +[runtime-identifier]: From 8a9f9832a0a9b98706a097d9736c44de90bbc3f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Garai=20M=C3=A1rton?= Date: Tue, 21 Sep 2021 15:25:27 +0200 Subject: [PATCH 3/3] Add gitignore. Add Writing a dotnet tool blog post --- .gitignore | 1 + assets/store-folder.png | Bin 0 -> 4732 bytes user/mgarai/20210921-writing-a-dotnet-tool.md | 181 ++++++++++++++++++ 3 files changed, 182 insertions(+) create mode 100644 .gitignore create mode 100644 assets/store-folder.png create mode 100644 user/mgarai/20210921-writing-a-dotnet-tool.md diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b25c15b --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*~ diff --git a/assets/store-folder.png b/assets/store-folder.png new file mode 100644 index 0000000000000000000000000000000000000000..bdf182fc7e06cd5492db9e48b2fb0bfdd64211ee GIT binary patch literal 4732 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 WQ|6+nf3f};0p=#w##NVY-2D&ShWe!d literal 0 HcmV?d00001 diff --git a/user/mgarai/20210921-writing-a-dotnet-tool.md b/user/mgarai/20210921-writing-a-dotnet-tool.md new file mode 100644 index 0000000..a7c70a8 --- /dev/null +++ b/user/mgarai/20210921-writing-a-dotnet-tool.md @@ -0,0 +1,181 @@ +--- +title: "Writing a dotnet tool" +categories: "dotnet,tool,cli,fsharp" +abstract: "Writing a dotnet tool with help of the dotnet CLI" +identity: "-1,-1" +--- + +## Intro + +Writing a dotnet tool from scratch have a lot of support from the dotnet CLI. With the following simple steps you can have a tool from the dotnet infrastructure. + +## Steps + +Create the console application for the tool: +```ps1 +> dotnet new console -lang F# -n +> cd +``` + +For the `tool-name` check the next section + +(Optional) Add a solution file for the project: +```ps1 +> dotnet new sln +> dotnet sln add .fsproj +``` + +Check build +```ps1 +> dotnet build +``` + +Edit Program.fs. A dotnet tool is basically a console application, so implement what you like. + +Add necessary variables for the `.fsproj`. You should see something like this: +```xml + + + + Exe + net5.0 + + 3390;$(WarnOn) + + true + + + + + + + + +``` + +Add `tool-manifest`: +```ps1 +> dotnet new tool-manifest +``` + +Edit `.config\dotnet-tools.json` (the tool manifest). Specify the version and available commands like this: +```json +{ + "version": 1, + "isRoot": true, + "tools": { + "": { + "version": "1.0.0", + "commands": [ ] + } + } +} +``` + +Run pack +```ps1 +> dotnet pack +> cd bin/Debug +``` + +Install the new tool globally. Here the `tool-name` is not the `.nupkg` file name. It's just the `` given at `dotnet new -n `. +```ps1 +> dotnet tool install -g --add-source . +``` + +Verify installation: +```ps1 +> cd %USERPROFILE%\.dotnet\tools +or +> cd $HOME/.dotnet/tools +--- +> dir +or +> ls +``` + +You have a dotnet tool installed. + +There is also a [tutorial][tutorial] for that in the `Microsoft` documentation. + +## Name of the executable. \ + +Before just creating the console application with the come up name for the tool, consider if `dotnet-` prefix should be added or not. + +It's important to understand if you issue command +```ps1 +> dotnet xy +``` +in the dotnet ecosystem it's searching for a tool named `xy` prefixed by `dotnet-` and runs it. If anywhere in your `PATH` variable's directories there is a `dotnet-xy` it will run it. It's just a nice to have to install them where `dotnet install` puts `dotnet-xy`. + +The installation is versioned. At the installation destination there will be subdirectories for the installed versions like that: + +![](/assets/store-folder.png) + +The important question is the usage of the tool. For `dotnet xy` go with `dotnet-xy`. For `xy` go with `xy`. + +Choosing between the 2 is first step, so the main directory at `dotnet new console -n xy` can be in par with the final executable's name. + +For more information go to [this tutorial][custom-location]. + +### ToolCommandName + +The `` variable in the `.csproj`/`.fsproj` file doesn't set the executable's name when installed into tools. `` does. `dotnet pack` will use `` which goes into +`%USERPROFILE%\.dotnet\tools` or `$HOME/.dotnet/tools` or where `--tool-path` points to in `dotnet tool install --tool-path ...`. All should be in the `PATH` environment variable (be careful, `--tool-path` is not by default). + +## FAQ +**Question:** I changed the code, but no change is visible in the installed tool. Why? + +**Answer:** Have you repacked the changes? +```ps1 +> dotnet pack +> cd bin/Debug +> dotnet tool install -g --add-source . +``` + +Have you changed version, and forgot to bump it accidentally? +```ps1 +> dotnet tool install -g --add-source . --version 1.0.0 +instead of +> dotnet tool install -g --add-source . --version 1.1.0 +``` + +### +**Q:** I have the following error: +``` +error NU1101: Unable to find package . No packages exist with this id in source(s): dotnet-websharper-GitHub, fsc feed, Microsoft Visual Studio Offline Packages, nuget.org +The tool package could not be restored. +Tool '' failed to install. This failure may have been caused by: + +* You are attempting to install a preview release and did not use the --version option to specify the version. +* A package by this name was found, but it was not a .NET tool. +* The required NuGet feed cannot be accessed, perhaps because of an Internet connection problem. +* You mistyped the name of the tool. + +For more reasons, including package naming enforcement, visit https://aka.ms/failure-installing-tool +``` +How can I solve it? + +**A:** This can mean anything. Usually the problem is missing `--add-source `, or using the file name instead of `[PACKAGE_NAME]`. +```ps1 +> dotnet tool install -g --version 1.0.0 .1.0.0.nupkg +instead of +> dotnet tool install -g --add-source . --version 1.0.0 +``` +### +**Q:** Can't find the executable in `%USERPROFILE%\.dotnet\tools`. Why? + +**A:** Have you provided `-g` flag? +```ps1 +> dotnet tool install -g ... +``` +### +**Q:** `dotnet tool list` doesn't list what I have provided in the tools manifest. Why? + +**A:** `dotnet tool list` also differentiate between global and local tools. Have you added `-g` flag for your installed global tool? +```ps1 +> dotnet tool list -g +``` + +[tutorial]: +[custom-location]: