# A Nix flake that sets up a complete Synapse development environment. Dependencies
# for the SyTest (https://github.com/matrix-org/sytest) and Complement
# (https://github.com/matrix-org/complement) Matrix homeserver test suites are also
# installed automatically.
#
# You must have already installed Nix (https://nixos.org/download/) on your system to use this.
# Nix can be installed on any Linux distribiution or MacOS; NixOS is not required.
# Windows is not directly supported, but Nix can be installed inside of WSL2 or even Docker
# containers. Please refer to https://nixos.org/download for details.
#
# You must also enable support for flakes in Nix. See the following for how to
# do so permanently: https://wiki.nixos.org/wiki/Flakes#Other_Distros,_without_Home-Manager
#
# Be warned: you'll need over 3.75 GB of free space to download all the dependencies.
#
# Usage:
#
# With Nix installed, navigate to the directory containing this flake and run
# `nix develop --impure`. The `--impure` is necessary in order to store state
# locally from "services", such as PostgreSQL and Redis.
#
# You should now be dropped into a new shell with all programs and dependencies
# available to you!
#
# You can start up pre-configured local Synapse, PostgreSQL and Redis instances by
# running: `devenv up`. To stop them, use Ctrl-C.
#
# All state (the venv, postgres and redis data and config) are stored in
# .devenv/state. Deleting a file from here and then re-entering the shell
# will recreate these files from scratch.
#
# You can exit the development shell by typing `exit`, or using Ctrl-D.
#
# If you would like this development environment to activate automatically
# upon entering this directory in your terminal, first install `direnv`
# (https://direnv.net/). Then run `echo 'use flake . --impure' >> .envrc` at
# the root of the Synapse repo. Finally, run `direnv allow .` to allow the
# contents of '.envrc' to run every time you enter this directory. Voilà!

