Last active
February 19, 2022 09:34
-
-
Save bitonic/75db0139605839706b346ca7f43ef308 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ lib | |
, stdenv | |
, coreutils | |
, callPackage | |
, makeWrapper | |
, requireFile | |
, python3 | |
, writeTextFile | |
, binutils | |
, patchelf | |
, fetchurl | |
, zlib | |
, xlibs | |
, llvm_11 | |
, libxkbcommon | |
, libdrm | |
, tre | |
, libGL | |
, libGLU | |
, lzma | |
, flite | |
, keyutils | |
, lcms2 | |
, glib | |
, pciutils | |
, alsaLib | |
, fontconfig | |
, pcre | |
, libpcap | |
, expat | |
, dbus_daemon | |
, cups | |
}: | |
let | |
# We need this fix <https://github.com/NixOS/patchelf/issues/255> | |
patchelf_0_13 = patchelf.overrideAttrs (old: { | |
version = "0.13"; | |
src = fetchurl { | |
url = "https://github.com/NixOS/${old.pname}/releases/download/0.13/${old.pname}-0.13.tar.bz2"; | |
sha256 = "1v8px6g0zvhfxqa1inmdqfj4gc8dm70x7874hri4s48szjyd8zjc"; | |
}; | |
}); | |
libs_before = [ | |
stdenv.cc.cc.lib | |
zlib | |
]; | |
libs_after = [ | |
xlibs.libXdamage | |
xlibs.libXrender | |
xlibs.libXcursor | |
xlibs.libXi | |
xlibs.libXrandr | |
xlibs.libXext | |
xlibs.libXtst | |
xlibs.libXScrnSaver | |
xlibs.libXmu | |
xlibs.libX11 | |
xlibs.libXinerama | |
xlibs.libXfixes | |
xlibs.libXcomposite | |
xlibs.libXau | |
xlibs.libxcb | |
llvm_11.lib | |
libxkbcommon | |
libdrm | |
tre | |
libGL | |
libGLU | |
lzma | |
flite | |
keyutils | |
lcms2 | |
glib | |
pciutils | |
alsaLib | |
fontconfig | |
pcre | |
libpcap | |
expat | |
dbus_daemon.lib | |
cups.lib | |
]; | |
in stdenv.mkDerivation rec { | |
version = "12.3.1"; | |
pname = "mathematica"; | |
src = requireFile rec { | |
name = "Mathematica_${version}_LINUX.sh"; | |
message = '' | |
This nix expression requires that ${name} is | |
already part of the store. Find the file on your Mathematica CD | |
and add it to the nix store with nix-store --add-fixed sha256 <FILE>. | |
''; | |
sha256 = "51b9cab12fd91b009ea7ad4968a2c8a59e94dc55d2e6cc1d712acd5ba2c4d509"; | |
}; | |
buildInputs = [ | |
coreutils | |
patchelf_0_13 | |
makeWrapper | |
] ++ libs_before ++ libs_after; | |
unpackPhase = '' | |
echo "=== Extracting makeself archive ===" | |
# find offset from file | |
offset=$(${stdenv.shell} -c "$(grep -axm1 -e 'offset=.*' $src); echo \$offset" $src) | |
dd if="$src" ibs=$offset skip=1 | tar -xf - | |
cd Unix | |
''; | |
installPhase = '' | |
cd Installer | |
# don't restrict PATH, that has already been done | |
sed -i -e 's/^PATH=/# PATH=/' MathInstaller | |
sed -i -e 's/\/bin\/bash/\/bin\/sh/' MathInstaller | |
echo "=== Running MathInstaller ===" | |
./MathInstaller -auto -createdir=y -execdir=$out/bin -targetdir=$out/libexec/Mathematica -silent | |
# Set environment variable to fix libQt errors - see https://github.com/NixOS/nixpkgs/issues/96490 | |
for path in mathematica Mathematica; do | |
wrapProgram $out/bin/$path --set USE_WOLFRAM_LD_LIBRARY_PATH 1 | |
done | |
''; | |
pythonPatchElfText = '' | |
#!${python3}/bin/python -u | |
# A Python port of | |
# <https://github.com/NixOS/nixpkgs/blob/6e4c36b3f7ec1400bd92ef42567b687f20f3c3c4/pkgs/build-support/setup-hooks/auto-patchelf.sh> | |
# Something like 100x faster | |
import pathlib | |
import subprocess | |
import re | |
import os | |
import sys | |
readelf = os.getenv("READELF") | |
patchelf = "${patchelf_0_13}/bin/patchelf" | |
# Libraries that will take precedence over mathematica's internal .so | |
libs_before = "${lib.makeLibraryPath libs_before}".split(":") | |
# Libraries that will not take precedence over mathematica's internal .so | |
libs_after = "${lib.makeLibraryPath libs_after}".split(":") | |
nix_cc = os.getenv("NIX_CC") | |
with open(f"{nix_cc}/nix-support/dynamic-linker", 'r') as file: | |
dynamic_linker = file.read().strip() | |
def add_dir_to_libs(libs, dir): | |
for path in pathlib.Path(dir).rglob('*.so'): | |
if path.name not in libs: | |
libs[path.name] = str(path) | |
for path in pathlib.Path(dir).rglob('*.so.*'): | |
if path.name not in libs: | |
libs[path.name] = str(path) | |
def is_elf(path): | |
with open(path, 'br') as f: | |
magic = f.read(4) | |
return magic == b'\177ELF' | |
def auto_patchelf_file(libs, missing, is_executable, path): | |
if is_executable: | |
subprocess.run([patchelf, "--set-interpreter", dynamic_linker, path]) | |
# We're going to find all dependencies based on ldd output, so we need to | |
# clear the RPATH first. | |
subprocess.run([patchelf, "--remove-rpath", path], check=True) | |
# If the file is not a dynamic executable, ldd will fail, | |
# in which case we return, since there is nothing left to do. | |
# | |
# We try to prevent dynamic executables ending up here but this | |
# is one last barrier. | |
ldd_proc = subprocess.run(["ldd", path], encoding="utf-8", capture_output=True) | |
if ldd_proc.returncode != 0: | |
return | |
rpaths = [] | |
for lib in re.findall(r'^[\t ]*([^ ]+) => not found.*', ldd_proc.stdout, re.MULTILINE): | |
if lib not in libs: | |
missing.add(lib) | |
else: | |
rpaths.append(str(pathlib.Path(libs[lib]).parent)) | |
# Sometimes we get warnings we do not care about | |
subprocess.run([patchelf, "--set-rpath", ":".join(rpaths), path], stderr=subprocess.DEVNULL) | |
# libs_before will be before in the search path compared to the .so files | |
# within the package. libs_after after. path contains what we want to patch. | |
def auto_patchelf(libs_before, libs_after, path): | |
print("Gathering libraries ...", end="", flush=True) | |
# Goes from name to full path | |
libs = {} | |
for dir in libs_before: | |
add_dir_to_libs(libs, dir) | |
add_dir_to_libs(libs, path) | |
for dir in libs_after: | |
add_dir_to_libs(libs, dir) | |
print(" done.") | |
missing_libs = set(); | |
print("Patching ELFs ...", end="", flush=True) | |
for path in pathlib.Path(path).rglob('*'): | |
if not path.is_file(): | |
continue | |
if not is_elf(path): | |
continue | |
# Skip if the ELF file doesn't have segment headers (eg. object files). | |
headers = subprocess.run( | |
[readelf, "-l", path], | |
encoding="utf-8", | |
capture_output=True, | |
check=True, | |
).stdout | |
if not re.findall(r'^Program Headers:', headers, re.MULTILINE): | |
continue | |
# We just want dynamically linked executable -- we can just check interp | |
executable = "INTERP" in headers | |
if "Elf file type is EXEC" in headers and not executable: | |
continue # we have a dynamic executable on our hands | |
# Jump file if patchelf is unable to parse it | |
# Some programs contain binary blobs for testing, | |
# which are identified as ELF but fail to be parsed by patchelf | |
if subprocess.run([patchelf, path]).returncode != 0: | |
continue | |
auto_patchelf_file(libs, missing_libs, executable, path) | |
print(" done.") | |
print("Missing libraries:") | |
for lib in sorted(missing_libs): | |
print(f" {lib}") | |
auto_patchelf(libs_before, libs_after, sys.argv[1]) | |
''; | |
pythonPatchElf = writeTextFile { | |
name = "python-patch-elf"; | |
executable = true; | |
text = pythonPatchElfText; | |
}; | |
preFixup = '' | |
${pythonPatchElf} $out | |
''; | |
dontBuild = true; | |
# This is primarily an IO bound build; there's little benefit to building remotely. | |
preferLocalBuild = true; | |
# all binaries are already stripped | |
dontStrip = true; | |
# we did this in prefixup already | |
dontPatchELF = true; | |
meta = with lib; { | |
description = "Wolfram Mathematica computational software system"; | |
homepage = "http://www.wolfram.com/mathematica/"; | |
license = licenses.unfree; | |
maintainers = with maintainers; [ herberteuler ]; | |
platforms = [ "x86_64-linux" ]; | |
}; | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment