ProjectScaffold icon indicating copy to clipboard operation
ProjectScaffold copied to clipboard

Create walk-through covering typical use cases

Open pblasucci opened this issue 10 years ago • 1 comments

Given all the new features that've been added, there are several requests in the community for a better description of how to use Scaffold. Ideally, this should be done using Scaffold itself (i.e. generated content).However it maybe less confusing (and reach a wider audience) to put it in a blog or wiki somewhere.

pblasucci avatar Oct 10 '14 22:10 pblasucci

Indeed this is something I am struggling with specifically:

  • How to convert an existing solution to the scaffold structure
  • How to use the scaffold structure, i.e. tweak the build script to handle the scenario in which the solution contains multiple packages.

For example I did something like this:

// --------------------------------------------------------------------------------------
// FAKE build script
// --------------------------------------------------------------------------------------

#r @"packages/FAKE/tools/FakeLib.dll"
open Fake
open Fake.Git
open Fake.AssemblyInfoFile
open Fake.ReleaseNotesHelper
open System
open System.IO
#if MONO
#else
#load "packages/SourceLink.Fake/tools/Fake.fsx"
open SourceLink
#endif


// --------------------------------------------------------------------------------------
// START TODO: Provide project-specific details below
// --------------------------------------------------------------------------------------

// Information about the project are used
//  - for version and project name in generated AssemblyInfo file
//  - by the generated NuGet package
//  - to run tests and to publish documentation on GitHub gh-pages
//  - for documentation, you also need to edit info in "docs/tools/generate.fsx"

// List of packages
// (used by attributes in AssemblyInfo, name of a NuGet package and directory in 'src')
// Tuple contains: package name, main project, short description
let packages = [
        ("Informedica.Utils", "Informedica.Utils", "Utilities framework")
        ("Informedica.Logging", "Informedica.Logging", "Logging framework")
        ("Informedica.Direct", "Informedica.Direct.Mvc", "MVC direct methods")
        ("Informedica.Sql", "Informedica.Sql", "Fsharp Sql framework")
        ("Informedica.RavenDb", "Informedica.RavenDb", "Fsharp RavenDb wrapper")
        ("Informedica.Units", "Informedica.Units", "Units framework")
        ("Informedica.Railway", "Informedica.Railway", "Railway framework")
        ("Informedica.Services", "Informedica.Services", "Services framework")
        ("Informedica.Core", "Informedica.Core", "Core types and functions")
        ("Informedica.Settings", "Informedica.Settings.Service", "Get, Add, and Delete Settings")
        ("Informedica.GenImport", "Informedica.GenImport.Service", "Import G-Standard")
        ("Informedica.GenForm", "Informedica.GenForm.Service", "Medical Drug Decision Support")
        ("Informedica.PDMS.MV", "Informedica.PDMS.MV.v545", "PDMS interface with MetaVision")
        ("Informedica.PDMS.Demo", "Informedica.PDMS.Demo", "Demo PDMS")
        ("Informedica.GenPDMS", "Informedica.GenPDMS.Service", "Interface with PDMS")
        ("Informedica.GenPres", "Informedica.GenPres.Service", "Create Prescriptions")
        ("Informedica.GenPresGUI", "Informedica.GenPresGUI.Web", "Graphical user interfaces")
    ]

let source = __SOURCE_DIRECTORY__
Environment.CurrentDirectory <- source

[<Literal>]
let release_notes = "RELEASE_NOTES.md"


let getProjects package = [
    let ps =
        !! ("./src/**/" + package + ".*.??proj")
        ++ ("./src/**/" + package + ".??proj")
        |> Seq.map (fun p -> p) 
    yield! ps ]


let getReleaseNotes package =
    match !! ("./src/" + package + "/**/" + release_notes) 
          ++ ("./src/" + package + ".*/**/" + release_notes)
          |> Seq.toList with
    | [notes] -> LoadReleaseNotes notes |> Some
    | [] -> None
    | _  -> "Too many notes" |> failwith

// Test script to check whether each package has a release notes md
//for pack, _, _ in packages do
//    printfn "Package: %s" pack
//    getReleaseNotes pack |> printfn "%A"

// Test script to get all the main projects for each package
for _, proj , _ in packages do
    !! ("./src/" + proj + "/" + proj + ".??proj")
    |> Seq.iter (fun f -> printfn "Project: %s, File: %s" proj f)

