Building for NixOS

I’ve been daily driving NixOS for a couple of months and recently I changed jobs and started working with medical imaging where Orthanc has been fantastic :pray:

To my surprise, Orthanc is not present in the nix package manager, so I wanted to give it a try and contribute it upstream.

I’m not familiar with building C++ projects and I’ve encountering an issue I’ve not been able to get around. Here are the error logs:

CMake Error at /build/hg-archive/OrthancFramework/Resources/CMake/DcmtkConfiguration.cmake:312 (message):
  Cannot locate the DICOM dictionary on this system
Call Stack (most recent call first):
  /build/hg-archive/OrthancFramework/Resources/CMake/OrthancFrameworkConfiguration.cmake:523 (include)
  CMakeLists.txt:80 (include)

I’ve tried a couple of things, mainly to set -DDCMTK_DICTIONARY_DIR to DCMTK’s dictionary directory:
"-DDCMTK_DICTIONARY_DIR=${pkgs.dcmtk}/share/dcmtk-${pkgs.dcmtk.version}/"
which evaluates to
DDCMTK_DICTIONARY_DIR=/nix/store/wj2r80pwpfndq9xq1v560c5pmgpgsahh-dcmtk-3.6.8/share/dcmtk-3.6.8/".
Locally inspecting the directory, it correctly contains the dicom.dic file, which I think it was it’s searching for?

If you have any suggestion on how to deal with this, it would be very helpful!

Here is a repo if you want to reproduce the error https://github.com/dvcorreia/healthcare-oss-flake.
To build, go to pkgs/orthanc and run docker build ..

Hello,

I don’t know the Nix build process but gave it a try.

I think your CMake flags are not taken into account.

First I changed this line:

cmake ../OrthancServer/

to

cmake -DDCMTK_DICTIONARY_DIR="${pkgs.dcmtk}/share/dcmtk-${pkgs.dcmtk.version}"  ../OrthancServer/

Which allowed the build to proceed a little further.

But then it complained about:

CMake is not allowed to download from Internet. Please set the ALLOW_DOWNLOADS option to ON

It made sense.

I then switched the -DALLOW_DOWNLOADS=OFF-DALLOW_DOWNLOADS=ON in the cmakeFlags list (just to check if the CMake flags are indeed passed) and the error was the same, which seems to suggest that the flags are not taken into account.

Then, I simply replaced the cmake invocation with:

cmake -DALLOW_DOWNLOADS=ON -DSTATIC_BUILD=OFF -DCMAKE_BUILD_TYPE=Debug -DUSE_SYSTEM_DCMTK=OFF -DSTANDALONE_BUILD=ON -DDCMTK_DICTIONARY_DIR="${pkgs.dcmtk}/share/dcmtk-${pkgs.dcmtk.version}" ../OrthancServer/

and the build proceeded…
(it will be very slow with make without -j flag, by the way)

I don’t know how CMake works in Nix, but it seems that passing CMake variables must be done differently, unless I made a mistake.

Also, I do not know why the “bare” invocation that you used triggered a dictionary lookup failure, and merely specifying it triggered the download process (hence the error). It sounds a bit strange that Orthanc tries to find the dictionary before downloading DCMTK. I don’t know the Orthanc build system well (it’s pretty complex and powerful) but you might want to fork the Orthanc repo and add a couple of diagnostic messages here and there to better understand the CMake flow.

Also, it seems that your combination of CMake flags cannot work. Since you specify -DUSE_SYSTEM_DCMTK=OFF, Orthanc needs to download DCMTK (unless you provide Orthanc with a pre-downloaded archive), which you want to prevent, for very good reasons, I assume, with -DALLOW_DOWNLOADS=ON). I think you might want to let the build use the system DCMTK, provided a suitable version is available.

Hope this helps,

And, btw, the link failed.
Missing symbol:

_ZN4absl12lts_2024011612log_internal17MakeCheckOpStringIPKvS4_EEPNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEET_T0_PKc

which is

std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >* absl::lts_20240116::log_internal::MakeCheckOpString<void const*, void const*>(void const*, void const*, char const*)

Could be related to protobuf. You might want to Google around. A workaround for a similar issue is listed here: C++: Undefine reference to functions defined in absl::lts_20230125 · Issue #12292 · protocolbuffers/protobuf (github.com)

