diff --git a/src/app/FakeLib/AssemblyInfo.fs b/src/app/Fake.Runtime/AssemblyInfo.fs similarity index 100% rename from src/app/FakeLib/AssemblyInfo.fs rename to src/app/Fake.Runtime/AssemblyInfo.fs diff --git a/src/app/Fake.Runtime/BuildServer.fs b/src/app/Fake.Runtime/BuildServer.fs new file mode 100644 index 00000000000..850ce97124e --- /dev/null +++ b/src/app/Fake.Runtime/BuildServer.fs @@ -0,0 +1,112 @@ +/// Contains functions which allow build scripts to interact with a build server. +module Fake.Runtime.BuildServer +open Fake.Runtime.Environment +open Fake.Runtime.String + +/// The server type option. +type BuildServer = + | TeamFoundation + | TeamCity + | CCNet + | Jenkins + | Travis + | AppVeyor + | GitLabCI + | Bamboo + | BitbucketPipelines + | LocalBuild + +/// The trace mode option. +type TraceMode = + | Console + | Xml + +/// Defines if FAKE will use verbose tracing. +/// This flag can be specified by setting the *verbose* build parameter. +let mutable verbose = hasBuildParam "verbose" + +/// A constant label for local builds +/// [omit] +let localBuildLabel = "LocalBuild" + +/// Defines the XML output file - used for build servers like CruiseControl.NET. +/// This output file can be specified by using the *logfile* build parameter. +let mutable xmlOutputFile = getBuildParamOrDefault "logfile" "./output/Results.xml" + +/// Build number retrieved from Bamboo +/// [omit] +let bambooBuildNumber = environVar "bamboo_buildNumber" + +/// Checks if we are on Bamboo +/// [omit] +let isBambooBuild = + isNotNullOrEmpty bambooBuildNumber + +/// Checks if we are on Team Foundation +/// [omit] +let isTFBuild = + let tfbuild = environVar "TF_BUILD" + tfbuild <> null && tfbuild.ToLowerInvariant() = "true" + +/// Build number retrieved from Team Foundation +/// [omit] +let tfBuildNumber = environVar "BUILD_BUILDNUMBER" + +/// Build number retrieved from TeamCity +/// [omit] +let tcBuildNumber = environVar "BUILD_NUMBER" + +/// Build number retrieved from Travis +/// [omit] +let travisBuildNumber = environVar "TRAVIS_BUILD_NUMBER" + +/// Checks if we are on GitLab CI +/// [omit] +let isGitlabCI = environVar "CI_SERVER_NAME" = "GitLab CI" + +/// Build number retrieved from GitLab CI +/// [omit] +let gitlabCIBuildNumber = if isGitlabCI then environVar "CI_BUILD_ID" else "" + +/// Build number retrieved from Jenkins +/// [omit] +let jenkinsBuildNumber = tcBuildNumber + +/// CruiseControl.NET Build label +/// [omit] +let ccBuildLabel = environVar "CCNETLABEL" + +/// AppVeyor build number +/// [omit] +let appVeyorBuildVersion = environVar "APPVEYOR_BUILD_VERSION" + +/// The current build server +let buildServer = + if hasBuildParam "JENKINS_HOME" then Jenkins + elif hasBuildParam "TEAMCITY_VERSION" then TeamCity + elif not (isNullOrEmpty ccBuildLabel) then CCNet + elif not (isNullOrEmpty travisBuildNumber) then Travis + elif not (isNullOrEmpty appVeyorBuildVersion) then AppVeyor + elif isGitlabCI then GitLabCI + elif isTFBuild then TeamFoundation + elif isBambooBuild then Bamboo + elif hasBuildParam "BITBUCKET_COMMIT" then BitbucketPipelines + else LocalBuild + +/// The current build version as detected from the current build server. +let buildVersion = + let getVersion = getBuildParamOrDefault "buildVersion" + match buildServer with + | Jenkins -> getVersion jenkinsBuildNumber + | TeamCity -> getVersion tcBuildNumber + | CCNet -> getVersion ccBuildLabel + | Travis -> getVersion travisBuildNumber + | AppVeyor -> getVersion appVeyorBuildVersion + | GitLabCI -> getVersion gitlabCIBuildNumber + | TeamFoundation -> getVersion tfBuildNumber + | Bamboo -> getVersion bambooBuildNumber + | LocalBuild -> getVersion localBuildLabel + | BitbucketPipelines -> getVersion "" + +/// Is true when the current build is a local build. +let isLocalBuild = LocalBuild = buildServer diff --git a/src/app/Fake.Runtime/Environment.fs b/src/app/Fake.Runtime/Environment.fs new file mode 100644 index 00000000000..4e2c3497d19 --- /dev/null +++ b/src/app/Fake.Runtime/Environment.fs @@ -0,0 +1,303 @@ +/// This module contains functions which allow to read and write environment variables and build parameters +module Fake.Runtime.Environment +type Environment = System.Environment + +open System +open System.IO +#if !CORE_CLR +open System.Configuration +#endif +open System.Diagnostics +open System.Collections.Generic +open System.Text +open System.Text.RegularExpressions +open Microsoft.Win32 + +/// Type alias for System.EnvironmentVariableTarget +#if !CORE_CLR +type EnvironTarget = EnvironmentVariableTarget +#endif + +/// Retrieves the environment variable with the given name +let environVar name = Environment.GetEnvironmentVariable name + +/// Combines two path strings using Path.Combine +let inline combinePaths path1 (path2 : string) = Path.Combine(path1, path2.TrimStart [| '\\'; '/' |]) +/// Combines two path strings using Path.Combine +let inline combinePathsNoTrim path1 path2 = Path.Combine(path1, path2) + +/// Combines two path strings using Path.Combine +let inline (@@) path1 path2 = combinePaths path1 path2 +/// Combines two path strings using Path.Combine +let inline () path1 path2 = combinePathsNoTrim path1 path2 + +// Normalizes path for different OS +let inline normalizePath (path : string) = + path.Replace('\\', Path.DirectorySeparatorChar).Replace('/', Path.DirectorySeparatorChar) + +let getCurrentDirectory () = + System.IO.Directory.GetCurrentDirectory() + +/// Retrieves all environment variables from the given target +#if !CORE_CLR +let environVars target = + let vars = Environment.GetEnvironmentVariables target +#else +let environVars () = + let vars = Environment.GetEnvironmentVariables () +#endif + [ for e in vars -> + let e1 = e :?> Collections.DictionaryEntry + e1.Key, e1.Value ] + +/// Sets the environment variable with the given name +let setEnvironVar name value = Environment.SetEnvironmentVariable(name, value) + +#if !CORE_CLR +/// Sets the environment variable with the given name for the current user. +let setUserEnvironVar name value = Environment.SetEnvironmentVariable(name, value, EnvironmentVariableTarget.User) + +/// Sets the environment variable with the given name for the current machine. +let setMachineEnvironVar name value = Environment.SetEnvironmentVariable(name, value, EnvironmentVariableTarget.Machine) +#endif + +/// Sets the environment variable with the given name for the current process. +#if !CORE_CLR +let setProcessEnvironVar name value = Environment.SetEnvironmentVariable(name, value, EnvironmentVariableTarget.Process) +#else +let setProcessEnvironVar name value = Environment.SetEnvironmentVariable(name, value) +#endif + +/// Clears the environment variable with the given name for the current process. +#if !CORE_CLR +let clearProcessEnvironVar name = Environment.SetEnvironmentVariable(name, null, EnvironmentVariableTarget.Process) +#else +let clearProcessEnvironVar name = Environment.SetEnvironmentVariable(name, null) +#endif + +/// Sets the build parameter with the given name for the current process. +let setBuildParam name value = setProcessEnvironVar name value + +/// Retrieves the environment variable with the given name or returns the default if no value was set +let environVarOrDefault name defaultValue = + let var = environVar name + if String.IsNullOrEmpty var then defaultValue + else var + +/// Retrieves the environment variable with the given name or fails if not found +let environVarOrFail name = + let var = environVar name + if String.IsNullOrEmpty var then failwith <| sprintf "Environment variable '%s' not found" name + else var + +/// Retrieves the environment variable with the given name or returns the default bool if no value was set +let getEnvironmentVarAsBoolOrDefault varName defaultValue= + try + (environVar varName).ToUpper() = "TRUE" + with + | _ -> defaultValue + +/// Retrieves the environment variable with the given name or returns the false if no value was set +let getEnvironmentVarAsBool varName = getEnvironmentVarAsBoolOrDefault varName false + +/// Retrieves the environment variable or None +let environVarOrNone name = + let var = environVar name + if String.IsNullOrEmpty var then None + else Some var + +/// Splits the entries of an environment variable and removes the empty ones. +let splitEnvironVar name = + let var = environVarOrNone name + if var = None then [ ] + else var.Value.Split([| Path.PathSeparator |]) |> Array.toList + +#if !CORE_CLR +/// Retrieves the application settings variable with the given name +let appSetting (name : string) = ConfigurationManager.AppSettings.[name] +#endif + +/// Returns if the build parameter with the given name was set +let inline hasBuildParam name = environVar name <> null + +/// Returns the value of the build parameter with the given name if it was set and otherwise the given default value +let inline getBuildParamOrDefault name defaultParam = + if hasBuildParam name then environVar name + else defaultParam + +/// Returns the value of the build parameter with the given name if it was set and otherwise an empty string +let inline getBuildParam name = getBuildParamOrDefault name String.Empty + +#if !CORE_CLR +/// The path of the "Program Files" folder - might be x64 on x64 machine +let ProgramFiles = Environment.GetFolderPath Environment.SpecialFolder.ProgramFiles +#endif + +/// The path of Program Files (x86) +/// It seems this covers all cases where PROCESSOR\_ARCHITECTURE may misreport and the case where the other variable +/// PROCESSOR\_ARCHITEW6432 can be null +let ProgramFilesX86 = + let wow64 = environVar "PROCESSOR_ARCHITEW6432" + let globalArch = environVar "PROCESSOR_ARCHITECTURE" + match wow64, globalArch with + | "AMD64", "AMD64" + | null, "AMD64" + | "x86", "AMD64" -> environVar "ProgramFiles(x86)" + | _ -> environVar "ProgramFiles" + |> fun detected -> if detected = null then @"C:\Program Files (x86)\" else detected + +/// The system root environment variable. Typically "C:\Windows" +let SystemRoot = environVar "SystemRoot" + +#if !CORE_CLR +/// Determines if the current system is an Unix system +let isUnix = Environment.OSVersion.Platform = PlatformID.Unix + +/// Determines if the current system is a MacOs system +let isMacOS = + (Environment.OSVersion.Platform = PlatformID.MacOSX) || + // Running on OSX with mono, Environment.OSVersion.Platform returns Unix + // rather than MacOSX, so check for osascript (the AppleScript + // interpreter). Checking for osascript for other platforms can cause a + // problem on Windows if the current-directory is on a mapped-drive + // pointed to a Mac's root partition; e.g., Parallels does this to give + // Windows virtual machines access to files on the host. + (Environment.OSVersion.Platform = PlatformID.Unix && (File.Exists "/usr/bin/osascript")) + +/// Determines if the current system is a Linux system +let isLinux = (not isMacOS) && (int System.Environment.OSVersion.Platform |> fun p -> (p = 4) || (p = 6) || (p = 128)) + +/// Determines if the current system is a mono system +/// Todo: Detect mono on windows +let isMono = isLinux || isUnix || isMacOS + +let monoPath = + if isMacOS && File.Exists "/Library/Frameworks/Mono.framework/Commands/mono" then + "/Library/Frameworks/Mono.framework/Commands/mono" + else + "mono" + +// Note: The same field is available in the old API, therefore we use a workaround to make the "new" api point to the old field. +let mutable internal monoArguments = ref "" + +let mutable internal monoArgumentsHelper = LegacyApiHelper.ofRef monoArguments +let getMonoArguments, setMonoArguments = LegacyApiHelper.toGetSet monoArgumentsHelper + + +/// Modifies the ProcessStartInfo according to the platform semantics +let platformInfoAction (psi : ProcessStartInfo) = + if isMono && psi.FileName.EndsWith ".exe" then + psi.Arguments <- getMonoArguments() + " \"" + psi.FileName + "\" " + psi.Arguments + psi.FileName <- monoPath + +/// The path of the current target platform +let mutable TargetPlatformPrefix = + let (<|>) a b = + match a with + | None -> b + | _ -> a + environVarOrNone "FrameworkDir32" <|> if (String.IsNullOrEmpty SystemRoot) then None + else Some(SystemRoot @@ @"Microsoft.NET\Framework") + <|> if (isUnix) then Some "/usr/lib/mono" + else Some @"C:\Windows\Microsoft.NET\Framework" + |> Option.get + +/// Base path for getting tools from windows SDKs +let sdkBasePath = ProgramFilesX86 @@ "Microsoft SDKs\Windows" + +/// Helper function to help find framework or sdk tools from the +/// newest toolkit available +let getNewestTool possibleToolPaths = + possibleToolPaths + |> Seq.sortBy (fun p -> p) + |> Array.ofSeq + |> Array.rev + |> Seq.ofArray + |> Seq.head + +/// Gets the local directory for the given target platform +let getTargetPlatformDir platformVersion = + if Directory.Exists(TargetPlatformPrefix + "64") then (TargetPlatformPrefix + "64") @@ platformVersion + else TargetPlatformPrefix @@ platformVersion + +/// The path to the personal documents +let documentsFolder = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments) +#endif + +/// The directory separator string. On most systems / or \ +let directorySeparator = Path.DirectorySeparatorChar.ToString() + +/// Convert the given windows path to a path in the current system +let convertWindowsToCurrentPath (windowsPath : string) = + if (windowsPath.Length > 2 && windowsPath.[1] = ':' && windowsPath.[2] = '\\') then windowsPath + else windowsPath.Replace(@"\", directorySeparator) + +/// Contains the IO encoding which is given via build parameter "encoding" or the default encoding if no encoding was specified. +let encoding = + match getBuildParamOrDefault "encoding" "default" with +#if !CORE_CLR + | "default" -> Text.Encoding.Default +#else + | "default" -> Text.Encoding.UTF8 +#endif + | enc -> Text.Encoding.GetEncoding(enc) + +#if !CORE_CLR +/// Returns a sequence with all installed .NET framework versions +let getInstalledDotNetFrameworks() = + let frameworks = new ResizeArray<_>() + try + let matches = + Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\NET Framework Setup\NDP").GetSubKeyNames() + |> Seq.filter (fun keyname -> Regex.IsMatch(keyname, @"^v\d")) + for item in matches do + match item with + | "v4.0" -> () + | "v4" -> + let key = @"SOFTWARE\Microsoft\NET Framework Setup\NDP\" + item + Registry.LocalMachine.OpenSubKey(key).GetSubKeyNames() + |> Seq.iter (fun subkey -> + let key = @"SOFTWARE\Microsoft\NET Framework Setup\NDP\" + item + @"\" + subkey + let version = Registry.LocalMachine.OpenSubKey(key).GetValue("Version").ToString() + frameworks.Add(String.Format("{0} ({1})", version, subkey))) + | "v1.1.4322" -> frameworks.Add item + | _ -> + let key = @"SOFTWARE\Microsoft\NET Framework Setup\NDP\" + item + frameworks.Add(Registry.LocalMachine.OpenSubKey(key).GetValue("Version").ToString()) + frameworks :> seq<_> + with e -> frameworks :> seq<_> //Probably a new unrecognisable version + +/// A record which allows to display lots of machine specific information like machine name, processor count etc. +type MachineDetails = + { ProcessorCount : int + Is64bit : bool + OperatingSystem : string + MachineName : string + NETFrameworks : seq + UserDomainName : string + AgentVersion : string + DriveInfo : seq } + +/// Retrieves information about the hard drives +let getDrivesInfo() = + Environment.GetLogicalDrives() + |> Seq.map (fun d -> IO.DriveInfo(d)) + |> Seq.filter (fun d -> d.IsReady) + |> Seq.map + (fun d -> + sprintf "%s has %0.1fGB free of %0.1fGB" (d.Name.Replace(":\\", "")) + (Convert.ToDouble(d.TotalFreeSpace) / (1024. * 1024. * 1024.)) + (Convert.ToDouble(d.TotalSize) / (1024. * 1024. * 1024.))) + +/// Retrieves lots of machine specific information like machine name, processor count etc. +let getMachineEnvironment() = + { ProcessorCount = Environment.ProcessorCount + Is64bit = Environment.Is64BitOperatingSystem + OperatingSystem = Environment.OSVersion.ToString() + MachineName = Environment.MachineName + NETFrameworks = getInstalledDotNetFrameworks() + UserDomainName = Environment.UserDomainName + AgentVersion = + sprintf "%A" ((System.Reflection.Assembly.GetAssembly(typedefof)).GetName().Version) + DriveInfo = getDrivesInfo() } +#endif \ No newline at end of file diff --git a/src/app/Fake.Runtime/FakeRuntime.fs b/src/app/Fake.Runtime/FakeRuntime.fs index c87f43485a1..2f8e4190988 100644 --- a/src/app/Fake.Runtime/FakeRuntime.fs +++ b/src/app/Fake.Runtime/FakeRuntime.fs @@ -1,8 +1,10 @@ -module Fake.FakeRuntime +module Fake.Runtime.FakeRuntime open System open System.IO -open Fake +open Fake.Runtime + +#if DOTNETCORE (* Runtime will restore packages before running the script @@ -162,14 +164,14 @@ let paketCachingProvider printDetails cacheDir (paketDependencies:Paket.Dependen |> Seq.choose (fun fi -> let fullName = fi.FullName try let assembly = Mono.Cecil.AssemblyDefinition.ReadAssembly fullName - { Fake.Fsi.AssemblyInfo.FullName = assembly.Name.FullName - Fake.Fsi.AssemblyInfo.Version = assembly.Name.Version.ToString() - Fake.Fsi.AssemblyInfo.Location = fullName } |> Some + { Fsi.AssemblyInfo.FullName = assembly.Name.FullName + Fsi.AssemblyInfo.Version = assembly.Name.Version.ToString() + Fsi.AssemblyInfo.Location = fullName } |> Some with e -> (if printDetails then Trace.log <| sprintf "Could not load '%s': %O" fullName e); None) |> Seq.toList // Restore or update immediatly, because or everything might be OK -> cached path. let mutable assemblies = restoreOrUpdate() - { new Fake.Fsi.ICachingProvider with + { new Fsi.ICachingProvider with member x.Invalidate cacheFile = if printDetails then Trace.log "Invalidating cache..." if File.Exists cacheFile then File.Delete cacheFile @@ -177,7 +179,7 @@ let paketCachingProvider printDetails cacheDir (paketDependencies:Paket.Dependen // Restore again -> cache might have been invalidated -> lock file might have changed. //assemblies <- restoreOrUpdate() let options = Yaaf.FSharp.Scripting.FsiOptions.ofArgs opts - let references = assemblies |> List.map (fun (a:Fake.Fsi.AssemblyInfo) -> a.Location) + let references = assemblies |> List.map (fun (a:Fsi.AssemblyInfo) -> a.Location) { options with NoFramework = true Defines = "FAKE" :: options.Defines @@ -231,11 +233,13 @@ let prepareFakeScript printDetails script = restoreDependencies printDetails cacheDir section | None -> if printDetails then Trace.traceFAKE "No dependencies section found in script: %s" script - Fake.Fsi.Cache.defaultProvider + Fsi.Cache.defaultProvider let prepareAndRunScriptRedirect printDetails (Fsi.FsiArgs(fsiOptions, scriptPath, scriptArgs) as fsiArgs) envVars onErrMsg onOutMsg useCache = let provider = prepareFakeScript printDetails scriptPath Fsi.runFakeWithCache provider printDetails fsiArgs envVars onErrMsg onOutMsg useCache let prepareAndRunScript printDetails fsiArgs envVars useCache = - prepareAndRunScriptRedirect printDetails fsiArgs envVars (Fake.Fsi.onMessage true) (Fake.Fsi.onMessage false) useCache \ No newline at end of file + prepareAndRunScriptRedirect printDetails fsiArgs envVars (Fsi.onMessage true) (Fsi.onMessage false) useCache + +#endif \ No newline at end of file diff --git a/src/app/Fake.Runtime/Fsi.fs b/src/app/Fake.Runtime/Fsi.fs new file mode 100644 index 00000000000..c0d042cabc2 --- /dev/null +++ b/src/app/Fake.Runtime/Fsi.fs @@ -0,0 +1,619 @@ +/// Contains helper functions which allow to interact with the F# Interactive. +module Fake.Runtime.Fsi +open Fake.Runtime.Environment +open Fake.Runtime.String +open Fake.Runtime.Trace +#if NETSTANDARD1_5 +open System.Runtime.Loader +#endif + +open System +open System.IO +open System.Diagnostics +open System.Threading +open System.Text.RegularExpressions +open System.Xml.Linq +open Yaaf.FSharp.Scripting + +let createDirectiveRegex id = + Regex("^\s*#" + id + "\s*(@\"|\"\"\"|\")(?.+?)(\"\"\"|\")", RegexOptions.Compiled ||| RegexOptions.Multiline) + +let loadRegex = createDirectiveRegex "load" +let rAssemblyRegex = createDirectiveRegex "r" +let searchPathRegex = createDirectiveRegex "I" + +let private extractDirectives (regex : Regex) scriptContents = + regex.Matches scriptContents + |> Seq.cast + |> Seq.map(fun m -> m.Groups.Item("path").Value) + +type Script = { + Content : string + Location : string + SearchPaths : string seq + IncludedAssemblies : Lazy +} + +let getAllScriptContents (pathsAndContents : seq