// List of author names (for NuGet package)
let authors = [ "halcwb" ]

// Tags for your project (for NuGet package)
let tags = ""

// File system information 
let solutionFile  = "GenPres.sln"

// Pattern specifying assemblies to be tested using NUnit
let testAssemblies = "tests/**/bin/Release/*Tests*.dll"

// Git configuration (used for publishing documentation in gh-pages branch)
// The profile where the project is posted
let gitOwner = "halcwb" 
let gitHome = "https://github.com/" + gitOwner

// The name of the project on GitHub
let gitName = "GenPres"

// The url for the raw files hosted
let gitRaw = environVarOrDefault "gitRaw" "https://raw.github.com/halcwb/GenPres"

// --------------------------------------------------------------------------------------
// END TODO: The rest of the file includes standard build steps
// --------------------------------------------------------------------------------------


// Helper active pattern for project types
let (|Fsproj|Csproj|Vbproj|) (projFileName:string) = 
    match projFileName with
    | f when f.EndsWith("fsproj") -> Fsproj
    | f when f.EndsWith("csproj") -> Csproj
    | f when f.EndsWith("vbproj") -> Vbproj
    | _                           -> failwith (sprintf "Project file %s not supported. Unknown project type." projFileName)

// Generate assembly info files with the right version & up-to-date information
Target "AssemblyInfo" (fun _ ->

    for package, _, summary in packages do

        let getAssemblyInfoAttributes projectName =
            let release = 
                match getReleaseNotes package with
                | Some n -> n
                | None -> 
                    sprintf "Could not get release notes for package %s" package
                    |> failwith

            [ Attribute.Title (projectName)
              Attribute.Product package
              Attribute.Description summary
              Attribute.Version release.AssemblyVersion
              Attribute.FileVersion release.AssemblyVersion ]

        let getProjectDetails projectPath =
            let projectName = System.IO.Path.GetFileNameWithoutExtension(projectPath)
            ( projectPath, 
              projectName,
              System.IO.Path.GetDirectoryName(projectPath),
              (getAssemblyInfoAttributes projectName)
            )

        package
        |> getProjects
        |> Seq.map getProjectDetails
        |> Seq.iter (fun (projFileName, projectName, folderName, attributes) ->
                match projFileName with
                | Fsproj -> CreateFSharpAssemblyInfo (folderName @@ "AssemblyInfo.fs") attributes
                | Csproj -> CreateCSharpAssemblyInfo ((folderName @@ "Properties") @@ "AssemblyInfo.cs") attributes
                | Vbproj -> CreateVisualBasicAssemblyInfo ((folderName @@ "My Project") @@ "AssemblyInfo.vb") attributes
        )
    )

// Copies binaries from default VS location to exepcted bin folder
// But keeps a subdirectory structure for each project in the 
// src folder to support multiple project outputs
Target "CopyBinaries" (fun _ ->
    !! "src/**/*.??proj"
    |>  Seq.map (fun f ->
        let fromDir =
            let dir = (System.IO.Path.GetDirectoryName f) @@ "bin/Release"
            if System.IO.Directory.Exists(dir) then dir
            else (System.IO.Path.GetDirectoryName f) @@ "bin"
        let toDir =  "bin" @@ (System.IO.Path.GetFileNameWithoutExtension f)
        fromDir, toDir)
    |>  Seq.iter (fun (fromDir, toDir) -> CopyDir toDir fromDir (fun _ -> true))
)

// --------------------------------------------------------------------------------------
// Clean build results

Target "Clean" (fun _ ->
    CleanDirs ["bin"; "temp"]
)

Target "CleanDocs" (fun _ ->
    CleanDirs ["docs/output"]
)

// --------------------------------------------------------------------------------------
// Build library & test project
MSBuildDefaults <- { MSBuildDefaults with Verbosity = Some MSBuildVerbosity.Quiet }
Target "Build" (fun _ ->
    for _, proj, _ in packages do
        !! ("./src/" + proj + "/" + proj + ".??proj")
        |> MSBuildReleaseExt "" [("RestorePackages", "False")] "Build"
        |> ignore
)