And the fix was to forcefully reference two items in the link (I honestly don’t understand this at all at this stage) :

find_package( absl REQUIRED )
find_package( Protobuf 3.21 REQUIRED )   # already in Orthanc !
# ...
target_link_libraries(
    ${Protobuf_LIBRARIES}
    absl::log_internal_message
    absl::log_internal_check_op
)

(this probably means that, unless you accept to replace some dependencies with non-system ones, you’ll have to maintain patches on top of the Orthanc source code… I don’t think it’s surprising, btw. I guess most package maintainers have to slightly adapt the build system of the library/application they want to package)

I have a feeling you’re not done yet.
Good luck !

Hello,

Thanks for your positive feedback about Orthanc, for trying to package Orthanc for NixOS, and for providing this Docker file!

Please find attached a patch that allows to fix the build, without using any network connection and without recompiling DCMTK from scratch.

A few remarks:

  • As already noticed by Benjamin, the parameter cmakeFlags seems to be simply ignored, so I removed it and provided CMake arguments manually while invoking the cmake command. NixOS developers (I’m not one of them) should be able to explain how to more elegantly handle this issue.
  • I have disabled the build of the “ConnectivityChecks” sample plugin for Orthanc, as this one requires either an Internet connection during the build (i.e., -DALLOW_DOWNLOADS=ON) or a manual pre-population of the ThirdPartyDownloads folders.
  • I have disabled the build of the “MultitenantDicom” sample plugin for Orthanc, as there is a linking problem I couldn’t readily understand.
  • I have manually enabled multi-processing when invoking make to speed up the build, but NixOS provides cleaner ways of doing so (cf. the arguments --max-jobs and --cores of nix-build). Again, NixOS developers could help here.
  • I have added the execution of the unit tests as a build step for quality assurance.

Now, there is one remaining big issue: Orthanc and its unit tests simply don’t run, because the standard file /etc/localtime is missing in the standard environment of NixOS. From what I understand, this is because the localtimed service is not running during the build and during the runtime in the stdenv environment. I have found different references on Internet that talk about setting the following options:

services.geoclue2.enable = true;
services.localtimed.enable = true;

However, those options do not seem to work if included in your orthanc.nix file. I was not able to find a quick solution, so I guess your best bet is (again) to get in touch with NixOS developers to know how to start the localtimed service during the buildPhase and as a runtime dependency. Please keep us updated!

Kind Regards,
Sébastien-

nix.patch.txt (896 Bytes)

1 Like

Update: I have just managed to also compile the “MultitenantDicom” sample plugin using the patch attached to this message.

nix-2.patch.txt (872 Bytes)

I was able to build Orthanc successfully! At first sight, all the features I usually use seem to be working as expected. Thank you for all the help, Benjamin and Sábastien.

The issue with the /etc/localtime is something I will have to look how to deal with in the checkPhase. In my local NixOS system /etc/localtime exits, so I was able to run the unit tests outside the nix derivation and everything seems ok! Here are the results for reference https://gist.github.com/dvcorreia/ef06c228d6d724d00821b234ee157c3c.

I’m thinking on adding support for Darwin next.
Then, deal with plugins and add declarations for people to configure and host it with NixOS. By this point, it should be ready to contribute upstream to nix packages.
I’m fairly inexperienced with nix, so this can take some time.

1 Like

In the derivation I specify two types of dependencies:

  • nativeBuildInputs the build-time dependencies
  • buildInputs: runtime dependencies

Is there any build-time dependencies that are in fact runtime ones?

nativeBuildInputs = with pkgs; [
    gnumake
    cmake
    python3
    curl
    gtest
    protobuf
  ];

  buildInputs = with pkgs; [
    libgcc
    unzip
    sqlite
    openssl
    civetweb
    libjpeg
    libpng
    lua
    pugixml
    jsoncpp
    libuuid
    boost
    dcmtk # implements the dicom standard
    #sqlitecpp
    #gflags
    locale
  ];

Hello,

It seems correct to me (I am far from being an expert of the Orthanc build system).

(I do not know how you plan to package plugins, but they will have their own dependencies. For instance, the Python plugin will require python3 and it should become a runtime dependency there)

