{
  # utils
  stdenv,
  fetchFromGitHub,
  fetchurl,
  lib,
  replaceVars,

  # source specification
  hash,
  muslPatches ? { },
  rev,
  version,

  # runtime dependencies
  darwin,
  freebsd,
  glibc,
  libuuid,
  libxml2,
  lz4,
  openssl,
  readline,
  tzdata,
  zlib,
  zstd,

  # build dependencies
  bison,
  docbook-xsl-nons,
  docbook_xml_dtd_45,
  flex,
  libxslt,
  makeBinaryWrapper,
  pkg-config,
  removeReferencesTo,

  # passthru
  buildPackages,
  newScope,
  nixosTests,
  self,
  testers,

  # Block size
  # Changing the block size will break on-disk database compatibility. See:
  # https://www.postgresql.org/docs/current/install-make.html#CONFIGURE-OPTION-WITH-BLOCKSIZE
  withBlocksize ? null,
  withWalBlocksize ? null,

  # bonjour
  bonjourSupport ? false,

  # Curl
  curlSupport ? lib.versionAtLeast version "18" && lib.meta.availableOn stdenv.hostPlatform curl,
  curl,

  # GSSAPI
  gssSupport ? with stdenv.hostPlatform; !isWindows,
  libkrb5,

  # icu
  icuSupport ? true,
  icu,

  # JIT
  jitSupport ? stdenv.hostPlatform.canExecute stdenv.buildPlatform,
  llvmPackages,
  nukeReferences,
  overrideCC,

  # LDAP
  ldapSupport ? false,
  openldap,

  # NLS
  nlsSupport ? false,
  gettext,

  # NUMA
  numaSupport ? lib.versionAtLeast version "18" && lib.meta.availableOn stdenv.hostPlatform numactl,
  numactl,

  # PAM
  pamSupport ? lib.meta.availableOn stdenv.hostPlatform linux-pam,
  linux-pam,

  # PL/Perl
  perlSupport ?
    lib.meta.availableOn stdenv.hostPlatform perl
    # configure tries to call the perl executable for the version
    && stdenv.buildPlatform.canExecute stdenv.hostPlatform,
  perl,

  # PL/Python
  pythonSupport ?
    lib.meta.availableOn stdenv.hostPlatform python3
    # configure tries to call the python executable
    && stdenv.buildPlatform.canExecute stdenv.hostPlatform,
  python3,

  # PL/Tcl
  tclSupport ?
    lib.meta.availableOn stdenv.hostPlatform tcl
    # configure fails with:
    #   configure: error: file 'tclConfig.sh' is required for Tcl
    && stdenv.buildPlatform.canExecute stdenv.hostPlatform,
  tcl,

  # SELinux
  selinuxSupport ? false,
  libselinux,

  # Systemd
  systemdSupport ? lib.meta.availableOn stdenv.hostPlatform systemdLibs,
  systemdLibs,

  # Uring
  uringSupport ? lib.versionAtLeast version "18" && lib.meta.availableOn stdenv.hostPlatform liburing,
  liburing,
}@args:
let
  atLeast = lib.versionAtLeast version;
  olderThan = lib.versionOlder version;
  lz4Enabled = atLeast "14";
  zstdEnabled = atLeast "15";

  dlSuffix = if olderThan "16" then ".so" else stdenv.hostPlatform.extensions.sharedLibrary;

  stdenv' =
    if !stdenv.cc.isClang then
      overrideCC llvmPackages.stdenv (
        llvmPackages.stdenv.cc.override {
          # LLVM bintools are not used by default, but are needed to make -flto work below.
          bintools = buildPackages."llvmPackages_${lib.versions.major llvmPackages.release_version}".bintools;
        }
      )
    else
      stdenv;