// --------------------------------------------------------------------------------------
// Run the unit tests using test runner
Target "RunTests" (fun _ ->
    !! testAssemblies
    |> NUnit (fun p ->
        { p with
            DisableShadowCopy = true
            TimeOut = TimeSpan.FromMinutes 20.
            OutputFile = "TestResults.xml" }
    )
)

#if MONO
#else
// --------------------------------------------------------------------------------------
// SourceLink allows Source Indexing on the PDB generated by the compiler, this allows
// the ability to step through the source code of external libraries https://github.com/ctaggart/SourceLink
Target "SourceLink" (fun _ ->

    for package, _ , _ in packages do

        let projects = package |> getProjects

        for project in projects do

            let baseUrl = sprintf "%s/%s/{0}/%%var2%%" gitRaw (project.ToLower())
            use repo = new GitRepo(__SOURCE_DIRECTORY__)

            let addAssemblyInfo (projFileName:String) = 
                match projFileName with
                | Fsproj -> (projFileName, "**/AssemblyInfo.fs")
                | Csproj -> (projFileName, "**/AssemblyInfo.cs")
                | Vbproj -> (projFileName, "**/AssemblyInfo.vb")

            !! "src/**/*.??proj"
            |> Seq.map addAssemblyInfo
            |> Seq.iter (fun (projFile, assemblyInfo) ->
                let proj = VsProj.LoadRelease projFile 
                logfn "source linking %s" proj.OutputFilePdb
                let files = proj.Compiles -- assemblyInfo
                repo.VerifyChecksums files
                proj.VerifyPdbChecksums files
                proj.CreateSrcSrv baseUrl repo.Revision (repo.Paths files)
                Pdbstr.exec proj.OutputFilePdb proj.OutputFilePdbSrcSrv
            )
)

#endif

// --------------------------------------------------------------------------------------
// Build a NuGet package

Target "NuGet" (fun _ -> ()
//    Paket.Pack(fun p -> 
//        { p with
//            OutputPath = "bin"
//            Version = release.NugetVersion
//            ReleaseNotes = toLines release.Notes})
)

Target "PublishNuget" (fun _ -> ()
//    Paket.Push(fun p -> 
//        { p with
//            WorkingDir = "bin" })
)

// --------------------------------------------------------------------------------------
// Generate the documentation

Target "GenerateReferenceDocs" (fun _ ->
    if not <| executeFSIWithArgs "docs/tools" "generate.fsx" ["--define:RELEASE"; "--define:REFERENCE"] [] then
      failwith "generating reference documentation failed"
)

let generateHelp' fail debug =
    let args =
        if debug then ["--define:HELP"]
        else ["--define:RELEASE"; "--define:HELP"]
    if executeFSIWithArgs "docs/tools" "generate.fsx" args [] then
        traceImportant "Help generated"
    else
        if fail then
            failwith "generating help documentation failed"
        else
            traceImportant "generating help documentation failed"

let generateHelp fail =
    generateHelp' fail false

Target "GenerateHelp" (fun _ ->
    DeleteFile "docs/content/release-notes.md"
    CopyFile "docs/content/" "RELEASE_NOTES.md"
    Rename "docs/content/release-notes.md" "docs/content/RELEASE_NOTES.md"

    DeleteFile "docs/content/license.md"
    CopyFile "docs/content/" "LICENSE.txt"
    Rename "docs/content/license.md" "docs/content/LICENSE.txt"

    generateHelp true
)

Target "GenerateHelpDebug" (fun _ ->
    DeleteFile "docs/content/release-notes.md"
    CopyFile "docs/content/" "RELEASE_NOTES.md"
    Rename "docs/content/release-notes.md" "docs/content/RELEASE_NOTES.md"

    DeleteFile "docs/content/license.md"
    CopyFile "docs/content/" "LICENSE.txt"
    Rename "docs/content/license.md" "docs/content/LICENSE.txt"

    generateHelp' true true
)

Target "KeepRunning" (fun _ ->    
    use watcher = new FileSystemWatcher(DirectoryInfo("docs/content").FullName,"*.*")
    watcher.EnableRaisingEvents <- true
    watcher.Changed.Add(fun e -> generateHelp false)
    watcher.Created.Add(fun e -> generateHelp false)
    watcher.Renamed.Add(fun e -> generateHelp false)
    watcher.Deleted.Add(fun e -> generateHelp false)

    traceImportant "Waiting for help edits. Press any key to stop."

    System.Console.ReadKey() |> ignore

    watcher.EnableRaisingEvents <- false
    watcher.Dispose()
)

