8
\$\begingroup\$

I have something like a generic build script for a group of projects. I am using Fake but my F# skills are pretty bad. Would you please take a look and point where I can improve it?

ORIGINAL

#I @"./bin/tools/FAKE/tools/"
#r @"./bin/tools/FAKE/tools/FakeLib.dll"
open System
open System.IO
open Fake
open Fake.Git
open Fake.FSharpFormatting
open Fake.AssemblyInfoFile
open Fake.ReleaseNotesHelper
type System.String with member x.contains (comp:System.StringComparison) str = x.IndexOf(str,comp) >= 0
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// BEGIN EDIT
let appName = getBuildParamOrDefault "appName" ""
let appType = getBuildParamOrDefault "appType" ""
let appSummary = getBuildParamOrDefault "appSummary" ""
let appDescription = getBuildParamOrDefault "appDescription" ""
let appAuthors = ["YourNameHere";]
// END EDIT
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
let buildDir = @"./bin/Release" @@ appName
let releaseNotes = @"./src/" @@ appName @@ @"RELEASE_NOTES.md"
let release = LoadReleaseNotes releaseNotes
let nuget = environVar "NUGET"
let nugetOutDir = "./bin/nuget" @@ appName
Target "Clean" (fun _ -> CleanDirs [buildDir; nugetOutDir;])
Target "RestorePackages" (fun _ ->
 let packagesDir = @"./src/packages"
 !! "./**/packages.config"
 |> Seq.iter (RestorePackage (fun p ->
 { p with
 ToolPath = nuget
 OutputPath = packagesDir }))
)
Target "Build" (fun _ ->
 let appProjectFile = match appType with
 | t when t = "msi" -> @"./src/" @@ appName + ".sln"
 | _ -> @"./src/" @@ appName @@ appName + ".csproj"
 !! appProjectFile
 |> MSBuildRelease buildDir "Build"
 |> Log "Build-Output: "
)
Target "CreateNuGet" (fun _ ->
 let packages = [appName, appType]
 for appName,appType in packages do
 let nugetOutArtifactsDir = nugetOutDir @@ "Artifacts"
 CleanDir nugetOutArtifactsDir
 // Exclude libraries which are part of the packages.config file only when nuget package is created.
 let nugetPackagesFile = "./src/" @@ appName @@ "packages.config"
 let nugetDependenciesFlat =
 match fileExists nugetPackagesFile with
 | t when t = true -> getDependencies nugetPackagesFile |> List.unzip |> fun(A,B) -> A
 | _ -> ["CodeContracts"]
 let excludePaths (pathsToExclude : string list) (path: string) = pathsToExclude |> List.exists (path.contains StringComparison.OrdinalIgnoreCase)|> not
 let exclude = excludePaths (nugetDependenciesFlat @ ["CodeContracts"])
 // Copy the build artifacts to the nuget pick dir
 match appType with
 | t when t = "web" -> CopyDir nugetOutArtifactsDir (buildDir @@ "_PublishedWebsites" @@ appName) allFiles
 | t when t = "nuget" -> CopyDir nugetOutDir buildDir exclude
 | _ -> CopyDir nugetOutArtifactsDir buildDir allFiles
 // Copy the deployment files if any to the nuget pick dir.
 let depl = @".\src\" @@ appName @@ @".\deployment\"
 if TestDir depl then XCopy depl nugetOutDir
 let nuspecFile = appName + ".nuspec"
 let nugetAccessKey =
 match appType with
 | t when t = "nuget" -> getBuildParamOrDefault "nugetkey" ""
 | _ -> ""
 let nugetDoPublish = nugetAccessKey.Equals "" |> not
 let nugetPublishUrl = getBuildParamOrDefault "nugetserver" "https://nuget.org"
 // Create/Publish the nuget package
 NuGet (fun app ->
 {app with
 NoPackageAnalysis = true
 Authors = appAuthors
 Project = appName
 Description = appDescription
 Version = release.NugetVersion
 Summary = appSummary
 ReleaseNotes = release.Notes |> toLines
 AccessKey = nugetAccessKey
 Publish = nugetDoPublish
 PublishUrl = nugetPublishUrl
 ToolPath = nuget
 OutputPath = nugetOutDir
 WorkingDir = nugetOutDir
 }) nuspecFile
)
"Clean"
 ==> "RestorePackages"
 ==> "Build"
 ==> "CreateNuGet"
RunParameterTargetOrDefault "target" "Build"

IMPROVED

