ProjectScaffold
ProjectScaffold copied to clipboard
Create walk-through covering typical use cases
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.
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"