Target "GenerateDocs" DoNothing

let createIndexFsx lang =
    let content = """(*** hide ***)
// This block of code is omitted in the generated HTML documentation. Use 
// it to define helpers that you do not want to show in the documentation.
#I "../../../bin"

(**
F# Project Scaffold ({0})
=========================
*)
"""
    let targetDir = "docs/content" @@ lang
    let targetFile = targetDir @@ "index.fsx"
    ensureDirectory targetDir
    System.IO.File.WriteAllText(targetFile, System.String.Format(content, lang))


Target "AddLangDocs" (fun _ ->
    let args = System.Environment.GetCommandLineArgs()
    if args.Length < 4 then
        failwith "Language not specified."

    args.[3..]
    |> Seq.iter (fun lang ->
        if lang.Length <> 2 && lang.Length <> 3 then
            failwithf "Language must be 2 or 3 characters (ex. 'de', 'fr', 'ja', 'gsw', etc.): %s" lang

        let templateFileName = "template.cshtml"
        let templateDir = "docs/tools/templates"
        let langTemplateDir = templateDir @@ lang
        let langTemplateFileName = langTemplateDir @@ templateFileName

        if System.IO.File.Exists(langTemplateFileName) then
            failwithf "Documents for specified language '%s' have already been added." lang

        ensureDirectory langTemplateDir
        Copy langTemplateDir [ templateDir @@ templateFileName ]

        createIndexFsx lang)
)

// --------------------------------------------------------------------------------------
// Release Scripts

Target "ReleaseDocs" (fun _ ->
    let tempDocsDir = "temp/gh-pages"
    let release = release_notes |> LoadReleaseNotes

    CleanDir tempDocsDir
    Repository.cloneSingleBranch "" (gitHome + "/" + gitName + ".git") "gh-pages" tempDocsDir

    CopyRecursive "docs/output" tempDocsDir true |> tracefn "%A"
    StageAll tempDocsDir
    Git.Commit.Commit tempDocsDir (sprintf "Update generated documentation for version %s" release.AssemblyVersion)
    Branches.push tempDocsDir
)

#load "paket-files/fsharp/FAKE/modules/Octokit/Octokit.fsx"
open Octokit

Target "Release" (fun _ ->
    let release = release_notes |> LoadReleaseNotes

    StageAll ""
    Git.Commit.Commit "" (sprintf "Bump version to %s" release.NugetVersion)
    Branches.push ""

    Branches.tag "" release.NugetVersion
    Branches.pushTag "" "origin" release.NugetVersion

    // release on github
    createClient (getBuildParamOrDefault "github-user" "") (getBuildParamOrDefault "github-pw" "")
    |> createDraft gitOwner gitName release.NugetVersion (release.SemVer.PreRelease <> None) release.Notes 
    // TODO: |> uploadFile "PATH_TO_FILE"    
    |> releaseDraft
    |> Async.RunSynchronously
)

Target "BuildPackage" DoNothing

// --------------------------------------------------------------------------------------
// Run all targets by default. Invoke 'build <Target>' to override

Target "All" DoNothing

"Clean"
  ==> "AssemblyInfo"
  ==> "Build"
  ==> "CopyBinaries"
  ==> "RunTests"
  =?> ("GenerateReferenceDocs",isLocalBuild)
  =?> ("GenerateDocs",isLocalBuild)
  ==> "All"
  =?> ("ReleaseDocs",isLocalBuild)

"All" 
#if MONO
#else
  =?> ("SourceLink", Pdbstr.tryFind().IsSome )
#endif
  ==> "NuGet"
  ==> "BuildPackage"

"CleanDocs"
  ==> "GenerateHelp"
  ==> "GenerateReferenceDocs"
  ==> "GenerateDocs"

"CleanDocs"
  ==> "GenerateHelpDebug"

"GenerateHelp"
  ==> "KeepRunning"

"ReleaseDocs"
  ==> "Release"

"BuildPackage"
  ==> "PublishNuget"
  ==> "Release"

RunTargetOrDefault "All"

halcwb avatar Apr 21 '15 09:04 halcwb