Finally, I’ve been able to get some free time to work on this again! I was able to compile the Orthanc SDK and I’m now in the middle of writing the configuration for the nix system service and packaging the plugins.

I decided to start with the DICOMweb plugin but I’ve hit a roadblock: it seems that it needs to download https://orthanc.uclouvain.be/downloads/third-party-downloads/bootstrap-5.3.3.zip, but in Nix this is not allowed, since we can’t fully reproduce in this way. Is there a way to disable this assertion and provide the bootstrap files ourselves?

Here is the plugin build derivation for reference.

Hello,

Thanks for your work! Please note that there is another ongoing effort to package Orthanc for NixOS. Maybe it be would be nice for you to get in touch with Pol Dellaiera (drupol) in order to synchronize your efforts?

Yes, certainly: It is sufficient to place the bootstrap-5.3.3.zip inside a directory named ThirdPartyDownloads placed next to the CMakeLists.txtfile. Check out the openSUSE package that adopts this approach.

Kind Regards,
Sébastien-

Hey there!

Thanks for bringing this to my attention! :slightly_smiling_face:

I packaged Orthanc last week orthanc: init at 1.12.6, nixos/orthanc: init by drupol · Pull Request #385329 · NixOS/nixpkgs · GitHub. Since I was on a roll, I also went ahead and packaged the corresponding NixOS module.

My main motivation for this work is my strong belief that Reproducibility and NixOS are essential for building secure and reliable servers, especially in sensitive environments.

On NixOS, enabling the Orthanc server is as simple as adding:

services.orthanc.enable = true;

The current state of things is very simple and there is room for improvements (e.g., by implementing plugins, SDK, etc etc).

I’ll be happy to have some people testing and contribute to this and report some feedback.

Let me know if you have any questions !

Edit: I just found out @dvcorreia’s repo at GitHub - dvcorreia/orthanc-nix: Orthanc packaged for Nix ❄️ which seems to be much more advanced, I hope we’ll find a way to backport all this work in nixpkgs !

Hey @drupol, it’s awesome to see that I’m not alone in this effort!
I feel exactly the same way, running things on NixOS its just a bliss :smile:

Most of the work I did was as a proof of concept. I’m lack a lot of knowledgeable in the C++ build systems or even NixOS itself, so there’s much to be improved in what I scribbled at late hours of the night ahah

I will go through your PRs to see how things stand and help where I can. Feel free to tag me, I’m happy to help!

Cool :slight_smile:

It feels good not being alone in this quest!

I am currently experimenting things here and there, feel free to let me know what I did wrong there. I have been working on orthanc-framework and orthanc-plugin-dicomweb and I made this: orthanc-framework: init at 1.12.6, orthanc-plugin-dicomweb: init at 1.18 by drupol · Pull Request #391361 · NixOS/nixpkgs · GitHub

Description of the 3 packages:

  1. orthanc: only contains the binaries and very few headers files.
  2. orthanc-framework: Build libOrthancFramework.so.1.12.6 and many headers files
  3. orthanc-plugin-dicomweb: a plugin for Orthanc that requires orthanc and orthanc-framework as dependencies (for the headers files)

Note: I have very limited knowledge in C++ build system (CMake) and I noticed that compiling OrthancServer compiles stuff in OrthancFramework. I was wondering if it would be possible to re-use the package orthanc-framework as a dependency (libOrthancFramework.so.1.12.6) of Orthanc to avoid recompiling things twice.

Let’s continue the discussion here or on Github, as you prefer.

Nice to read that you are now connected!

FYI, this is exactly what is done in Debian: The libOrthancDicomWeb.so.1.18 shared library (from package orthanc-dicomweb) is linked against the libOrthancFramework.a static library (from package liborthancframework1 that results from the compilation of Orthanc). This is static dependency is tracked in the Built-Using: orthanc field of the debian/control file.

You should not try to link libOrthancDicomWeb.so against a dynamic version of libOrthancFramework.so, as binary compatibility is not guaranteed between successive versions of the Orthanc framework. In other words, make sure to use static linking if you want to share orthanc-framework between plugins.

Regards,
Sébastien-

mmh mmh ! Interesting.

Actually, I’m doing that already in Nix, there are two packages, but they are sharing the same source.

It would be nice to organize an informal meeting, the 3 of us to discuss on what’s the best approach to tackle this.

