
Platform Designer previously would not run due to not having libXi in the library path. This patch adds the library to both the 32 bit and 64 bit versions of the package.
455 lines
19 KiB
Nix
455 lines
19 KiB
Nix
{ stdenv, fetchurl, utillinux, file, bash, glibc, pkgsi686Linux, writeScript
|
|
, nukeReferences, glibcLocales, libfaketime, coreutils, gnugrep, gnused, proot
|
|
# Runtime dependencies
|
|
, zlib, glib, libpng12, freetype, libSM, libICE, libXrender, fontconfig
|
|
, libXext, libX11, libXtst, gtk2, bzip2, libelf, libXi
|
|
}:
|
|
|
|
{ baseName
|
|
, prettyName ? baseName
|
|
, version
|
|
, components ? []
|
|
, updateComponents ? []
|
|
|
|
# Set to true for the old installers that are 32-bit only
|
|
, is32bitPackage ? false
|
|
|
|
# There are .so files inside .jar files bundled with Quartus that lack RPATH
|
|
# directives. This breaks starting e.g. eclipse-nios2:
|
|
#
|
|
# java.lang.UnsatisfiedLinkError: Could not load SWT library. Reasons:
|
|
# $HOME/.altera.sbt4e/16.1.0.196-linux64/configuration/org.eclipse.osgi/bundles/404/1/.cp/libswt-pi-gtk-4335.so: libXtst.so.6: cannot open shared object file: No such file or directory
|
|
# no swt-pi-gtk in java.library.path
|
|
# $HOME/.swt/lib/linux/x86_64/libswt-pi-gtk-4335.so: libXtst.so.6: cannot open shared object file: No such file or directory
|
|
# Can't load library: $HOME/.swt/lib/linux/x86_64/libswt-pi-gtk.so
|
|
#
|
|
# Although we _could_ fixup these .so files that live inside .jar files (or
|
|
# find all java interpreters and specify "-Djava.library.path=" on their
|
|
# command line), I don't think it's worth it. Quartus *itself* sets
|
|
# LD_LIBRARY_PATH, thus breaking spawning firefox from it, so I see no reason
|
|
# we shouldn't be equally lazy.
|
|
, wrapWithLdLibraryPath ? true
|
|
}:
|
|
|
|
let
|
|
# Somewhere between NixOS 16.09 and 17.03 (for instance, commit 9e6eec201b)
|
|
# the glibc attribute lacked $out/lib{,64}. The glibc_lib attribute below
|
|
# helped when bisecting build issues between 16.03 and 17.03.
|
|
glibc_lib =
|
|
if glibc ? out then glibc.out else glibc;
|
|
glibc_lib32 =
|
|
if pkgsi686Linux.glibc ? out then pkgsi686Linux.glibc.out else pkgsi686Linux.glibc;
|
|
|
|
# Using glibc>=2.25 causes the Quartus*Setup*run installer to hang.
|
|
# Use 2.24 instead.
|
|
commonGlibcAttrs224 = rec {
|
|
name = "glibc-${version}";
|
|
version = "2.24";
|
|
src = fetchurl {
|
|
url = "http://ftpmirror.gnu.org/glibc/${name}.tar.xz";
|
|
sha256 = "1lxmprg9gm73gvafxd503x70z32phwjzcy74i0adfi6ixzla7m4r";
|
|
};
|
|
};
|
|
|
|
# Backport upstream patch to fix build against nixpkgs-18.09.
|
|
glibc_lib_for_installer = glibc_lib.overrideAttrs (oldAttrs:
|
|
commonGlibcAttrs224 // { patches = [ ./patches/glibc/0001-Avoid-.symver-on-common-symbols-BZ-21666.patch ]; }
|
|
);
|
|
glibc_lib32_for_installer = glibc_lib32.overrideAttrs (oldAttrs:
|
|
commonGlibcAttrs224 // { patches = [ ./patches/glibc/0001-Avoid-.symver-on-common-symbols-BZ-21666.patch ]; }
|
|
);
|
|
|
|
# Keep in sync with runtimeLibPath64
|
|
# (with pkgsi686Linux; [ .. ] doesn't bind strongly enough.)
|
|
runtimeLibPath32 =
|
|
stdenv.lib.makeLibraryPath
|
|
[ pkgsi686Linux.zlib pkgsi686Linux.glib pkgsi686Linux.libpng12
|
|
pkgsi686Linux.freetype pkgsi686Linux.xorg.libSM pkgsi686Linux.xorg.libICE
|
|
pkgsi686Linux.xorg.libXrender pkgsi686Linux.fontconfig.lib
|
|
pkgsi686Linux.xorg.libXext pkgsi686Linux.xorg.libX11 pkgsi686Linux.xorg.libXtst
|
|
pkgsi686Linux.gtk2 pkgsi686Linux.bzip2.out pkgsi686Linux.libelf
|
|
pkgsi686Linux.xorg.libXi
|
|
pkgsi686Linux.stdenv.cc.cc.lib
|
|
];
|
|
|
|
# Keep in sync with runtimeLibPath32
|
|
runtimeLibPath64 =
|
|
stdenv.lib.makeLibraryPath
|
|
[ zlib glib libpng12 freetype libSM libICE libXrender fontconfig.lib
|
|
libXext libX11 libXtst gtk2 bzip2.out libelf libXi
|
|
stdenv.cc.cc.lib
|
|
];
|
|
|
|
runtimeLibPath =
|
|
if is32bitPackage then runtimeLibPath32 else runtimeLibPath64;
|
|
|
|
runtimeBinPath = stdenv.lib.makeBinPath
|
|
[ coreutils gnugrep gnused glibc proot ];
|
|
|
|
setup-chroot-and-exec = writeScript "setup-chroot-and-exec"
|
|
(''
|
|
#!${bash}/bin/sh
|
|
chrootdir=chroot # relative to the current directory
|
|
mkdir -p "$chrootdir"/host
|
|
mkdir -p "$chrootdir"/proc
|
|
mkdir -p "$chrootdir"/nix
|
|
mkdir -p "$chrootdir"/tmp
|
|
mkdir -p "$chrootdir"/dev
|
|
mkdir -p "$chrootdir"/lib
|
|
mkdir -p "$chrootdir"/lib64
|
|
mkdir -p "$chrootdir"/bin
|
|
${utillinux}/bin/mount --rbind / "$chrootdir"/host
|
|
${utillinux}/bin/mount --rbind /proc "$chrootdir"/proc
|
|
${utillinux}/bin/mount --rbind /nix "$chrootdir"/nix
|
|
${utillinux}/bin/mount --rbind /tmp "$chrootdir"/tmp
|
|
${utillinux}/bin/mount --rbind /dev "$chrootdir"/dev
|
|
'' + (if is32bitPackage then ''
|
|
${utillinux}/bin/mount --rbind "${glibc_lib32_for_installer}"/lib "$chrootdir"/lib
|
|
'' else ''
|
|
${utillinux}/bin/mount --rbind "${glibc_lib_for_installer}"/lib64 "$chrootdir"/lib64
|
|
'') + ''
|
|
${utillinux}/bin/mount --rbind "${bash}"/bin "$chrootdir"/bin
|
|
chroot "$chrootdir" "$@"
|
|
'');
|
|
|
|
# buildFHSUserEnv from nixpkgs tries to mount a few directories that are not
|
|
# available in sandboxed Nix builds (/sys, /run), hence we have our own
|
|
# slimmed down variant.
|
|
run-in-fhs-env = writeScript "run-in-fhs-env"
|
|
''
|
|
#!${bash}/bin/sh
|
|
if [ "$*" = "" ]; then
|
|
echo "Usage: run-in-fhs-env <COMMAND> [ARGS...]"
|
|
exit 1
|
|
fi
|
|
"${utillinux}/bin/unshare" -r -U -m "${setup-chroot-and-exec}" "$@"
|
|
'';
|
|
|
|
mkInstallersDir = srcs:
|
|
stdenv.mkDerivation rec {
|
|
name = "${baseName}-installers";
|
|
inherit srcs version;
|
|
buildCommand =
|
|
''
|
|
# The files are copied, not symlinked, because
|
|
# - We must add execute bit to *.run files
|
|
# - Quartus*Setup*.run fails to use the *.qdz files if they are symlinks.
|
|
# Example error message (which doesn't abort the installer!):
|
|
# Error copying file from /nix/store/HASH1-altera-quartus-prime-lite-installers-16.1.0.196/cyclonev-16.1.0.196.qdz/cyclonev-16.1.0.196.qdz to /nix/store/HASH2-altera-quartus-prime-lite-16.1.0.196/cyclonev-16.1.0.196.qdz:
|
|
# /nix/store/HASH1-altera-quartus-prime-lite-installers-16.1.0.196/cyclonev-16.1.0.196.qdz/cyclonev-16.1.0.196.qdz does not exist
|
|
# Abort
|
|
# Unable to copy file
|
|
set -x
|
|
|
|
mkdir -p "$out"
|
|
${stdenv.lib.concatStringsSep "\n"
|
|
(map
|
|
(p: ''
|
|
cp "${p}" "$out/$(stripHash "${p}")"
|
|
'')
|
|
srcs
|
|
)
|
|
}
|
|
for f in $out/*run; do
|
|
[ -e "$f" ] && chmod +x "$f"
|
|
done
|
|
'';
|
|
};
|
|
|
|
componentInstallers = mkInstallersDir components;
|
|
|
|
updateComponentInstallers = mkInstallersDir updateComponents;
|
|
|
|
quartusUnwrapped = stdenv.mkDerivation rec {
|
|
name = "${baseName}-unwrapped-${version}";
|
|
inherit version;
|
|
# srcs is for keeping track of inputs used for the build.
|
|
srcs = components ++ updateComponents;
|
|
buildInputs = [ file nukeReferences ];
|
|
|
|
# Fix this:
|
|
# /nix/store/...-altera-quartus-ii-web-13.1.4.182/quartus/adm/qenv.sh: line 83: \
|
|
# warning: setlocale: LC_CTYPE: cannot change locale (en_US.UTF-8): No such file or directory
|
|
LOCALE_ARCHIVE = "${glibcLocales}/lib/locale/locale-archive";
|
|
|
|
# Prebuilt binaries need special treatment
|
|
dontStrip = true;
|
|
dontPatchELF = true;
|
|
# Fix "RPATH of binary X contains a forbidden reference to /build" issue.
|
|
# (These are prebuilt binaries, not much we can do.)
|
|
noAuditTmpdir = true;
|
|
|
|
configurePhase = "true";
|
|
buildPhase = "true";
|
|
unpackPhase = "true";
|
|
|
|
# Quartus' setup.sh (from the all-in-one-installers) doesn't fit our needs
|
|
# (we want automatic and distro-agnostic install), so call the actual setup
|
|
# program directly instead.
|
|
#
|
|
# Quartus*Setup*.run files are statically linked ELF executables that run
|
|
# open("/lib64/ld-linux-x86-64.so.2", ...) (or "/lib/ld-linux.so.2" for
|
|
# 32-bit versions) . That obviously doesn't work in sandboxed Nix builds.
|
|
#
|
|
# Things that do not work:
|
|
# * patchelf the installer (there is no .interp section in static ELF)
|
|
# * dynamic linker tricks (again, static ELF)
|
|
# * proot (the installer somehow detects something is wrong and aborts)
|
|
#
|
|
# We need bigger guns: user namespaces and chroot. That's how we make /lib64/
|
|
# available to the installer. The installer installs dynamically linked ELF
|
|
# files, so those we can fixup with usual tools.
|
|
#
|
|
# For runtime, injecting (or wrapping with) LD_LIBRARY_PATH is easier, but it
|
|
# messes with the environment for all child processes. We take the less
|
|
# invasive approach here, patchelf + RPATH. Unfortunately, Quartus itself
|
|
# uses LD_LIBRARY_PATH in its wrapper scripts. This cause e.g. firefox to
|
|
# fail due to LD_LIBRARY_PATH pulling in wrong libraries for it (happens if
|
|
# clicking any URL in Quartus).
|
|
installPhase = ''
|
|
set -x
|
|
|
|
run_quartus_installer()
|
|
{
|
|
installer="$1"
|
|
if [ ! -x "$installer" ]; then
|
|
echo "ERROR: \"$installer\" either doesn't exist or is not executable"
|
|
exit 1
|
|
fi
|
|
maybe_accept_eula="${if stdenv.lib.versionAtLeast version "17.1" then "--accept_eula 1" else ""}"
|
|
echo "### ${run-in-fhs-env} $installer --mode unattended --installdir $out" $maybe_accept_eula
|
|
"${run-in-fhs-env}" "$installer" --mode unattended --installdir "$out" $maybe_accept_eula
|
|
echo "...done"
|
|
}
|
|
|
|
echo "Running Quartus Setup (in FHS sandbox)..."
|
|
run_quartus_installer "$(echo "${componentInstallers}"/Quartus*Setup*)"
|
|
|
|
${stdenv.lib.optionalString (updateComponents != []) ''
|
|
echo "Running Quartus Update (in FHS sandbox)..."
|
|
run_quartus_installer "$(echo "${updateComponentInstallers}"/Quartus*Setup*)"
|
|
''}
|
|
|
|
echo "Removing unneeded \"uninstall\" binaries (saves $(du -sh "$out"/uninstall | cut -f1))"
|
|
rm -rf "$out"/uninstall
|
|
|
|
echo "Prevent retaining a runtime dependency on the installer binaries (saves $(du -sh "${componentInstallers}" | cut -f1) + $(du -sh "${updateComponentInstallers}" | cut -f1))"
|
|
nuke-refs "$out/logs/"*
|
|
|
|
echo "Fixing ELF interpreter paths with patchelf"
|
|
find "$out" -type f | while read f; do
|
|
case "$f" in
|
|
*.debug) continue;;
|
|
esac
|
|
# A few files are read-only. Make them writeable for patchelf. (Nix
|
|
# will make all files read-only after the build.)
|
|
chmod +w "$f"
|
|
magic=$(file "$f") || { echo "file \"$f\" failed"; exit 1; }
|
|
case "$magic" in
|
|
*ELF*dynamically\ linked*)
|
|
orig_rpath=$(patchelf --print-rpath "$f") || { echo "FAILED: patchelf --print-rpath $f"; exit 1; }
|
|
# Take care not to add ':' at start or end of RPATH, because
|
|
# that is the same as '.' (current directory), and that's
|
|
# insecure.
|
|
if [ "$orig_rpath" != "" ]; then
|
|
orig_rpath="$orig_rpath:"
|
|
fi
|
|
new_rpath="$orig_rpath${runtimeLibPath}"
|
|
case "$magic" in
|
|
*ELF*pie\ executable*|*ELF*shared\ object*x86-64*)
|
|
patchelf --set-rpath "$new_rpath" "$f" || { echo "FAILED: patchelf --set-rpath $f"; exit 1; }
|
|
;;
|
|
*ELF*executable*)
|
|
interp=$(patchelf --print-interpreter "$f") || { echo "FAILED: patchelf --print-interpreter $f"; exit 1; }
|
|
# Note the LSB interpreters, required by some files
|
|
case "$interp" in
|
|
/lib64/ld-linux-x86-64.so.2|/lib64/ld-lsb-x86-64.so.3)
|
|
new_interp=$(cat "$NIX_CC"/nix-support/dynamic-linker)
|
|
;;
|
|
/lib/ld-linux.so.2|/lib/ld-lsb.so.3)
|
|
new_interp="${glibc_lib32}/lib/ld-linux.so.2"
|
|
;;
|
|
/lib/ld-linux-armhf.so.3|/lib64/ld64.so.1|/lib64/ld64.so.2)
|
|
# Ignore ARM/ppc64/ppc64le executables, they
|
|
# are not meant to be run on the build machine.
|
|
# Example files:
|
|
# altera-quartus-prime-lite-15.1.0.185/hld/host/arm32/bin/aocl-binedit
|
|
# altera-quartus-prime-lite-15.1.0.185/hld/host/ppc64/bin/aocl-binedit
|
|
# altera-quartus-prime-lite-15.1.0.185/hld/host/ppc64le/bin/aocl-binedit
|
|
continue
|
|
;;
|
|
/usr/lib/ld.so.1)
|
|
echo "Unclear if this will work?"
|
|
new_interp="${glibc_lib32}/lib/ld-linux.so.2"
|
|
;;
|
|
*)
|
|
echo "FIXME: unhandled interpreter \"$interp\" in $f"
|
|
exit 1
|
|
;;
|
|
esac
|
|
test -f "$new_interp" || { echo "$new_interp is missing"; exit 1; }
|
|
patchelf --set-interpreter "$new_interp" \
|
|
--set-rpath "$new_rpath" "$f" || { echo "FAILED: patchelf --set-interpreter $new_interp --set-rpath $new_rpath $f"; exit 1; }
|
|
;;
|
|
esac
|
|
;;
|
|
*ELF*statically\ linked*)
|
|
echo "WARN: $f is statically linked. Needs fixup?"
|
|
;;
|
|
esac
|
|
done
|
|
|
|
# Modelsim is optional
|
|
f="$out"/modelsim_ase/vco
|
|
if [ -f "$f" ]; then
|
|
echo "Fix hardcoded \"/bin/ls\" in .../modelsim_ase/vco"
|
|
sed -i -e "s,/bin/ls,ls," "$f"
|
|
|
|
echo "Fix support for Linux 4.x in .../modelsim_ase/vco"
|
|
sed -i -e "/case \$utype in/a 4.[0-9]*) vco=\"linux\" ;;" "$f"
|
|
fi
|
|
'';
|
|
};
|
|
|
|
in
|
|
|
|
stdenv.mkDerivation rec {
|
|
name = "${baseName}-${version}";
|
|
# version and srcs are unused by this derivation, but keep them as metadata
|
|
# (for users).
|
|
inherit (quartusUnwrapped) version srcs;
|
|
buildCommand = ''
|
|
set -x
|
|
|
|
# Provide convenience wrappers in $out/bin, so that the tools can be
|
|
# started directly from PATH. Plain symlinks don't work, due to assumptions
|
|
# of resources relative to arg0.
|
|
wrap()
|
|
{
|
|
dest="$out/bin/$(basename "$1")"
|
|
if [ -f "$dest" ]; then
|
|
echo "ERROR: $dest already exist"
|
|
exit 1
|
|
fi
|
|
cat > "$dest" << EOF
|
|
#!${bash}/bin/sh
|
|
|
|
# Some tools seem to forget sourcing their environment setup file (e.g
|
|
# elf2hex), so help them by setting QUARTUS_ROOTDIR (and *OVERRIDE).
|
|
# (Perhaps these tools were never tested _not_ being started from Quartus
|
|
# IDE?)
|
|
export QUARTUS_ROOTDIR="${quartusUnwrapped}/quartus"
|
|
export QUARTUS_ROOTDIR_OVERRIDE="\$QUARTUS_ROOTDIR"
|
|
|
|
# To prevent e.g. "alt-file-convert" from aborting due to not being able to
|
|
# write to __pycache__ in the (read-only) nix store.
|
|
export PYTHONDONTWRITEBYTECODE=1
|
|
|
|
${stdenv.lib.optionalString wrapWithLdLibraryPath ''
|
|
if [ "x\$LD_LIBRARY_PATH" != x ]; then
|
|
export LD_LIBRARY_PATH="${runtimeLibPath}:\$LD_LIBRARY_PATH"
|
|
else
|
|
export LD_LIBRARY_PATH="${runtimeLibPath}"
|
|
fi
|
|
''}
|
|
|
|
# Inject path to known tools. This is important not only for having a
|
|
# complete package (all deps), but also to ensure that LD_PRELOAD etc.
|
|
# won't break the CLI tools. (Think non-NixOS setups.)
|
|
export PATH="${runtimeBinPath}:\$PATH"
|
|
|
|
# Check if we need to isolate ourself from /bin/sh. This is to work around
|
|
# a long standing bug in nixpkgs that glibc system() calls /bin/sh. The
|
|
# reasoning behind the test is that Nix tools don't depend on things in
|
|
# /lib. The test should be positive only on non-sandboxed, non-NixOS builds.
|
|
bin_sh_is_clean=1
|
|
if ldd /bin/sh | grep -q "^\s*/lib"; then
|
|
bin_sh_is_clean=0
|
|
fi
|
|
|
|
# Implement the SOURCE_DATE_EPOCH specification, for reproducible builds:
|
|
# https://reproducible-builds.org/specs/source-date-epoch
|
|
if [ "x\$SOURCE_DATE_EPOCH" != x ]; then
|
|
# Create a minimal "sandbox" with proot, or else programs running
|
|
# /bin/sh will fail because we're setting LD_PRELOAD etc.
|
|
if [ \$bin_sh_is_clean -eq 0 ]; then
|
|
maybe_proot_cmd="proot -b ${bash}/bin/sh:/bin/sh"
|
|
# Work around bug with linux 4.8.4+ (costs some performance, but
|
|
# prevents breakage).
|
|
export PROOT_NO_SECCOMP=1
|
|
fi
|
|
# Prepare LD_LIBRARY_PATH, LD_PRELOAD
|
|
if [ "x\$LD_LIBRARY_PATH" != x ]; then
|
|
export LD_LIBRARY_PATH="${libfaketime}/lib:\$LD_LIBRARY_PATH"
|
|
else
|
|
export LD_LIBRARY_PATH="${libfaketime}/lib"
|
|
fi
|
|
if [ "x${toString is32bitPackage}" = "x${toString true}" ]; then
|
|
export LD_LIBRARY_PATH="${pkgsi686Linux.libfaketime}/lib:\$LD_LIBRARY_PATH"
|
|
fi
|
|
if [ "x\$LD_PRELOAD" != x ]; then
|
|
export LD_PRELOAD="libfaketime.so.1:\$LD_PRELOAD"
|
|
else
|
|
export LD_PRELOAD=libfaketime.so.1
|
|
fi
|
|
# Set the time to SOURCE_DATE_EPOCH
|
|
export FAKETIME_FMT="%s"
|
|
export FAKETIME=\$(date +%s -d @\$SOURCE_DATE_EPOCH)
|
|
fi
|
|
|
|
# Fix this:
|
|
# /nix/store/...-altera-quartus-ii-web-13.1.4.182/quartus/adm/qenv.sh: line 83: \
|
|
# warning: setlocale: LC_CTYPE: cannot change locale (en_US.UTF-8): No such file or directory
|
|
export LOCALE_ARCHIVE="${glibcLocales}/lib/locale/locale-archive"
|
|
|
|
exec \$maybe_proot_cmd "$1" "\$@"
|
|
EOF
|
|
chmod +x "$dest"
|
|
}
|
|
|
|
echo "Creating top-level bin/ directory with wrappers for common tools"
|
|
mkdir -p "$out/bin"
|
|
for p in "${quartusUnwrapped}/"*"/bin/"*; do
|
|
test -f "$p" || continue
|
|
wrap "$p"
|
|
done
|
|
|
|
echo "Installing Desktop file..."
|
|
mkdir -p "$out/share/applications"
|
|
f="$out"/share/applications/quartus.desktop
|
|
cat >> "$f" << EOF
|
|
[Desktop Entry]
|
|
Type=Application
|
|
Name=${prettyName} ${version}
|
|
Comment=${prettyName} ${version}
|
|
Icon=${quartusUnwrapped}/quartus/adm/quartusii.png
|
|
Exec=$out/bin/quartus
|
|
Terminal=false
|
|
Path=$out
|
|
EOF
|
|
|
|
# udev rules based on
|
|
# https://www.intel.com/content/www/us/en/programmable/support/support-resources/download/drivers/dri-usb_b-lnx.html
|
|
echo "Installing udev rules"
|
|
mkdir -p "$out/lib/udev/rules.d"
|
|
cat > "$out/lib/udev/rules.d/51-usbblaster.rules" << EOF
|
|
# USB-Blaster / Intel FPGA Download Cable
|
|
SUBSYSTEM=="usb", ATTR{idVendor}=="09fb", ATTR{idProduct}=="6001", MODE="0666"
|
|
SUBSYSTEM=="usb", ATTR{idVendor}=="09fb", ATTR{idProduct}=="6002", MODE="0666"
|
|
SUBSYSTEM=="usb", ATTR{idVendor}=="09fb", ATTR{idProduct}=="6003", MODE="0666"
|
|
|
|
# USB-Blaster II / Intel FPGA Download Cable II
|
|
SUBSYSTEM=="usb", ATTR{idVendor}=="09fb", ATTR{idProduct}=="6010", MODE="0666"
|
|
SUBSYSTEM=="usb", ATTR{idVendor}=="09fb", ATTR{idProduct}=="6810", MODE="0666"
|
|
EOF
|
|
'';
|
|
|
|
meta = with stdenv.lib; {
|
|
description = "Development tools for Altera FPGA, CPLD and SoC designs";
|
|
homepage = https://www.altera.com/;
|
|
license = licenses.unfree;
|
|
platforms = [ "x86_64-linux" ];
|
|
maintainers = [ maintainers.bjornfor ];
|
|
};
|
|
}
|