diff options
author | Alyssa Ross <hi@alyssa.is> | 2019-02-24 01:09:00 +0000 |
---|---|---|
committer | Alyssa Ross <hi@alyssa.is> | 2019-02-24 01:09:00 +0000 |
commit | 072c01a28f865e9487df09aed7ddff328252fb36 (patch) | |
tree | 6df6e652915940255f294ed8998cce1c4c7c2d40 /nixpkgs/pkgs/build-support | |
parent | 024b46ff20027c15322e5d868ecec42632556d4b (diff) | |
parent | 969cff2691a02b3d7e5468beda26c482d8986644 (diff) | |
download | nixlib-072c01a28f865e9487df09aed7ddff328252fb36.tar nixlib-072c01a28f865e9487df09aed7ddff328252fb36.tar.gz nixlib-072c01a28f865e9487df09aed7ddff328252fb36.tar.bz2 nixlib-072c01a28f865e9487df09aed7ddff328252fb36.tar.lz nixlib-072c01a28f865e9487df09aed7ddff328252fb36.tar.xz nixlib-072c01a28f865e9487df09aed7ddff328252fb36.tar.zst nixlib-072c01a28f865e9487df09aed7ddff328252fb36.zip |
Merge commit '969cff2691a02b3d7e5468beda26c482d8986644'
Diffstat (limited to 'nixpkgs/pkgs/build-support')
13 files changed, 288 insertions, 30 deletions
diff --git a/nixpkgs/pkgs/build-support/bintools-wrapper/default.nix b/nixpkgs/pkgs/build-support/bintools-wrapper/default.nix index 4122af898de9..142f5255caad 100644 --- a/nixpkgs/pkgs/build-support/bintools-wrapper/default.nix +++ b/nixpkgs/pkgs/build-support/bintools-wrapper/default.nix @@ -187,6 +187,7 @@ stdenv.mkDerivation { else if targetPlatform.isPower then if targetPlatform.isBigEndian then "ppc" else "lppc" else if targetPlatform.isSparc then "sparc" else if targetPlatform.isAvr then "avr" + else if targetPlatform.isAlpha then "alpha" else throw "unknown emulation for platform: " + targetPlatform.config; in targetPlatform.platform.bfdEmulation or (fmt + sep + arch); diff --git a/nixpkgs/pkgs/build-support/build-bazel-package/default.nix b/nixpkgs/pkgs/build-support/build-bazel-package/default.nix index 28247bac1021..f39f4e65e454 100644 --- a/nixpkgs/pkgs/build-support/build-bazel-package/default.nix +++ b/nixpkgs/pkgs/build-support/build-bazel-package/default.nix @@ -1,4 +1,4 @@ -{ stdenv, bazel, enableNixHacks ? true }: +{ stdenv, bazel, cacert, enableNixHacks ? true }: args@{ name, bazelFlags ? [], bazelTarget, buildAttrs, fetchAttrs, ... }: @@ -20,6 +20,8 @@ in stdenv.mkDerivation (fBuildAttrs // { export bazelOut="$(echo ''${NIX_BUILD_TOP}/output | sed -e 's,//,/,g')" export bazelUserRoot="$(echo ''${NIX_BUILD_TOP}/tmp | sed -e 's,//,/,g')" export HOME="$NIX_BUILD_TOP" + # This is needed for git_repository with https remotes + export GIT_SSL_CAINFO="${cacert}/etc/ssl/certs/ca-bundle.crt" ''; buildPhase = fFetchAttrs.buildPhase or '' @@ -31,7 +33,9 @@ in stdenv.mkDerivation (fBuildAttrs // { # sandbox enabled. Code here # https://github.com/bazelbuild/bazel/blob/9323c57607d37f9c949b60e293b573584906da46/src/main/cpp/startup_options.cc#L123-L124 # - USER=homeless-shelter bazel --output_base="$bazelOut" --output_user_root="$bazelUserRoot" fetch $bazelFlags $bazelTarget + # On macOS Bazel will use the system installed Xcode or CLT toolchain instead of the one in the PATH unless we pass BAZEL_USE_CPP_ONLY_TOOLCHAIN + # + BAZEL_USE_CPP_ONLY_TOOLCHAIN=1 USER=homeless-shelter bazel --output_base="$bazelOut" --output_user_root="$bazelUserRoot" fetch $bazelFlags $bazelTarget runHook postBuild ''; @@ -88,7 +92,42 @@ in stdenv.mkDerivation (fBuildAttrs // { buildPhase = fBuildAttrs.buildPhase or '' runHook preBuild - bazel --output_base="$bazelOut" --output_user_root="$bazelUserRoot" build -j $NIX_BUILD_CORES $bazelFlags $bazelTarget + # Bazel sandboxes the execution of the tools it invokes, so even though we are + # calling the correct nix wrappers, the values of the environment variables + # the wrappers are expecting will not be set. So instead of relying on the + # wrappers picking them up, pass them in explicitly via `--copt`, `--linkopt` + # and related flags. + # + copts=() + host_copts=() + for flag in $NIX_CFLAGS_COMPILE; do + copts+=( "--copt=$flag" ) + host_copts+=( "--host_copt=$flag" ) + done + for flag in $NIX_CXXSTDLIB_COMPILE; do + copts+=( "--copt=$flag" ) + host_copts+=( "--host_copt=$flag" ) + done + linkopts=() + host_linkopts=() + for flag in $NIX_LD_FLAGS; do + linkopts+=( "--linkopt=$flag" ) + host_linkopts+=( "--host_linkopt=$flag" ) + done + + BAZEL_USE_CPP_ONLY_TOOLCHAIN=1 \ + USER=homeless-shelter \ + bazel \ + --output_base="$bazelOut" \ + --output_user_root="$bazelUserRoot" \ + build \ + -j $NIX_BUILD_CORES \ + "''${copts[@]}" \ + "''${host_copts[@]}" \ + "''${linkopts[@]}" \ + "''${host_linkopts[@]}" \ + $bazelFlags \ + $bazelTarget runHook postBuild ''; diff --git a/nixpkgs/pkgs/build-support/cc-wrapper/default.nix b/nixpkgs/pkgs/build-support/cc-wrapper/default.nix index c36181130478..176df51cbd9a 100644 --- a/nixpkgs/pkgs/build-support/cc-wrapper/default.nix +++ b/nixpkgs/pkgs/build-support/cc-wrapper/default.nix @@ -13,6 +13,7 @@ , extraPackages ? [], extraBuildCommands ? "" , isGNU ? false, isClang ? cc.isClang or false, gnugrep ? null , buildPackages ? {} +, libcxx ? null }: with stdenvNoCC.lib; @@ -44,8 +45,11 @@ let # The wrapper scripts use 'cat' and 'grep', so we may need coreutils. coreutils_bin = if nativeTools then "" else getBin coreutils; - default_cxx_stdlib_compile = optionalString (targetPlatform.isLinux && !(cc.isGNU or false) && !nativeTools && cc ? gcc) - "-isystem $(echo -n ${cc.gcc}/include/c++/*) -isystem $(echo -n ${cc.gcc}/include/c++/*)/$(${cc.gcc}/bin/gcc -dumpmachine)"; + default_cxx_stdlib_compile = if (targetPlatform.isLinux && !(cc.isGNU or false) && !nativeTools && cc ? gcc) then + "-isystem $(echo -n ${cc.gcc}/include/c++/*) -isystem $(echo -n ${cc.gcc}/include/c++/*)/$(${cc.gcc}/bin/gcc -dumpmachine)" + else if targetPlatform.isDarwin && (libcxx != null) && (cc.isClang or false) then + "-isystem ${libcxx}/include/c++/v1" + else ""; # The "infix salt" is a arbitrary string added in the middle of env vars # defined by cc-wrapper's hooks so that multiple cc-wrappers can be used @@ -256,9 +260,9 @@ stdenv.mkDerivation { echo "$ccLDFlags" > $out/nix-support/cc-ldflags echo "$ccCFlags" > $out/nix-support/cc-cflags - '' - - + optionalString propagateDoc '' + '' + optionalString (targetPlatform.isDarwin && (libcxx != null) && (cc.isClang or false)) '' + echo " -L${libcxx}/lib" >> $out/nix-support/cc-ldflags + '' + optionalString propagateDoc '' ## ## Man page and info support ## diff --git a/nixpkgs/pkgs/build-support/docker/default.nix b/nixpkgs/pkgs/build-support/docker/default.nix index 83ff846db7ab..4b20c5624607 100644 --- a/nixpkgs/pkgs/build-support/docker/default.nix +++ b/nixpkgs/pkgs/build-support/docker/default.nix @@ -22,7 +22,8 @@ referencesByPopularity, writeScript, writeText, - closureInfo + closureInfo, + substituteAll }: # WARNING: this API is unstable and may be subject to backwards-incompatible changes in the future. @@ -279,6 +280,13 @@ rec { # of room for extension maxLayers ? 24 }: + let + storePathToLayer = substituteAll + { inherit (stdenv) shell; + isExecutable = true; + src = ./store-path-to-layer.sh; + }; + in runCommand "${name}-granular-docker-layers" { inherit maxLayers; paths = referencesByPopularity closure; @@ -298,9 +306,9 @@ rec { # following head and tail call lines, double-check that your # code behaves properly when the number of layers equals: # maxLayers-1, maxLayers, and maxLayers+1 - head -n $((maxLayers - 1)) $paths | cat -n | xargs -P$NIX_BUILD_CORES -n2 ${./store-path-to-layer.sh} + head -n $((maxLayers - 1)) $paths | cat -n | xargs -P$NIX_BUILD_CORES -n2 ${storePathToLayer} if [ $(cat $paths | wc -l) -ge $maxLayers ]; then - tail -n+$maxLayers $paths | xargs ${./store-path-to-layer.sh} $maxLayers + tail -n+$maxLayers $paths | xargs ${storePathToLayer} $maxLayers fi echo "Finished building layer '$name'" @@ -776,7 +784,7 @@ rec { imageJson=$(cat ${baseJson} | jq ". + {\"rootfs\": {\"diff_ids\": [], \"type\": \"layers\"}}") manifestJson=$(jq -n "[{\"RepoTags\":[\"$imageName:$imageTag\"]}]") - for layerTar in $(cat ./layer-list); do + for layerTar in $(tac ./layer-list); do layerChecksum=$(sha256sum image/$layerTar | cut -d ' ' -f1) imageJson=$(echo "$imageJson" | jq ".history |= [{\"created\": \"$(jq -r .created ${baseJson})\"}] + .") imageJson=$(echo "$imageJson" | jq ".rootfs.diff_ids |= [\"sha256:$layerChecksum\"] + .") diff --git a/nixpkgs/pkgs/build-support/fetchsvn/default.nix b/nixpkgs/pkgs/build-support/fetchsvn/default.nix index 194ce3b39b1d..68433d1471d6 100644 --- a/nixpkgs/pkgs/build-support/fetchsvn/default.nix +++ b/nixpkgs/pkgs/build-support/fetchsvn/default.nix @@ -1,4 +1,4 @@ -{stdenvNoCC, subversion, glibcLocales, sshSupport ? false, openssh ? null}: +{stdenvNoCC, subversion, glibcLocales, sshSupport ? true, openssh ? null}: {url, rev ? "HEAD", md5 ? "", sha256 ? "" , ignoreExternals ? false, ignoreKeywords ? false, name ? null , preferLocalBuild ? true }: diff --git a/nixpkgs/pkgs/build-support/fetchsvnssh/default.nix b/nixpkgs/pkgs/build-support/fetchsvnssh/default.nix index f76bd10247ba..fbd74efd750a 100644 --- a/nixpkgs/pkgs/build-support/fetchsvnssh/default.nix +++ b/nixpkgs/pkgs/build-support/fetchsvnssh/default.nix @@ -1,4 +1,4 @@ -{stdenvNoCC, subversion, sshSupport ? false, openssh ? null, expect}: +{stdenvNoCC, subversion, sshSupport ? true, openssh ? null, expect}: {username, password, url, rev ? "HEAD", md5 ? "", sha256 ? ""}: diff --git a/nixpkgs/pkgs/build-support/fetchurl/mirrors.nix b/nixpkgs/pkgs/build-support/fetchurl/mirrors.nix index eccfe1964acf..404c744eea2a 100644 --- a/nixpkgs/pkgs/build-support/fetchurl/mirrors.nix +++ b/nixpkgs/pkgs/build-support/fetchurl/mirrors.nix @@ -44,6 +44,8 @@ rec { # GCC. gcc = [ + https://bigsearcher.com/mirrors/gcc/ + http://mirror.koddos.net/gcc/ ftp://ftp.nluug.nl/mirror/languages/gcc/ ftp://ftp.fu-berlin.de/unix/languages/gcc/ ftp://ftp.irisa.fr/pub/mirrors/gcc.gnu.org/gcc/ diff --git a/nixpkgs/pkgs/build-support/nix-gitignore/default.nix b/nixpkgs/pkgs/build-support/nix-gitignore/default.nix new file mode 100644 index 000000000000..28ee6bad5540 --- /dev/null +++ b/nixpkgs/pkgs/build-support/nix-gitignore/default.nix @@ -0,0 +1,178 @@ +# https://github.com/siers/nix-gitignore/ + +{ lib, runCommand }: + +# An interesting bit from the gitignore(5): +# - A slash followed by two consecutive asterisks then a slash matches +# - zero or more directories. For example, "a/**/b" matches "a/b", +# - "a/x/b", "a/x/y/b" and so on. + +with builtins; + +let + debug = a: trace a a; + last = l: elemAt l ((length l) - 1); + + throwIfOldNix = let required = "2.0"; in + if compareVersions nixVersion required == -1 + then throw "nix (v${nixVersion} =< v${required}) is too old for nix-gitignore" + else true; +in rec { + # [["good/relative/source/file" true] ["bad.tmpfile" false]] -> root -> path + filterPattern = patterns: root: + (name: _type: + let + relPath = lib.removePrefix ((toString root) + "/") name; + matches = pair: (match (head pair) relPath) != null; + matched = map (pair: [(matches pair) (last pair)]) patterns; + in + last (last ([[true true]] ++ (filter head matched))) + ); + + # string -> [[regex bool]] + gitignoreToPatterns = gitignore: + assert throwIfOldNix; + let + # ignore -> bool + isComment = i: (match "^(#.*|$)" i) != null; + + # ignore -> [ignore bool] + computeNegation = l: + let split = match "^(!?)(.*)" l; + in [(elemAt split 1) (head split == "!")]; + + # ignore -> regex + substWildcards = + let + special = "^$.+{}()"; + escs = "\\*?"; + splitString = + let recurse = str : [(substring 0 1 str)] ++ + (if str == "" then [] else (recurse (substring 1 (stringLength(str)) str) )); + in str : recurse str; + chars = s: filter (c: c != "" && !isList c) (splitString s); + escape = s: map (c: "\\" + c) (chars s); + in + replaceStrings + ((chars special) ++ (escape escs) ++ ["**/" "**" "*" "?"]) + ((escape special) ++ (escape escs) ++ ["(.*/)?" ".*" "[^/]*" "[^/]"]); + + # (regex -> regex) -> regex -> regex + mapAroundCharclass = f: r: # rl = regex or list + let slightFix = replaceStrings ["\\]"] ["]"]; + in + concatStringsSep "" + (map (rl: if isList rl then slightFix (elemAt rl 0) else f rl) + (split "(\\[([^\\\\]|\\\\.)+])" r)); + + # regex -> regex + handleSlashPrefix = l: + let + split = (match "^(/?)(.*)" l); + findSlash = l: if (match ".+/.+" l) != null then "" else l; + hasSlash = mapAroundCharclass findSlash l != l; + in + (if (elemAt split 0) == "/" || hasSlash + then "^" + else "(^|.*/)" + ) + (elemAt split 1); + + # regex -> regex + handleSlashSuffix = l: + let split = (match "^(.*)/$" l); + in if split != null then (elemAt split 0) + "($|/.*)" else l; + + # (regex -> regex) -> [regex, bool] -> [regex, bool] + mapPat = f: l: [(f (head l)) (last l)]; + in + map (l: # `l' for "line" + mapPat (l: handleSlashSuffix (handleSlashPrefix (mapAroundCharclass substWildcards l))) + (computeNegation l)) + (filter (l: !isList l && !isComment l) + (split "\n" gitignore)); + + gitignoreFilter = ign: root: filterPattern (gitignoreToPatterns ign) root; + + # string|[string|file] (→ [string|file] → [string]) -> string + gitignoreCompileIgnore = file_str_patterns: root: + let + onPath = f: a: if typeOf a == "path" then f a else a; + str_patterns = map (onPath readFile) (lib.toList file_str_patterns); + in concatStringsSep "\n" str_patterns; + + gitignoreFilterPure = filter: patterns: root: name: type: + gitignoreFilter (gitignoreCompileIgnore patterns root) root name type + && filter name type; + + # This is a very hacky way of programming this! + # A better way would be to reuse existing filtering by making multiple gitignore functions per each root. + # Then for each file find the set of roots with gitignores (and functions). + # This would make gitignoreFilterSource very different from gitignoreFilterPure. + # rootPath → gitignoresConcatenated + compileRecursiveGitignore = root: + let + dirOrIgnore = file: type: baseNameOf file == ".gitignore" || type == "directory"; + ignores = builtins.filterSource dirOrIgnore root; + in readFile ( + runCommand "${baseNameOf root}-recursive-gitignore" {} '' + cd ${ignores} + + find -type f -exec sh -c ' + rel="$(realpath --relative-to=. "$(dirname "$1")")/" + if [ "$rel" = "./" ]; then rel=""; fi + + awk -v prefix="$rel" -v root="$1" -v top="$(test -z "$rel" && echo 1)" " + BEGIN { print \"# \"root } + + /^!?[^\\/]+\/?$/ { + match(\$0, /^!?/, negation) + sub(/^!?/, \"\") + + if (top) { middle = \"\" } else { middle = \"**/\" } + + print negation[0] prefix middle \$0 + } + + /^!?(\\/|.*\\/.+$)/ { + match(\$0, /^!?/, negation) + sub(/^!?/, \"\") + + if (!top) sub(/^\//, \"\") + + print negation[0] prefix \$0 + } + + END { print \"\" } + " "$1" + ' sh {} \; > $out + ''); + + withGitignoreFile = patterns: root: + lib.toList patterns ++ [(root + "/.gitignore")]; + + withRecursiveGitignoreFile = patterns: root: + lib.toList patterns ++ [(compileRecursiveGitignore root)]; + + # filterSource derivatives + + gitignoreFilterSourcePure = filter: patterns: root: + filterSource (gitignoreFilterPure filter patterns root) root; + + gitignoreFilterSource = filter: patterns: root: + gitignoreFilterSourcePure filter (withGitignoreFile patterns root) root; + + gitignoreFilterRecursiveSource = filter: patterns: root: + gitignoreFilterSourcePure filter (withRecursiveGitignoreFile patterns root) root; + + # "Filter"-less alternatives + + gitignoreSourcePure = gitignoreFilterSourcePure (_: _: true); + gitignoreSource = patterns: let type = typeOf patterns; in + if (type == "string" && pathExists patterns) || type == "path" + then throw + "type error in gitignoreSource(patterns -> source -> path), " + "use [] or \"\" if there are no additional patterns" + else gitignoreFilterSource (_: _: true) patterns; + + gitignoreRecursiveSource = gitignoreFilterSourcePure (_: _: true); +} diff --git a/nixpkgs/pkgs/build-support/setup-hooks/fix-darwin-dylib-names.sh b/nixpkgs/pkgs/build-support/setup-hooks/fix-darwin-dylib-names.sh index 1b36f5f555da..af2ff0cc9662 100644 --- a/nixpkgs/pkgs/build-support/setup-hooks/fix-darwin-dylib-names.sh +++ b/nixpkgs/pkgs/build-support/setup-hooks/fix-darwin-dylib-names.sh @@ -23,7 +23,14 @@ fixDarwinDylibNames() { for fn in "$@"; do if [ -L "$fn" ]; then continue; fi echo "$fn: fixing dylib" - install_name_tool -id "$fn" "${flags[@]}" "$fn" + int_out=$(install_name_tool -id "$fn" "${flags[@]}" "$fn" 2>&1) + result=$? + if [ "$result" -ne 0 ] && + ! grep "shared library stub file and can't be changed" <<< "$out" + then + echo "$int_out" >&2 + exit "$result" + fi done } diff --git a/nixpkgs/pkgs/build-support/setup-hooks/make-wrapper.sh b/nixpkgs/pkgs/build-support/setup-hooks/make-wrapper.sh index bc12be0fa36c..06891893e8c1 100644 --- a/nixpkgs/pkgs/build-support/setup-hooks/make-wrapper.sh +++ b/nixpkgs/pkgs/build-support/setup-hooks/make-wrapper.sh @@ -8,7 +8,7 @@ assertExecutable() { } # construct an executable file that wraps the actual executable -# makeWrapper EXECUTABLE ARGS +# makeWrapper EXECUTABLE OUT_PATH ARGS # ARGS: # --argv0 NAME : set name of executed process to NAME diff --git a/nixpkgs/pkgs/build-support/setup-hooks/wrap-gapps-hook.sh b/nixpkgs/pkgs/build-support/setup-hooks/wrap-gapps-hook.sh index 25ac12996cc1..b5ceb4a13d85 100644 --- a/nixpkgs/pkgs/build-support/setup-hooks/wrap-gapps-hook.sh +++ b/nixpkgs/pkgs/build-support/setup-hooks/wrap-gapps-hook.sh @@ -36,16 +36,40 @@ wrapGAppsHook() { done if [[ -z "$dontWrapGApps" ]]; then + targetDirsThatExist=() + targetDirsRealPath=() + + # wrap binaries targetDirs=( "${prefix}/bin" "${prefix}/libexec" ) for targetDir in "${targetDirs[@]}"; do if [[ -d "${targetDir}" ]]; then - find -L "${targetDir}" -type f -executable -print0 \ + targetDirsThatExist+=("${targetDir}") + targetDirsRealPath+=("$(realpath "${targetDir}")/") + find "${targetDir}" -type f -executable -print0 \ | while IFS= read -r -d '' file; do - echo "Wrapping program ${file}" + echo "Wrapping program '${file}'" wrapProgram "${file}" "${gappsWrapperArgs[@]}" done fi done + + # wrap links to binaries that point outside targetDirs + # Note: links to binaries within targetDirs do not need + # to be wrapped as the binaries have already been wrapped + if [[ ${#targetDirsThatExist[@]} -ne 0 ]]; then + find "${targetDirsThatExist[@]}" -type l -xtype f -executable -print0 \ + | while IFS= read -r -d '' linkPath; do + linkPathReal=$(realpath "${linkPath}") + for targetPath in "${targetDirsRealPath[@]}"; do + if [[ "$linkPathReal" == "$targetPath"* ]]; then + echo "Not wrapping link: '$linkPath' (already wrapped)" + continue 2 + fi + done + echo "Wrapping link: '$linkPath'" + wrapProgram "${linkPath}" "${gappsWrapperArgs[@]}" + done + fi fi } diff --git a/nixpkgs/pkgs/build-support/singularity-tools/default.nix b/nixpkgs/pkgs/build-support/singularity-tools/default.nix index f9b4c8a7fd35..4206b0f33ff9 100644 --- a/nixpkgs/pkgs/build-support/singularity-tools/default.nix +++ b/nixpkgs/pkgs/build-support/singularity-tools/default.nix @@ -87,19 +87,14 @@ rec { # Create runScript ln -s ${runScriptFile} singularity - # Size calculation - cd .. - umount disk - size=$(resize2fs -P /dev/${vmTools.hd} | awk '{print $NF}') - mount /dev/${vmTools.hd} disk - cd disk + # Fill out .singularity.d + mkdir -p .singularity.d/env + touch .singularity.d/env/94-appsbase.sh - export PATH=$PATH:${e2fsprogs}/bin/ - echo creating - singularity image.create -s $((1 + size * 4 / 1024 + ${toString extraSpace})) $out - echo importing + cd .. mkdir -p /var/singularity/mnt/{container,final,overlay,session,source} - tar -c . | singularity image.import $out + echo "root:x:0:0:System administrator:/root:/bin/sh" > /etc/passwd + singularity build $out ./disk ''); in result; diff --git a/nixpkgs/pkgs/build-support/vm/default.nix b/nixpkgs/pkgs/build-support/vm/default.nix index 3612be1120ca..de6a5e3b62f9 100644 --- a/nixpkgs/pkgs/build-support/vm/default.nix +++ b/nixpkgs/pkgs/build-support/vm/default.nix @@ -164,7 +164,7 @@ rec { # Set up automatic kernel module loading. export MODULE_DIR=${kernel}/lib/modules/ ${coreutils}/bin/cat <<EOF > /run/modprobe - #! /bin/sh + #! ${bash}/bin/sh export MODULE_DIR=$MODULE_DIR exec ${kmod}/bin/modprobe "\$@" EOF |