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"
\$\endgroup\$
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.

\$\endgroup\$
  • \$\begingroup\$ Thank you. I did not know about this. Would you please help me with the fst? \$\endgroup\$ – mynkow Feb 25 '15 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\$ – mjolka Feb 25 '15 at 9:31
  • \$\begingroup\$ Got it. Is there something for the second element? \$\endgroup\$ – mynkow Feb 25 '15 at 9:33
  • \$\begingroup\$ @mynkow Yep, it's called snd. \$\endgroup\$ – mjolka Feb 25 '15 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\$ – mynkow Feb 25 '15 at 9:42

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.