in
stdenv'.mkDerivation (finalAttrs: {
  inherit version;
  pname = "postgresql";

  src = fetchFromGitHub {
    owner = "postgres";
    repo = "postgres";
    # rev, not tag, on purpose: allows updating when new versions
    # are "stamped" a few days before release (tag).
    inherit hash rev;
  };

  __structuredAttrs = true;

  outputs = [
    "out"
    "dev"
    "doc"
    "lib"
    "man"
  ]
  ++ lib.optionals jitSupport [ "jit" ]
  ++ lib.optionals perlSupport [ "plperl" ]
  ++ lib.optionals pythonSupport [ "plpython3" ]
  ++ lib.optionals tclSupport [ "pltcl" ];

  outputChecks = {
    out = {
      disallowedReferences = [
        "dev"
        "doc"
        "man"
      ]
      ++ lib.optionals jitSupport [ "jit" ];
      disallowedRequisites = [
        stdenv'.cc
        llvmPackages.llvm.out
        llvmPackages.llvm.lib
      ]
      ++ (map lib.getDev (builtins.filter (drv: drv ? "dev") finalAttrs.buildInputs));
    };

    lib = {
      disallowedReferences = [
        "out"
        "dev"
        "doc"
        "man"
      ]
      ++ lib.optionals jitSupport [ "jit" ];
      disallowedRequisites = [
        stdenv'.cc
        llvmPackages.llvm.out
        llvmPackages.llvm.lib
      ]
      ++ (map lib.getDev (builtins.filter (drv: drv ? "dev") finalAttrs.buildInputs));
    };

    doc = {
      disallowedReferences = [
        "out"
        "dev"
        "man"
      ]
      ++ lib.optionals jitSupport [ "jit" ];
    };

    man = {
      disallowedReferences = [
        "out"
        "dev"
        "doc"
      ]
      ++ lib.optionals jitSupport [ "jit" ];
    };
  }
  // lib.optionalAttrs jitSupport {
    jit = {
      disallowedReferences = [
        "dev"
        "doc"
        "man"
      ];
      disallowedRequisites = [
        stdenv'.cc
        llvmPackages.llvm.out
      ]
      ++ (map lib.getDev (builtins.filter (drv: drv ? "dev") finalAttrs.buildInputs));
    };
  };

  strictDeps = true;

  buildInputs = [
    zlib
    readline
    openssl
    libxml2
    libuuid
  ]
  ++ lib.optionals icuSupport [ icu ]
  ++ lib.optionals jitSupport [ llvmPackages.llvm ]
  ++ lib.optionals lz4Enabled [ lz4 ]
  ++ lib.optionals zstdEnabled [ zstd ]
  ++ lib.optionals systemdSupport [ systemdLibs ]
  ++ lib.optionals uringSupport [ liburing ]
  ++ lib.optionals curlSupport [ curl ]
  ++ lib.optionals numaSupport [ numactl ]
  ++ lib.optionals gssSupport [ libkrb5 ]
  ++ lib.optionals pamSupport [ linux-pam ]
  ++ lib.optionals perlSupport [ perl ]
  ++ lib.optionals ldapSupport [ openldap ]
  ++ lib.optionals selinuxSupport [ libselinux ]
  ++ lib.optionals nlsSupport [ gettext ];

  nativeBuildInputs = [
    bison
    docbook-xsl-nons
    docbook_xml_dtd_45
    flex
    libxml2
    libxslt
    makeBinaryWrapper
    perl
    pkg-config
    removeReferencesTo
  ]
  ++ lib.optionals jitSupport [
    llvmPackages.llvm.dev
    nukeReferences
  ];

  enableParallelBuilding = true;

  separateDebugInfo = true;

  buildFlags = [ "world" ];

  env = {
    # libpgcommon.a and libpgport.a contain all paths returned by pg_config and are linked
    # into all binaries. However, almost no binaries actually use those paths. The following
    # flags will remove unused sections from all shared libraries and binaries - including
    # those paths. This avoids a lot of circular dependency problems with different outputs,
    # and allows splitting them cleanly.
    CFLAGS = "-fdata-sections -ffunction-sections -flto";

    # This flag was introduced upstream in:
    # https://github.com/postgres/postgres/commit/b6c7cfac88c47a9194d76f3d074129da3c46545a
    # It causes errors when linking against libpq.a in pkgsStatic:
    #   undefined reference to `pg_encoding_to_char'
    # Unsetting the flag fixes it. The upstream reasoning to introduce it is about the risk
    # to have initdb load a libpq.so from a different major version and how to avoid that.
    # This doesn't apply to us with Nix.
    NIX_CFLAGS_COMPILE = "-UUSE_PRIVATE_ENCODING_FUNCS";
  }
  // lib.optionalAttrs perlSupport { PERL = lib.getExe perl; }
  // lib.optionalAttrs pythonSupport { PYTHON = lib.getExe python3; }
  // lib.optionalAttrs tclSupport { TCLSH = "${lib.getBin tcl}/bin/tclsh"; };

  configureFlags =
    let
      inherit (lib) withFeature;
    in
    [
      "--with-openssl"
      "--with-libxml"
      (withFeature icuSupport "icu")
      "--sysconfdir=/etc"
      "--with-system-tzdata=${tzdata}/share/zoneinfo"
      "--enable-debug"
      (lib.optionalString systemdSupport "--with-systemd")
      (if stdenv.hostPlatform.isFreeBSD then "--with-uuid=bsd" else "--with-uuid=e2fs")
      (withFeature perlSupport "perl")
    ]
    ++ lib.optionals (withBlocksize != null) [ "--with-blocksize=${toString withBlocksize}" ]
    ++ lib.optionals (withWalBlocksize != null) [ "--with-wal-blocksize=${toString withWalBlocksize}" ]
    ++ lib.optionals lz4Enabled [ "--with-lz4" ]
    ++ lib.optionals zstdEnabled [ "--with-zstd" ]
    ++ lib.optionals uringSupport [ "--with-liburing" ]
    ++ lib.optionals curlSupport [ "--with-libcurl" ]
    ++ lib.optionals numaSupport [ "--with-libnuma" ]
    ++ lib.optionals gssSupport [ "--with-gssapi" ]
    ++ lib.optionals pythonSupport [ "--with-python" ]
    ++ lib.optionals jitSupport [ "--with-llvm" ]
    ++ lib.optionals pamSupport [ "--with-pam" ]
    # This can be removed once v17 is removed. v18+ ships with it.
    ++ lib.optionals (stdenv'.hostPlatform.isDarwin && atLeast "16" && olderThan "18") [
      "LDFLAGS_EX_BE=-Wl,-export_dynamic"
    ]
    # some version of this flag is required in all cross configurations
    # since it cannot be automatically detected
    ++
      lib.optionals
        (
          (!stdenv'.hostPlatform.isDarwin)
          && (!(stdenv'.buildPlatform.canExecute stdenv.hostPlatform))
          && atLeast "16"
        )
        [
          "LDFLAGS_EX_BE=-Wl,--export-dynamic"
        ]
    ++ lib.optionals ldapSupport [ "--with-ldap" ]
    ++ lib.optionals tclSupport [ "--with-tcl" ]
    ++ lib.optionals selinuxSupport [ "--with-selinux" ]
    ++ lib.optionals nlsSupport [ "--enable-nls" ]
    ++ lib.optionals bonjourSupport [ "--with-bonjour" ]
    # Configure needs a little help to find `nm` when cross-compiling.
    ++ lib.optionals (atLeast "19") [ "NM=${stdenv'.cc}/bin/${stdenv'.cc.targetPrefix}nm" ];

  patches = [
    (
      if atLeast "16" then
        ./patches/relative-to-symlinks-16+.patch
      else
        ./patches/relative-to-symlinks.patch
    )
    (
      if atLeast "15" then
        ./patches/empty-pg-config-view-15+.patch
      else
        ./patches/empty-pg-config-view.patch
    )
    ./patches/less-is-more.patch
    ./patches/paths-for-split-outputs.patch
    ./patches/paths-with-postgresql-suffix.patch

    (replaceVars ./patches/locale-binary-path.patch {
      locale = "${
        if stdenv.hostPlatform.isDarwin then
          darwin.adv_cmds
        else if stdenv.hostPlatform.isFreeBSD then
          freebsd.locale
        else
          lib.getBin stdenv.cc.libc
      }/bin/locale";
    })
  ]
  ++ lib.optionals stdenv'.hostPlatform.isMusl (
    # Using fetchurl instead of fetchpatch on purpose: https://github.com/NixOS/nixpkgs/issues/240141
    map fetchurl (lib.attrValues muslPatches)
  )
  ++ lib.optionals stdenv'.hostPlatform.isLinux [
    ./patches/socketdir-in-run-13+.patch
  ]
  ++ lib.optionals (stdenv'.hostPlatform.isDarwin && olderThan "16") [
    ./patches/export-dynamic-darwin-15-.patch
  ];

  installTargets = [ "install-world" ];

  postPatch = ''
    substituteInPlace "src/Makefile.global.in" --subst-var out
    substituteInPlace "src/common/config_info.c" --subst-var dev
    cat ${./pg_config.env.mk} >> src/common/Makefile
  ''
  # This test always fails on hardware with >1 NUMA node: the sysfs
  # dirs providing information about the topology are hidden in the sandbox,
  # so postgres assumes there's only a single node `0`. However,
  # the test checks on which NUMA nodes the allocated pages are which is >1
  # on such hardware. This in turn triggers a safeguard in the view
  # which breaks the test.
  # Manual tests confirm that the testcase behaves properly outside of the
  # Nix sandbox.
  + lib.optionalString (atLeast "18") ''
    substituteInPlace src/test/regress/parallel_schedule \
      --replace-fail numa ""
  '';

  postInstall = ''
    moveToOutput "bin/ecpg" "$dev"
    moveToOutput "lib/pgxs" "$dev"
  ''
  + lib.optionalString (stdenv'.buildPlatform.canExecute stdenv'.hostPlatform) ''
    mkdir -p "$dev/nix-support"
    "$out/bin/pg_config" > "$dev/nix-support/pg_config.expected"
  ''
  + ''
      rm "$out/bin/pg_config"
      make -C src/common pg_config.env
      substituteInPlace src/common/pg_config.env \
        --replace-fail "$out" "@out@" \
        --replace-fail "$man" "@man@"
      install -D src/common/pg_config.env "$dev/nix-support/pg_config.env"

    # postgres exposes external symbols get_pkginclude_path and similar. Those
    # can't be stripped away by --gc-sections/LTO, because they could theoretically
    # be used by dynamically loaded modules / extensions. To avoid circular dependencies,
    # references to -dev, -doc and -man are removed here. References to -lib must be kept,
    # because there is a realistic use-case for extensions to locate the /lib directory to
    # load other shared modules.
    remove-references-to -t "$dev" -t "$doc" -t "$man" "$out/bin/postgres"

    if [ -z "''${dontDisableStatic:-}" ]; then
      # Remove static libraries in case dynamic are available.
      for i in $lib/lib/*.a; do
        name="$(basename "$i")"
        ext="${stdenv'.hostPlatform.extensions.sharedLibrary}"
        if [ -e "$lib/lib/''${name%.a}$ext" ] || [ -e "''${i%.a}$ext" ]; then
          rm "$i"
        fi
      done
    fi

    # The remaining static libraries are libpgcommon.a, libpgport.a and related.
    # Those are only used when building e.g. extensions, so go to $dev.
    moveToOutput "lib/*.a" "$dev"
  ''
  + lib.optionalString jitSupport ''
    # In the case of JIT support, prevent useless dependencies on header files
    find "$out/lib" -iname '*.bc' -type f -exec nuke-refs '{}' +

    # Stop lib depending on the -dev output of llvm
    remove-references-to -t ${llvmPackages.llvm.dev} "$out/lib/llvmjit${dlSuffix}"

    moveToOutput "lib/bitcode" "$jit"
    moveToOutput "lib/llvmjit*" "$jit"
  ''
  + lib.optionalString stdenv'.hostPlatform.isDarwin ''
    # The darwin specific Makefile for PGXS contains a reference to the postgres
    # binary. Some extensions (here: postgis), which are able to set bindir correctly
    # to their own output for installation, will then fail to find "postgres" during linking.
    substituteInPlace "$dev/lib/pgxs/src/Makefile.port" \
      --replace-fail '-bundle_loader $(bindir)/postgres' "-bundle_loader $out/bin/postgres"
  ''
  + lib.optionalString perlSupport ''
    moveToOutput "lib/*plperl*" "$plperl"
    moveToOutput "share/postgresql/extension/*plperl*" "$plperl"
  ''
  + lib.optionalString pythonSupport ''
    moveToOutput "lib/*plpython3*" "$plpython3"
    moveToOutput "share/postgresql/extension/*plpython3*" "$plpython3"
  ''
  + lib.optionalString tclSupport ''
    moveToOutput "lib/*pltcl*" "$pltcl"
    moveToOutput "share/postgresql/extension/*pltcl*" "$pltcl"
  '';

  postFixup = lib.optionalString stdenv'.hostPlatform.isGnu ''
    # initdb needs access to "locale" command from glibc.
    wrapProgram $out/bin/initdb --prefix PATH ":" ${glibc.bin}/bin
  '';

  # Running tests as "install check" to work around SIP issue on macOS:
  # https://www.postgresql.org/message-id/flat/4D8E1BC5-BBCF-4B19-8226-359201EA8305%40gmail.com
  # Also see <nixpkgs>/doc/stdenv/platform-notes.chapter.md
  doCheck = false;
  doInstallCheck =
    # Tests currently can't be run on darwin, because of a Nix bug:
    # https://github.com/NixOS/nix/issues/12548
    # https://git.lix.systems/lix-project/lix/issues/691
    # The error appears as this in the initdb logs:
    #   FATAL:  could not create shared memory segment: Cannot allocate memory
    # Don't let yourself be fooled when trying to remove this condition: Running
    # the tests works fine most of the time. But once the tests (or any package using
    # postgresqlTestHook) fails on the same machine for a few times, enough IPC objects
    # will be stuck around, and any future builds with the tests enabled *will* fail.
    !(stdenv'.hostPlatform.isDarwin)
    &&
      # Regression tests currently fail in pkgsMusl because of a difference in EXPLAIN output.
      !(stdenv'.hostPlatform.isMusl)
    &&
      # Modifying block sizes is expected to break regression tests.
      # https://www.postgresql.org/message-id/E1TJOeZ-000717-Lg%40wrigleys.postgresql.org
      (withBlocksize == null && withWalBlocksize == null);
  installCheckTarget = "check-world";

  passthru = {
    inherit dlSuffix;

    psqlSchema = lib.versions.major version;

    withJIT =
      if jitSupport then
        finalAttrs.finalPackage.withPackages (_: [ finalAttrs.finalPackage.jit ])
      else
        null;
    withoutJIT = finalAttrs.finalPackage.withPackages (_: [ ]);

    pkgs =
      let
        scope = {
          inherit
            jitSupport
            pythonSupport
            perlSupport
            tclSupport
            ;
          inherit (llvmPackages) llvm;
          postgresql = finalAttrs.finalPackage;
          stdenv = stdenv';
          postgresqlTestExtension = newSuper.callPackage ./postgresqlTestExtension.nix { };
          postgresqlBuildExtension = newSuper.callPackage ./postgresqlBuildExtension.nix { };
        };
        newSelf = self // scope;
        newSuper = {
          callPackage = newScope (scope // finalAttrs.finalPackage.pkgs);
        };
      in
      import ./ext.nix newSelf newSuper;

    withPackages = self.callPackage ./wrapper.nix { postgresql = finalAttrs.finalPackage; };

    pg_config = buildPackages.callPackage ./pg_config.nix {
      inherit (finalAttrs) finalPackage;
      outputs = {
        out = lib.getOutput "out" finalAttrs.finalPackage;
        man = lib.getOutput "man" finalAttrs.finalPackage;
      };
    };

    tests = {
      postgresql = nixosTests.postgresql.postgresql.passthru.override finalAttrs.finalPackage;
      postgresql-replication = nixosTests.postgresql.postgresql-replication.passthru.override finalAttrs.finalPackage;
      postgresql-tls-client-cert = nixosTests.postgresql.postgresql-tls-client-cert.passthru.override finalAttrs.finalPackage;
      postgresql-wal-receiver = nixosTests.postgresql.postgresql-wal-receiver.passthru.override finalAttrs.finalPackage;
      pkg-config = testers.testMetaPkgConfig finalAttrs.finalPackage;
    }
    // lib.optionalAttrs jitSupport {
      postgresql-jit = nixosTests.postgresql.postgresql-jit.passthru.override finalAttrs.finalPackage;
    };
  };

  meta = {
    homepage = "https://www.postgresql.org";
    description = "Powerful, open source object-relational database system";
    license = lib.licenses.postgresql;
    changelog = "https://www.postgresql.org/docs/release/${finalAttrs.version}/";
    teams = [ lib.teams.postgres ];
    pkgConfigModules = [
      "libecpg"
      "libecpg_compat"
      "libpgtypes"
      "libpq"
    ];
    platforms = lib.platforms.unix;

    broken =
      # JIT support doesn't work with cross-compilation. It is attempted to build LLVM-bytecode
      # (`%.bc` is the corresponding `make(1)`-rule) for each sub-directory in `backend/` for
      # the JIT apparently, but with a $(CLANG) that can produce binaries for the build, not the
      # host-platform.
      #
      # I managed to get a cross-build with JIT support working with
      # `depsBuildBuild = [ llvmPackages.clang ] ++ buildInputs`, but considering that the
      # resulting LLVM IR isn't platform-independent this doesn't give you much.
      # In fact, I tried to test the result in a VM-test, but as soon as JIT was used to optimize
      # a query, postgres would coredump with `Illegal instruction`.
      #
      # Note: This is "host canExecute build" on purpose, since this is about the LLVM that is called
      # to do JIT at **runtime**.
      (jitSupport && !stdenv.hostPlatform.canExecute stdenv.buildPlatform)
      # While PostgreSQL claims to support static builds, it does not do so in a way that
      # would work properly and consistently in pkgsStatic. The server heavily depends on
      # the ability to load shared modules at runtime, but dlopen() is stubbed out in static
      # musl builds. The important part is, that the separate libpq package builds in pkgsStatic.
      || stdenv.hostPlatform.isStatic;
  };
})
