diff --git a/pkgs/development/compilers/nim/default.nix b/pkgs/development/compilers/nim/default.nix index 25b1c4873572..ed46b988d2c6 100644 --- a/pkgs/development/compilers/nim/default.nix +++ b/pkgs/development/compilers/nim/default.nix @@ -1,8 +1,9 @@ # https://nim-lang.github.io/Nim/packaging.html # https://nim-lang.org/docs/nimc.html -{ lib, buildPackages, stdenv, fetchurl, fetchgit, fetchFromGitHub, makeWrapper -, openssl, pcre, readline, boehmgc, sqlite, nim-unwrapped }: +{ lib, callPackage, buildPackages, stdenv, fetchurl, fetchgit, fetchFromGitHub +, makeWrapper, openssl, pcre, readline, boehmgc, sqlite, nim-unwrapped +, nimble-unwrapped }: let parseCpu = platform: @@ -186,138 +187,141 @@ in { nim' = buildPackages.nim-unwrapped; nimble' = buildPackages.nimble-unwrapped; inherit (stdenv) targetPlatform; - in stdenv.mkDerivation { - name = "${targetPlatform.config}-nim-wrapper-${nim'.version}"; - inherit (nim') version; - preferLocalBuild = true; - strictDeps = true; + self = stdenv.mkDerivation { + name = "${targetPlatform.config}-nim-wrapper-${nim'.version}"; + inherit (nim') version; + preferLocalBuild = true; + strictDeps = true; - nativeBuildInputs = [ makeWrapper ]; + nativeBuildInputs = [ makeWrapper ]; - patches = [ - ./nim.cfg.patch - # Remove configurations that clash with ours - ]; + patches = [ + ./nim.cfg.patch + # Remove configurations that clash with ours + ]; - unpackPhase = '' - runHook preUnpack - tar xf ${nim'.src} nim-$version/config - cd nim-$version - runHook postUnpack - ''; - - dontConfigure = true; - - buildPhase = - # Configure the Nim compiler to use $CC and $CXX as backends - # The compiler is configured by two configuration files, each with - # a different DSL. The order of evaluation matters and that order - # is not documented, so duplicate the configuration across both files. - '' - runHook preBuild - cat >> config/config.nims << WTF - - switch("os", "${nimTarget.os}") - switch("cpu", "${nimTarget.cpu}") - switch("define", "nixbuild") - - # Configure the compiler using the $CC set by Nix at build time - import strutils - let cc = getEnv"CC" - if cc.contains("gcc"): - switch("cc", "gcc") - elif cc.contains("clang"): - switch("cc", "clang") - WTF - - mv config/nim.cfg config/nim.cfg.old - cat > config/nim.cfg << WTF - os = "${nimTarget.os}" - cpu = "${nimTarget.cpu}" - define:"nixbuild" - WTF - - cat >> config/nim.cfg < config/nim.cfg.old - rm config/nim.cfg.old - - cat >> config/nim.cfg << WTF - - clang.cpp.exe %= "\$CXX" - clang.cpp.linkerexe %= "\$CXX" - clang.exe %= "\$CC" - clang.linkerexe %= "\$CC" - gcc.cpp.exe %= "\$CXX" - gcc.cpp.linkerexe %= "\$CXX" - gcc.exe %= "\$CC" - gcc.linkerexe %= "\$CC" - WTF - - runHook postBuild + unpackPhase = '' + runHook preUnpack + tar xf ${nim'.src} nim-$version/config + cd nim-$version + runHook postUnpack ''; - wrapperArgs = [ - "--prefix PATH : ${lib.makeBinPath [ buildPackages.gdb ]}:${ - placeholder "out" - }/bin" - # Used by nim-gdb + dontConfigure = true; - "--prefix LD_LIBRARY_PATH : ${lib.makeLibraryPath [ openssl pcre ]}" - # These libraries may be referred to by the standard library. - # This is broken for cross-compilation because the package - # set will be shifted back by nativeBuildInputs. + buildPhase = + # Configure the Nim compiler to use $CC and $CXX as backends + # The compiler is configured by two configuration files, each with + # a different DSL. The order of evaluation matters and that order + # is not documented, so duplicate the configuration across both files. + '' + runHook preBuild + cat >> config/config.nims << WTF - "--set NIM_CONFIG_PATH ${placeholder "out"}/etc/nim" - # Use the custom configuration + switch("os", "${nimTarget.os}") + switch("cpu", "${nimTarget.cpu}") + switch("define", "nixbuild") - ''--set NIX_HARDENING_ENABLE "''${NIX_HARDENING_ENABLE/fortify}"'' - # Fortify hardening appends -O2 to gcc flags which is unwanted for unoptimized nim builds. - ]; + # Configure the compiler using the $CC set by Nix at build time + import strutils + let cc = getEnv"CC" + if cc.contains("gcc"): + switch("cc", "gcc") + elif cc.contains("clang"): + switch("cc", "clang") + WTF - installPhase = '' - runHook preInstall + mv config/nim.cfg config/nim.cfg.old + cat > config/nim.cfg << WTF + os = "${nimTarget.os}" + cpu = "${nimTarget.cpu}" + define:"nixbuild" + WTF - mkdir -p $out/bin $out/etc + cat >> config/nim.cfg < config/nim.cfg.old + rm config/nim.cfg.old - cp -r config $out/etc/nim + cat >> config/nim.cfg << WTF + + clang.cpp.exe %= "\$CXX" + clang.cpp.linkerexe %= "\$CXX" + clang.exe %= "\$CC" + clang.linkerexe %= "\$CC" + gcc.cpp.exe %= "\$CXX" + gcc.cpp.linkerexe %= "\$CXX" + gcc.exe %= "\$CC" + gcc.linkerexe %= "\$CC" + WTF + + runHook postBuild + ''; + + wrapperArgs = [ + "--prefix PATH : ${lib.makeBinPath [ buildPackages.gdb ]}:${ + placeholder "out" + }/bin" + # Used by nim-gdb + + "--prefix LD_LIBRARY_PATH : ${lib.makeLibraryPath [ openssl pcre ]}" + # These libraries may be referred to by the standard library. + # This is broken for cross-compilation because the package + # set will be shifted back by nativeBuildInputs. + + "--set NIM_CONFIG_PATH ${placeholder "out"}/etc/nim" + # Use the custom configuration + + ''--set NIX_HARDENING_ENABLE "''${NIX_HARDENING_ENABLE/fortify}"'' + # Fortify hardening appends -O2 to gcc flags which is unwanted for unoptimized nim builds. + ]; + + installPhase = '' + runHook preInstall + + mkdir -p $out/bin $out/etc + + cp -r config $out/etc/nim + + for binpath in ${nim'}/bin/nim?*; do + local binname=`basename $binpath` + makeWrapper \ + $binpath $out/bin/${targetPlatform.config}-$binname \ + $wrapperArgs + ln -s $out/bin/${targetPlatform.config}-$binname $out/bin/$binname + done - for binpath in ${nim'}/bin/nim?*; do - local binname=`basename $binpath` makeWrapper \ - $binpath $out/bin/${targetPlatform.config}-$binname \ + ${nim'}/nim/bin/nim $out/bin/${targetPlatform.config}-nim \ + --set-default CC $(command -v $CC) \ + --set-default CXX $(command -v $CXX) \ $wrapperArgs - ln -s $out/bin/${targetPlatform.config}-$binname $out/bin/$binname - done + ln -s $out/bin/${targetPlatform.config}-nim $out/bin/nim - makeWrapper \ - ${nim'}/nim/bin/nim $out/bin/${targetPlatform.config}-nim \ - --set-default CC $(command -v $CC) \ - --set-default CXX $(command -v $CXX) \ - $wrapperArgs - ln -s $out/bin/${targetPlatform.config}-nim $out/bin/nim + makeWrapper \ + ${nim'}/bin/testament $out/bin/${targetPlatform.config}-testament \ + $wrapperArgs + ln -s $out/bin/${targetPlatform.config}-testament $out/bin/testament - makeWrapper \ - ${nim'}/bin/testament $out/bin/${targetPlatform.config}-testament \ - $wrapperArgs - ln -s $out/bin/${targetPlatform.config}-testament $out/bin/testament + makeWrapper \ + ${nimble'}/bin/nimble $out/bin/${targetPlatform.config}-nimble \ + --suffix PATH : $out/bin + ln -s $out/bin/${targetPlatform.config}-nimble $out/bin/nimble - makeWrapper \ - ${nimble'}/bin/nimble $out/bin/${targetPlatform.config}-nimble \ - --suffix PATH : $out/bin - ln -s $out/bin/${targetPlatform.config}-nimble $out/bin/nimble + runHook postInstall + ''; - runHook postInstall - ''; + passthru = { + nim = nim'; + nimble = nimble'; + }; - passthru = { - nim = nim'; - nimble = nimble'; - }; - - meta = nim'.meta // { - description = nim'.meta.description - + " (${targetPlatform.config} wrapper)"; - platforms = with lib.platforms; unix ++ genode; + meta = nim'.meta // { + description = nim'.meta.description + + " (${targetPlatform.config} wrapper)"; + platforms = with lib.platforms; unix ++ genode; + }; }; + in self // { + pkgs = callPackage ../../../top-level/nim-packages.nix { nim = self; }; }; } diff --git a/pkgs/development/nim-packages/build-nim-package/default.nix b/pkgs/development/nim-packages/build-nim-package/default.nix new file mode 100644 index 000000000000..6c7aafd22c81 --- /dev/null +++ b/pkgs/development/nim-packages/build-nim-package/default.nix @@ -0,0 +1,43 @@ +{ lib, stdenv, nim, nim_builder }: + +{ strictDeps ? true, nativeBuildInputs ? [ ], configurePhase ? null +, buildPhase ? null, checkPhase ? null, installPhase ? null, meta ? { }, ... +}@attrs: + +stdenv.mkDerivation (attrs // { + inherit strictDeps; + nativeBuildInputs = [ nim nim_builder ] ++ nativeBuildInputs; + + configurePhase = if isNull configurePhase then '' + runHook preConfigure + find $NIX_BUILD_TOP -name .attrs.json + nim_builder --phase:configure + runHook postConfigure + '' else + buildPhase; + + buildPhase = if isNull buildPhase then '' + runHook preBuild + nim_builder --phase:build + runHook postBuild + '' else + buildPhase; + + checkPhase = if isNull checkPhase then '' + runHook preCheck + nim_builder --phase:check + runHook postCheck + '' else + checkPhase; + + installPhase = if isNull installPhase then '' + runHook preInstall + nim_builder --phase:install + runHook postInstall + '' else + installPhase; + + meta = meta // { + maintainers = (meta.maintainers or [ ]) ++ [ lib.maintainers.ehmry ]; + }; +}) diff --git a/pkgs/development/nim-packages/fetch-nimble/builder.sh b/pkgs/development/nim-packages/fetch-nimble/builder.sh new file mode 100644 index 000000000000..693ab339408e --- /dev/null +++ b/pkgs/development/nim-packages/fetch-nimble/builder.sh @@ -0,0 +1,12 @@ +source $stdenv/setup +export HOME=$NIX_BUILD_TOP + +nimble --accept --noSSLCheck develop "${pkgname}@${version}" +# TODO: bring in the certificates for Nimble to verify the fetch of +# the package list. + +pkgdir=${NIX_BUILD_TOP}/${pkgname} + +find "$pkgdir" -name .git -print0 | xargs -0 rm -rf + +cp -a "$pkgdir" "$out" diff --git a/pkgs/development/nim-packages/fetch-nimble/default.nix b/pkgs/development/nim-packages/fetch-nimble/default.nix new file mode 100644 index 000000000000..ccdacc8e27b9 --- /dev/null +++ b/pkgs/development/nim-packages/fetch-nimble/default.nix @@ -0,0 +1,20 @@ +{ lib, makeOverridable, stdenv, gitMinimal, nim, cacert }: + +makeOverridable ( + + { pname, version, hash ? lib.fakeHash, + + meta ? { }, passthru ? { }, preferLocalBuild ? true }: + stdenv.mkDerivation { + inherit version meta passthru preferLocalBuild; + pname = pname + "-src"; + pkgname = pname; + builder = ./builder.sh; + nativeBuildInputs = [ gitMinimal nim ]; + outputHash = hash; + outputHashAlgo = null; + outputHashMode = "recursive"; + impureEnvVars = lib.fetchers.proxyImpureEnvVars + ++ [ "GIT_PROXY_COMMAND" "SOCKS_SERVER" ]; + GIT_SSL_CAINFO = "${cacert}/etc/ssl/certs/ca-bundle.crt"; + }) diff --git a/pkgs/development/nim-packages/nim_builder/default.nix b/pkgs/development/nim-packages/nim_builder/default.nix new file mode 100644 index 000000000000..34da4dfa61a0 --- /dev/null +++ b/pkgs/development/nim-packages/nim_builder/default.nix @@ -0,0 +1,19 @@ +{ lib, stdenv, nim }: + +stdenv.mkDerivation { + pname = "nim_builder"; + inherit (nim) version; + dontUnpack = true; + nativeBuildInputs = [ nim ]; + buildPhase = '' + cp ${./nim_builder.nim} nim_builder.nim + nim c --nimcache:$TMPDIR nim_builder + ''; + installPhase = '' + install -Dt $out/bin nim_builder + ''; + meta = { + description = "Internal Nixpkgs utility for buildNimPackage."; + maintainers = [ lib.maintainers.ehmry ]; + }; +} diff --git a/pkgs/development/nim-packages/nim_builder/nim_builder.nim b/pkgs/development/nim-packages/nim_builder/nim_builder.nim new file mode 100644 index 000000000000..b8881b700047 --- /dev/null +++ b/pkgs/development/nim-packages/nim_builder/nim_builder.nim @@ -0,0 +1,166 @@ +# SPDX-FileCopyrightText: 2021 Nixpkgs/NixOS contributors +## Custom Nim builder for Nixpkgs. + +import std/[os, osproc, parseutils, sequtils, streams, strutils] + +proc findNimbleFile(): string = + ## Copied from Nimble. + ## Copyright (c) 2015, Dominik Picheta + ## BSD3 + let dir = getCurrentDir() + result = "" + var hits = 0 + for kind, path in walkDir(dir): + if kind in {pcFile, pcLinkToFile}: + let ext = path.splitFile.ext + if ext == ".nimble": + result = path + inc hits + if hits >= 2: + quit("Only one .nimble file should be present in " & dir) + elif hits == 0: + quit("Could not find a file with a .nimble extension in " & dir) + +proc getEnvBool(key: string; default = false): bool = + ## Parse a boolean environmental variable. + let val = getEnv(key) + if val == "": default + else: parseBool(val) + +proc getNimbleFilePath(): string = + ## Get the Nimble file for the current package. + if existsEnv"nimbleFile": + getEnv"nimbleFile" + else: + findNimbleFile() + +proc getNimbleValue(filePath, key: string; default = ""): string = + ## Extract a string value from the Nimble file at ``filePath``. + var + fs = newFileStream(filePath, fmRead) + line: string + if fs.isNil: + quit("could not open " & filePath) + while fs.readline(line): + if line.startsWith(key): + var i = key.len + i.inc skipWhile(line, Whitespace, i) + if line[i] == '=': + inc i + i.inc skipWhile(line, Whitespace, i) + discard parseUntil(line, result, Newlines, i) + if result.len > 0 and result[0] == '"': + result = result.unescape + return + default + +proc getNimbleValues(filePath, key: string): seq[string] = + ## Extract a string sequence from the Nimble file at ``filePath``. + var gunk = getNimbleValue(filePath, key) + result = gunk.strip(chars = {'@', '[', ']'}).split(',') + if result == @[""]: reset result + apply(result) do (s: var string): + s = s.strip() + if s.len > 0 and s[0] == '"': + s = s.unescape() + +proc configurePhase*() = + ## Generate "config.nims" which will be read by the Nim + ## compiler during later phases. + const configFilePath = "config.nims" + echo "generating ", configFilePath + let + nf = getNimbleFilePath() + mode = + if fileExists configFilePath: fmAppend + else: fmWrite + var cfg = newFileStream(configFilePath, mode) + proc switch(key, val: string) = + cfg.writeLine("switch(", key.escape, ",", val.escape, ")") + switch("backend", nf.getNimbleValue("backend", "c")) + switch("nimcache", getEnv("NIX_BUILD_TOP", ".") / "nimcache") + if getEnvBool("nimRelease", true): + switch("define", "release") + for def in getEnv("nimDefines").split: + if def != "": + switch("define", def) + for input in getEnv("buildInputs").split: + if input != "": + for nimbleFile in walkFiles(input / "*.nimble"): + let inputSrc = normalizedPath( + input / nimbleFile.getNimbleValue("srcDir", ".")) + echo "found nimble input ", inputSrc + switch("path", inputSrc) + close(cfg) + +proc buildPhase*() = + ## Build the programs listed in the Nimble file and + ## optionally some documentation. + var cmds: seq[string] + proc before(idx: int) = + echo "build job ", idx, ": ", cmds[idx] + let + nf = getNimbleFilePath() + bins = nf.getNimbleValues("bin") + srcDir = nf.getNimbleValue("srcDir", ".") + binDir = getenv("outputBin", getenv("out", "/dev/null")) / "bin" + if bins != @[]: + for bin in bins: + cmds.add("nim compile $# --outdir:$# $#" % + [getenv"nimFlags", binDir, normalizedPath(srcDir / bin)]) + if getEnvBool"nimDoc": + echo "generating documentation" + let docDir = getenv("outputDoc", (getenv("out", "/dev/null") / "doc")) + for path in walkFiles(srcDir / "*.nim"): + cmds.add("nim doc --outdir:$# $#" % [docDir, path]) + if cmds.len > 0: + let err = execProcesses( + cmds, n = 1, + beforeRunEvent = before) + if err != 0: quit("build phase failed", err) + +proc installPhase*() = + ## Install the Nim sources if ``nimBinOnly`` is not + ## set in the environment. + if not getEnvBool"nimBinOnly": + let + nf = getNimbleFilePath() + srcDir = nf.getNimbleValue("srcDir", ".") + devDir = getenv("outputDev", getenv("out", "/dev/null")) + echo "Install ", srcDir, " to ", devDir + copyDir(normalizedPath(srcDir), normalizedPath(devDir / srcDir)) + copyFile(nf, devDir / nf.extractFilename) + +proc checkPhase*() = + ## Build and run the tests in ``tests``. + var cmds: seq[string] + proc before(idx: int) = + echo "check job ", idx, ": ", cmds[idx] + for path in walkPattern("tests/t*.nim"): + cmds.add("nim r $#" % [path]) + let err = execProcesses( + cmds, n = 1, + beforeRunEvent = before) + if err != 0: quit("check phase failed", err) + +when isMainModule: + import std/parseopt + var phase: string + + for kind, key, val in getopt(): + case kind + of cmdLongOption: + case key.toLowerAscii + of "phase": + if phase != "": quit("only a single phase may be specified") + phase = val + else: quit("unhandled argument " & key) + of cmdEnd: discard + else: quit("unhandled argument " & key) + + case phase + of "configure": configurePhase() + of "build": buildPhase() + of "install": installPhase() + of "check": checkPhase() + else: quit("unhandled phase " & phase) diff --git a/pkgs/top-level/all-packages.nix b/pkgs/top-level/all-packages.nix index e2be394d249c..1d74908ad9f4 100644 --- a/pkgs/top-level/all-packages.nix +++ b/pkgs/top-level/all-packages.nix @@ -12064,6 +12064,7 @@ with pkgs; inherit (callPackages ../development/compilers/nim { }) nim-unwrapped nimble-unwrapped nim; + nimPackages = recurseIntoAttrs nim.pkgs; nrpl = callPackage ../development/tools/nrpl { }; diff --git a/pkgs/top-level/nim-packages.nix b/pkgs/top-level/nim-packages.nix new file mode 100644 index 000000000000..f426870a6402 --- /dev/null +++ b/pkgs/top-level/nim-packages.nix @@ -0,0 +1,12 @@ +{ lib, pkgs, stdenv, newScope, nim, fetchFromGitHub }: + +lib.makeScope newScope (self: + let callPackage = self.callPackage; + in { + inherit nim; + nim_builder = callPackage ../development/nim-packages/nim_builder { }; + buildNimPackage = + callPackage ../development/nim-packages/build-nim-package { }; + fetchNimble = callPackage ../development/nim-packages/fetch-nimble { }; + + })