/* This file contains various functions that take a stdenv and return
   a new stdenv with different behaviour, e.g. using a different C
   compiler. */

{dietlibc, fetchurl, runCommand}:
   
   
rec {


  # Override the compiler in stdenv for specific packages.
  overrideGCC = stdenv: gcc: stdenv //
    { mkDerivation = args: stdenv.mkDerivation (args // { NIX_GCC = gcc; });
    };

    
  # Add some arbitrary packages to buildInputs for specific packages.
  # Used to override packages in stenv like Make.  Should not be used
  # for other dependencies.
  overrideInStdenv = stdenv: pkgs: stdenv //
    { mkDerivation = args: stdenv.mkDerivation (args //
        { buildInputs = (if args ? buildInputs then args.buildInputs else []) ++ pkgs; }
      );
    };


  # Override the setup script of stdenv.  Useful for testing new
  # versions of the setup script without causing a rebuild of
  # everything.
  #
  # Example:
  #   randomPkg = import ../bla { ...
  #     stdenv = overrideSetup stdenv ../stdenv/generic/setup-latest.sh;
  #   };
  overrideSetup = stdenv: setup: stdenv.regenerate setup;


  # Return a modified stdenv that uses dietlibc to create small
  # statically linked binaries.
  useDietLibC = stdenv: stdenv //
    { mkDerivation = args: stdenv.mkDerivation (args // {
        NIX_CFLAGS_LINK = "-static";

        # libcompat.a contains some commonly used functions.
        NIX_LDFLAGS = "-lcompat";

        # These are added *after* the command-line flags, so we'll
        # always optimise for size.
        NIX_CFLAGS_COMPILE =
          (if args ? NIX_CFLAGS_COMPILE then args.NIX_CFLAGS_COMPILE else "")
          + " -Os -s -D_BSD_SOURCE=1";

        configureFlags =
          (if args ? configureFlags then args.configureFlags else "")
          + " --disable-shared"; # brrr...

        NIX_GCC = import ../build-support/gcc-wrapper {
          inherit stdenv;
          libc = dietlibc;
          inherit (stdenv.gcc) gcc binutils nativeTools nativePrefix;
          nativeLibc = false;
        };
      });
      isDietLibC = true;
    } // {inherit fetchurl;};

    
  # Return a modified stdenv that uses klibc to create small
  # statically linked binaries.
  useKlibc = stdenv: klibc: stdenv //
    { mkDerivation = args: stdenv.mkDerivation (args // {
        NIX_CFLAGS_LINK = "-static";

        # These are added *after* the command-line flags, so we'll
        # always optimise for size.
        NIX_CFLAGS_COMPILE =
          (if args ? NIX_CFLAGS_COMPILE then args.NIX_CFLAGS_COMPILE else "")
          + " -Os -s";

        configureFlags =
          (if args ? configureFlags then args.configureFlags else "")
          + " --disable-shared"; # brrr...

        NIX_GCC = runCommand "klibc-wrapper" {} ''
          ensureDir $out/bin
          ln -s ${klibc}/bin/klcc $out/bin/gcc
          ln -s ${klibc}/bin/klcc $out/bin/cc
          ensureDir $out/nix-support
          echo 'PATH=$PATH:${stdenv.gcc.binutils}/bin' > $out/nix-support/setup-hook
        '';
      });
      isKlibc = true;
      isStatic = true;
    } // {inherit fetchurl;};

    
  # Return a modified stdenv that tries to build statically linked
  # binaries.
  makeStaticBinaries = stdenv: stdenv //
    { mkDerivation = args: stdenv.mkDerivation (args // {
        NIX_CFLAGS_LINK = "-static";

        configureFlags =
          (if args ? configureFlags then args.configureFlags else "")
          + " --disable-shared"; # brrr...
      });
      isStatic = true;
    } // {inherit fetchurl;};

    
  # Return a modified stdenv that enables building static libraries.
  enableStaticLibraries = stdenv: stdenv //
    { mkDerivation = args: stdenv.mkDerivation (args // {
        dontDisableStatic = true;
        configureFlags =
          (if args ? configureFlags then args.configureFlags else "")
          + " --enable-static";
      });
    } // {inherit fetchurl;};

    
  # Return a modified stdenv that adds a cross compiler to the
  # builds.
  makeStdenvCross = stdenv: cross: binutilsCross: gccCross: stdenv //
    { mkDerivation = {name ? "", buildInputs ? [], buildNativeInputs ? [],
            propagatedBuildInputs ? [], propagatedBuildNativeInputs ? [],
            selfBuildNativeInput ? false, ...}@args: let

            # *BuildInputs exists temporarily as another name for
            # *HostInputs.

            getBuildDrv = drv : if (drv ? buildDrv) then drv.buildDrv else drv;
            buildNativeInputsDrvs = map (getBuildDrv) buildNativeInputs;
            buildInputsDrvs = map (drv: drv.hostDrv) buildInputs;
            buildInputsDrvsAsBuildInputs = map (getBuildDrv) buildInputs;
            propagatedBuildInputsDrvs = map (drv: drv.hostDrv) (propagatedBuildInputs);
            propagatedBuildNativeInputsDrvs = map (drv: drv.buildDrv)
                (propagatedBuildNativeInputs);

            # The base stdenv already knows that buildNativeInputs and
            # buildInputs should be built with the usual gcc-wrapper
            # And the same for propagatedBuildInputs.
            buildDrv = stdenv.mkDerivation args;

            # Temporary expression until the cross_renaming, to handle the
            # case of pkgconfig given as buildInput, but to be used as
            # buildNativeInput.
            hostAsBuildDrv = drv: builtins.unsafeDiscardStringContext
                drv.buildDrv.drvPath == builtins.unsafeDiscardStringContext
                drv.hostDrv.drvPath;
            nativeInputsFromBuildInputs = stdenv.lib.filter (hostAsBuildDrv) buildInputs;

            # We should overwrite the input attributes in hostDrv, to overwrite
            # the defaults for only-native builds in the base stdenv
            hostDrv = if (cross == null) then buildDrv else
                stdenv.mkDerivation (args // {
                    name = name + "-" + cross.config;
                    buildNativeInputs = buildNativeInputsDrvs
                      ++ nativeInputsFromBuildInputs
                      ++ [ gccCross binutilsCross ] ++
                      stdenv.lib.optional selfBuildNativeInput buildDrv;

                    # Cross-linking dynamic libraries, every buildInput should
                    # be propagated because ld needs the -rpath-link to find
                    # any library needed to link the program dynamically at
                    # loader time. ld(1) explains it.
                    buildInputs = [];
                    propagatedBuildInputs = propagatedBuildInputsDrvs ++
                      buildInputsDrvs;
                    propagatedBuildNativeInputs = propagatedBuildNativeInputsDrvs;

                    crossConfig = cross.config;
                });
        in buildDrv // {
            inherit hostDrv buildDrv;
        };
    } // { inherit cross; };

    
  /* Modify a stdenv so that the specified attributes are added to
     every derivation returned by its mkDerivation function.

     Example:
       stdenvNoOptimise =
         addAttrsToDerivation
           { NIX_CFLAGS_COMPILE = "-O0"; }
           stdenv;
  */
  addAttrsToDerivation = extraAttrs: stdenv: stdenv //
    { mkDerivation = args: stdenv.mkDerivation (args // extraAttrs); };


  /* Return a modified stdenv that perfoms the build under $out/.build
     instead of in $TMPDIR.  Thus, the sources are kept available.
     This is useful for things like debugging or generation of
     dynamic analysis reports. */ 
  keepBuildTree = stdenv:
    addAttrsToDerivation
      { prePhases = "moveBuildDir";

        moveBuildDir =
          ''
            ensureDir $out/.build
            cd $out/.build
          '';
      } stdenv;


  cleanupBuildTree = stdenv:
    addAttrsToDerivation
      { postPhases = "cleanupBuildDir";

        # Get rid of everything that isn't a gcno file or a C source
        # file.  This also includes the gcda files; we're not
        # interested in coverage resulting from the package's own test
        # suite.  Also strip the `.tmp_' prefix from gcno files.  (The
        # Linux kernel creates these.)
        cleanupBuildDir =
          ''
            find $out/.build/ -type f -a ! \
              \( -name "*.c" -o -name "*.h" -o -name "*.gcno" \) \
              | xargs rm -f --

            for i in $(find $out/.build/ -name ".tmp_*.gcno"); do 
                mv "$i" "$(echo $i | sed s/.tmp_//)"
            done
          '';
      } stdenv;          
      

  /* Return a modified stdenv that builds packages with GCC's coverage
     instrumentation.  The coverage note files (*.gcno) are stored in
     $out/.build, along with the source code of the package, to enable
     programs like lcov to produce pretty-printed reports.
  */
  addCoverageInstrumentation = stdenv:
    addAttrsToDerivation
      { NIX_CFLAGS_COMPILE = "-O0 --coverage";

        # This is an uberhack to prevent libtool from removing gcno
        # files.  This has been fixed in libtool, but there are
        # packages out there with old ltmain.sh scripts.
        # See http://www.mail-archive.com/libtool@gnu.org/msg10725.html
        postUnpack =
          ''
            for i in $(find -name ltmain.sh); do
                substituteInPlace $i --replace '*.$objext)' '*.$objext | *.gcno)'
            done
          '';
      }
      
      # Object files instrumented with coverage analysis write
      # runtime coverage data to <path>/<object>.gcda, where <path>
      # is the location where gcc originally created the object
      # file.  That would be /tmp/nix-build-<something>, which will
      # be long gone by the time we run the program.  Furthermore,
      # the <object>.gcno files created at compile time are also
      # written there.  And to make nice coverage reports with lcov,
      # we need the source code.  So we have to use the
      # `keepBuildTree' adapter as well.
      (cleanupBuildTree (keepBuildTree stdenv));
      

  /* Replace the meta.maintainers field of a derivation.  This is useful
     when you want to fork to update some packages without disturbing other
     developers.

     e.g.:  in all-packages.nix:

     # remove all maintainers.
     defaultStdenv = replaceMaintainersField allStdenvs.stdenv pkgs [];
  */
  replaceMaintainersField = stdenv: pkgs: maintainers: stdenv //
    { mkDerivation = args:
        pkgs.lib.recursiveUpdate
          (stdenv.mkDerivation args)
          { meta.maintainers = maintainers; };
    };


  /* Use the trace output to report all processed derivations with their
     license name.
  */
  traceDrvLicenses = stdenv: stdenv //
    { mkDerivation = args:
        let
          pkg = stdenv.mkDerivation args;
          printDrvPath = val: let
            drvPath = builtins.unsafeDiscardStringContext pkg.drvPath;
            license =
              if pkg ? meta && pkg.meta ? license then
                pkg.meta.license
              else
                null;
          in
            builtins.trace "@:drv:${toString drvPath}:${builtins.exprToString license}:@"
              val;
        in pkg // {
          outPath = printDrvPath pkg.outPath;
          drvPath = printDrvPath pkg.drvPath;
        };
    };

    
  /* Abort if the license predicate is not verified for a derivation
     declared with mkDerivation.

     One possible predicate to avoid all non-free packages can be achieved
     with the following function:

     isFree = license: with builtins;
       if isNull license then true
       else if isList license then lib.all isFree license
       else license != "non-free" && license != "unfree";

     This adapter can be defined on the defaultStdenv definition.  You can
     use it by patching the all-packages.nix file or by using the override
     feature of ~/.nixpkgs/config.nix .
  */
  validateLicenses = licensePred: stdenv: stdenv //
    { mkDerivation = args:
        let
          pkg = stdenv.mkDerivation args;
          license =
            if pkg ? meta && pkg.meta ? license then
              pkg.meta.license
            else
              null;

          validate = arg:
            if licensePred license then arg
            else abort "
              Error while building ${builtins.unsafeDiscardStringContext pkg.drvPath}:
              The license predicate is not verified.

              bad license: ${builtins.exprToString license}
            ";

        in pkg // {
          outPath = validate pkg.outPath;
          drvPath = validate pkg.drvPath;
        };
    };
}