Sharing const variables across FAKE fsx scripts - f#

Is there any way to share a variable by including a fsx script within another fsx script.
e.g script buildConsts.fsx contains
let buildDir = "./build/"
I want to reference this in other build scripts e.g.
#load #".\buildConsts.fsx"
let testDlls = !! (buildDir + "*Test*.dll")
When I attempt to run the script the 'buildDir' variable the script fails to compile.
This is a fairly common approach that is used with tools such as MSBuild and PSAKE to modularise scripts. Is this the correct approach with FAKE ?

What you're doing should work - what exactly is the error message that you're getting?
I suspect that the problem is that F# automatically puts the contents of a file in a module and you need to open the module before you can access the constants. The module is named based on the file name, so in your case buildConsts.fsx will generate a module named BuildConsts. You should be able to use it as follows:
#load #".\buildConsts.fsx"
open BuildConsts
let testDlls = !! (buildDir + "*Test*.dll")
You can also add an explicit module declaration to buildconsts.fsx, which is probably a better idea as it is less fragile (won't change when you rename the file):
moule BuildConstants
let buildDir = "./build/"

Related

Reading the content of directory declared with `actions.declare_directory`

Imagine I have a java_binary target triggered by a custom rule that generates source code and places the generated sources under a directory, let's call it "root".
So after the code generation we will have something like this:
// bazel-bin/...../src/com/example/root
root:
-> Foo.java
-> Bar.java
-> utils
-> Baz.java
Now, I have another target, a java_library, that depends on the previously generated sources, so it depends on the custom rule.
My custom rule definition currently looks something like this:
def _code_generator(ctx):
outputDir = ctx.actions.declare_directory("root")
files = [
ctx.actions.declare_file("root/Foo.java"),
ctx.actions.declare_file("root/Bar.java"),
ctx.actions.declare_file("root/utils/Baz.java"),
// and many,
// many other files
]
outputs = []
outputs.append(outputDir)
outputs.extend(files)
ctx.actions.run(
executable = // executable pointing to the java_binary
outputs = outputs
// ....
)
This works. But as you can see, every anticipated file that is to be generated, is hard-coded in the rule definition. This makes it very fragile, should the code generation produce a different set of files in the future (which it will).
(Without specifying each of the files, as shown above, Bazel will fail the build saying that the files have no generating action)
So I was wondering, is there a way to read the content of the root directory and automatically, somehow, declare each of the files as an output?
What I tried:
The documentation of declare_directory says:
The contents of the directory are not directly accessible from Starlark, but can be expanded in an action command with Args.add_all().
And add_all says:
[...] Each directory File item is replaced by all Files recursively contained in that directory.
This sounds like there could be a way to get access to the individual files in the directory, but I am not sure how.
I tried:
outputDir = ctx.actions.declare_directory("root")
//...
args = ctx.actions.args()
args.add_all(outputDir)
with the intention to access the individual files later from args, but the build fails with: "Error in add_all: expected value of type sequence or depset for values, got File".
Any other ideas on how to implement the rule, so that I don't have to hard-code each and every file that will be generated?

How can I use the JAR tool with Bazel v0.19+?

Starting with Bazel v0.19, if you have Starlark (formerly known as "Skylark") code that references #bazel_tools//tools/jdk:jar, you see messages like this at build time:
WARNING: <trimmed-path>/external/bazel_tools/tools/jdk/BUILD:79:1: in alias rule #bazel_tools//tools/jdk:jar: target '#bazel_tools//tools/jdk:jar' depends on deprecated target '#local_jdk//:jar': Don't depend on targets in the JDK workspace; use #bazel_tools//tools/jdk:current_java_runtime instead (see https://github.com/bazelbuild/bazel/issues/5594)
I think I could make things work with #bazel_tools//tools/jdk:current_java_runtime if I wanted access to the java command, but I'm not sure what I'd need to do to get the jar tool to work. The contents of the linked GitHub issue didn't seem to address this particular problem.
I stumbled across a commit to Bazel that makes a similar adjustment to the Starlark java rules. It uses the following pattern: (I've edited the code somewhat)
# in the rule attrs:
"_jdk": attr.label(
default = Label("//tools/jdk:current_java_runtime"),
providers = [java_common.JavaRuntimeInfo],
),
# then in the rule implementation:
java_runtime = ctx.attr._jdk[java_common.JavaRuntimeInfo]
jar_path = "%s/bin/jar" % java_runtime.java_home
ctx.action(
inputs = ctx.files._jdk + other inputs,
outputs = [deploy_jar],
command = "%s cmf %s" % (jar_path, input_files),
)
Additionally, java is available at str(java_runtime.java_executable_exec_path) and javac at "%s/bin/javac" % java_runtime.java_home.
See also, a pull request with a simpler example.
Because my reference to the jar tool is inside a genrule within top-level macro, rather than a rule, I was unable to use the approach from Rodrigo's answer. I instead explicitly referenced the current_java_runtime toolchain and was then able to use the JAVABASE make variable as the base path for the jar tool.
native.genrule(
name = genjar_rule,
srcs = [<rules that create files being jar'd>],
cmd = "some_script.sh $(JAVABASE)/bin/jar $# $(SRCS)",
tools = ["some_script.sh", "#bazel_tools//tools/jdk:current_java_runtime"],
toolchains = ["#bazel_tools//tools/jdk:current_java_runtime"],
outs = [<some outputs>]
)

Can I create a conditional literal?

In order to create a Json provider I need to pass a literal with the path. There are several people working on the project from different locations, and the paths are different in each case. (Actually only the beginning of each path). I tried to create a literal with pattern matching but the compiler does not accept it. Is there another way to do this?
My failed attempt is below:
open FSharp.Data
[<Literal>]
let bitbucketRoot = // Error message: This is not a valid constant expression
let computerName = Environment.MachineName
match computerName with
| "DESKTOP-G3OF32U" -> "C:\\Users\\Fernando"
| "HPW8" -> #"H:\Dropbox\"
| _ -> failwith "Unknown computer"
[<Literal>] // Error message: This is not a valid constant expression
let projDataPath = bitbucketRoot + #"Bitbucket\VSProjects\Fractal10\Fractal10\data\"
[<Literal>] // Error message: This is not a valid constant expression
let jsonPath = projDataPath + "fractal.json"
type PathInfo = JsonProvider<Sample=jsonPath>
I would advise that you store it in source control and make it a path relative to your project root, assuming you are working out of a common source control repository.
Either that, or host the sample on a public URL. (I wouldn't actually recommend this because including it in your source repository allows versioning and doesn't publicly expose your data)
You cannot create a conditional literal as the other comments point it out. However this is a fairly frequent use case and the way to deal with it is as follows:
#r #"..\packages\FSharp.Data\lib\net40\FSharp.Data.dll"
open FSharp.Data
open System
open System.IO
[<Literal>]
let JsonSource = __SOURCE_DIRECTORY__ + #"\test.json"
type JSonType = JsonProvider<JsonSource>
let json1 = JSonType.GetSamples()
let anotherPath = #"C:\tmp"
let anotherJson = anotherPath + #"\test.json"
let json2 = JSonType.Load(anotherJson)
The __SOURCE_DIRECTORY__ directive will point to the project root (just display it in the REPL) and then you can add the filename to it and make that a literal. If you check in this file into a git repo, then everyone who checks it out can have it in a relative path, and you can refer it when generating the type. When actually using the type or referring to the full file you can just use the .Load() method to load any file, and this doesn't have to be a literal.
There is actually a second way, which could work for you depending on the circumstances, compile a sample, and distribute it as a .dll. You can refer to this and use it directly without having access to the actual file. Please see the Using the JSON Provider in a Library section at the end of the documentation.
I have not tried referring to the json in a config file, it might also be possible.

Should it be possible to put the #load directive inside #if directive in F# fsx file?

I have a fsx file where I try to do this
#if DEV
#load "MyFile.fs"
#endif
// Later in the file
#if DEV
callSomethingFromMyFile()
#endif
The callSomethingFromMyFile() works if I remove the #if DEV ... #endif around the #load directive.
I realize that this might be a weird thing, but it is because I'm using fable to compile to F# to js, and if I want to exclude a file when production "build" to reduce js file size.
In regular F# scripts it is possible, it seems like fable doesn't handle it.
To verify that it works in regular F# I created the following to files:
MyModule.fs:
module MyModule
type A = {b: string}
script.fsx:
#if DEV
#load "./MyModule.fs"
#endif
#if DEV
open MyModule
printfn "Hello A: %A" {b = "yolo"}
#endif
printfn "Done"
running fsharpi --define:DEV --exec script.fsx works as expected. I expect fsi on Windows to work as well.
Ive fixed this in the following PR: https://github.com/fable-compiler/Fable/pull/429
You can currently pass defines to the interactive checker its just that for fsx files this was not currently being done.
"
Some directives are available when you are executing scripts in F# Interactive that are not available when you are executing the compiler. The following table summarizes directives that are available when you are using F# Interactive.
"
https://learn.microsoft.com/en-us/dotnet/articles/fsharp/tutorials/fsharp-interactive/index#differences-between-the-interactive-scripting-and-compiled-environments
The table then lists #load amongst others. Not entirely clear that text (compiler vs. preprocessor), but it also kind of makes sense...

Fsx execution path

I have a c# .net library I am looking to use within FSI/FSX. As part of the initialization of the .net lib, by default it expects and references a custom config file (MyAppConfig.xml) which loads various things before it can be used. When using it in c# it gets copied to the bin folder and the app by default expects it to be there and references it there unless there is a specific entry in the app.config to tell it otherwise. (I should add that it does it all by convention rather than injecting a path + filename, as per NLog, say)
I have an f# source file in a console app which will execute this initialization find, but I can't quite work out how to achieve this with FSI/FSX.
So my program.fs looks simply like
open System
open myApp
module Program =
[<EntryPoint>]
let Main(args) =
myApp.Initialization.Load() // references MyAppConfig.xml
Console.WriteLine("do my stuff!")
Console.ReadLine() |> ignore
0
If I try and do the same in FSI or using FSX, I have
#r #"E:\...path to MyApp...\MyApp.dll"
#I #"E:\...path to MyAppConfig.xml ..."
Environment.CurrentDirectory <- #"E:\...path to MyAppConfig.xml ..."
myApp.Initialization.Load() |> ignore // fails ... can't find MyAppConfig.xml
//do my stuff
I suspect that I've not got the paths quite right.
I'd be grateful of a steer
EDIT:
So I've managed to attach a debugger to the c# lib and see where it is looking for the config file - turns out it is "c:\Program Files\Microsoft F#\v4.0\" ( System.AppDomain.CurrentDomain.BaseDirectory) which again shows I've not quite understood how to tell FSI/FSX to use a particular path. If I copy the config file (MyAppConfig.xml) to that location it works fine.
Many thx
S
I'm not sure of the implications, but one possiblity might be temporarily changing the app base:
let origAppBase = AppDomain.CurrentDomain.BaseDirectory
AppDomain.CurrentDomain.SetData("APPBASE", "path_to_MyAppConfig.xml")
myApp.Initialization.Load() |> ignore
AppDomain.CurrentDomain.SetData("APPBASE", origAppBase) //restore original app base

Resources