Home

Awesome

BinDeps.jl

BinDeps Travis AppVeyor

Easily build binary dependencies for Julia packages

FAQ

Since there seems to be a lot of confusion surrounding the package systems and the role of this package, before we get started looking at the actual package, I want to answer a few common questions:

run(`make`)
deps/
    build.jl        # This is your build file
    downloads/      # Store any binary/source downloads here
    builds/
        dep1/       # out-of-tree build for dep1, is possible
        dep2/       # out-of-tree build for dep2, is possible
        ...
    src/
        dep1/       # Source code for dep1
        dep2/       # Source code for dep2
        ...
    usr/            # "prefix", install your binaries here
        lib/        # Dynamic libraries (yes even on Windows)
        bin/        # Excecutables
        include/    # Headers
        ...

The high level interface - Declaring dependencies

To get a feel for the high level interface provided by BinDeps, have a look at real-world examples. The build script from the GSL pakage illustrates the simple case where only one library is needed. On the other hand, the build script from the Cairo package uses almost all the features that BinDeps currently provides and offers a complete overview. Let's take it apart, to see exactly what's going on.

As you can see Cairo depends on a lot of libraries that all need to be managed by this build script. Every one of these library dependencies is introduced by the library_dependency function. The only required argument is the name of the library, so the following would be an entirely valid call:

foo = library_dependency("libfoo")

However, you'll most likely quickly run into the issue that this library is named differently on different systems. (If this happens, you will receive an error such as Provider Binaries failed to satisfy dependency libfoo.) This is why BinDeps provides the handy aliases keyword argument. So suppose our library is sometimes known as libfoo.so, but other times as libfoo-1.so or libfoo-1.0.0.dylib or even libbar.dll on windows, because the authors of the library decided to punish windows users. In either case, we can easily declare all these in our library dependency:

foo = library_dependency("libfoo", aliases = ["libfoo", "libfoo-1", "libfoo-1.0.0", "libbar"])

So far so good! There are a couple of other keyword arguments that are currently implemented:

gettext = library_dependency("gettext", aliases = ["libgettext", "libgettextlib"], os = :Unix)
cairo = library_dependency("cairo", aliases = ["libcairo-2", "libcairo"], depends = [gobject, fontconfig, libpng])
function validate_cairo_version(name,handle)
    f = Libdl.dlsym_e(handle, "cairo_version")
    f == C_NULL && return false
    v = ccall(f, Int32,())
    return v > 10800
end
...
cairo = library_dependency("cairo", aliases = ["libcairo-2", "libcairo"], validate = validate_cairo_version)

Other keyword arguments will most likely be added as necessary.

The high level interface - Declaring build mechanisms

Alright, now that we have declared all the dependencies that we need let's tell BinDeps how to build them. One of the easiest ways to do so is to use the system package manager. So suppose we have defined the following dependencies:

foo = library_dependency("libfoo")
baz = library_dependency("libbaz")

Let's suppose that these libraries are available in the libfoo-dev and libbaz-dev in apt-get and that both libraries are installed by the baz or the baz1 yum package, and the baz pacman package. We may declare this as follows:

provides(AptGet, Dict("libfoo-dev" => foo, "libbaz-dev" => baz))
provides(Yum, ["baz", "baz1"], [foo, baz])
provides(Pacman, "baz", [foo, baz])

One may remember the provides function by thinking AptGet provides the dependencies foo and baz.

The basic signature of the provides function is

provides(Provider, data, dependency, options...)

where data is provider-specific (e.g. a string in all of the package manager cases) and dependency is the return value from library_dependency. As you saw above multiple definitions may be combined into one function call as such:

provides(Provider, Dict(data1=>dep1, data2=>dep2), options...)

which is equivalent to (and in fact will be internally dispatched) to:

provides(Provider, data1, dep1, options...)
provides(Provider, data2, dep2, options...)

If one provide satisfied multiple dependencies simultaneously, dependency may also be an array of dependencies (as in the Yum and Pacman cases above).

There are also several builtin options. Some of them are:

Provides a SHA-256 checksum to validate a downloaded source or binary file against.

The high level interface - built in providers

We have already seen the AptGet and Yum providers, which all take a string naming the package as their data argument. The other build-in providers are:

provides(Sources,URI("http://libvirt.org/sources/libvirt-1.1.1-rc2.tar.gz"), libvirt,
    unpacked_dir = "libvirt-1.1.1")
Autotools(; options...)

The high level interface - Loading dependencies

To load dependencies without a runtime dependence on BinDeps, place code like the following near the start of the Package's primary file. Don't forget to change the error message to reflect the name of the package.

const depsfile = joinpath(dirname(@__FILE__), "..", "deps", "deps.jl")
if isfile(depsfile)
    include(depsfile)
else
    error("HDF5 not properly installed. Please run Pkg.build(\"HDF5\") then restart Julia.")
end

This will make all your libraries available as variables named by the names you gave the dependency. E.g. if you declared a dependency as

library_dependency("libfoo")

The libfoo variable will now contain a reference to that library that may be passed to ccall or similar functions.

The low level interface

The low level interface provides a number of utilities to write cross platform build scripts. It looks something like this (from the Cairo build script):

@build_steps begin
    GetSources(libpng)
    CreateDirectory(pngbuilddir)
    @build_steps begin
        ChangeDirectory(pngbuilddir)
        FileRule(joinpath(prefix,"lib","libpng15.dll"),@build_steps begin
            `cmake -DCMAKE_INSTALL_PREFIX="$prefix" -G"MSYS Makefiles" $pngsrcdir`
            `make`
            `cp libpng*.dll $prefix/lib`
            `cp libpng*.a $prefix/lib`
            `cp libpng*.pc $prefix/lib/pkgconfig`
            `cp pnglibconf.h $prefix/include`
            `cp $pngsrcdir/png.h $prefix/include`
            `cp $pngsrcdir/pngconf.h $prefix/include`
        end)
    end
end

All the steps are executed synchronously. The result of the @build_steps macro may be passed to run to execute it directly, thought this is not recommended other than for debugging purposes. Instead, please use the high level interface to tie the build process to dependencies.

Some of the builtin build steps are:

Diagnostics

A simple way to see what libraries are required by a package, and to detect missing dependencies, is to use BinDeps.debug("PackageName"):

julia> using BinDeps

julia> BinDeps.debug("Cairo")
INFO: Reading build script...
The package declares 1 dependencies.
 - Library Group "cairo" (satisfied by BinDeps.SystemPaths, BinDeps.SystemPaths)
     - Library "png" (not applicable to this system)
     - Library "pixman" (not applicable to this system)
     - Library "ffi" (not applicable to this system)
     - Library "gettext"
        - Satisfied by:
          - System Paths at /usr/lib64/preloadable_libintl.so
          - System Paths at /usr/lib64/libgettextpo.so
        - Providers:
          - BinDeps.AptGet package gettext (can't provide)
          - BinDeps.Yum package gettext-libs (can't provide)
          - Autotools Build