Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ All notable changes to Terrabuild are documented in this file.

## [Unreleased]

- Upgrade Terrabuild FScript runtime to 0.40.0 and initialize `Env` (`ScriptName`, `Arguments`) when loading `.fss` extension scripts.
- Fix FScript `Env` prelude injection to preserve leading `import` directives in embedded scripts.

## [0.189.4-next]

- Create annotated release tags in `release-prepare` so `git push --follow-tags` pushes releases
Expand Down
15 changes: 15 additions & 0 deletions src/Terrabuild.Scripting.Tests/Scripting.fs
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,21 @@ let invokeFScriptMethodWithStructuredArgumentsDefaults() =
let res = invocable.Value.Invoke<string> args
res |> should equal "build|0|<none>"

[<Test>]
let invokeFScriptMethodHasEnvInitialized() =
let root = NUnit.Framework.TestContext.CurrentContext.TestDirectory
let script = Terrabuild.Scripting.loadScript root [] "TestFiles/Env.fss"
let invocable = script.GetMethod("run")
let context = { ActionContext.Debug = false
ActionContext.CI = false
ActionContext.Command = "run"
ActionContext.Hash = "abc"
ActionContext.Directory = "TestFiles"
ActionContext.Batch = None }
let args = Value.Map (Map [ "context", Value.Object context ])
let res = invocable.Value.Invoke<string> args
res |> should equal "Env.fss|0"

[<Test>]
let invokeFScriptMethodMissingContextFails() =
let root = NUnit.Framework.TestContext.CurrentContext.TestDirectory
Expand Down
21 changes: 21 additions & 0 deletions src/Terrabuild.Scripting.Tests/TestFiles/Env.fss
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
[<export>]
let run (context: {| Command: string |}) =
let scriptName =
match Env.ScriptName with
| Some name -> name
| None -> "<none>"

$"{scriptName}|{Env.Arguments |> List.length}"

type ExportFlag =
| Dispatch
| Default
| Batchable
| Never
| Local
| External
| Remote

{
[nameof run] = []
}
80 changes: 79 additions & 1 deletion src/Terrabuild.Scripting/Scripting.fs
Original file line number Diff line number Diff line change
Expand Up @@ -549,11 +549,87 @@ let private toFScriptScript (loaded: FScript.Runtime.ScriptHost.LoadedScript) =
let dispatchMethod, defaultMethod = Descriptor.ResolveDispatchAndDefault descriptor
Script(FScript(loaded, descriptor, dispatchMethod, defaultMethod))

let private toFScriptStringLiteral (value: string) =
let escaped =
value
.Replace("\\", "\\\\", StringComparison.Ordinal)
.Replace("\"", "\\\"", StringComparison.Ordinal)
$"\"{escaped}\""

let private prependEnvironmentBinding (scriptName: string option) (arguments: string list) (source: string) =
let scriptNameLiteral =
match scriptName with
| Some value -> $"Some {toFScriptStringLiteral value}"
| None -> "None"

let argumentsLiteral =
match arguments with
| [] -> "[]"
| values ->
values
|> List.map toFScriptStringLiteral
|> String.concat "; "
|> sprintf "[%s]"

let prelude =
String.concat
"\n"
[ "let asEnvironment (value: Environment) = value"
$"let Env = asEnvironment {{ ScriptName = {scriptNameLiteral}; Arguments = {argumentsLiteral} }}"
"" ]

let newline =
if source.Contains("\r\n", StringComparison.Ordinal) then "\r\n"
else "\n"

let lines =
source.Replace("\r\n", "\n", StringComparison.Ordinal).Split('\n')

let isCommentOrBlank (line: string) =
let trimmed = line.Trim()
String.IsNullOrWhiteSpace(trimmed) || trimmed.StartsWith("//", StringComparison.Ordinal)

let isImport (line: string) =
line.TrimStart().StartsWith("import ", StringComparison.Ordinal)

let mutable index = 0
let mutable seenImport = false
let mutable keepScanning = true

while index < lines.Length && keepScanning do
let line = lines[index]
if isImport line then
seenImport <- true
index <- index + 1
elif isCommentOrBlank line then
index <- index + 1
else
keepScanning <- false

let insertionIndex = if seenImport then index else 0
let before = lines |> Array.take insertionIndex |> String.concat newline
let after = lines |> Array.skip insertionIndex |> String.concat newline

if insertionIndex = 0 then
prelude + source
elif String.IsNullOrEmpty(after) then
before + newline + prelude
else
before + newline + prelude + after

let private loadFScript (rootDirectory: string) (scriptFile: string) =

let fullPath = Path.GetFullPath(scriptFile)
let externs = FScript.Runtime.Registry.all { FScript.Runtime.HostContext.RootDirectory = rootDirectory }
let loaded = FScript.Runtime.ScriptHost.loadFile externs fullPath
let scriptName = Path.GetFileName(fullPath) |> Option.ofObj
let entrySource = File.ReadAllText(fullPath) |> prependEnvironmentBinding scriptName []
let loaded =
FScript.Runtime.ScriptHost.loadSourceWithIncludes
externs
rootDirectory
fullPath
entrySource
(fun resolvedPath -> File.ReadAllText(resolvedPath) |> Some)
toFScriptScript loaded

let private loadFScriptFromSourceWithIncludes
Expand All @@ -563,6 +639,8 @@ let private loadFScriptFromSourceWithIncludes
(entrySource: string)
(resolveImportedSource: string -> string option) =
let externs = FScript.Runtime.Registry.all { FScript.Runtime.HostContext.RootDirectory = hostRootDirectory }
let scriptName = Path.GetFileName(entryFile) |> Option.ofObj
let entrySource = entrySource |> prependEnvironmentBinding scriptName []
let loaded =
FScript.Runtime.ScriptHost.loadSourceWithIncludes
externs
Expand Down
4 changes: 2 additions & 2 deletions src/Terrabuild.Scripting/Terrabuild.Scripting.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@

<ItemGroup>
<PackageReference Include="FSharp.Compiler.Service" Version="43.10.102" />
<PackageReference Include="MagnusOpera.FScript.Language" Version="0.37.0" />
<PackageReference Include="MagnusOpera.FScript.Runtime" Version="0.37.0" />
<PackageReference Include="MagnusOpera.FScript.Language" Version="0.40.0" />
<PackageReference Include="MagnusOpera.FScript.Runtime" Version="0.40.0" />
</ItemGroup>

<ItemGroup>
Expand Down