Skip to content

Instantly share code, notes, and snippets.

@drvink
Created April 20, 2015 16:51
Show Gist options
  • Save drvink/37782838e3611b5b7a83 to your computer and use it in GitHub Desktop.
Save drvink/37782838e3611b5b7a83 to your computer and use it in GitHub Desktop.
F# inline MSBuild tasks via FSharp.Compiler.CodeDom
namespace Example
open System
open System.IO
open System.Runtime.InteropServices
open System.Text
open Microsoft.Build.Framework
open Microsoft.Build.Utilities
module internal Externs =
[<DllImport("shlwapi.dll",
EntryPoint = "AssocQueryString",
CharSet = CharSet.Auto)>]
extern int AssocQueryString(int flags,
int str,
string pszAssoc,
string pszExtra,
[<Out>] StringBuilder pszOut,
[<In; Out>] int& pcchOut)
type FindExecutable() =
inherit Task()
[<Literal>]
static let ASSOCF_INIT_BYEXENAME = 0x00000002
[<Literal>]
static let ASSOCF_NOTRUNCATE = 0x00000020
[<Literal>]
static let ASSOCSTR_EXECUTABLE = 2
static let windir =
System.Environment.GetFolderPath(System.Environment.SpecialFolder.Windows)
static let sysdir = System.Environment.SystemDirectory
static let find_in_paths name paths =
let f p =
let full_path = Path.Combine(p, name)
File.Exists(full_path)
match Array.tryFind f paths with
| None -> failwith "file not found"
| Some x -> x
static let find_executable name paths =
if File.Exists(name) then Path.GetFullPath(name) else
let path = Path.Combine(windir, name)
if File.Exists(path) then Path.GetFullPath(path) else
let path = Path.Combine(sysdir, name)
if File.Exists(path) then Path.GetFullPath(path) else
let res =
try
let envpaths =
let ep = System.Environment.GetEnvironmentVariable("PATH")
if ep <> null then ep else ""
if String.Empty <> envpaths
then find_in_paths name (envpaths.Split(';'))
else ""
with
| _ -> ""
if String.Empty <> res then res else
let extrapaths = if paths <> null
then paths
else [| TaskItem() :> ITaskItem |]
if 0 <> extrapaths.Length then
let strpaths = extrapaths |> Array.map (fun l -> l.ItemSpec)
find_in_paths name strpaths
else failwith "file not found"
let mutable _name : string = null
let mutable _additional_paths : ITaskItem [] = null
let mutable _location : string = null
[<Required>]
member x.Name with get() = _name and set v = _name <- v
member x.AdditionalPaths with get () = _additional_paths
and set v = _additional_paths <- v
[<Output>]
member x.Location with get () = _location and private set v = _location <- v
override this.Execute() =
let res =
try
this.Location <- find_executable this.Name this.AdditionalPaths
Some true
with
| _ -> None
match res with
| Some x -> x
| None ->
let mutable sz = 0
let hr =
Externs.AssocQueryString(
ASSOCF_INIT_BYEXENAME ||| ASSOCF_NOTRUNCATE,
ASSOCSTR_EXECUTABLE,
this.Name,
null,
null,
&sz)
if 0 > hr then
this.Location <- ""
false
else
let sb = StringBuilder(sz + 1)
Externs.AssocQueryString(
ASSOCF_INIT_BYEXENAME ||| ASSOCF_NOTRUNCATE,
ASSOCSTR_EXECUTABLE,
this.Name,
null,
sb,
&sz) |> ignore
this.Location <- sb.ToString()
true
C:\Users\msjp_000\Documents\Visual Studio 2013\Projects\tmp>msbuild Test.proj
Microsoft (R) Build Engine version 12.0.31101.0
[Microsoft .NET Framework, version 4.0.30319.34209]
Copyright (C) Microsoft Corporation. All rights reserved.
Build started 2015/04/21 1:49:09.
Project "C:\Users\msjp_000\Documents\Visual Studio 2013\Projects\tmp\Test.proj" on node 1 (default targets).
Build:
C:\Windows\notepad.exe
Done Building Project "C:\Users\msjp_000\Documents\Visual Studio 2013\Projects\tmp\Test.proj" (default targets) .
Build succeeded.
0 Warning(s)
0 Error(s)
Time Elapsed 00:00:00.68
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<UsingTask TaskName="Example.FindExecutable"
TaskFactory="CodeTaskFactory"
AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v12.0.dll">
<ParameterGroup>
<Name ParameterType="System.String" Required="true" />
<AdditionalPaths ParameterType="Microsoft.Build.Framework.ITaskItem[]" />
<Location ParameterType="System.String" Output="true" />
</ParameterGroup>
<Task>
<Code Type="Class" Language="f#" Source="$(MSBuildThisFileDirectory)FindExecutable.fs" />
</Task>
</UsingTask>
<Target Name="Build">
<Example.FindExecutable Name="notepad.exe" ContinueOnError="WarnAndContinue">
<Output PropertyName="NotepadPath" TaskParameter="Location" />
</Example.FindExecutable>
<Message Text="$(NotepadPath)" Importance="High" />
</Target>
</Project>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment