Manipulate an XML file with FAKE - f#

Question: How do I set a specific attribute in an XML file with FAKE?
I want to build a Windows Phone 8.1 project with the help of FAKE. The version tag is a parameter of the build script. The tag is already baked into the assemply info:
let version = getBuildParamOrDefault "version" "0.0.0.1"
Target "AssemblyInfo" (fun _ ->
CreateCSharpAssemblyInfo "./src/SharedAssemblyInfo.cs"
[
Attribute.Product product
Attribute.Version version
Attribute.FileVersion version
Attribute.Metadata ("githash", commitHash)
]
)
There is another file in a Windows Phone 8.1 project that contains version information: Package.appxmanifest.
<?xml version="1.0" encoding="utf-8"?>
<Package ...>
<Identity Name="..." Publisher="..." Version="1.0.0.0" />
...
</Package>
I want to change the value of the Version attribute of the Identity tag. It should contain the version tag given as build parameter. I want to do it in a separate target with the name "AppxManifest" and it should execute after manipulating the assembly info file but before the MSBuild build is executed:
Target "AppxManifest" (fun _ ->
???
)
"Clean"
==> "RestorePackages"
==> "AssemblyInfo"
==> "AppxManifest"
==> "Build"
I have found an XMLHelper in the documentation of FAKE (http://fsharp.github.io/FAKE/apidocs/fake-xmlhelper.html) and it looks like that this thing can do the job. But there are no examples. I could not figure out how to do it.

You can use the XMLHelper library to manipulate XML files. The function XmlPoke can be used to change an element in an XML file by referenceing it with an XPath expression. The Package.appxmanifest file contains namespaces, therefore you have to take the sister function XmlPokeNS which can handle namespaces properly.
The last difficulty is the default namespace in Package.appxmanifest. You have to specify a prefix which you associate with the default namespace. In your XPath expression this prefix is used to reference the XML elements.
There is no special prefix string, you can choose whatever you want. In this example I choose the prefix "df":
open Fake.XMLHelper
Target "PackageAppxmanifest" <| fun _ ->
let file = "<path to Package.appxmanifest>"
let ns = [ "df", "http://schemas.microsoft.com/appx/2010/manifest" ]
let xpath = #"df:Package/df:Identity/#Version"
XmlPokeNS file ns xpath version

Related

Substitutions and Java library lifecycle

I've got a Java project I'm converting to Bazel.
As is typical with Java projects, there are property files with placeholders that need to be resolved/substituted at build time.
Some of the values can be hardcoded in a BUILD or BZL file:
BUILD_PROPERTIES = { "pom.version": "1.0.0", "pom.group.id": "com.mygroup"}
Some of the variables are "stamps" (e.g. BUILD_TIMESTAMP, GIT_REVISION, etc): The source for these variables are volatile-status.txt and stable-status.txt
I must generate a POM for publish, so I use #bazel_common//tools/maven:pom_file in BUILD
(assume that I need ALL the values described above for my pom template):
_local_build_properties = {}
_local_build_properties.update(BUILD_PROPERTIES)
# somehow add workspace status properties?
# add / override
_local_build_properties.update({
"pom.project.name": "my-submodule",
"pom.project.description": "My submodule description",
"pom.artifact.id": "my-submodule",
})
# Variable placeholders in the pom template are wrapped with {}
_pom_substitutions = { '{'+k+'}':v for (k,v) in _local_build_properties.items()}
pom_file(
name = "my_submodule_pom",
targets = [
"//my-submodule",
],
template_file = "//:pom_template.xml",
substitutions = _pom_substitutions,
)
So, my questions are:
How do I get key-value pairs from volatile/stable -status.txt into the
dictionary I need for pom_file.substitutions?
pom_file depends on java_library so that it can write its dependencies
into the POM. How do I update the jar generated by java_library with the
pom?
Once I have the pom and the updated jar containing the pom, how do I publish to a Maven repo?
When I look at existing code, for example rules_docker, it seems that the implementation always bails to a local executable (shell | python | go) to do the real work of substitution, jar manipulation and image publication. Am I trying to do too much in BUILD and BZL files? Should I be thinking, "Ultimately, what do I need to pass to local shell/python/go scripts to get real build work done?
(Answered on bazel-discuss group)
Hi,
You can't get these values from Starlark. You need a genrule to read the stable/volatile files and do the substitutions using an
external tool like 'sed'.
A file cannot be both an input and output of an action, i.e. you can't update the .jar from which you generate the pom. The action has
to produce a new .jar file.
I don't know -- how would you publish outside of Bazel, is there a tool to do so? Can you write a genrule / Starlark rule to wrap this
tool?
Cheers, László

How to read attribute value from xml file - FAKE F#MAKE

Hi I am having an XML file , I want to read value of a particular attribute from XML file , how will I do this in FAKE . Please Help , I am new to F# and FAKE.And I just figured it out on fake documentation page and found that I can use XMLRead function of XMLHelper Class whose description is available at
https://github.com/fsharp/FAKE/blob/master/src/app/FakeLib/XMLHelper.fs#L14-14
But I could not understand how to do this , as there are no examples as such .
I have a huge XML file but for simplicity I am mentioning a piece of that file. XML File is as follows :
<version>
<major number="2">
<minor>1</minor>
<build>1</build>
<revised>1</revised>
</major>
</version>
Please tell me , how to read values from mentioned attributes .
If you look back a few posts you can find a related question.
The short answer is that in my 'build.fsx' file I can do somthing like this:
// Build the main module with MSBuild
Target "BuildMain" (fun _ ->
for s in XMLHelper.XMLRead true "./myxml.xml" "" "" "/version/major/minor"
do trace s
!! "./Kapoin_03_Main/Kapoin_03_Main.fsproj"
|> MSBuild buildDir "Build" buildProps
|> Log "Main build output: " )
That will in this case just write "1" in the console when you build.
Edit: If you are using a different target you may want to do something along the lines of the following to store a value as a variable:
let minver =
XMLHelper.XMLRead
true "./myxml.xml" "" "" "/version/major/minor"
|> Seq.head

Using the FSharp.Configuration type provider

I'd like to the use app.config file of my F# to store versioning information. I discovered the FSharp.Configuration type provider which seemed like it'd be simple enough. However, I'm running in to an error I can't diagnose.
Below is a screen shot of a version.config file (identical to the one in the link above) and a scratch pad.
As you can see, calling Settings auto populates a drop-down of everything in the <appSettings> chunk of the config but when I try to run something,
I get an error saying that the thing I'm looking for can't be found in the <appSettings> section of the config file.
What's causing this error, especially considering that it clearly is finding it in the config file, given it's auto-populating? What can I do to prevent this from happening again?
You have bumped into this issue.
When you run the Configuration provider in FSI it will look not for the app's config file but FSI's config file. One way to get around this is by specifying the exe's config file explicitly. Here's an example:
open FSharp.Configuration
open System
type Settings = AppSettings<"app.config">
[<EntryPoint>]
let main argv =
let path = System.IO.Path.Combine [|__SOURCE_DIRECTORY__ ;"bin";"release";"ConfigApplication.exe" |]
Settings.SelectExecutableFile path
Settings.TestBool <- false // change a setting
printfn "%A" Settings.Test2 // read another setting
Console.ReadLine() |> ignore
0 // return an integer exit code
This will take the App.config file in the source directory, but use the ConfigApplication.exe.config file in the binaries directory.
If you just need to set the DB's connection string, it's actually easier, if the SQL type provider has a config setting parameter, just specify the config file there (and set it to Always copy in VS), if you add that to .gitignore you can have many different app.config files with different connection strings.
You could also use the YAML provider, it has two advantages, it's not XML and it's not an erasing type provider.

How can we use variables in wxl file [duplicate]

I need to use variable in WIX localization file WIXUI_en-us.wxl.
I tried use it like this:
<String Id="Message_SomeVersionAlreadyInstalled" Overridable="yes">A another version of product $(var.InstallationVersionForGUI) is already installed</String>
But it doesn't work. And when I declared property and used it this way:
<String Id="Message_SomeVersionAlreadyInstalled" Overridable="yes">A another version of product [InstallationVersionForGUI] is already installed</String>
doesn't work either.
Where was I wrong?
Thanks for help and your time.
Localization strings are processed at link time, so you can't use $(var) preprocessor variables. Using a [property] reference is supported, as long as the place where the localization string is used supports run-time formatting (e.g., using the Formatted field type).
Your second method should work just fine. This is the same method used by the default .wxl files.
For example, in your .wxl file you would declare your string:
<String Id="Message_Foo">Foo blah blah [Property1]</String>
And in your .wxs file, you declare the property. If you wish, you can declare the property to match a WiX variable (which it sounds like you're trying to do)
<Property Id="Property1">$(var.Property1)</Property>
I was trying to get localization file to use variables. Came across this post:
There are different layers of variables in WiX (candle's preprocessor
variables, Light's WixVariables/localization variables/binder
variables, and MSI's properties). Each have different syntax and are
evaluated at different times:
Candle's preprocessor variables "$(var.VariableName)" are evaluated
when candle runs, and can be set from candle's commandline and from
"" statements. Buildtime environment
properties as well as custom variables can also be accessed similarly
(changing the "var." prefix with other values).
Light's variables accessible from the command-line are the
WixVariables, and accessing them is via the "!(wix.VariableName)"
syntax. To access your variable from your commandline, you would need
to change your String to: This build was prepared on
!(wix.BuildMachine)
If you instead need to have the BuildMachine value exist as an MSI
property at installation time (which is the "[VariableName]" syntax)
you would need to add the following to one of your wxs files in a
fragment that is already linked in:
Now, the environment variable COMPUTERNAME always has held the name of
my build machines in the past, and you can access that this way:
$(env.COMPUTERNAME). So, you can get rid of the commandline addition
to light.exe and change your wxs file like this:
<WixProperty Id="BuildMachine" Value="$(env.COMPUTERNAME)"/>
Preprocessor variables $(var.VariableName) are are processed at link time, so ideally you would use [PropertyName] which would be defined on the main Product element.
The issue sometimes is that property is not yet defined, for instance using the product name on the localization file seems not posible.
This solution was done aiming to only type the product name once given "Super product" as product name:
In case of running through visual studio extension:
Project properties -> Build -> Define variables -> "MyProductName=Super product" (No quotes)
In case of runing from cmd or some other place:
On Light.exe, add -d"MyProductName=Super product"
Into the localization .wxl file:
<String Id="Description" Overridable="yes">Description of !(wix.MyProductName)
to make it more interesting</String>
I have an aditional config file .wxi I include on other files to have some vars, for instance, here i had hardcoded the value but now it's harcoded on the variable definition and I use the given value:
<?xml version="1.0" encoding="utf-8"?>
<Include>
<!-- Define the product name preprocesor variable -->
<?define ProductName="!(wix.ProductNameDefVar)" ?>
<!-- From this point, can use the preprocesor var -->
<?define ProductName_x64="$(var.ProductName) (64bit)" ?>
<?define ProductName_x32="$(var.ProductName) (32bit)" ?>
<?define CompanyDirName = "My company name" ?>
</Include>
Finally, the place where the localization value where the localization text was not interpolating, is like this:
<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<!-- Include the config file with the preprocesor var -->
<?include $(sys.CURRENTDIR)\Config.wxi?>
<!-- Main product definition -->
<Product Id="$(var.ProductCode)"
Name="$(var.ProductName)"
Language="!(loc.Language)"
Version="$(var.BuildVersion)"
Manufacturer="!(loc.Company)"
UpgradeCode="$(var.UpgradeCode)">
<!-- Package details -->
<!-- Here, Description was not interpolating -->
<Package InstallerVersion="200"
Compressed="yes"
InstallScope="perMachine"
Platform="$(var.Platform)"
Manufacturer="!(loc.Company)"
Description="!(loc.Description)"
Keywords="!(loc.Keywords)"
Comments="!(loc.Comments)"
Languages="!(loc.Language)"
/>
[...]

Using NLog with F# Interactive in Visual Studio - Need documentation

I have a need to capture the input and output of F# functions when using F# Interactive. I am able to get NLog to work just fine when the program is run under Visual Studio using F5 or Ctrl-F5. Also the same methods that contain statements to output to the log work just fine and are called when invoked via F# Interactive; just nothing in the log file.
I also tried the following with F# Interactive to setup references to NLog and still nothing in the log when run from F# Interactive.
#I #"..\packages\NLog.2.0.0.2000\lib\net40"
#r #"NLog.dll"
And I even found this which led me to try each of these
NLog.Config.SimpleConfigurator.ConfigureForConsoleLogging()
NLog.Config.SimpleConfigurator.ConfigureForFileLogging(<full file name>)
and still nothing in the log file.
Anyone know if Nlog can be used with F# Interactive?
If so, how is it done?
EDIT
I was able to get NLog to work with fsi.exe when run as a stand alone. So now the problem appears to be getting NLog to find the config file because NLog cannot find the config file starting from the location of fsi.exe for Visual Studio. Looking at using NLog.dll.nlog in the NLog.dll directory.
The Problem
The problem with using NLog from F# Interactive is that NLog thinks that the Temp directory is where to find NLog.config and never succeeds. The way around this is to programmatically locate NLog.config for NLog.
Things to know to solve this problem:
When you run F# Interactive from within Visual Studio, it sets the current working directory to a temp file.
> System.Environment.CurrentDirectory;;
val it : string = "C:\Users\Eric\AppData\Local\Temp"
NLog logging requires three components:
a. reference to NLog.dll.
b. configuration file.
c. calls to a logger method from code.
NLog can be configured in many ways, both programmatically and using config files.
AppData is a hidden folder. Guess what that means when using Windows Explorer.
To get the location of the application with F# Interactive within Visual Studio you need __SOURCE_DIRECTORY__. See F# Spec 3.11 Identifier Replacements
NLog.conf can use a full file path. Obvious but necessary.
NLog file targets have an autoFlush option.
NLog can be installed into a Visual Studio project using NuGet.
Most of the info here comes from NLog Wiki.
Instead of jumping right into the F# Interactive solution, the following progression will be used because a DLL will need to be created to setup and hold the functions for use with NLog from F# Interactive.
Create a solution with three projects and install NLog.
Solution Name: NLogExample
Project 1 - Library, Name: Log - holds extension functions that call NLog
Project 2 - Library, Name: MyLibrary - used to generate a demo DLL that uses Log functions.
Project 3 - Console Application, Name: Main - used to generate a demo EXE that uses Log functions.
a. Manually create NLog.config
b. Access NLog.config from as a running project
c. Log a message to the file
a. Programmatically create a configuration
b. Create a configuration for a running project and log a message to the file
Create a configuration and log a message to the file using F# Interactive
1. Create a solution with three projects and install NLog
Using Visual Studio create the three projects.
Install NLog for all three projects.
2.a. Manually create NLog.config
Note: For these examples to work when __SOURCE_DIRECTORY__;; is run from F# Interactive it should report a directory that is part of the project and NOT the Temp directory.
Note: All the paths in this answer are relative to the solution directory.
When you see <Solution directory> substitute in your actual solution directory.
Path: <Solution director>\NLog.config
<?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
throwExceptions="true">
<targets>
<target xsi:type="File"
name="file"
fileName="<Solution directory>\log.txt"
autoFlush="true"
/>
</targets>
<rules>
<logger name="*"
minlevel="Trace"
writeTo="file"
/>
</rules>
</nlog>
Note: Remember to change <Solution directory> to an actual path and set autoFlush="true"
Note: Adding NLog.config to the solution makes it easier to view/modify the file.
2.b. Access NLog.config from as a running project
In Log.Library1.fs
namespace Log
module MyLog =
let configureNLog () =
let projectPath = __SOURCE_DIRECTORY__
let soulutionPath = projectPath + "\.."
let configPath = soulutionPath + #"\NLog.config"
let xmlConfig = new NLog.Config.XmlLoggingConfiguration(configPath)
NLog.LogManager.Configuration <- xmlConfig
let NLogConfigToString () =
let targets = NLog.LogManager.Configuration.AllTargets
let out = ""
let out = Seq.fold (fun out target -> out + (sprintf "%A\n" target)) out targets
let rules = NLog.LogManager.Configuration.LoggingRules
let out = Seq.fold (fun out rule -> out + (sprintf "%A\n" rule)) out rules
out
let printNLogConfig () =
Printf.printfn "%s" (NLogConfigToString ())
and for the Log project add a reference to System.XML
In Main.Program.fs
open Log
[<EntryPoint>]
let main argv =
MyLog.configureNLog ()
MyLog.printNLogConfig ()
0 // return an integer exit code
and for the Main project add a reference to the Log project and set the Main project as the startup project.
When run this should output to the console:
File Target[file]
logNamePattern: (:All) levels: [ Trace Debug Info Warn Error Fatal ] appendTo: [ file ]
2.c. Log a message to the file
In Log.Library1.fs
namespace Log
open NLog
module MyLog =
let configureNLog () =
let projectPath = __SOURCE_DIRECTORY__
let soulutionPath = projectPath + "\.."
let configPath = soulutionPath + #"\NLog.config"
let xmlConfig = new NLog.Config.XmlLoggingConfiguration(configPath)
NLog.LogManager.Configuration <- xmlConfig
let NLogConfigToString () =
let targets = NLog.LogManager.Configuration.AllTargets
let out = ""
let out = Seq.fold (fun out target -> out + (sprintf "%A\n" target)) out targets
let rules = NLog.LogManager.Configuration.LoggingRules
let out = Seq.fold (fun out rule -> out + (sprintf "%A\n" rule)) out rules
out
let printNLogConfig () =
Printf.printfn "%s" (NLogConfigToString ())
let evalTracer = LogManager.GetLogger("file")
In Main.Program.fs
open Log
open Library1
[<EntryPoint>]
let main argv =
MyLog.configureNLog ()
MyLog.printNLogConfig ()
// Add as many of these as needed
MyLog.evalTracer.Trace("In Main #1.")
MyFunctions.test001 ()
0 // return an integer exit code
and for the Main project add a reference to the MyLibrary project.
In MyLibrary.Library1.fs
namespace Library1
open Log
module MyFunctions =
let test001 () =
MyLog.evalTracer.Trace("In Library #1.")
and for the MyLibrary project add a reference to the Log project.
When run the log file log.txt should contain something similar to:
2016-03-28 11:03:52.4963|TRACE|file|In Main #1.
2016-03-28 11:03:52.5263|TRACE|file|In Library #1
3.a. Programmatically create a configuration
If a NLog.config file exist delete it to verify that the code created a new configuration but did not create a file.
To set the configuration programmatically using F# you need to know:
This FileName string is a layout which may include instances of layout renderers. This lets you use a single target to write to multiple files.
SimpleLayout - Represents a string with embedded placeholders that can render contextual information.
To Log.Library1.fs add
let configureNLogPrgramatically () =
let config = new NLog.Config.LoggingConfiguration()
let fileTarget = new NLog.Targets.FileTarget()
let projectPath = __SOURCE_DIRECTORY__
let soulutionPath = projectPath + "\.."
let filePath = soulutionPath + #"\log.txt"
let layout = new NLog.Layouts.SimpleLayout(filePath)
fileTarget.Name <- "file"
fileTarget.FileName <- layout
fileTarget.AutoFlush <- true
config.AddTarget("file", fileTarget)
let rule1 = new NLog.Config.LoggingRule("*",NLog.LogLevel.Trace,fileTarget)
config.LoggingRules.Add(rule1)
NLog.LogManager.Configuration <- config
3.b. Create a configuration for a running project and log a message to the file
In Main.Program.fs
open Log
open Library1
[<EntryPoint>]
let main argv =
MyLog.configureNLogPrgramatically ()
MyLog.printNLogConfig ()
// Add as many of these as needed
MyLog.evalTracer.Trace("In Main #1.")
MyFunctions.test001 ()
0 // return an integer exit code
When run the log file log.txt should contain something similar to:
2016-03-28 11:16:07.2901|TRACE|file|In Main #1.
2016-03-28 11:16:07.3181|TRACE|file|In Library #1.
and note that a NLog.config file was NOT created.
4. Create a configuration and log a message to the file using F# Interactive
In MyLibrary.Script.fsx
// print out __SOURCE_DIRECTORY__ to make sure we are not using the Temp directory
printfn __SOURCE_DIRECTORY__
#I __SOURCE_DIRECTORY__
// Inform F# Interactive where to find functions in Log module
#I "../Log/bin/Debug/"
#r "Log.dll"
open Log
// Functions in Log module can now be run.
MyLog.configureNLogPrgramatically ()
MyLog.printNLogConfig ()
// Inform F# Interactive where to find functions in MyLibrary module
#I "../MyLibrary/bin/Debug/"
#r "MyLibrary.dll"
open Library1
// Functions in MyLibrary module can now be run.
MyFunctions.test001 ()
When the script is executed with F# Interactive
Microsoft (R) F# Interactive version 14.0.23413.0
Copyright (c) Microsoft Corporation. All Rights Reserved.
For help type #help;;
>
<Solution directory>\MyLibrary
val it : unit = ()
--> Added <Solution directory>\MyLibrary' to library include path
--> Added <Solution directory>\MyLibrary\../Log/bin/Debug/' to library include path
--> Referenced <Solution directory>\MyLibrary\../Log/bin/Debug/Log.dll'
File Target[file]
logNamePattern: (:All) levels: [ Trace Debug Info Warn Error Fatal ] appendTo: [ file ]
--> Added <Solution directory>\MyLibrary\../MyLibrary/bin/Debug/' to library include path
--> Referenced <Solution directory>\MyLibrary\../MyLibrary/bin/Debug/MyLibrary.dll'
val it : unit = ()
>
The log file log.txt should contain something similar to:
2016-03-28 11:42:41.5417|TRACE|file|In Library #1.
Also, this will log while you still have an active F# Interactive session, so you can peek at the log between executing commands.

Resources