@drupol was able to go through your PRs and actually run Orthanc with the DicomWeb plugin. Great work, thanks a lot!

I didn’t test is thoroughly, just queried some studies with the dicomweb API, but seems to work great. At this point, this is all I ever did with Orthanc, setting it up with the dicomweb plugin so I could develop and test some software that needed a dicomweb store. I may need to get acquainted with the rest of the plugins, tho.

Regarding the code, while being lost in cmake land, I learned a bunch. Its much cleaner and complete than what I had, so I don’t think there’s anything to backport from my repo.

Either way is works for me :slight_smile:

Thanks! Glad it works :slight_smile:
Can you describe what you did to link the plugin to Orthanc? This is one of the missing piece I need to find a way to have a plugin system.

Great news! On my side, I never used it, I am just looking for packaging it. I wish I would understand how it works, actually.

OK Fair enough.


I have some questions now:

  1. In my current working PR (orthanc-framework: init at 1.12.6, orthanc-plugin-dicomweb: init at 1.18 by drupol · Pull Request #391361 · NixOS/nixpkgs · GitHub), you’ll notice there’s a patch for orthanc-dicomweb (orthanc-framework: init at 1.12.6, orthanc-plugin-dicomweb: init at 1.18 by drupol · Pull Request #391361 · NixOS/nixpkgs · GitHub), improving the detection of headers files with CMake. Ideally, I’d like to contribute this fix upstream myself, but the fact that the repository uses Mercurial is a bit intimidating. Is there a Git mirror available where I could contribute instead? It would definitely be faster for me. On top of that, if we want to provide all the plugins in Nix, there might be some other patches needed to improve the CMake files.

  2. Are there plan in the future to switch to pkg-config and/or meson ?

Hello,

Check out the instructions in the Orthanc Book about “submitting a simple patch”.

No.

Sébastien-

I cloned your PR branch and added it to my NixOS configuration as such:

{
  config,
  pkgs,
  lib,
  ...
}:

let
  drupolPkgs = import /path/to/pr/branch {
    config = config.nixpkgs.config;
    system = pkgs.system;
  };

  dicomwebPluginPath = "${drupolPkgs.orthanc-plugin-dicomweb}/share/orthanc/plugins/libOrthancDicomWeb.so";
in
{
  services.orthanc = {
    enable = true;
    settings = {
      HttpPort = 8042;
      IndexDirectory = "/var/lib/orthanc/";
      StorageDirectory = "/var/lib/orthanc/";
      Plugins = [ dicomwebPluginPath ];

      RemoteAccessAllowed = true;
      AuthenticationEnabled = false;
      DicomWeb = {
        Enable = true;
        Root = "/pacs/dicom-web/";
        EnableWado = true;
        WadoRoot = "/wado";
        Host = "";
        StudiesMetadata = "Full";
        SeriesMetadata = "Full";
        PublicRoot = "/pacs/dicom-web/";
      };
    };
  };
}

In the Orthanc UI at localhost:8042, when you go to Plugins at the top left corner, you should see the DicomWeb plugin being recognized.

You should also be able to request things from the DicomWeb API, e.g. curl localhost:8042/pacs/dicom-web/studies. Without studies loaded up in to Orthanc you should see an empty list being returned.

You can see more on the plugin configuration at DICOMweb plugin — Orthanc Book documentation. I think the other plugins follow the same installation and configuration strategy.


If I read correctly, Orthanc already assumes a default configuration for the plugins. It would be pretty nice if we could automatically enable the plugins when we specify them. My first thought when working on the Orthanc module was an option, something like:

plugins = mkOption {
  type = types.listOf types.package;
  default = [ ];
  description = mdDoc ''
    List of Orthanc plugins to enable. Each plugin should be a package
    containing the plugin's shared library (.so file).
  '';
  example = literalExpression ''
    with pkgs.orthancPlugins; [
      dicomweb
      webviewer
    ]
  '';
};

Were we could then iterate over and write in the configuration. I’m sure there’s a proper way of doing this kinda of stuff :laughing:

Now that the base packages has been merged (orthanc, orthanc-framework) and one plugin (orthanc-plugin-dicomweb), we should focus on bringing as many plugins as possible in Nix, then think on how we can make a nice interface to enable them.