args: with args; with stringsWithDeps; with lib;
let inherit (builtins) head tail trace; in
(rec
{
        inherit writeScript; 

        src = attrByPath ["src"] "" args;

        addSbinPath = attrByPath ["addSbinPath"] false args;

        forceShare = if args ? forceShare then args.forceShare else ["man" "doc" "info"];
        forceCopy = ["COPYING" "LICENSE" "DISTRIBUTION" "LEGAL" 
          "README" "AUTHORS" "ChangeLog" "CHANGES" "LICENCE" "COPYRIGHT"] ++ 
          (optional (attrByPath ["forceCopyDoc"] true args) "doc"); 

        hasSuffixHack = a: b: hasSuffix (a+(substring 0 0 b)) ((substring 0 0 a)+b);
        
        archiveType = s: 
                (if hasSuffixHack ".tar" s then "tar"
                else if (hasSuffixHack ".tar.gz" s) || (hasSuffixHack ".tgz" s) then "tgz" 
                else if (hasSuffixHack ".tar.bz2" s) || (hasSuffixHack ".tbz2" s) || 
			(hasSuffixHack ".tbz" s) then "tbz2"
                else if hasSuffixHack ".tar.Z" s then "tZ" 
                else if hasSuffixHack ".tar.lzma" s then "tar.lzma"
                else if hasSuffixHack ".tar.xz" s then "tar.xz"
                else if (hasSuffixHack ".zip" s) || (hasSuffixHack ".ZIP" s) then "zip"
                else if hasSuffixHack "-cvs-export" s then "cvs-dir"
                else if hasSuffixHack "-git-export" s then "git-dir"
                else if hasSuffixHack ".nar.bz2" s then "narbz2"
                else if hasSuffixHack ".rpm" s then "rpm"

                # Mostly for manually specified directories..
                else if hasSuffixHack "/" s then "dir"

                # Last block - for single files!! It should be always after .tar.*
                else if hasSuffixHack ".bz2" s then "plain-bz2"
                else if hasSuffixHack ".gz" s then "plain-gz"

		# For bootstrap calls
		else if (s ==("" + (substring 0 0 s))) then "empty"

                else (abort "unknown archive type : ${s}"));

        # changing this ? see [1]
        defAddToSearchPath = fullDepEntry ("
                addToSearchPathWithCustomDelimiter() {
                        local delimiter=\$1
                        local varName=\$2
                        local needDir=\$3
                        local addDir=\${4:-\$needDir}
                        local prefix=\$5
                        if [ -d \$prefix\$needDir ]; then
                                if [ -z \${!varName} ]; then
                                        eval export \${varName}=\${prefix}\$addDir
                                else
                                        eval export \${varName}=\${!varName}\${delimiter}\${prefix}\$addDir
                                fi
                        fi
                }

                addToSearchPath()
                {
                        addToSearchPathWithCustomDelimiter \"\${PATH_DELIMITER}\" \"\$@\"
                }
        ") ["defNest"];

        # changing this ? see [1]
        defNest = noDepEntry ("
                nestingLevel=0

                startNest() {
                        nestingLevel=\$((\$nestingLevel + 1))
                                echo -en \"\\e[\$1p\"
                }

                stopNest() {
                        nestingLevel=\$((\$nestingLevel - 1))
                                echo -en \"\\e[q\"
                }

                header() {
                        startNest \"\$2\"
                                echo \"\$1\"
                }

                # Make sure that even when we exit abnormally, the original nesting
                # level is properly restored.
                closeNest() {
                        while test \$nestingLevel -gt 0; do
                                stopNest
                                        done
                }

                trap \"closeNest\" EXIT
        ");


        # changing this ? see [1]
        minInit = fullDepEntry ("
                ${stdenv.preHook}
                
                set -e
                NIX_GCC=${stdenv.gcc}
                export SHELL=${stdenv.shell}
                PATH_DELIMITER=':'
                
                # Set up the initial path.
                PATH=
                for i in \$NIX_GCC ${toString stdenv.initialPath}; do
                    PATH=\$PATH\${PATH:+\"\${PATH_DELIMITER}\"}\$i/bin
                done

                export TZ=UTC

                prefix=${if args ? prefix then (toString args.prefix) else "\$out"}

                ") ["defNest" "defAddToSearchPath"];
                
        # if you change this rewrite using '' instead of "" to get rid of indentation in builder scripts
        addInputs = fullDepEntry ("
                # Recursively find all build inputs.
                findInputs()
                {
                    local pkg=\$1

                    case \$pkgs in
                        *\\ \$pkg\\ *)
                            return 0
                            ;;
                    esac
                    
                    pkgs=\"\$pkgs \$pkg \"

                        echo \$pkg
                    if test -f \$pkg/nix-support/setup-hook; then
                        source \$pkg/nix-support/setup-hook
                    fi
                }

                pkgs=\"\"
                for i in \$NIX_GCC ${toString realBuildInputs}; do
                    findInputs \$i
                done


                # Set the relevant environment variables to point to the build inputs
                # found above.
                addToEnv()
                {
                    local pkg=\$1
                "+
                (if !((args ? ignoreFailedInputs) && (args.ignoreFailedInputs == 1)) then "
                    if [ -e \$1/nix-support/failed ]; then
                        echo \"failed input \$1\" >&2
                        fail
                    fi
                " else "")
                +(if addSbinPath then "
                    if test -d \$1/sbin; then
                        export _PATH=\$_PATH\${_PATH:+\"\${PATH_DELIMITER}\"}\$1/sbin
                    fi
                " else "")
                +"
                    if test -d \$1/bin; then
                        export _PATH=\$_PATH\${_PATH:+\"\${PATH_DELIMITER}\"}\$1/bin
                    fi

                    for i in \"\${envHooks[@]}\"; do
                        \$i \$pkg
                    done
                }

                for i in \$pkgs; do
                    addToEnv \$i
                done


                # Add the output as an rpath.
                if test \"\$NIX_NO_SELF_RPATH\" != \"1\"; then
                    export NIX_LDFLAGS=\"-rpath \$out/lib \$NIX_LDFLAGS\"
                fi

                PATH=\$_PATH\${_PATH:+\"\${PATH_DELIMITER}\"}\$PATH
        ") ["minInit"];
        
        # changing this ? see [1]
        defEnsureDir = fullDepEntry ("
                # Ensure that the given directories exists.
                ensureDir() {
                    local dir
                    for dir in \"\$@\"; do
                        if ! test -x \"\$dir\"; then mkdir -p \"\$dir\"; fi
                    done
                }
        ") ["minInit"];

        # changing this ? see [1]
        toSrcDir = s : fullDepEntry ((if (archiveType s) == "tar" then "
                tar xvf '${s}'
                cd \"\$(tar tf '${s}' | head -1 | sed -e 's@/.*@@' )\"
        " else if (archiveType s) == "tgz" then "
                tar xvzf '${s}'
                cd \"\$(tar tzf '${s}' | head -1 | sed -e 's@/.*@@' )\"
        " else if (archiveType s) == "tbz2" then "
                tar xvjf '${s}'
                cd \"\$(tar tjf '${s}' | head -1 | sed -e 's@/.*@@' )\"
        " else if (archiveType s) == "tZ" then "
                uncompress < '${s}' | tar x
                cd \"\$(uncompress < '${s}' | tar t | head -1 | sed -e 's@/.*@@' )\"
        " else if (archiveType s) == "tar.lzma" then "
                unlzma -d -c <'${s}' | tar xv
                cd \"\$(unlzma -d -c <'${s}' | tar t | head -1 | sed -e 's@/.*@@' )\"
        " else if (archiveType s) == "tar.xz" then "
                xz -d -c <'${s}' | tar xv
                cd \"\$(xz -d -c <'${s}' | tar t | head -1 | sed -e 's@/.*@@' )\"
        " else if (archiveType s) == "zip" then "
                unzip '${s}'
                cd \"$( unzip -lqq '${s}' | tail -1 | 
                        sed -e 's@^\\(\\s\\+[-0-9:]\\+\\)\\{3,3\\}\\s\\+\\([^/]\\+\\)/.*@\\2@' )\"
        " else if (archiveType s) == "cvs-dir" then "
                cp -r '${s}' .
                cd \$(basename ${s})
                chmod u+rwX -R .
        " else if (archiveType s) == "git-dir" then "
                cp -r '${s}' .
                cd \$(basename ${s})
                chmod u+rwX -R .
        " else if (archiveType s) == "dir" then "
                cp -r '${s}' .
                cd \$(basename ${s})
                chmod u+rwX -R .
        " else if (archiveType s) == "narbz2" then "
                bzip2 <${s} | nix-store --restore \$PWD/\$(basename ${s} .nar.bz2)
                cd \$(basename ${s} .nar.bz2)
        " else if (archiveType s) == "rpm" then ''
                rpm2cpio ${s} > ${s}.cpio
                cpio -iv < ${s}.cpio
		test -f *.tar.* && tar -xvf *.tar.*
		test -d */ && cd */
        '' else if (archiveType s) == "plain-bz2" then "
                mkdir \$PWD/\$(basename ${s} .bz2)
                NAME=\$(basename ${s} .bz2)
                bzip2 -d <${s} > \$PWD/\$(basename ${s} .bz2)/\${NAME#*-}
                cd \$(basename ${s} .bz2)
        " else if (archiveType s) == "plain-gz" then "
                mkdir \$PWD/\$(basename ${s} .gz)
                NAME=\$(basename ${s} .gz)
                gzip -d <${s} > \$PWD/\$(basename ${s} .gz)/\${NAME#*-}
                cd \$(basename ${s} .gz)
        " else if (archiveType s) == "empty" then "
	        echo No source to unpack - doing nothing ..
        " else (abort "unknown archive type : ${s}"))+
                # goSrcDir is typically something like "cd mysubdir" .. but can be anything else 
                (if args ? goSrcDir then args.goSrcDir else "")
        ) ["minInit"];

        configureCommand = attrByPath ["configureCommand"] "./configure" args;

        # changing this ? see [1]
        doConfigure = fullDepEntry ("
                ${configureCommand} --prefix=\"\$prefix\" ${toString configureFlags}
        ") ["minInit" "addInputs" "doUnpack"];

        # changing this ? see [1]
	doIntltool = fullDepEntry ("
		mkdir -p config
		intltoolize --copy --force
	") ["minInit" "addInputs" "doUnpack"];

        # changing this ? see [1]
        doAutotools = fullDepEntry ("
                mkdir -p config
                libtoolize --copy --force
                aclocal --force
                #Some packages do not need this
                autoheader || true; 
                automake --add-missing --copy
                autoconf
        ")["minInit" "addInputs" "doUnpack"];

        # changing this ? see [1]
        doAutogen = fullDepEntry (''
                ./autogen.sh
        '')["minInit" "addInputs" "doUnpack"];

        # changing this ? see [1]
        doMake = fullDepEntry ("        
                make ${toString makeFlags}
        ") ["minInit" "addInputs" "doUnpack"];

        doUnpack = toSrcDir (toString src);

        # changing this ? see [1]
        installPythonPackage = fullDepEntry ("
                python setup.py install --prefix=\"\$prefix\" 
                ") ["minInit" "addInputs" "doUnpack"];

        doPythonConfigure = fullDepEntry ('' 
          pythonVersion=$(toPythonPath "$prefix")
          pythonVersion=''${pythonVersion#*/lib/python}
          pythonVersion=''${pythonVersion%%/site-packages}
          ${if args ? extraPythonConfigureCommand then 
            args.extraPythonConfigureCommand 
          else ""}
          python configure.py -b "$prefix/bin" -d "$(toPythonPath "$prefix")" -v "$prefix/share/sip" ${toString configureFlags}
        '') ["minInit" "addInputs" "doUnpack"]; 

        # changing this ? see [1]
        doMakeInstall = fullDepEntry ("
                make ${toString (attrByPath ["makeFlags"] "" args)} "+
                        "${toString (attrByPath ["installFlags"] "" args)} install") ["doMake"];

        # changing this ? see [1]
        doForceShare = fullDepEntry (" 
                mkdir -p \"\$prefix/share\"
                for d in ${toString forceShare}; do
                        if [ -d \"\$prefix/\$d\" -a ! -d \"\$prefix/share/\$d\" ]; then
                                mv -v \"\$prefix/\$d\" \"\$prefix/share\"
                                ln -sv share/\$d \"\$prefix\"
                        fi;
                done;
        ") ["minInit" "defEnsureDir"];

        doForceCopy = fullDepEntry (''
                name="$(basename $out)"
                name="''${name#*-}"
                mkdir -p "$prefix/share/$name"
                for f in ${toString forceCopy}; do
                        cp -r "$f" "$prefix/share/$name/$f" || true
                done;
        '') ["minInit" "defEnsureDir"];

        doDump = n: noDepEntry "echo Dump number ${n}; set";

	saveEnv = noDepEntry ''export > $TMP/env-vars'';

	doDumpBuildInputs = noDepEntry (''
	  echo "${toString realBuildInputs}"
	'');

        patchFlags = if args ? patchFlags then args.patchFlags else "-p1";

        patches = attrByPath ["patches"] [] args;

        toPatchCommand = s: "cat ${s} | patch ${toString patchFlags}";

        doPatch = fullDepEntry (concatStringsSep ";"
                (map toPatchCommand patches)
        ) ["minInit" "doUnpack"];

        envAdderInner = s: x: if x==null then s else y: 
                a: envAdderInner (s+"echo export ${x}='\"'\"\$${x}:${y}\";'\"'\n") a;

        envAdder = envAdderInner "";

        envAdderList = l:  if l==[] then "" else 
        "echo export ${head l}='\"'\"\\\$${head l}:${head (tail l)}\"'\"';\n" +
                envAdderList (tail (tail l));

        # changing this ? see [1]
        wrapEnv = cmd: env: "
                mv \"${cmd}\" \"${cmd}-orig\";
                touch \"${cmd}\";
                chmod a+rx \"${cmd}\";
                (${envAdderList env}
                echo '\"'\"${cmd}-orig\"'\"' '\"'\\\$@'\"' \n)  > \"${cmd}\"";

        doWrap = cmd: fullDepEntry (wrapEnv cmd (attrByPath ["wrappedEnv"] [] args)) ["minInit"];

        makeManyWrappers = wildcard : wrapperFlags : fullDepEntry (''
          for i in ${wildcard}; do
            wrapProgram "$i" ${wrapperFlags}
          done
        '') ["minInit" "addInputs" "defEnsureDir"];

        wrapBinContentsPython = (makeManyWrappers 
          ''$out/bin/*'' 
          pythonWrapperArguments
        );

        pythonWrapperArguments = 
          (''--prefix PYTHONPATH : $(toPythonPath $out)'' +
          ''''${PYTHONPATH:+ --prefix PYTHONPATH : $PYTHONPATH}'');

        preservePathWrapperArguments = ''''${PATH:+ --prefix PATH : $PATH }'';

        doPropagate = fullDepEntry ("
                mkdir -p \$out/nix-support
                echo '${toString (attrByPath ["propagatedBuildInputs"] [] args)}' >\$out/nix-support/propagated-build-inputs
        ") ["minInit" "defEnsureDir"];

        cmakeFlags = attrByPath ["cmakeFlags"] [] args;

        cmakeRPathFlag = if attrByPath ["cmakeSkipRpath "] true args then " -DCMAKE_SKIP_BUILD_RPATH=ON " else "";

        cmakeBuildDir = attrByPath ["cmakeBuildDir"] "build" args;

	doCmake = fullDepEntry (''
          mkdir ${cmakeBuildDir}
	  cd ${cmakeBuildDir}
	  cmake -D CMAKE_INSTALL_PREFIX="$out" ${cmakeRPathFlag}${toString cmakeFlags} ..
	'') ["minInit" "addInputs" "doUnpack"];

	doScons = fullDepEntry (''
		mkdir -p $out
		${if (attrByPath ["sconsCleanEnv"] false args)
		 then ""
		 else ''
                   sed -e '1iimport os' -i SConstruct
                   sed -e 's/env *= *Environment *.*/&; env['"'"'ENV'"'"']=os.environ;/' -i SConstruct
		 ''
		}
		scons ${toString (attrByPath ["sconsFlags"] [] args)} PREFIX=$out 
		scons ${toString (attrByPath ["sconsFlags"] [] args)} PREFIX=$out install
	'') ["minInit" "doUnpack" "addInputs" "defEnsureDir"];

        /*debug = x:(trace x x);
        debugX = x:(trace (toXML x) x);*/

        makeNest = x: if x == defNest.text then x else "startNest\n" + x + "\nstopNest\n";
        textClosure = a: steps: textClosureMap makeNest a (["defNest"] ++ steps);

        inherit noDepEntry fullDepEntry packEntry;

        defList = attrByPath ["defList"] [] args;
        getVal = getValue args defList;
        check = checkFlag args;
        reqsList = attrByPath ["reqsList"] [] args;
        buildInputsNames = filter (x: null != getVal x)
                (uniqList {inputList = 
                  (concatLists (map 
                    (x: if x==[] then [] else builtins.tail x) 
                  reqsList));});
        configFlags = attrByPath ["configFlags"] [] args;
        buildFlags = attrByPath ["buildFlags"] [] args;
        nameSuffixes = attrByPath ["nameSuffixes"] [] args;
        autoBuildInputs = assert (checkReqs args defList reqsList);
                filter (x: x!=null) (map getVal buildInputsNames);
        autoConfigureFlags = condConcat "" configFlags check;
        autoMakeFlags = condConcat "" buildFlags check;
        useConfig = attrByPath ["useConfig"] false args;
        realBuildInputs = 
                lib.closePropagation ((if useConfig then 
                        autoBuildInputs else 
                        attrByPath ["buildInputs"] [] args)++
                        (attrByPath ["propagatedBuildInputs"] [] args));
        configureFlags = if useConfig then autoConfigureFlags else 
            attrByPath ["configureFlags"] "" args;
        makeFlags = if useConfig then autoMakeFlags else attrByPath ["makeFlags"] "" args;

        inherit lib;

        surroundWithCommands = x : before : after : {deps=x.deps; text = before + "\n" +
                x.text + "\n" + after ;};

	createDirs = fullDepEntry (concatStringsSep ";"
		(map (x: "mkdir -p ${x}") (attrByPath ["neededDirs"] [] args))
	) ["minInit" "defEnsureDir"];

	copyExtraDoc = fullDepEntry (''
          name="$(basename $out)"
          name="''${name#*-}"
          mkdir -p "$out/share/doc/$name"
	'' + (concatStringsSep ";"
               (map 
	         (x: ''cp "${x}" "$out/share/doc/$name" || true;'') 
		 (attrByPath ["extraDoc"] [] args)))) ["minInit" "defEnsureDir" "doUnpack"];

        realPhaseNames = 
	  (optional ([] != attrByPath ["neededDirs"] [] args) "createDirs")
	  ++
	  (attrByPath ["phaseNames"] [] args)
	  ++ 
          ["doForceShare" "doPropagate" "doForceCopy"]
	  ++
	  (optional ([] != attrByPath ["extraDoc"] [] args) "copyExtraDoc")
          ++
          (optional (attrByPath ["doCheck"] false args) "doMakeCheck")
          ++
          (optional (attrByPath ["alwaysFail"] false args) "doFail")
          ;

        doFail = noDepEntry "
          echo 'Failing to keep builddir (and to invalidate result).'
          a() { return 127; } ; a ;
        ";

        doMakeCheck = fullDepEntry (''
          make check
        '') ["minInit"];

        extraDerivationAttrs = lib.attrByPath ["extraDerivationAttrs"] {} args;

        # for overrides..
	builderDefsArgs = args;

        innerBuilderDefsPackage = bd: args: (
        let localDefs = bd.passthru.function args; in

        stdenv.mkDerivation ((rec {
          inherit (localDefs) name;
          buildCommand = textClosure localDefs localDefs.realPhaseNames;
          meta = localDefs.meta;
	  passthru = localDefs.passthru // {inherit (localDefs) src; };
        }) // (if localDefs ? propagatedBuildInputs then {
          inherit (localDefs) propagatedBuildInputs;
        } else {}) // extraDerivationAttrs)
        );

	builderDefsPackage = bd: func:
	  if builtins.isFunction func then 
	    (foldArgs 
	      (x: y: ((func (bd // x // y)) // y))
              (innerBuilderDefsPackage bd)
	      {})
	  else
	    (builderDefsPackage bd (import (toString func)))
	    ;

   generateFontsFromSFD = fullDepEntry (''
           for i in *.sfd; do
                echo $i;
                fontforge -c \
                        'Open($1);
                        ${optionalString (args ? extraFontForgeCommands) args.extraFontForgeCommands
                        }Reencode("unicode");
                         ${optionalString (attrByPath ["createTTF"] true args) ''Generate($1:r + ".ttf");''}
                         ${optionalString (attrByPath ["createOTF"] true args) ''Generate($1:r + ".otf");''}
                         Reencode("TeX-Base-Encoding");
                         ${optionalString (attrByPath ["createAFM"] true args) ''Generate($1:r + ".afm");''}
                         ${optionalString (attrByPath ["createPFM"] true args) ''Generate($1:r + ".pfm");''}
                         ${optionalString (attrByPath ["createPFB"] true args) ''Generate($1:r + ".pfb");''}
                         ${optionalString (attrByPath ["createMAP"] true args) ''Generate($1:r + ".map");''}
                         ${optionalString (attrByPath ["createENC"] true args) ''Generate($1:r + ".enc");''}
                        ' $i; 
        done
   '') ["minInit" "addInputs" "doUnpack"];

   installFonts = 
      let retrievedName = (if args ? name then args.name else ""); in
   fullDepEntry (''
           mkdir -p $out/share/fonts/truetype/public/${retrievedName}
           mkdir -p $out/share/fonts/opentype/public/${retrievedName}
           mkdir -p $out/share/fonts/type1/public/${retrievedName}
           mkdir -p $out/share/texmf/fonts/enc/${retrievedName}
           mkdir -p $out/share/texmf/fonts/map/${retrievedName}

        cp *.ttf $out/share/fonts/truetype/public/${retrievedName} || echo No TrueType fonts
        cp *.otf $out/share/fonts/opentype/public/${retrievedName} || echo No OpenType fonts
           cp *.{pfm,afm,pfb} $out/share/fonts/type1/public/${retrievedName} || echo No Type1 Fonts
           cp *.enc $out/share/texmf/fonts/enc/${retrievedName} || echo No fontenc data
           cp *.map $out/share/texmf/fonts/map/${retrievedName} || echo No fontmap data
   '') ["minInit" "defEnsureDir"];

   simplyShare = shareName: fullDepEntry (''
     mkdir -p $out/share
     cp -r . $out/share/${shareName}
   '') ["doUnpack" "defEnsureDir"];

   doPatchShebangs = dir: fullDepEntry (''
     patchShebangFun() {
     # Rewrite all script interpreter file names (`#! /path') under the
     # specified  directory tree to paths found in $PATH.  E.g.,
     # /bin/sh will be rewritten to /nix/store/<hash>-some-bash/bin/sh.
     # Interpreters that are already in the store are left untouched.
         echo "patching script interpreter paths"
         local f
         for f in $(find "${dir}" -type f -perm +0100); do
             local oldPath=$(sed -ne '1 s,^#![ ]*\([^ ]*\).*$,\1,p' "$f")
             if test -n "$oldPath" -a "''${oldPath:0:''${#NIX_STORE}}" != "$NIX_STORE"; then
                 local newPath=$(type -P $(basename $oldPath) || true)
                 if test -n "$newPath" -a "$newPath" != "$oldPath"; then
                     echo "$f: interpreter changed from $oldPath to $newPath"
                     sed -i "1 s,$oldPath,$newPath," "$f"
                 fi
             fi
         done
     }
     patchShebangFun;
   '') ["minInit"];

   createPythonInstallationTarget = fullDepEntry (''
     mkdir -p $(toPythonPath $out)
     export PYTHONPATH=$PYTHONPATH''${PYTHONPATH:+:}$(toPythonPath $out)
   '') ["minInit" "addInputs" "defEnsureDir"];

   fetchUrlFromSrcInfo = srcInfo: fetchurl ({
     url = srcInfo.url;
     sha256 = srcInfo.hash;
   } // 
   (if srcInfo ? downloadName then {name = srcInfo.downloadName;} else {}));

   fetchGitFromSrcInfo = srcInfo: fetchgit {
     url = srcInfo.url;
     rev = srcInfo.rev;
     sha256 = srcInfo.hash;
   };
}) // args

# [1]: rewrite using '' instead of " so that indentation gets stripped. It's
# only about some spaces but in the end they will sum up
