diff --git a/.envrc b/.envrc new file mode 100644 index 0000000000000..7d1d144ed6608 --- /dev/null +++ b/.envrc @@ -0,0 +1,53 @@ +#!/usr/bin/env bash + +# This file provides some tooling on an opt-in basis via `direnv` +# (https://direnv.net/) + +# To use the functionality here, create an `.envrc.local` file in this folder +# that runs the functions you need. +# There is an example in `.envrc.local.example` you can start with + +# `use nvm` +# if `nvm` is installed, use it to select the right node.js version +# as defined in the `.nvmrc` file +use_nvm() { + local NVM_DIR + if [[ -z "${XDG_CONFIG_HOME:-}" ]]; then + NVM_DIR="${HOME}/.nvm" + else + NVM_DIR="${XDG_CONFIG_HOME}/nvm" + fi + if ! [[ -d "$NVM_DIR" ]]; then + echo >&2 "ERROR: nvm not found. Could not set the node.js version." + return 1 + fi + + # shellcheck source=/dev/null + [[ -s "$NVM_DIR/nvm.sh" ]] && \. "$NVM_DIR/nvm.sh" # This loads nvm + nvm use +} + +# `use ghcup` +# if `ghcup` is available, use it to install and set the GHC version +# as defined in the `.ghcversion` file +use_ghcup() { + local GHC_VERSION GHCUP_PATH + GHC_VERSION="$(<.ghcversion)" + GHCUP_PATH="$(which ghcup)" + if [[ -z "$GHC_VERSION" ]]; then + echo >&2 'ERROR: Required GHC version not found.' + return 1 + fi + if [[ -z "$GHCUP_PATH" ]]; then + echo 'ERROR: ghcup not found. Could not set the GHC version.' + return 1 + fi + + if ! ghcup whereis ghc "$GHC_VERSION" &> /dev/null; then + ghcup install ghc "$GHC_VERSION" + fi + ghcup set ghc "$GHC_VERSION" +} + +# this line sources your `.envrc.local` file +source_env_if_exists .envrc.local diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000000000..692d25c69078f --- /dev/null +++ b/flake.lock @@ -0,0 +1,42 @@ +{ + "nodes": { + "flake-utils": { + "locked": { + "lastModified": 1659877975, + "narHash": "sha256-zllb8aq3YO3h8B/U0/J1WBgAL8EX5yWf5pMj3G0NAmc=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "c0e246b9b83f637f4681389ecabcb2681b4f3af0", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1664957017, + "narHash": "sha256-beO1xAHLXXfVTAq4E/pbdt+vUMgYtOGjAUyPq81LV7c=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "1237bfb999d269c46868b34bb4b33b5ff084ac5f", + "type": "github" + }, + "original": { + "owner": "NixOS", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000000000..685c22f190f04 --- /dev/null +++ b/flake.nix @@ -0,0 +1,55 @@ +# This Nix flake defines the dependencies of the Hasura GraphQL Engine. +# Currently, we do not use it for building anything, just for providing a shell. +# +# The shell provides tooling for the Server, CLI, Console, and other components. +# You can start it with `nix develop`, or by using `direnv` (see .envrc). +# +# If you are using macOS, note that we apply a patch to GHC, and therefore all +# tooling needs to be built from scratch. If you're averse to waiting several +# hours for your environment to become usable, you can add @SamirTalwar's Cachix +# repository to save yourself the trouble: +# +# https://app.cachix.org/cache/hasura-samirtalwar#pull +{ + description = "Hasura GraphQL Engine"; + + inputs = { + flake-utils = { + url = github:numtide/flake-utils; + }; + + nixpkgs = { + url = github:NixOS/nixpkgs; + }; + }; + + outputs = + { self + , flake-utils + , nixpkgs + }: + flake-utils.lib.eachDefaultSystem (system: + let + pkgs = import nixpkgs { + inherit system; + config = { + allowUnfree = true; + }; + overlays = [ + (import ./nix/overlays/ghc.nix) + ]; + }; + in + { + packages.graphql-parser = (pkgs.haskell.packages.${pkgs.ghcName}.callCabal2nix "graphql-parser" ./server/lib/graphql-parser-hs { }).overrideScope ( + self: super: { + hedgehog = self.hedgehog_1_1_1; + } + ); + + formatter = pkgs.nixpkgs-fmt; + + devShells.default = import ./nix/shell.nix { inherit pkgs; }; + } + ); +} diff --git a/nix/overlays/ghc.nix b/nix/overlays/ghc.nix new file mode 100644 index 0000000000000..7b1958fa8fb8b --- /dev/null +++ b/nix/overlays/ghc.nix @@ -0,0 +1,62 @@ +self: super: +let + versions = import ../versions.nix { pkgs = super; }; + + ghcVersion = "9.2.4"; + ghcName = "ghc${builtins.replaceStrings ["."] [""] ghcVersion}"; + ghcPatches = + if super.stdenv.targetPlatform.isDarwin + then [ + # Copied from https://github.com/NixOS/nixpkgs/pull/149942 + # If the GHC version is updated, we must update the patch URL too. + # --- + # Reverts the linking behavior of GHC to not resolve `-libc++` to `c++`. + # Without this, we get the following error on macOS: + # ghc: loadArchive: Neither an archive, nor a fat archive: `/path/to/clang++' + (super.fetchpatch { + url = "https://raw.githubusercontent.com/input-output-hk/haskell.nix/4b6ee9767daaf5fc1d5419e07733ab006b95ec93/overlays/patches/ghc/ghc-9.2-macOS-loadArchive-fix.patch"; + sha256 = "qOfIOqI5oM9695iWtK//OoP7NeF9K6ykGsdSOWJQN/I="; + }) + ] else [ ]; +in +{ + haskell = super.haskell // { + compiler = super.haskell.compiler // { + ${ghcName} = (versions.ensureVersion super.haskell.compiler.${ghcName}).overrideAttrs (oldAttrs: { + patches = (if oldAttrs ? patches then oldAttrs.patches else [ ]) ++ ghcPatches; + }); + }; + + packages = super.haskell.packages // { + ${ghcName} = super.haskell.packages.${ghcName}.override { + overrides = hself: hsuper: + if super.stdenv.targetPlatform.isDarwin + then + # macOS-specific overrides: + let + # Ormolu v0.5.0.1 doesn't build correctly on aarch64-darwin. + # Disabling the "fixity-th" flag seems to fix it. + # https://github.com/tweag/ormolu/issues/927 + fixOrmolu = p: super.lib.pipe p [ + (super.haskell.lib.compose.addExtraLibrary hself.file-embed) + (super.haskell.lib.compose.disableCabalFlag "fixity-th") + ]; + in + { + # On aarch64-darwin, this creates a cycle for some reason; didn't look too much into it. + ghcid = super.haskell.lib.overrideCabal hsuper.ghcid (drv: { enableSeparateBinOutput = false; }); + # See above. + ormolu = hself.ormolu_0_5_0_1; + ormolu_0_5_0_1 = fixOrmolu hsuper.ormolu_0_5_0_1; + fourmolu = hself.fourmolu_0_8_2_0; + fourmolu_0_8_2_0 = fixOrmolu hsuper.fourmolu_0_8_2_0; + } + else + # We don't need to override anything on Linux: + { }; + }; + }; + }; + + inherit ghcVersion ghcName; +} diff --git a/nix/shell.nix b/nix/shell.nix new file mode 100644 index 0000000000000..832971999d5b9 --- /dev/null +++ b/nix/shell.nix @@ -0,0 +1,132 @@ +{ pkgs }: +let + versions = import ./versions.nix { inherit pkgs; }; + + # Unix ODBC Support + freetdsWithODBC = pkgs.freetds.override { + odbcSupport = true; + inherit (pkgs) unixODBC; + }; + + # Verify this works by running `odbcinst -q -d`. + # The output should be the headings from the odbcinst.ini file. + # (You can easily see the generated file by running `cat $ODBCINSTINI`.) + # If you see any errors, please contact your friendly MSSQL and/or Nix expert. + odbcinstFile = + let msodbcsql = pkgs.unixODBCDrivers.msodbcsql17; + in + pkgs.writeTextFile { + name = "odbcinst.ini"; + text = '' + [${msodbcsql.fancyName}] + Description = ${msodbcsql.meta.description} + Driver = ${msodbcsql}/${msodbcsql.driver} + ''; + }; + + baseInputs = [ + pkgs.stdenv + pkgs.jq + ]; + + consoleInputs = [ + pkgs.google-cloud-sdk + pkgs."nodejs-${versions.nodejsVersion}_x" + ]; + + docsInputs = [ + pkgs.yarn + ]; + + integrationTestInputs = [ + pkgs.python3 + ]; + + # The version of GHC in `ghcName` is set in nix/overlays/ghc.nix. + # + # We list top-level packages before packages scoped to the GHC version, so + # that they appear first in the PATH. Otherwise we might end up with older + # versions of transitive dependencies (e.g. HLS depending on Ormolu). + haskellInputs = [ + pkgs.cabal2nix + + # The correct version of GHC. + pkgs.haskell.compiler.${pkgs.ghcName} + + # We use the default versions of these packages. + (versions.ensureVersion pkgs.haskellPackages.hlint) + (versions.ensureVersion pkgs.haskellPackages.hpack) + (versions.ensureVersion pkgs.haskellPackages.ormolu) + + # We build these packages using our custom GHC. + pkgs.haskell.packages.${pkgs.ghcName}.alex + pkgs.haskell.packages.${pkgs.ghcName}.apply-refact + pkgs.haskell.packages.${pkgs.ghcName}.cabal-install + pkgs.haskell.packages.${pkgs.ghcName}.ghcid + pkgs.haskell.packages.${pkgs.ghcName}.happy + pkgs.haskell.packages.${pkgs.ghcName}.haskell-language-server + pkgs.haskell.packages.${pkgs.ghcName}.hoogle + pkgs.haskell.packages.${pkgs.ghcName}.hspec-discover + ]; + + devInputs = [ + pkgs.nixpkgs-fmt + pkgs.shellcheck + ]; + + ciInputs = [ + pkgs.go + ]; + + dynamicLibraries = [ + pkgs.gmp + pkgs.libkrb5 # Includes required `gssapi` headers. + pkgs.ncurses + pkgs.openssl_3 + pkgs.pcre + pkgs.zlib + pkgs.zstd + + # PostgreSQL, Microsoft SQL Server, & MySQL dependencies. + freetdsWithODBC + pkgs.libmysqlclient + pkgs.mariadb + pkgs.postgresql + pkgs.unixODBC + ] + # Linux-specific libraries. + ++ pkgs.lib.optionals pkgs.stdenv.targetPlatform.isLinux [ + pkgs.stdenv.cc.cc.lib + # Microsoft SQL Server drivers don't work on aarch64 yet. + pkgs.unixODBCDrivers.msodbcsql17 + ]; + + includeLibraries = [ + pkgs.libkrb5.dev + pkgs.ncurses.dev + pkgs.openssl_3.dev + pkgs.pcre.dev + pkgs.zlib.dev + ]; + + serverDeps = + haskellInputs + ++ dynamicLibraries + ++ includeLibraries + ++ integrationTestInputs; +in +pkgs.mkShell { + buildInputs = baseInputs ++ consoleInputs ++ docsInputs ++ serverDeps ++ devInputs ++ ciInputs; + + # We set the ODBCINSTINI to the file defined above, which points to the MSSQL ODBC driver. + # The path is relative to `ODBCSYSINI`, which we set to empty. + # Microsoft SQL Server drivers don't work on aarch64 yet, so we also disable this. + ODBCSYSINI = ""; + ODBCINSTINI = pkgs.lib.strings.optionalString pkgs.stdenv.targetPlatform.isLinux "${odbcinstFile}"; + + LD_LIBRARY_PATH = pkgs.lib.strings.makeLibraryPath dynamicLibraries; + shellHook = pkgs.lib.strings.optionalString pkgs.stdenv.targetPlatform.isDarwin '' + # Without this, GHC will use the system `libcrypto` and `libssl` libraries, which fail. + export DYLD_LIBRARY_PATH="$LD_LIBRARY_PATH"; + ''; +} diff --git a/nix/versions.nix b/nix/versions.nix new file mode 100644 index 0000000000000..a30c49884303a --- /dev/null +++ b/nix/versions.nix @@ -0,0 +1,14 @@ +{ pkgs }: +let + versions = pkgs.lib.trivial.importJSON ../server/VERSIONS.json; +in +{ + ensureVersion = package: + let expected = versions.${package.pname}; + in + if expected == package.version + then package + else throw "Invalid version for package ${package.pname}: expected ${expected}, got ${package.version}"; + + nodejsVersion = pkgs.lib.strings.fileContents ../.nvmrc; +} diff --git a/shell.nix b/shell.nix new file mode 100644 index 0000000000000..a3b17662f5107 --- /dev/null +++ b/shell.nix @@ -0,0 +1,3 @@ +# See flake.nix for commentary before using this. +{ system ? builtins.currentSystem }: +(builtins.getFlake (toString ./.)).devShells.${system}