lecaillon / Evolve

Database migration tool for .NET and .NET Core projects. Inspired by Flyway.
https://evolve-db.netlify.com
MIT License
849 stars 110 forks source link

Can't run evolve on Alpine Linux #124

Closed oliver-dungey closed 5 years ago

oliver-dungey commented 5 years ago

Love your software - I've been using Flyway for years and Evolve is perfect for my dotnet needs, thanks for sharing it with the world.

I am running Alpine Linux on my Docker containers and struggling to get Evolve to work due to dynamic linking problems. No problems on Fedora and other distros but Alpine uses muscl instead of glibc (see: https://stackoverflow.com/questions/37818831/is-there-a-best-practice-on-setting-up-glibc-on-docker-alpine-linux-base-image).

Here is the crux of the matter (after installing libc6-compat package which fixed one of the dependency issues):

/var/www/tmp $ ./evolve sqlserver
Error relocating ./evolve: __register_atfork: symbol not found
Error relocating ./evolve: __rawmemchr: symbol not found
/var/www/tmp $ ldd ./evolve
    /lib64/ld-linux-x86-64.so.2 (0x7fef9df20000)
    libdl.so.2 => /lib64/ld-linux-x86-64.so.2 (0x7fef9df20000)
    librt.so.1 => /lib64/ld-linux-x86-64.so.2 (0x7fef9df20000)
    libpthread.so.0 => /lib64/ld-linux-x86-64.so.2 (0x7fef9df20000)
    libgcc_s.so.1 => /usr/lib/libgcc_s.so.1 (0x7fef9da60000)
    libc.so.6 => /lib64/ld-linux-x86-64.so.2 (0x7fef9df20000)
Error relocating ./evolve: __register_atfork: symbol not found
Error relocating ./evolve: __rawmemchr: symbol not found

I'm not familiar enough with cross compiling to know what the best answer is, looking at your source code it's all C# so not quite sure what's going on.

oliver-dungey commented 5 years ago

It looks like there is a target runtime for Alpine Linux named linux-musl-x64, found it here: https://docs.microsoft.com/en-us/dotnet/core/rid-catalog

Not managed to build Evolve with that yet but looks possible.

oliver-dungey commented 5 years ago

Ok, so it was a simple step to build it and it run's on Alpine very nicely: dotnet build 'Evolve.Cli' --configuration Release --runtime linux-musl-x64

Would be nice to have an official Alpine release from GitHub.

oliver-dungey commented 5 years ago

@lecaillon The use case for Alpine Linux is:

And we love Evolve because it's a like for like match to Flyway which we use with all our Java applications and have done for a number of years.

lecaillon commented 5 years ago

Did you manage to generate a single binary, or you just used the result of the dotnet build 'Evolve.Cli' --configuration Release --runtime linux-musl-x64 command ?

oliver-dungey commented 5 years ago

@lecaillon I just tar.gz the build output as that is the easiest format to install on Docker containers. Here is the directory listing after the build - there are a couple of .so (shared object libraries) so you need a little bit more than the basic dll:

PS E:\Evolve\src\Evolve.Cli\bin\Release\netcoreapp2.2\linux-musl-x64> dir

    Directory: E:\Evolve\src\Evolve.Cli\bin\Release\netcoreapp2.2\linux-musl-x64

Mode                LastWriteTime         Length Name
----                -------------         ------ ----
-a----       30/10/2019     08:43         122216 Evolve.Cli.Cli
-a----       30/10/2019     08:43          58074 Evolve.Cli.deps.json
-a----       30/10/2019     08:43          15360 Evolve.Cli.dll
-a----       30/10/2019     08:43           2376 Evolve.Cli.pdb
-a----       30/10/2019     08:43            246 Evolve.Cli.runtimeconfig.dev.json
-a----       30/10/2019     08:43             28 Evolve.Cli.runtimeconfig.json
-a----       30/10/2019     08:43         153088 Evolve.dll
-a----       30/10/2019     08:43          40728 Evolve.pdb
-a----       10/11/2018     23:33         719376 libhostfxr.so
-a----       10/11/2018     23:34         735752 libhostpolicy.so

And this is my build script that I'm running on Ubuntu Agents in our Azure DevOps pipeline:

#!/bin/bash

# Uncomment to debug
# set -x

# Build the Evolve database migration CLI for Alpine Linux direct from the GitHub source
# (they don't build Alpine as a standard target)

rootDir=$(pwd)
# Clear down any previous git clone
rm -rf Evolve \
  && git clone https://github.com/lecaillon/Evolve.git \
  && cd Evolve/src \
  && dotnet build 'Evolve.Cli' --configuration Release --runtime linux-musl-x64 \
  && cd Evolve.Cli/bin/Release/netcoreapp2.2/linux-musl-x64 \
  && tar -zcvf ${rootDir}/pipeline-utils/linux/alpine/evolve-cli.tar.gz . \
  && echo 'Evolve CLI Build Complete'
oliver-dungey commented 5 years ago

@lecaillon An aside - some of my colleagues don't like the idea of the container startup race condition I am creating with this pattern i.e. 10 containers starting at the same time trying to create/migrate the database but it is a rock solid pattern as all the migration uses database level locking and creating the initial database is pretty much instant. Just for reference this is the sort of code I put in my Docker container startup (the Python code just attempts to create the database and CRUD user, if they already exist an exception is caught and it just returns success). The credentials for the admin/CRUD user are all sourced from the AWS Systems Manager Parameter store. When we launch the application it runs using the CRUD user to reduce the security threat of running an application with DBA creds:

# Create the database if it isn't there already
python3 /opt/ssp/bin/SQLServerDatabaseInitiator.py \
  --server ${dbHost} \
  --port ${dbPort} \
  --db ${dbName} \
  --admin-user ${dbAdminUser} \
  --admin-password ${dbAdminPassword} \
  --crud-user ${dbCrudUser} \
  --crud-password ${dbCrudPassword}
error=$?
if [ $error -ne 0 ]; then
  log_error_exit "Failed to create [$dbName] database and CRUD user with error code [$error]"
fi

# Run the database migrations
dotnet /opt/evolve/Evolve.Cli.dll migrate sqlserver -c "${dbAdminConnectionString}" -l /opt/ssp/sql
error=$?
if [ $error -ne 0 ]; then
  log_error_exit "Failed to run Evolve database migrations with error code [$error]"
fi
lecaillon commented 5 years ago

Superb! Thank's for the insights. I'm going to try to build the release using the "new" .NET Core 3 PublishTrimmed option, because the MSBuild task ILLink.Tasks I currently use, does not work for a linux-musl-x64 build. And then use warp to package it in a self-contained single binary.

oliver-dungey commented 5 years ago

@lecaillon Thanks for giving thought cycles to my use case, much appreciated. I really like the idea of a single binary although not sure if I can use as I am stuck on .NET 2.2 at the moment due to constraints in the applications I'm deploying. So in my Dockerfile I reference the Microsoft 2.2 base image:

FROM microsoft/dotnet:2.2-aspnetcore-runtime-alpine
lecaillon commented 5 years ago

No problem, the runtime is included in the "package". It would be another asset available in the releases page. At least it is the idea ^^

See the Linux version for an example: evolve_2.3.0_Linux-64bit.tar.gz

oliver-dungey commented 5 years ago

@lecaillon that would be fantastic, and hopefully would get Evolve a bit more exposure in the Docker world.

lecaillon commented 5 years ago

When I will have something (soon hopefully) I will use you to be my beta tester for that part ;) Thx again Oliver

