Cross-Platform Development with F#
To contribute to this guide, log on to GitHub, edit this page and send the pull request.
Table of Contents
- Table of Contents
- Getting Started
- Command Line Tools
- Projects and Build Automation
- Package Repositories
- Portable Libraries
- Unit Testing
- Continuous Integration builds
- Miscellaneous Notes on Open Source and Cross Platform Development
- Having trouble?
- Getting Started with F# on Mac
- Getting Started with F# on Linux
- Getting Started with F# for iOS Programming
- Getting Started with F# for Android Programming
Command Line Tools
You can start F# Interactive using
$ fsharpi > 1+1;; val it : int = 2
You’re off! Some common commands are:
fsharpi (starts F# interactive) fsharpc file.fs (F# compiler) xbuild (builds .fsproj projects and .sln files) mono file.exe arg1 ... argN (runs a compiled F# program) mkbundle --static file.exe -o file (makes a static native image, including the F# runtime)
Some editors have specific support for F#, either builtin or through addons provided by the F# community:
Xamarin Studio has built-in support for F# development on OSX and Windows.
On Debian Linux or derivatives (e.g. Ubuntu, Mint), you can install MonoDevelop via a graphical package manager, such as Synaptic, or through the command line with:
sudo apt-get install monodevelop
You can also build/install MonoDevelop from source if a package is not available/updated for your distribution. You will likely need to build and install the F# AddIn for MonoDevelop yourself, even if you install MonoDevelop from a pre-built package.
Sublime Text 2
If running F# Interactive in Emacs or another similar environment, use
> fsharpi --readline-
to turn of console processing.
For most F# documentation, see the documentation pages.
Projects and Build Automation
.fsproj and .sln files
You can use xbuild to build projects and solutions from Visual Studio without change. Xamarin Studio and MonoDevelop can also create and manage .fsproj and .sln files.
Use xbuild to build projects and solutions:
xbuild RocketPart.fsproj xbuild RocketSolution.sln
Many people doing cross-platform or Mac/Linux development don’t like .sln files. If so, you can also create a .fsproj file that brings together a collection of .fsproj files. Include, for example, root.traversals.targets used in the F# compiler source in a .fsproj like this:
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0"> <ItemGroup> <ProjectFiles Include="fsharp-proto-build.fsproj"/> <ProjectFiles Include="fsharp-library-build.fsproj"/> <ProjectFiles Include="fsharp-compiler-build.fsproj"/> </ItemGroup> <Import Project="root.traversal.targets"/> </Project>
To create a .fsproj file from scratch yourself, either install Xamarin Studio or MonoDevelop, or find an existing one, copy it and edit it by hand.
The F# command-line compiler (fsharpc) can be used with Makefiles in the usual way.
FAKE is a build-and-publish automation utility sometimes used by F# programmers (partly because builds are automated using F# itself, and partly because it is a great tool).
FAKE can be fetched using NuGet.exe, e.g.:
# Get nuget.exe sudo mozroots --import --sync curl -L http://nuget.org/nuget.exe -o nuget.exe # Get FAKE.exe mono nuget.exe install FAKE -OutputDirectory lib -ExcludeVersion -Prerelease
nuget is an open-source, cross-platform package management tool with many thousands of packages available. See the documentation. It is used extensively on Windows ecosystem but is growing in its cross-platform use.
Using nuget from the command line
For those on Mac/Linux, familiarity with the command-line NuGet.exe utility is highly useful. Get the command line utility like this:
# Get nuget.exe sudo mozroots --import --sync curl -L http://nuget.org/nuget.exe -o nuget.exe
Before using NuGet.exe on Mac/Linux, be sure to run
mozroots --import --sync
Typical usage is:
mono nuget.exe install -- installs from packages.config mono nuget.exe install packageId -Version 126.96.36.199 -- installs particular version of particular package
An example packages.config is:
<?xml version="1.0" encoding="utf-8"?> <packages> <package id="FsUnit" version="188.8.131.52" targetFramework="net40" /> <package id="NUnit" version="2.6.2" targetFramework="net40" /> </packages>
See also the documentation.
Using nuget packages as part of a build
F# project files (.fsproj) can be configure to automatically get NuGet packages during a build. The project file should have a line like this (adjust the ‘…’ to reference a copy of NuGet.targets checked into your project).
<Import Project="...\NuGet.targets" Condition="Exists('...\NuGet.targets')" />
A copy of NuGet.exe should be in that directory with executable permissions set. You may also need to set:
It is quite common to check a copy of NuGet.exe into a project, e.g. in lib/NuGet/NuGet.exe.
Using nuget in MonoDevelop and Xamarin Studio
You can add NuGet support to these IDEs. See NuGet for MonoDevelop and Xamarin Studio.
Making nuget packages
See http://nuget.org to learn how to make and publish NuGet packages, or look at examples from other F# community projects.
Other Packaging Mechanisms
Other packaging mechanisms include: * Git sub-modules (especially when building from source) * Traditional Unix packages * Simple .fs files that can be included into projects
Some Examples of Portable, Cross-Platform Packages
Some F# and CLI packages are more cross-platform friendly than others. Many will work with no alteration. Here are some of interest:
FSharp.Core Library, the core F# library
In the wider F# exosystem there are many cross-platform and/or portable packages and libraries. Here are some examples:
Math.Net Numerics - Math.NET Numerics provides cross-platform and portable methods and algorithms for numerical computations in science, engineering and everyday use. Covered topics include special functions, linear algebra, probability models, random numbers, interpolation, integral transforms and more.
ServiceStack - “Thoughtfully architected, obscenely fast, thoroughly enjoyable web services for all”
ReactiveUI - Reactive UI is an MVVM framework built on top of the Reactive Extensions. Version 5.0 is “totally Portable-Friendly”, and supports Xamarin.iOS, Xamarin.Android, Xamarin.Mac, .NET 4.5 (WPF), Windows Phone 8 and Windows Store Apps (WinRT).
Akavache - Akavache is an asynchronous, persistent (i.e. writes to disk) key-value store created for writing desktop and mobile applications in C# and F#. Think of it like memcached for desktop apps. At the time of writing, it is compatible with .NET 4.0/4.5, Mono 3.0 (including Xamarin.Mac), Silverlight 5, Windows Phone 7.1/8.0, and WinRT (Metro / Modern UI / Windows Store / Whatever Microsoft Is Calling That Tablet’y OS Thing That They Make).
Splat – It has cross platform APIs for images and colors, with platform-specific extension methods to go back and forth between the platform-specific native types.
OxyPlot – OxyPlot is a cross-platform plotting library for .NET. The core is a portable library, the package OxyPlot.GtkSharp is usable on Mac/OSX.
There are a wide range of cross-platform libraries available as part of the core libraries available to F#, for example:
Portable .NET libraries have access to less core functionality, called a “portable profile”, but can be used across multiple platforms and multiple profiles of .NET. For example, a portable library may be usable on Mac, Linux, Android, iOS, Windows and Windows Store apps (depending on the versions of runtime machinery used and other factors).
See Cross-Platform Portable Class Libraries with .NET are Happening for a perspective on cross-platform portable libraries for Visual Studio users.
F# portable libraries use FSharp.Core versions such as 184.108.40.206, with matching mscorlib versions. A binding redirect may be neeeded to ensure bindings to these libraries redirect correctly, e.g. to FSharp.Core 220.127.116.11.
At the time of writing, creating portable libraries was not yet fully supported in IDES on Mac and Linux, though you can normally build portable libraries successfully using xbuild and command-line tools on these platforms. If you are developing on Windows, or using libraries on Windows, then consider creating portable libraries to ease cross-platfom use.
A table of .NET unit testing frameworks can be found here.
There are two major ways of writing unit tests in .Net; in a method-per-test style, having the framework use reflection to read the tests and execute them or with the test-is-a-value style, where you pass the framework your test values (much like immutable objects are values).
Fuchu is a test library for .NET, supporting C# and VB.NET but with a special focus on F#. It draws heavily from Haskell’s test-framework and HUnit. You can read about the rationale and underlying concepts in this blog post, or tests should be first-class values so that you can move them around and execute them in any context that you want. Also, if they are first-class values, then you can take extra care with what the test methods return, making integrations with external libraries much cheaper.
Since tests are values, it’s easy to extend the framework to integrate with other tooling, e.g. with FsCheck to use a fuzzing/randomization approach to testing (see below)
Fuchu also has an integration with PerfUtil which can be used to automate performance testing and trending in a continuous integration environment.
FsCheck is a tool for testing .NET programs automatically. The programmer provides a specification of the program, in the form of properties which functions, methods or objects should satisfy, and FsCheck then tests that the properties hold in a large number of randomly generated cases.
NUnit is an open-source, cross-platform unit-testing framework for F# and other .NET languages. It is written in C# and has been completely redesigned to take advantage of many .NET language features, for example custom attributes and other reflection related capabilities. Also xUnit is a good alternative for NUnit.
Some guides to using F# and NUnit together are:
Continuous Integration builds
Using Travis and AppVeyor
Perhaps the simplest way to regularly build and test your work across multiple platforms is to use Travis.
You can automate the build and test of all commits and pull requests to GitHub projects on Linux and OSX by using Travis. This is very easy to arrange, just add a .travis.yml file to your project root (example, example ), and register the project in your Travis account.
Setting the language to “objective-c” causes Travis to use an OSX machine for build.
Travis is free for open source projects.
To also automate your build and test on Windows, AppVeyor is a good choice. Here is an example configuration file.
Miscellaneous Notes on Open Source and Cross Platform Development
Testing on multiple platforms
If you are Windows developers, you can set up a Vagrant box in order to test your libraries and tools on Mono (though often a Travis build is simpler, see above) A detailed guide of setting up Vagrant is available here.
Dos and Don’ts
- Generally use
\on paths. In .fsproj files you can generally use either.
- In .fsproj files, don’t use copy commands on PostBuildEvent’s, but use the MSBuild Copy task itself (example)
- Don’t assume pdbs are always created after the compilation
- Executables included in .NET may not exist in Mono or may have a different name or location e.g. SvcUtil etc
- Fake build scripts may not work as intended due to Mono issues
- MSBuild API is incomplete in Mono, programatic API usage might fail
- Changing the build order inside Xamarin Studio won’t have effect when using MSBuild or Visual Studio, prefer editing the project file by hand
- NuGet can be troublesome
- External components that would be available via NuGet in Windows might be included as part of Mono - Rx, TDF etc
- MSBuild targets might be different in Mono
- Don’t rely the registry, also Mono can use a version of it, it can be fright with issues
- Avoid Windows Forms/WPF in favour of native UI frameworks
- Beware differences in behaviour with loading assemblies which is a very niche problem though. Generally the less trodden the code is, the more subtle differences there are.
- When using NUnit, create your test fixtures with classes and methods, exactly the way you’d do in C# (Trying to use modules as test fixtures will trigger odd behaviors on Xamarin Studio).
Differences in F# Interactive DLL resolution. Use
#I @"./lib/FAKE/tools" #r @"./lib/FAKE/tools/FakeLib.dll"
If your build executes binaries and tasks, make sure the “x” permissions are set for Fsc.exe etc. and all other executables triggered by xbuild.
- Beware of NuGet package restore bug. In NuGet.targets, the “solutionDir” argument has an extra space. This breaks package restore on Mono.
Developing Cross-Platform and Multi-Targeting Type Providers
F# type providers are compile-time components that must execute on your machine during build and development.
A type provider executing on Mac/Linux can expose some small differences in the implementation of .NET, for example in the System.Type implementation. The ProvidedTypes API can normally be adjusted to account for these.
To help isolate the problem, try the following:
- Start with a simple file that uses the type provider and compile it using
fsc.exeon Windows. This should succeed.
- Now compile the file on Windows using the open source
fsc.exe(this will run using .NET). This should succeed (if not, there is a bug in the open source compiler).
- Now compile the file on Windows using the open source
mono fsc.exe(this will run using Mono). If this fails, then there are differences in Mono vs .NET exposed by the type provider. The type provider can probably be adjusted.
- If that succeeded, then try the same command-line compilation on, say, OSX. If this fails then the type provider may not be cross-platform, e.g. may rely on Windows-only functionality. Diagnostics from the type provider may need improving.
- If that succeeded, then check if the type provider works from MonoDevelop. If not then the problem is with the MonoDevelop binding (but that is very unlikely because it doesn’t know anything specific about type providers).
Switching to command-line compilations will help localize the problem.