我正在尝试使用FAKE的TemplateHelper
来实例化一些成为shell脚本(Unix和DOS)的模板文件。我在这样的FAKE任务中这样做:
CopyFile (buildApp @@ product + ".sh") (srcTem @@ "mono-launcher.sh")
processTemplates substs [(buildApp @@ product + ".sh")]
其中substs
是替换的var->值映射,srcTem
和buildApp
分别是模板源目录和构建输出目录。
我遇到了3个问题:
首先,TemplateHelper.processTemplates
在生成的脚本的前面写了一个UTF-8 BOM。至少在Unix上,这会弄乱shebang行并在脚本运行时给出错误消息。原始模板的第一行的八进制转储和生成的文件显示这三个麻烦的字节的外观:
$ head -1 ./src/templates/mono-launcher.sh | od -c
0000000 # ! / b i n / s h \n
$ head -1 ./build/app/Hello-Fsharp.sh | od -c
0000000 357 273 277 # ! / b i n / s h \n
其次,没有办法说生成的Unix sh脚本中应该包含Unix EOL,而Windows .bat脚本应该有DOS EOL。
第三,没有明显的方法可以说生成的文件应具有世界读取和执行权限。
这个问题是关于是否有人知道如何处理这些问题,至少以比我在下面提出的方式更好的方式。
我在TemplateHelper
来源和周围进行了一些探索。我发现了processTemplate
次调用WriteFile
调用了WriteToFile
,encoding
使用EnvironmentHelper
中定义的值为System.Text.Encoding.Default
ASCIIEncoding
,TemplateHelper
在我的系统上为UTF- 8。这已经足够了,但没有明显的方法来传递自定义编码,例如/// <summary>Copy a template file from source to destination, making string substitutions
/// along the way and allowing for different file encodings at each end.</summary>
///
/// <remarks>This only exists because TemplateHelper put UTF-8 BOM at the front, breaking
/// script shebang lines. The EOL choice here works from Unix but needs testing on
/// Windows (will it double up on returns if the build machine is Window?), and the
/// read/execute permission setting is iffy. </remarks>
///
/// <param name="substs">IDictionary of key, value string replacements to make</param>
/// <param name="fromEncoding">Encoding of fromTemplate</param>
/// <param name="fromTemplate">file with the source template</param>
/// <param name="toEncoding">Encoding of toFile</param>
/// <param name="toFile">file to which the instantiated template gets written</param>
/// <param name="readExecute">whether to mark toFile as readable & executable by all</param>
///
/// <returns>Nothing of interest.</returns>
let processTemplate substs fromEncoding fromTemplate toEncoding toFile unixEOL readExecute =
let ReadTemplate encoding fileName = // Read lines of template file, but lazily:
File.ReadLines(fileName, encoding) // start processing before entirety is read
let SubstituteVariables substs = // Substitute variables in seq of line strs
Seq.map (fun (line : string) -> // Loop over lines
substs // Fold in effect of each key, val pair
|> Seq.fold (fun (sb : StringBuilder) (KeyValue(k : string, v : string)) ->
sb.Replace(k, v)) // Generates new StringBuilder w/each var
(new StringBuilder(line)) // Initial accumulator is original line
|> toText) // Convert final StringBuilder back to text
let MaybeAddReturn = // Add extra return if not Unix EOLs
Seq.map (fun line -> if unixEOL then line else line + "\r") // *** Test if works on Windows?
let WriteTemplate encoding fileName (lines : seq<string>) =
File.WriteAllLines(fileName, lines, encoding) // Write out altered lines (eol chosen how?)
fileName // Return filename for next step in pipeline
let MaybeSetReadExecute fileName = // Platform neutral, mark read + exec perms
if readExecute then // Only do this if asked
if isMono then // We're running on Mono
Shell.Exec("chmod", "a+rx " + fileName, here) |> ignore // *** Horrible kludge?
else // Not under Mono: do it the .Net way
// *** Equiv kludge: Shell.Exec("icacls", fileName + " /grant S-1-1-0:RX", here) |> ignore
let fs = File.GetAccessControl(fileName) // Mac OSX: System.NotSupportedException
fs.AddAccessRule(new FileSystemAccessRule( //
new SecurityIdentifier(WellKnownSidType.WorldSid, null),
FileSystemRights.ReadAndExecute,
AccessControlType.Allow)) // *** Seems pretty complex for such a simple
File.SetAccessControl(fileName, fs) // task as just setting rx permissions!
let shortClassName obj = // Just class name, no namespacey qualifiers
let str = obj.ToString() in str.Substring(str.LastIndexOf('.') + 1)
logfn " Template %s (%s) generating %s (%s, %s EOLs%s)."
fromTemplate (shortClassName fromEncoding) // Where we're coming from,
toFile (shortClassName toEncoding) // where we're going,
(if unixEOL then "Unix" else "DOS") // and in what condition we propose to
(if readExecute then ", read/execute" else "") // arrive there
fromTemplate // Start with the input template filename
|> ReadTemplate fromEncoding // read it in lazily (enumeration of lines)
|> SubstituteVariables substs // substitute variables in each line
|> MaybeAddReturn // fiddle with EOLs for target platform
|> WriteTemplate toEncoding toFile // write out in destination encoding
|> MaybeSetReadExecute // finally set permissions if asked
。
所以......如果没有任何方法说服TemplateHelper
做我想做的事,我用以下内容代替它:
Target "BuildLauncherScripts" (fun _ -> // Scripts really only needed for mono
let asciiEncoding = new ASCIIEncoding() // Avoid UTF-8 BOM which breaks shebang lines
let generateAScript (fromTempl : String) = // Instantiate a script from template file
let ext = fromTempl.Substring(fromTempl.LastIndexOf('.'))
processTemplate projProps // projProps to substitute template vars
asciiEncoding (srcTem @@ fromTempl) // copy from templates to build area
asciiEncoding (buildApp @@ (projProps.Item("@product")) + ext)
(ext = ".sh") true // always executable, because it's a script
generateAScript "mono-launcher.sh" // Unix script to launch mono + application
generateAScript "bat-launcher.bat" // Windows script to just launch application
) //
它一次处理一个文件,而不是像TemplateHelper
这样的序列处理文件,因为它太毛了,不能提供并行的读取和读取序列。写编码。但作为奖励,它消除了我在源中发现的可变状态的使用,使其更具功能性,如果有人关心的话。增加的功能是提供输入和输出编码,处理EOL以及可选地为生成的文件设置读/执行权限的能力。
以下是使用中的示例:
MaybeSetReadExecute