oliver-dungey commented 5 years ago

@lecaillon cool, very happy to do that. Let me know if you need any further info.

lecaillon commented 5 years ago

And by the way you never noticed the release section where the Evolve CLI is avalable for Windows and Linux as a single self contained binary. If no, I could perhaps make a clearer statement about it in the doc ...?

lecaillon commented 5 years ago

And another (and last I hope ^^) question, could you confirmed that this Evolve CLI does not work in a Docker Alpine image. Thx

lecaillon commented 5 years ago

I built Evolve.CLI with the linux-musl-x64 RID and then use warp to produce the self-contained single binary you can find here

Feel free to test it and tell me if it works for you. (I've got a little doubt about the warp package on an Alpine release.)

PS: This binary (like all Evolve.CLI releases) embeds the netcore3 runtime. No need to install it.

oliver-dungey commented 5 years ago

@lecaillon No problem, I will try and get to testing today and get back to you

lecaillon commented 5 years ago

You said,

all the migration uses database level locking

I remind you Evolve uses a session level lock to coordinate the migrations on multiple nodes. This prevents two distinct Evolve executions from executing an Evolve command on the same database at the same time. See Configuration section, option: Evolve.EnableClusterMode

In other words, only one instance of Evolve runs at a time.

So you don't have to manage it in your migrations (if I understood correctly what you described)

oliver-dungey commented 5 years ago

@lecaillon thanks, that's brilliant - I had totally missed that option and will use it. I was relying on your session locks but hadn't spotted I needed to enable it!

I hadn't noticed that the releases are a single binary as there wasn't an Alpine one to try. I just tried the Linux release on Fedora and it works great. I've also tried the evolve-musl binary from your link but something isn't working right as all I get is:

bash-5.0# ./evolve-musl 
bash: ./evolve-musl: No such file or directory

Which is kind of odd, I've tried downloading it a few times but it stays as:

-rwxr-xr-x    1 root     root      20997424 Nov  1 11:03 evolve-musl
lecaillon commented 5 years ago

Ok, I think wrap's not working on Alpine release. I will try something else in order to produce a single file for Evolve Alpine.

Anyway, Evolve.EnableClusterMode is true by default, no need to specify it. I was just thinking that you were taking care of that part in each of your migration script. My bad.

lecaillon commented 5 years ago

I use the new .NET Core 3 option PublishSingleFile instead of Warp. Here is the result you can try: Evolve-CLI

The advantages are it is an entirely self-contained single binary that includes the runtime. So no dependency on anything. The drawback is the size. Warp is currently more efficient. I went from 20 MB to 50 MB.

Please tell me if it works for a start ^^ and if the size is not a no go in your use case.

oliver-dungey commented 5 years ago

I have just given the new 50MB file a quick basic startup test. It starts and has the appearance that it works but not quite as it dumped a core file on asking for the help:

bash-5.0# ./evolve-cli
The Command field is required.
Specify --help for a list of available options and commands.
bash-5.0# ./evolve-cli --help
less: unrecognized option: K
BusyBox v1.30.1 (2019-06-12 17:51:55 UTC) multi-call binary.

Usage: less [-EFIMmNSRh~] [FILE]...

View FILE (or stdin) one screenful at a time

    -E  Quit once the end of a file is reached
    -F  Quit if entire file fits on first screen
    -I  Ignore case in all searches
    -M,-m   Display status line with line numbers
        and percentage through the file
    -N  Prefix line number to each line
    -S  Truncate long lines
    -R  Remove color escape codes in input
    -~  Suppress ~s displayed past EOF
Unhandled exception. System.IO.IOException: Broken pipe
   at System.IO.FileStream.WriteNative(ReadOnlySpan`1 source)
   at System.IO.FileStream.FlushWriteBuffer()
   at System.IO.FileStream.Dispose(Boolean disposing)
   at System.IO.Stream.Close()
   at System.IO.StreamWriter.CloseStreamFromDispose(Boolean disposing)
   at System.IO.StreamWriter.Dispose(Boolean disposing)
   at System.IO.TextWriter.Dispose()
   at McMaster.Extensions.CommandLineUtils.Pager.Dispose()
   at McMaster.Extensions.CommandLineUtils.CommandLineApplication.ShowHelp(Boolean usePager)
   at McMaster.Extensions.CommandLineUtils.CommandLineProcessor.ProcessOption(OptionArgument arg)
   at McMaster.Extensions.CommandLineUtils.CommandLineProcessor.ProcessNext()
   at McMaster.Extensions.CommandLineUtils.CommandLineProcessor.Process()
   at McMaster.Extensions.CommandLineUtils.CommandLineApplication.Parse(String[] args)
   at McMaster.Extensions.CommandLineUtils.CommandLineApplication.ExecuteAsync(String[] args, CancellationToken cancellationToken)
   at McMaster.Extensions.CommandLineUtils.CommandLineApplication.ExecuteAsync[TApp](CommandLineContext context, CancellationToken cancellationToken)
   at McMaster.Extensions.CommandLineUtils.CommandLineApplication.Execute[TApp](CommandLineContext context)
   at McMaster.Extensions.CommandLineUtils.CommandLineApplication.Execute[TApp](IConsole console, String[] args)
   at McMaster.Extensions.CommandLineUtils.CommandLineApplication.Execute[TApp](String[] args)
   at Evolve.Cli.Program.Main(String[] args)
Aborted (core dumped)

I also compared the size of your single binary with the size of the multi file build I'm running:

bash-5.0# du -sh evolve
57.2M   evolve

So I suspect your build must very close to working. It was a while back that I put together my Docker build and I've just noticed that I'm not actually running the CLI but the DLL version with this command:

dotnet /opt/evolve/Evolve.Cli.dll migrate mysql -c "$dbAdminConnectionString" -l /opt/sql

I was just hacking when I got it working and haven't been back to try the CLI - I was struggling with VisualStudio basics as I haven't done any Windows development since about 2005 and so was just happy it was running on Alpine. Out of interest this is the directory listing of the only version I have fully built and fully tested - there is a lot of files and I'm not sure all of them are required:

ash-5.0# ls -al
total 58580
drwxrwx--x    1 root     root         12288 Nov  1 12:01 .
drwxr-xr-x    1 root     root          4096 Nov  1 12:01 ..
-rw-r--r--    1 root     root        665088 Apr  2  2019 Cassandra.dll
-rw-r--r--    1 root     root        122216 May 21 10:13 Evolve.Cli.Cli
-rw-r--r--    1 root     root         54541 May 22 08:26 Evolve.Cli.deps.json
-rw-r--r--    1 root     root         15360 May 22 08:26 Evolve.Cli.dll
-rw-r--r--    1 root     root          2396 May 22 08:26 Evolve.Cli.pdb
-rw-r--r--    1 root     root           246 May 22 08:26 Evolve.Cli.runtimeconfig.dev.json
-rw-r--r--    1 root     root            28 May 22 08:26 Evolve.Cli.runtimeconfig.json
-rw-r--r--    1 root     root        136704 May 22 08:25 Evolve.dll
-rw-r--r--    1 root     root         35072 May 22 08:25 Evolve.pdb
-rw-r--r--    1 root     root        141288 Apr 11  2019 McMaster.Extensions.CommandLineUtils.dll
-rw-r--r--    1 root     root         35840 Jun 22  2016 Microsoft.Extensions.DependencyInjection.Abstractions.dll
-rw-r--r--    1 root     root         43512 Jun 22  2016 Microsoft.Extensions.Logging.Abstractions.dll
-rw-r--r--    1 root     root         18424 Jun 22  2016 Microsoft.Extensions.Logging.dll
-rw-r--r--    1 root     root         10240 Mar 22  2019 Microsoft.Win32.Primitives.dll
-rw-r--r--    1 root     root         45200 May 22 07:23 Microsoft.Win32.Registry.dll
-rw-r--r--    1 root     root        416256 May 11 19:32 MySqlConnector.dll
-rw-r--r--    1 root     root        731136 Apr 11  2019 Npgsql.dll
-rw-r--r--    1 root     root       2644400 Mar  3  2019 SQLite.Interop.dll
-rw-r--r--    1 root     root          4608 Mar 22  2019 System.Buffers.dll
-rw-r--r--    1 root     root        198656 Mar 22  2019 System.Collections.Concurrent.dll
-rw-r--r--    1 root     root        653824 Mar 22  2019 System.Collections.Immutable.dll
-rw-r--r--    1 root     root         88064 Mar 22  2019 System.Collections.NonGeneric.dll
-rw-r--r--    1 root     root         80896 Mar 22  2019 System.Collections.Specialized.dll
-rw-r--r--    1 root     root        326144 Mar 22  2019 System.Collections.dll
-rw-r--r--    1 root     root        140288 Mar 22  2019 System.ComponentModel.Annotations.dll
-rw-r--r--    1 root     root         44032 Mar 22  2019 System.ComponentModel.Primitives.dll
-rw-r--r--    1 root     root        668160 Mar 22  2019 System.ComponentModel.TypeConverter.dll
-rw-r--r--    1 root     root          6144 Mar 22  2019 System.ComponentModel.dll
-rw-r--r--    1 root     root        189440 Mar 22  2019 System.Console.dll
-rw-r--r--    1 root     root       2872320 Mar 22  2019 System.Data.Common.dll
-rw-r--r--    1 root     root        350208 Mar  3  2019 System.Data.SQLite.dll
-rw-r--r--    1 root     root        943152 Apr 24  2019 System.Data.SqlClient.dll
-rw-r--r--    1 root     root          4608 Mar 22  2019 System.Diagnostics.Debug.dll
-rw-r--r--    1 root     root         57344 Mar 22  2019 System.Diagnostics.DiagnosticSource.dll
-rw-r--r--    1 root     root         27648 Mar 22  2019 System.Diagnostics.FileVersionInfo.dll
-rw-r--r--    1 root     root        186880 Mar 22  2019 System.Diagnostics.Process.dll
-rw-r--r--    1 root     root         23552 Mar 22  2019 System.Diagnostics.StackTrace.dll
-rw-r--r--    1 root     root          6144 Mar 22  2019 System.Diagnostics.Tools.dll
-rw-r--r--    1 root     root        114688 Mar 22  2019 System.Diagnostics.TraceSource.dll
-rw-r--r--    1 root     root         26112 Mar 22  2019 System.Diagnostics.Tracing.dll
-rw-r--r--    1 root     root         92672 Mar 22  2019 System.Drawing.Primitives.dll
-rw-r--r--    1 root     root         95560 Mar 21  2019 System.Globalization.Native.so
-rw-r--r--    1 root     root          4608 Mar 22  2019 System.Globalization.dll
-rw-r--r--    1 root     root        801160 Mar 21  2019 System.IO.Compression.Native.so
-rw-r--r--    1 root     root        257536 Mar 22  2019 System.IO.Compression.dll
-rw-r--r--    1 root     root        202240 Mar 22  2019 System.IO.FileSystem.dll
-rw-r--r--    1 root     root         57344 Mar 22  2019 System.IO.MemoryMappedFiles.dll
-rw-r--r--    1 root     root        112128 Mar 22  2019 System.IO.Pipes.dll
-rw-r--r--    1 root     root       1620480 Mar 22  2019 System.Linq.Expressions.dll
-rw-r--r--    1 root     root        427520 Mar 22  2019 System.Linq.dll
-rw-r--r--    1 root     root        245760 Mar 22  2019 System.Memory.dll
-rw-r--r--    1 root     root         59528 Mar 21  2019 System.Native.so
-rw-r--r--    1 root     root         14392 Mar 21  2019 System.Net.Http.Native.so
-rw-r--r--    1 root     root       1184768 Mar 22  2019 System.Net.Http.dll
-rw-r--r--    1 root     root         69120 Mar 22  2019 System.Net.NameResolution.dll
-rw-r--r--    1 root     root        148480 Mar 22  2019 System.Net.NetworkInformation.dll
-rw-r--r--    1 root     root        201216 Mar 22  2019 System.Net.Primitives.dll
-rw-r--r--    1 root     root        337408 Mar 22  2019 System.Net.Requests.dll
-rw-r--r--    1 root     root         10416 Mar 21  2019 System.Net.Security.Native.so
-rw-r--r--    1 root     root        497152 Mar 22  2019 System.Net.Security.dll
-rw-r--r--    1 root     root         24064 Mar 22  2019 System.Net.ServicePoint.dll
-rw-r--r--    1 root     root        570880 Mar 22  2019 System.Net.Sockets.dll
-rw-r--r--    1 root     root         56832 Mar 22  2019 System.Net.WebHeaderCollection.dll
-rw-r--r--    1 root     root         76288 Mar 22  2019 System.ObjectModel.dll
-rw-r--r--    1 root     root      11768832 Mar 21  2019 System.Private.CoreLib.dll
-rw-r--r--    1 root     root        230400 Mar 22  2019 System.Private.Uri.dll
-rw-r--r--    1 root     root       8493568 Mar 22  2019 System.Private.Xml.dll
-rw-r--r--    1 root     root          4608 Mar 22  2019 System.Reflection.Emit.ILGeneration.dll
-rw-r--r--    1 root     root          4608 Mar 22  2019 System.Reflection.Emit.Lightweight.dll
-rw-r--r--    1 root     root          4608 Mar 22  2019 System.Reflection.Emit.dll
-rw-r--r--    1 root     root       1069056 Mar 22  2019 System.Reflection.Metadata.dll
-rw-r--r--    1 root     root          5120 Mar 22  2019 System.Reflection.Primitives.dll
-rw-r--r--    1 root     root          5632 Mar 22  2019 System.Reflection.dll
-rw-r--r--    1 root     root          4608 Mar 22  2019 System.Resources.ResourceManager.dll
-rw-r--r--    1 root     root         33280 Mar 22  2019 System.Resources.Writer.dll
-rw-r--r--    1 root     root         23088 Sep 18  2018 System.Runtime.CompilerServices.Unsafe.dll
-rw-r--r--    1 root     root        384512 Mar 22  2019 System.Runtime.Extensions.dll
-rw-r--r--    1 root     root         14848 Mar 22  2019 System.Runtime.InteropServices.RuntimeInformation.dll
-rw-r--r--    1 root     root         36352 Mar 22  2019 System.Runtime.InteropServices.dll
-rw-r--r--    1 root     root        187904 Mar 22  2019 System.Runtime.Numerics.dll
-rw-r--r--    1 root     root        288256 Mar 22  2019 System.Runtime.Serialization.Formatters.dll
-rw-r--r--    1 root     root         16896 Mar 22  2019 System.Runtime.Serialization.Primitives.dll
-rw-r--r--    1 root     root         39424 Mar 22  2019 System.Runtime.dll
-rw-r--r--    1 root     root         54416 May 22 07:21 System.Security.AccessControl.dll
-rw-r--r--    1 root     root         78336 Mar 22  2019 System.Security.Claims.dll
-rw-r--r--    1 root     root        357888 Mar 22  2019 System.Security.Cryptography.Algorithms.dll
-rw-r--r--    1 root     root         62976 Mar 22  2019 System.Security.Cryptography.Encoding.dll
-rw-r--r--    1 root     root        141616 Mar 21  2019 System.Security.Cryptography.Native.OpenSsl.so
-rw-r--r--    1 root     root        156672 Mar 22  2019 System.Security.Cryptography.OpenSsl.dll
-rw-r--r--    1 root     root         79872 Mar 22  2019 System.Security.Cryptography.Primitives.dll
-rw-r--r--    1 root     root        436224 Mar 22  2019 System.Security.Cryptography.X509Certificates.dll
-rw-r--r--    1 root     root         29184 Mar 22  2019 System.Security.Principal.Windows.dll
-rw-r--r--    1 root     root          5120 Mar 22  2019 System.Security.Principal.dll
-rw-r--r--    1 root     root        758928 May 22 07:19 System.Text.Encoding.CodePages.dll
-rw-r--r--    1 root     root          4608 Mar 22  2019 System.Text.Encoding.Extensions.dll
-rw-r--r--    1 root     root        365568 Mar 22  2019 System.Text.RegularExpressions.dll
-rw-r--r--    1 root     root          5120 Mar 22  2019 System.Threading.Tasks.Extensions.dll
-rw-r--r--    1 root     root          5632 Mar 22  2019 System.Threading.Tasks.dll
-rw-r--r--    1 root     root         31232 Mar 22  2019 System.Threading.Thread.dll
-rw-r--r--    1 root     root          4608 Mar 22  2019 System.Threading.ThreadPool.dll
-rw-r--r--    1 root     root          4608 Mar 22  2019 System.Threading.Timer.dll
-rw-r--r--    1 root     root         66560 Mar 22  2019 System.Threading.dll
-rw-r--r--    1 root     root        341504 Mar 22  2019 System.Transactions.Local.dll
-rw-r--r--    1 root     root         11776 Mar 22  2019 System.Xml.ReaderWriter.dll
-rw-r--r--    1 root     root          7168 Mar 22  2019 System.Xml.XmlSerializer.dll
-rw-r--r--    1 root     root       3117400 Mar 21  2019 libclrjit.so
-rw-r--r--    1 root     root      10136712 Mar 21  2019 libcoreclr.so
-rw-r--r--    1 root     root        719376 Mar 22  2019 libhostfxr.so
-rw-r--r--    1 root     root        735752 Mar 22  2019 libhostpolicy.so
-rw-r--r--    1 root     root         96768 Mar 22  2019 netstandard.dll
lecaillon commented 5 years ago

Ok so not that simple to have the CLI running on Alpine.

Can you explain in detail when Evolve runs, what start it, when the app runs, what start it ? Not sure I cleary understood it yet :)

Can you give me a dockerfile or at least an example that I can work on on my side that mimics your usage ?

Thx Oliver

oliver-dungey commented 5 years ago

Sorry haven't had time to get together a nice set of working files for you yet but it's on my urgent list!

lecaillon commented 5 years ago

Let's move to this issue #144 now :)