{
  inputs = {
    # Use the rolling/unstable branch of nixpkgs. Used to fetch the latest
    # available versions of packages.
    nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
    # Output a development shell for x86_64/aarch64 Linux/Darwin (MacOS).
    systems.url = "github:nix-systems/default";
    # A development environment manager built on Nix. See https://devenv.sh.
    devenv.url = "github:cachix/devenv/v0.6.3";
    # Rust toolchain.
    rust-overlay.url = "github:oxalica/rust-overlay";
  };

  outputs = { nixpkgs, devenv, systems, rust-overlay, ... } @ inputs:
    let
      forEachSystem = nixpkgs.lib.genAttrs (import systems);
    in {
      devShells = forEachSystem (system:
        let
          overlays = [ (import rust-overlay) ];
          pkgs = import nixpkgs {
            inherit system overlays;
          };
        in {
          # Everything is configured via devenv - a Nix module for creating declarative
          # developer environments. See https://devenv.sh/reference/options/ for a list
          # of all possible options.
          default = devenv.lib.mkShell {
            inherit inputs pkgs;
            modules = [
              {
                # Make use of the Starship command prompt when this development environment
                # is manually activated (via `nix develop --impure`).
                # See https://starship.rs/ for details on the prompt itself.
                starship.enable = true;

                # Configure packages to install.
                # Search for package names at https://search.nixos.org/packages?channel=unstable
                packages = with pkgs; [
                  # The rust toolchain and related tools.
                  # This will install the "default" profile of rust components.
                  # https://rust-lang.github.io/rustup/concepts/profiles.html
                  #
                  # NOTE: We currently need to set the Rust version unnecessarily high
                  # in order to work around https://github.com/matrix-org/synapse/issues/15939
                  (rust-bin.stable."1.82.0".default.override {
                    # Additionally install the "rust-src" extension to allow diving into the
                    # Rust source code in an IDE (rust-analyzer will also make use of it).
                    extensions = [ "rust-src" ];
                  })
                  # The rust-analyzer language server implementation.
                  rust-analyzer

                  # GCC includes a linker; needed for building `ruff`
                  gcc
                  # Needed for building `ruff`
                  gnumake

                  # Native dependencies for running Synapse.
                  libffi
                  libjpeg
                  libpqxx
                  libwebp
                  libxml2
                  libxslt
                  sqlite

                  # Native dependencies for unit tests (SyTest also requires OpenSSL).
                  openssl
                  xmlsec

                  # Native dependencies for running Complement.
                  olm

                  # For building the Synapse documentation website.
                  mdbook

                  # For releasing Synapse
                  debian-devscripts # (`dch` for manipulating the Debian changelog)
                  libnotify # (the release script uses `notify-send` to tell you when CI jobs are done)
                ];

                # Install Python and manage a virtualenv with Poetry.
                languages.python.enable = true;
                languages.python.poetry.enable = true;
                # Automatically activate the poetry virtualenv upon entering the shell.
                languages.python.poetry.activate.enable = true;
                # Install all extra Python dependencies; this is needed to run the unit
                # tests and utilise all Synapse features.
                languages.python.poetry.install.arguments = ["--extras all"];
                # Install the 'matrix-synapse' package from the local checkout.
                languages.python.poetry.install.installRootPackage = true;

                # This is a work-around for NixOS systems. NixOS is special in
                # that you can have multiple versions of packages installed at
                # once, including your libc linker!
                #
                # Some binaries built for Linux expect those to be in a certain
                # filepath, but that is not the case on NixOS. In that case, we
                # force compiling those binaries locally instead.
                env.POETRY_INSTALLER_NO_BINARY = "ruff";

                # Install dependencies for the additional programming languages
                # involved with Synapse development.
                #
                # * Golang is needed to run the Complement test suite.
                # * Perl is needed to run the SyTest test suite.
                # * Rust is used for developing and running Synapse.
                #   It is installed manually with `packages` above.
                languages.go.enable = true;
                languages.perl.enable = true;

                # Postgres is needed to run Synapse with postgres support and
                # to run certain unit tests that require postgres.
                services.postgres.enable = true;

                # On the first invocation of `devenv up`, create a database for
                # Synapse to store data in.
                services.postgres.initdbArgs = ["--locale=C" "--encoding=UTF8"];
                services.postgres.initialDatabases = [
                  { name = "synapse"; }
                ];
                # Create a postgres user called 'synapse_user' which has ownership
                # over the 'synapse' database.
                services.postgres.initialScript = ''
                  CREATE USER synapse_user;
                  ALTER DATABASE synapse OWNER TO synapse_user;
                '';

                # Redis is needed in order to run Synapse in worker mode.
                services.redis.enable = true;

                # Configure and start Synapse. Before starting Synapse, this shell code:
                #  * generates a default homeserver.yaml config file if one does not exist, and
                #  * ensures a directory containing two additional homeserver config files exists;
                #    one to configure using the development environment's PostgreSQL as the
                #    database backend and another for enabling Redis support.
                process.before = ''
                  python -m synapse.app.homeserver -c homeserver.yaml --generate-config --server-name=synapse.dev --report-stats=no
                  mkdir -p homeserver-config-overrides.d
                  cat > homeserver-config-overrides.d/database.yaml << EOF
                  ## Do not edit this file. This file is generated by flake.nix
                  database:
                    name: psycopg2
                    args:
                      user: synapse_user
                      database: synapse
                      host: $PGHOST
                      cp_min: 5
                      cp_max: 10
                  EOF
                  cat > homeserver-config-overrides.d/redis.yaml << EOF
                  ## Do not edit this file. This file is generated by flake.nix
                  redis:
                    enabled: true
                  EOF
                '';
                # Start synapse when `devenv up` is run.
                processes.synapse.exec = "poetry run python -m synapse.app.homeserver -c homeserver.yaml -c homeserver-config-overrides.d";

                # Define the perl modules we require to run SyTest.
                #
                # This list was compiled by cross-referencing https://metacpan.org/
                # with the modules defined in './cpanfile' and then finding the
                # corresponding Nix packages on https://search.nixos.org/packages.
                #
                # This was done until `./install-deps.pl --dryrun` produced no output.
                env.PERL5LIB = "${with pkgs.perl538Packages; makePerlPath [
                  DBI
                  ClassMethodModifiers
                  CryptEd25519
                  DataDump
                  DBDPg
                  DigestHMAC
                  DigestSHA1
                  EmailAddressXS
                  EmailMIME
                  EmailSimple  # required by Email::Mime
                  EmailMessageID  # required by Email::Mime
                  EmailMIMEContentType  # required by Email::Mime
                  TextUnidecode  # required by Email::Mime
                  ModuleRuntime  # required by Email::Mime
                  EmailMIMEEncodings  # required by Email::Mime
                  FilePath
                  FileSlurper
                  Future
                  GetoptLong
                  HTTPMessage
                  IOAsync
                  IOAsyncSSL
                  IOSocketSSL
                  NetSSLeay
                  JSON
                  ListUtilsBy
                  ScalarListUtils
                  ModulePluggable
                  NetAsyncHTTP
                  MetricsAny  # required by Net::Async::HTTP
                  NetAsyncHTTPServer
                  StructDumb
                  URI
                  YAMLLibYAML
                ]}";

                # Clear the LD_LIBRARY_PATH environment variable on shell init.
                #
                # By default, devenv will set LD_LIBRARY_PATH to point to .devenv/profile/lib. This causes
                # issues when we include `gcc` as a dependency to build C libraries, as the version of glibc
                # that the development environment's cc compiler uses may differ from that of the system.
                #
                # When LD_LIBRARY_PATH is set, system tools will attempt to use the development environment's
                # libraries. Which, when built against a different glibc version lead, to "version 'GLIBC_X.YY'
                # not found" errors.
                enterShell = ''
                  unset LD_LIBRARY_PATH
                '';
              }
            ];
          };
        });
    };
}