#I @"./bin/tools/FAKE/tools/"
#r @"./bin/tools/FAKE/tools/FakeLib.dll"
open System
open System.IO
open Fake
open Fake.Git
open Fake.FSharpFormatting
open Fake.AssemblyInfoFile
open Fake.ReleaseNotesHelper
type System.String with member x.contains (comp:System.StringComparison) str = x.IndexOf(str,comp) >= 0
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// BEGIN EDIT
let appName = getBuildParamOrDefault "appName" ""
let appType = getBuildParamOrDefault "appType" ""
let appSummary = getBuildParamOrDefault "appSummary" ""
let appDescription = getBuildParamOrDefault "appDescription" ""
let appAuthors = ["YourNameHere";]
// END EDIT
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
let buildDir = @"./bin/Release" @@ appName
let releaseNotes = @"./src/" @@ appName @@ @"RELEASE_NOTES.md"
let release = LoadReleaseNotes releaseNotes
let nuget = environVar "NUGET"
let nugetOutDir = "./bin/nuget" @@ appName
Target "Clean" (fun _ -> CleanDirs [buildDir; nugetOutDir;])
Target "RestorePackages" (fun _ ->
 let packagesDir = @"./src/packages"
 !! "./**/packages.config"
 |> Seq.iter (RestorePackage (fun p ->
 { p with
 ToolPath = nuget
 OutputPath = packagesDir }))
)
Target "Build" (fun _ ->
 let appProjectFile = match appType with
 | "msi" -> @"./src/" @@ appName + ".sln"
 | _ -> @"./src/" @@ appName @@ appName + ".csproj"
 !! appProjectFile
 |> MSBuildRelease buildDir "Build"
 |> Log "Build-Output: "
)
Target "CreateNuGet" (fun _ ->
 let packages = [appName, appType]
 for appName,appType in packages do
 let nugetOutArtifactsDir = nugetOutDir @@ "Artifacts"
 CleanDir nugetOutArtifactsDir
 // Exclude libraries which are part of the packages.config file only when nuget package is created.
 let nugetPackagesFile = "./src/" @@ appName @@ "packages.config"
 let nugetDependenciesFlat =
 match fileExists nugetPackagesFile with
 | true -> getDependencies nugetPackagesFile |> List.unzip |> fst
 | _ -> []
 let excludePaths (pathsToExclude : string list) (path: string) = pathsToExclude |> List.exists (path.contains StringComparison.OrdinalIgnoreCase) |> not
 let exclude = excludePaths ("CodeContracts" :: nugetDependenciesFlat)
 // Copy the build artifacts to the nuget pick dir
 match appType with
 | "web" -> CopyDir nugetOutArtifactsDir (buildDir @@ "_PublishedWebsites" @@ appName) allFiles
 | "nuget" -> CopyDir nugetOutDir buildDir exclude
 | _ -> CopyDir nugetOutArtifactsDir buildDir allFiles
 // Copy the deployment files if any to the nuget pick dir.
 let depl = @".\src\" @@ appName @@ @".\deployment\"
 if TestDir depl then XCopy depl nugetOutDir
 let nuspecFile = appName + ".nuspec"
 let nugetAccessKey =
 match appType with
 | "nuget" -> getBuildParamOrDefault "nugetkey" ""
 | _ -> ""
 let nugetDoPublish = nugetAccessKey.Equals "" |> not
 let nugetPublishUrl = getBuildParamOrDefault "nugetserver" "https://nuget.org"
 // Create/Publish the nuget package
 NuGet (fun app ->
 {app with
 NoPackageAnalysis = true
 Authors = appAuthors
 Project = appName
 Description = appDescription
 Version = release.NugetVersion
 Summary = appSummary
 ReleaseNotes = release.Notes |> toLines
 AccessKey = nugetAccessKey
 Publish = nugetDoPublish
 PublishUrl = nugetPublishUrl
 ToolPath = nuget
 OutputPath = nugetOutDir
 WorkingDir = nugetOutDir
 }) nuspecFile
)
"Clean"
 ==> "RestorePackages"
 ==> "Build"
 ==> "CreateNuGet"
RunParameterTargetOrDefault "target" "Build"
asked Feb 24, 2015 at 22:37
\$\endgroup\$

1 Answer 1

6
\$\begingroup\$

All of the match expressions can be simplified. For example,

match fileExists nugetPackagesFile with
| t when t = true -> ...
| _ -> ...

Can be

match fileExists nugetPackagesFile with
| true -> ...
| _ -> ...

Similarly,

 match appType with
 | t when t = "web" -> ...
 | t when t = "nuget" -> ...
 | _ -> ...

Can be

 match appType with
 | "web" -> ...
 | "nuget" -> ...
 | _ -> ...

In the first example above, fun(A,B) -> A can be written as fst.


It looks like nugetDependenciesFlat is only used by exclude, where you're already appending "CodeContracts". So you can change the second match in nugetDependenciesFlat from _ -> ["CodeContracts"] to _ -> [].

While you're at it, change nugetDependenciesFlat @ ["CodeContracts"] to "CodeContracts" :: nugetDependenciesFlat. The reason being, prepending an element to a list is a constant-time operation, while appending will take time proportional to to the length of nugetDependenciesFlat. It won't make a noticeable difference since I assume nugetDependenciesFlat will be very short, but it's a good practice to get into.

answered Feb 24, 2015 at 22:54
\$\endgroup\$
8
  • \$\begingroup\$ Thank you. I did not know about this. Would you please help me with the fst? \$\endgroup\$ Commented Feb 25, 2015 at 9:21
  • \$\begingroup\$ @mynkow Sure, so instead of getDependencies nugetPackagesFile |> List.unzip |> fun(A,B) -> A you can write getDependencies nugetPackagesFile |> List.unzip |> fst. Documentation is here. \$\endgroup\$ Commented Feb 25, 2015 at 9:31
  • \$\begingroup\$ Got it. Is there something for the second element? \$\endgroup\$ Commented Feb 25, 2015 at 9:33
  • \$\begingroup\$ @mynkow Yep, it's called snd. \$\endgroup\$ Commented Feb 25, 2015 at 9:37
  • \$\begingroup\$ you rock. If you go to "// Exclude libraries which are part of the..." The idea there is to have "CodeContracts" directory excluded always and if packages.config exists to exclude files described there. I do not like this piece of code especially "| _ -> ["CodeContracts"]" just to make the compile happy. Any ideas how to make it better? \$\endgroup\$ Commented Feb 25, 2015 at 9:42

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.