FAKE - Start process in new window - f#

I am trying to launch an application in a new console window from a FAKE script.
In normal .NET code, the method System.Diagnostics.Process.Start can be used to do this.
I've also done this in the past with CAKE, like this:
Task("run-myapp")
.Does(() => {
var info = new ProcessStartInfo {
FileName = "dotnet",
Arguments = "run myapp.fsproj",
WorkingDirectory = "C:\\path\\to\\my\\app\\"
};
Process.Start(info);
});
In FAKE, I have tried the same thing, but this starts a new background process and outputs to the console window where I run FAKE. I then later need to use Task Manager to kill that process.
Target.create "run-myapp" (fun _ ->
let psi = ProcessStartInfo()
psi.FileName <- "dotnet"
psi.Arguments <- "run myapp.fsproj"
psi.WorkingDirectory <- "C:\\path\\to\\my\\app\\"
Process.Start psi |> ignore
)
I have also tried explicitly setting ProcessStartInfo.CreateNoWindow <- false (although false is the default) and this does not change anything.
Is there a way to do this in FAKE?

I found the answer in the issue on this closed GitHub issue: https://github.com/dotnet/corefx/issues/21767#issuecomment-312328630
The reason is the default value of UseShellExecute is false in .NET Framework, but true in .NET Core. CAKE is in Framework and FAKE 5 is in Core.
Solution:
Target.create "run-myapp" (fun _ ->
let psi = ProcessStartInfo()
psi.FileName <- "dotnet"
psi.Arguments <- "run myapp.fsproj"
psi.WorkingDirectory <- "C:\\path\\to\\my\\app\\"
psi.UseShellExecute <- true
Process.Start psi |> ignore
)

Related

not understanding the Suave API, always the same result is returned

Here is a test:
open System
open System.Threading
open Newtonsoft.Json
open Suave
open Suave.Logging
open Suave.Operators
open Suave.Filters
open Suave.Writers
let private configuration = {
defaultConfig with
bindings = [ HttpBinding.createSimple HTTP "0.0.0.0" 80 ]
}
let private getServerTime () : WebPart =
DateTime.UtcNow |> JsonConvert.SerializeObject |> Successful.OK >=> setMimeType "application/json"
let private webApplication =
choose
[
GET >=> choose
[
path "/servertime" >=> getServerTime ()
]
]
let start () =
let listening, server = startWebServerAsync configuration webApplication
server |> Async.Start
listening |> Async.RunSynchronously |> ignore
[<EntryPoint>]
let main _ =
start()
Thread.Sleep(Timeout.Infinite)
0
with this example, the getServerTime function is called once and this is it, every subsequent call to the endpoint will return the original result.
I don't understand why? When I use pathScan with parameters, then the function is called each time, as expected, but in this case, with a simple get, only the first call is done while this is defined as a function.
But I don't understand the doc at all either (from the flow of the doc, its contents and the overall document structure...), so the answer is probably simple :)
First of all, I would highly recommend that you study monadic composition. This is a necessary foundation for understanding these things. It will give you an idea of what >=> and >>= are and how to deal with them.
As for the problem at hand: yes, you defined getServerTime as a function, but that kind of doesn't matter, because that function is only called once, during construction of the webApplication value.
The structure of the server is such that it's literally a function HttpContext -> Async<HttpContext option>. It gets a request context and returns a modified version of it. And all of those combinators - choose and >=> and so on - they work with such functions.
The expression path "/servertime" is also such function. Literally. You can call it like this:
let httpCtx = ...
let newCtxAsync = path "/servertime" httpCtx
Moreover, the expression getServerTime() is ALSO such function. So you can do:
let httpCtx = ...
let newCtxAsync = getServerTime () httpCtx
That's what the WebPart type is. It's an async function from context to new context.
Now, what the >=> operator does is combine these functions. Makes them pipe the context from one webpart to the next. That's all.
When you wrote your getServerTime function, you created a WebPart that always returns the same thing. It's kind of like this:
let f x y = printf "x = %d" x
let g = f 42
Here, g is a function (just like a WebPart is a function), but whenever it's called, it will always return "x = 42". Why? Because I partially applied that parameter, it's sort of "baked in" in the definition of g now. The same way the current time is "baked in" in the WebPart that you have created inside getServerTime.
If you want a different time to be returned every time, you need to recreate the WebPart every time. Construct a new WebPart on every call, one with that call's time baked in. The smallest change to do that is probably this:
let private getServerTime () : WebPart =
let time : WebPart = fun ctx -> (DateTime.UtcNow |> string |> Successful.OK) ctx
time >=> setMimeType "text/plain"
On the surface it may look like the definition of time is silly: after all, let f x = g x can always be replaced by let f = g, right? Well, not always. Only as long as g is pure. But your webpart here is not: it depends on the current time.
This way, every time the time webpart is "run" (which means it gets a context as a parameter), it will run DateTime.UtcNow, then pass it to Successful.OK, and then pass the context to the resulting function.

F# / FAKE - How to specify `--platform:x64` when invoking MSBuild

I am very new to FAKE. I use the following code (based on the official FAKE example) to build some F# project:
Target.create "BuildApp" (fun _ ->
[ p.buildTarget ]
|> MSBuild.runRelease id buildDir "Build"
|> Trace.logItems "AppBuild-Output: "
)
Everything is great except that it builds a 32-bit application, while I explicitly need a 64-bit one (with <gcAllowVeryLargeObjects enabled="true" />, of course) as otherwise it would not make a dent. This answer: FAKE: How to define MSBuild properties? seems to reference FAKE 4 but it looks that I have FAKE 5.
How can I tell FAKE 5 to build a 64-bit application? Thanks a lot!
You can set properties by supplying a setParams function other than id to MSBuild.runRelease:
Target.create "BuildApp" (fun _ ->
[ p.buildTarget ]
|> MSBuild.runRelease (fun p ->
{ p with Properties = [ "platform", "x64" ] } ) buildDir "Build"
|> Trace.logItems "AppBuild-Output: "

Can't get OpenCover to work in fake

EDITED to show the ignore return as pointed out by Fyodor and the resulting error
I have a .fsx file with several targets that work as expected, but I can't get a target for OpenCover to work. This is what I have for the Target code:
Target "Coverage" (fun _ ->
OpenCover
(fun p -> { p with ExePath = "./packages/OpenCover.4.6.519/tools/OpenCover.Console.exe"
TestRunnerExePath = "./packages/Machine.Specifications.Runner.Console.0.10.0-Unstable0005/tools/mspec-clr4.exe"
Output = reportDir + "MspecOutput.xml"
Register = "-register:user"
}
)
testDir ## "FakeTest2UnitTesting.dll" + "--xml " + reportDir + "MspecOutput.xml" |> ignore
)
But I now get the following build error:
build.fsx(45,3): error FS0039: The value or constructor 'OpenCover' is not defined. Maybe you want one of the following:
OpenCoverHelper
NCover
I don't know what I am doing wrong. Can someone show me how to use the OpenCoverHelper from the FAKE API?
Thanks
After a lot of playing around an googling, I finally came up with the solution. The basic problem was that I didn't open the OpenCoverHelper. I made the assumption that it was included in FAKE as it is in the Api and there was no documentation saying anything else. So, here is the code I use:
// include Fake lib
#r #"packages/FAKE.4.61.2/tools/FakeLib.dll"
open Fake
open Fake.OpenCoverHelper
Target "Coverage" (fun _ ->
OpenCover (fun p -> { p with
ExePath = "./packages/OpenCover.4.6.519/tools/OpenCover.Console.exe"
TestRunnerExePath = "./packages/Machine.Specifications.Runner.Console.0.10.0-Unstable0005/tools/mspec-clr4.exe"
Output = "./report/MspecOutput.xml"
Register = RegisterUser
})
"./test/FakeTest2UnitTesting.dll + --xml ./report/MspecOutput.xml"
)
Hopefully this will help someone in the future.

Error Installing a Windows Service (in F#)

my question is the following:
When I try to install my Windows Service I get the following error:
snippet:
...
No public installers with the RunInstallerAttribute.Yes attribute could be found in the <path to exe> assembly.
...
I follow this tutorial
I have one Program.fs file containing:
[<RunInstaller(true)>]
type public FSharpServiceInstaller() =
inherit Installer()
do
< some logic, doesn't really matter >
This should be sufficient, as a matter of fact, I don't even think I need to add the public keyword to the type definition. Installing this executable with InstallUtil.exe gives me the same error as installing it using the following code:
[<EntryPoint>]
let main args =
if Environment.UserInteractive then
let parameter = String.Concat(args);
match parameter with
| "-i" -> ManagedInstallerClass.InstallHelper [| Assembly.GetExecutingAssembly().Location |]
| "-u" -> ManagedInstallerClass.InstallHelper [| "/u"; Assembly.GetExecutingAssembly().Location |]
| _ -> printf "Not allowed!\n"
else
ServiceBase.Run [| new CreditToolsService() :> ServiceBase |];
0
I have tried running this script in PowerShell, cmd and Visual Studio CLI as both administrator and my normal account but I keep getting the same error. If anyone knows what I'm doing wrong I would really appreciate some help.
OK, so here goes...
I've looked at the code provided by user1758475 and just randomly started copy pasting solutions into an application. Don Symes's solution "just worked" and I finally figured out why: I did not (and he does) have a namespace declaration, in my source. Seems like this was the culprit! After I added the namespace the installer worked like a charm.
As Curt Nichols pointed out, the installer should not be in a module because a module effectively hides the type from the calling code.
Thank you for help in figuring this out.
For those of you who want to see a working example:
namespace FileWatcher
open System
open System.Reflection
open System.ComponentModel
open System.Configuration.Install
open System.ServiceProcess
open System.IO
open System.Configuration
type FileWatcherService() =
inherit ServiceBase(ServiceName = "FileWatcher")
let createEvent = fun (args: FileSystemEventArgs) ->
printf "%s has been %s\n" args.FullPath (args.ChangeType.ToString().ToLower())
|> ignore
override x.OnStart(args) =
let fsw = new FileSystemWatcher ()
fsw.Path <- "C:\TEMP"
fsw.NotifyFilter <- NotifyFilters.LastAccess ||| NotifyFilters.LastWrite ||| NotifyFilters.FileName ||| NotifyFilters.DirectoryName ||| NotifyFilters.CreationTime
fsw.Filter <- "*.txt"
fsw.EnableRaisingEvents <- true
fsw.IncludeSubdirectories <- true
fsw.Created.Add(createEvent)
override x.OnStop() =
printf "Stopping the FileWatcher service"
[<RunInstaller(true)>]
type public FSharpServiceInstaller() =
inherit Installer()
do
// Specify properties of the hosting process
new ServiceProcessInstaller
(Account = ServiceAccount.LocalSystem)
|> base.Installers.Add |> ignore
// Specify properties of the service running inside the process
new ServiceInstaller
( DisplayName = "AAA FileWatcher Service",
ServiceName = "AAAFileWatcherService",
StartType = ServiceStartMode.Automatic )
|> base.Installers.Add |> ignore
module Program =
[<EntryPoint>]
let main args =
printf "starting the application...\n"
if Environment.UserInteractive then
let parameter = String.Concat(args);
match parameter with
| "-i" -> ManagedInstallerClass.InstallHelper [| Assembly.GetExecutingAssembly().Location |]
| "-u" -> ManagedInstallerClass.InstallHelper [| "/u"; Assembly.GetExecutingAssembly().Location |]
| _ -> printf "Not allowed!\n"
else
ServiceBase.Run [| new FileWatcherService() :> ServiceBase |];
0
Working live production example at https://github.com/zbilbo/TB4TG/blob/master/TourneyBot.Service/Installer.fs
Think it needs to be installed with InstallUtil.exe though.
Possibly not the finest moment in coding, but that specific service code is from Don Syme more or less: http://blogs.msdn.com/b/dsyme/archive/2011/05/31/a-simple-windows-service-template-for-f.aspx, so it is probably fine, but the rest of the "surrounding" code on that repository may not be idiomatic ;-)
Don Symes blog also explains a lot more so it should be easily to adept it to your needs. It also links to a Win Service Template on VS Gallery: http://blogs.msdn.com/b/mcsuksoldev/archive/2011/05/31/f-windows-application-template-for-windows-service.aspx

How to call/invoke fsc, the F# compiler, from FAKE?

How do I tell FAKE to compile a .fs file using fsc?
Bonus points for explaining how to also pass arguments like -a and -target:dll.
EDIT: I should clarify that I'm trying to do this without having an MSBuild/xbuild/.sln file. In other words, I want FAKE to fully take the place of MSBuild/xbuild.
FAKE has tasks to directly invoke the F# compiler. Usually you can use the Fsc task. If you want a target to compile an F# source file MyFile.fs to MyFile.exe, you can do:
Target "MyFile.exe" (fun _ ->
["MyFile.fs"]
|> Fsc (fun ps -> ps))
The Fsc task lets you compile multiple source files and specify every F# compile parameter. This simple example doesn't do that, but you can. To read up on the details, head over to the tutorial.
I recommend you read Ian Battersby's page on FAKE make on this topic
Code Excerpt:
first:
#!/bin/bash
TARGET=$1
BUILDTARGETS=$2
if [ -z "$BUILDTARGETS" ]
then
BUILDTARGETS="/Library/Frameworks/Mono.framework/Libraries/mono/xbuild/Microsoft/VisualStudio/v9.0"
fi
if [ -z "$TARGET" ]
then
CTARGET="Default"
else
CTARGET=`echo ${TARGET:0:1} | tr "[:lower:]" "[:upper:]"``echo ${TARGET:1} | tr "[:upper:]" "[:lower:]"`
fi
if [ ! -d "$BUILDTARGETS" ]
then
echo "BuildTargets directory '${BUILDTARGETS}' does not exist."
exit $?
else
export BUILDTARGETS="$BUILDTARGETS"
fi
echo "Executing command: $CTARGET"
mono packages/FAKE.1.64.6/tools/Fake.exe build.fsx target=$CTARGET
then
#I #"packages/FAKE.1.64.6/tools"
#r "FakeLib.dll"
open Fake
let buildDir = #"./build/"
let testDir = #"./test"
let fxReferences = !! #"*/*.csproj"
let testReferences = !! #"Tests/**/*.csproj"
let buildTargets = environVarOrDefault "BUILDTARGETS" ""
Target "Clean" (fun _ ->
CleanDirs [buildDir; testDir]
)
Target "Build" (fun _ ->
MSBuild buildDir "Build" ["Configuration","Debug"; "VSToolsPath",buildTargets] fxReferences
|> Log "Build-Output: "
)
Target "BuildTest" (fun _ ->
MSBuildRelease testDir "Build" testReferences
|> Log "Test-Output: "
)
Target "Test" (fun _ ->
!! (testDir + #"/*.Tests.dll")
|> xUnit (fun p ->
{ p with
ShadowCopy = true;
HtmlOutput = true;
XmlOutput = true;
OutputDir = testDir })
)
"Clean"
==> "Build"
"Build"
==> "BuildTest"
Target "Default" DoNothing
RunParameterTargetOrDefault "target" "Default"
Here's a possibly-useful -- though admittedly small -- twist on Yawar's answer above, which might make it fit in a bit better with the other FAKE examples you're likely to encounter.
The Fsc helper function wants a string list of filenames, which is fine as far as it goes. But most examples use the !! operator to find files, which results in FileIncludes which happens not to be a string list. You can convert a FileIncludes to a string list suitable for feeding to Fsc with Seq.toList.
Just to be thorough, I also convert things to relative pathnames (possibly just a personal quirk).
So here's an example of searching for all *.fs files and compiling them with some representative compiler options:
Target "BuildApp" (fun _ -> // Compile application source code
!! (srcApp ## #"**/*.fs") // Look for F# source files
|> Seq.map toRelativePath // Pathnames relative to current directory
|> Seq.toList // Convert FileIncludes to string list
|> Fsc (fun p -> // which is what the Fsc task wants
{p with // Fsc parameters: making an executable,
FscTarget = Exe // for any CPU, directing output to build
Platform = AnyCpu // area (both assembly & XML doc).
Output = ...exe file... // Executable generated
OtherParams = ["--doc:" + ...xmldoc file...) ]})
) //
As Yawar pointed out, there are a boatload of other compiler options covered in the tutorial.

Resources