about summary refs log tree commit diff
path: root/pkgs/by-name/pu/pulsar
diff options
context:
space:
mode:
Diffstat (limited to 'pkgs/by-name/pu/pulsar')
-rw-r--r--pkgs/by-name/pu/pulsar/001-patch-wrapper.patch11
-rw-r--r--pkgs/by-name/pu/pulsar/package.nix243
-rw-r--r--pkgs/by-name/pu/pulsar/pulsar.nemo_action9
-rwxr-xr-xpkgs/by-name/pu/pulsar/update.mjs106
4 files changed, 369 insertions, 0 deletions
diff --git a/pkgs/by-name/pu/pulsar/001-patch-wrapper.patch b/pkgs/by-name/pu/pulsar/001-patch-wrapper.patch
new file mode 100644
index 0000000000000..2270ad3c8aea8
--- /dev/null
+++ b/pkgs/by-name/pu/pulsar/001-patch-wrapper.patch
@@ -0,0 +1,11 @@
+--- a/resources/pulsar.sh	2023-03-16 04:11:14.000000000 +0100
++++ b/resources/pulsar.sh	2023-03-24 14:37:13.468813964 +0100
+@@ -123,7 +123,7 @@
+ elif [ $OS == 'Linux' ]; then
+   SCRIPT=$(readlink -f "$0")
+ 
+-  PULSAR_PATH="/opt/Pulsar/pulsar"
++  # PULSAR_PATH is set-up via `wrapProgram` in the postFixup phase
+ 
+   #Set tmpdir only if tmpdir is unset
+   : ${TMPDIR:=/tmp}
diff --git a/pkgs/by-name/pu/pulsar/package.nix b/pkgs/by-name/pu/pulsar/package.nix
new file mode 100644
index 0000000000000..443ac74c32db2
--- /dev/null
+++ b/pkgs/by-name/pu/pulsar/package.nix
@@ -0,0 +1,243 @@
+{ lib
+, stdenv
+, git
+, git-lfs
+, fetchurl
+, wrapGAppsHook3
+, alsa-lib
+, at-spi2-atk
+, cairo
+, cups
+, dbus
+, expat
+, gdk-pixbuf
+, glib
+, gtk3
+, mesa
+, nss
+, nspr
+, xorg
+, libdrm
+, libsecret
+, libxkbcommon
+, pango
+, systemd
+, hunspellDicts
+, useHunspell ? true
+, languages ? [ "en_US" ]
+, withNemoAction ? true
+, makeDesktopItem
+, copyDesktopItems
+, asar
+, python3
+}:
+
+let
+  pname = "pulsar";
+  version = "1.117.0";
+
+  sourcesPath = {
+    x86_64-linux.tarname = "Linux.${pname}-${version}.tar.gz";
+    x86_64-linux.hash = "sha256-iDQV4wcb+TY5qv8X6UW6PumK9+i5cn705ZzCSx5VgMs=";
+    aarch64-linux.tarname = "ARM.Linux.${pname}-${version}-arm64.tar.gz";
+    aarch64-linux.hash = "sha256-NJc6CQA7ZCX70ui+QcVcLW2qxM05A93yqpiiW+YosGc=";
+  }.${stdenv.hostPlatform.system} or (throw "Unsupported system: ${stdenv.hostPlatform.system}");
+
+  newLibpath = lib.makeLibraryPath [
+    alsa-lib
+    at-spi2-atk
+    cairo
+    cups
+    dbus
+    expat
+    gdk-pixbuf
+    glib
+    gtk3
+    libsecret
+    mesa
+    nss
+    nspr
+    libdrm
+    xorg.libX11
+    xorg.libxcb
+    xorg.libXcomposite
+    xorg.libXdamage
+    xorg.libXext
+    xorg.libXfixes
+    xorg.libXrandr
+    xorg.libxshmfence
+    libxkbcommon
+    xorg.libxkbfile
+    pango
+    stdenv.cc.cc.lib
+    systemd
+  ];
+
+  # Hunspell
+  hunspellDirs = builtins.map (lang: "${hunspellDicts.${lang}}/share/hunspell") languages;
+  hunspellTargetDirs = "$out/opt/Pulsar/resources/app.asar.unpacked/node_modules/spellchecker/vendor/hunspell_dictionaries";
+  hunspellCopyCommands = lib.concatMapStringsSep "\n" (lang: "cp -r ${lang}/* ${hunspellTargetDirs};") hunspellDirs;
+in
+stdenv.mkDerivation rec {
+  inherit pname version;
+
+  src = with sourcesPath; fetchurl {
+    url = "https://github.com/pulsar-edit/pulsar/releases/download/v${version}/${tarname}";
+    inherit hash;
+  };
+
+  patches = [
+    ./001-patch-wrapper.patch
+  ];
+
+  nativeBuildInputs = [
+    wrapGAppsHook3
+    copyDesktopItems
+    asar
+  ];
+
+  buildInputs = [
+    gtk3
+    xorg.libxkbfile
+  ];
+
+  dontBuild = true;
+  dontConfigure = true;
+
+  installPhase = ''
+    runHook preInstall
+
+    mkdir -p $out/opt/Pulsar
+    mv * $out/opt/Pulsar
+
+    runHook postInstall
+  '';
+
+  preFixup = ''
+    gappsWrapperArgs+=(
+      # needed for gio executable to be able to delete files
+      --prefix "PATH" : "${lib.makeBinPath [ glib ]}"
+    )
+  '' + lib.optionalString useHunspell ''
+    # On all platforms, we must inject our dictionnaries
+    ${hunspellCopyCommands}
+  '';
+
+  postFixup = ''
+    opt=$out/opt/Pulsar
+    # Patch the prebuilt binaries
+    patchelf --set-interpreter "$(cat $NIX_CC/nix-support/dynamic-linker)" \
+      --set-rpath "${newLibpath}:$opt" \
+      --add-needed libffmpeg.so \
+      --add-needed libxshmfence.so.1 \
+      --add-needed libxkbcommon.so.0 \
+      --add-needed libxkbfile.so.1 \
+      --add-needed libsecret-1.so.0 \
+      $opt/pulsar
+    patchelf --set-interpreter "$(cat $NIX_CC/nix-support/dynamic-linker)" \
+      --set-rpath "${newLibpath}" \
+      $opt/resources/app/ppm/bin/node
+    patchelf --set-interpreter "$(cat $NIX_CC/nix-support/dynamic-linker)" \
+      $opt/resources/app.asar.unpacked/node_modules/symbol-provider-ctags/vendor/ctags-linux
+
+    # Replace the bundled git with the one from nixpkgs
+    dugite=$opt/resources/app.asar.unpacked/node_modules/dugite
+    rm -f $dugite/git/bin/git
+    ln -s ${git}/bin/git $dugite/git/bin/git
+
+    # Not only do we need to replace the git binary itself, we also need to replace
+    # all the symlinks in dugite/git/libexec/git-core.
+    for file in "$dugite/git/libexec/git-core"/*; do
+      if [ -x "$file" ] && file "$file" | grep -q "ELF"; then
+          # Remove ELF executable
+          rm "$file"
+
+          # Get the corresponding filename in nixpkgs's git
+          filename=$(basename "$file")
+          git_executable="${git}/libexec/git-core/$filename"
+
+          # Create symlink to $git_executable
+          ln -s "$git_executable" "$file"
+
+          echo "Replaced $file with symlink to $git_executable"
+        fi
+    done
+
+    # Was symlinked in previous loop, but actually, nixpkgs has a separate package for git-lfs
+    # Unlink to avoid a "File exists" error and relink correctly
+    unlink $dugite/git/libexec/git-core/git-lfs
+    ln -s ${git-lfs}/bin/git-lfs $dugite/git/libexec/git-core/git-lfs
+  '' + lib.optionalString (stdenv.hostPlatform.system == "x86_64-linux") ''
+    # We have to patch a prebuilt binary in the asar archive
+    # But asar complains because the node_gyp unpacked dependency uses a prebuilt Python3 itself
+
+    rm $opt/resources/app.asar.unpacked/node_modules/tree-sitter-bash/build/node_gyp_bins/python3
+    ln -s ${python3.interpreter} $opt/resources/app.asar.unpacked/node_modules/tree-sitter-bash/build/node_gyp_bins/python3
+  '' + ''
+    # Patch the bundled node executables
+    find $opt -name "*.node" -exec patchelf --set-rpath "${newLibpath}:$opt" {} \;
+    # Also patch the node executable for apm
+    patchelf --set-rpath "${newLibpath}:$opt" $opt/resources/app/ppm/bin/node
+
+    # The pre-packaged ASAR bundle comes with prebuild binaries, expecting libstdc++.so.6
+    asarBundle=$TMPDIR/asarbundle
+    asar e $opt/resources/app.asar $asarBundle
+    find $asarBundle -name "*.node" -exec patchelf --set-rpath "${newLibpath}:$opt" --add-needed libstdc++.so.6 {} \;
+    unlink $asarBundle/node_modules/document-register-element/dre # Self referencing symlink, breaking asar rebundling
+    asar p $asarBundle $opt/resources/app.asar
+    rm -rf $asarBundle
+
+    # We have patched the original wrapper, but now it needs the "PULSAR_PATH" env var
+    mkdir -p $out/bin
+    wrapProgram $opt/resources/pulsar.sh \
+      --prefix "PULSAR_PATH" : "$opt/pulsar"
+    ln -s $opt/resources/pulsar.sh $out/bin/pulsar
+    ln -s $opt/resources/app/ppm/bin/apm $out/bin/ppm
+
+    # Copy the icons
+    mkdir -p $out/share/icons/hicolor/scalable/apps $out/share/icons/hicolor/1024x1024/apps
+    cp $opt/resources/pulsar.svg $out/share/icons/hicolor/scalable/apps/pulsar.svg
+    cp $opt/resources/pulsar.png $out/share/icons/hicolor/1024x1024/apps/pulsar.png
+  '' + lib.optionalString withNemoAction ''
+    # Copy the nemo action file
+    mkdir -p $out/share/nemo/actions
+    cp ${./pulsar.nemo_action} $out/share/nemo/actions/pulsar.nemo_action
+  '';
+
+  desktopItems = [
+    (makeDesktopItem {
+      name = "Pulsar";
+      desktopName = "Pulsar";
+      exec = "pulsar";
+      icon = "pulsar";
+      comment = "A Community-led Hyper-Hackable Text Editor";
+      genericName = "Text Editor";
+      categories = [ "Development" "TextEditor" "Utility" ];
+      mimeTypes = [ "text/plain" ];
+    })
+  ];
+
+  passthru.updateScript = ./update.mjs;
+
+  meta = {
+    description = "A Community-led Hyper-Hackable Text Editor";
+    longDescription = ''
+      A Community-led Hyper-Hackable Text Editor, Forked from Atom, built on Electron.
+      Designed to be deeply customizable, but still approachable using the default configuration.
+    '';
+    homepage = "https://github.com/pulsar-edit/pulsar";
+    sourceProvenance = with lib.sourceTypes; [ binaryNativeCode ];
+    license = lib.licenses.mit;
+    platforms = lib.platforms.linux;
+    maintainers = with lib.maintainers; [ bryango ];
+    knownVulnerabilities = [
+      "CVE-2023-5217"
+      "CVE-2022-21718"
+      "CVE-2022-29247"
+      "CVE-2022-29257"
+      "CVE-2022-36077"
+      "CVE-2023-29198"
+      "CVE-2023-39956"
+    ];
+  };
+}
diff --git a/pkgs/by-name/pu/pulsar/pulsar.nemo_action b/pkgs/by-name/pu/pulsar/pulsar.nemo_action
new file mode 100644
index 0000000000000..a74d7324ecce3
--- /dev/null
+++ b/pkgs/by-name/pu/pulsar/pulsar.nemo_action
@@ -0,0 +1,9 @@
+[Nemo Action]
+Active=true
+Name=Open in Pulsar
+Comment=Open in Pulsar
+#%U is the current selected file, this will also work on current directory
+Exec=pulsar -n %U
+Icon-Name=pulsar
+Selection=any
+Extensions=any
diff --git a/pkgs/by-name/pu/pulsar/update.mjs b/pkgs/by-name/pu/pulsar/update.mjs
new file mode 100755
index 0000000000000..4f3d2993e9730
--- /dev/null
+++ b/pkgs/by-name/pu/pulsar/update.mjs
@@ -0,0 +1,106 @@
+#!/usr/bin/env nix-shell
+/*
+#!nix-shell -i node -p nodejs_18
+*/
+
+import { promises as fs } from 'node:fs';
+
+const constants = {
+    githubUrl: "https://api.github.com/repos/pulsar-edit/pulsar/releases",
+    sha256FileURL: (newVersion) => `https://github.com/pulsar-edit/pulsar/releases/download/v${newVersion}/SHA256SUMS.txt`,
+    x86_64FileName: (newVersion) => `Linux.pulsar-${newVersion}.tar.gz`,
+    aarch64FileName: (newVersion) => `ARM.Linux.pulsar-${newVersion}-arm64.tar.gz`,
+    targetFile: new URL("default.nix", import.meta.url).pathname,
+};
+
+async function utf16ToUtf8(blob) {
+    // Sometime, upstream saves the SHA256SUMS.txt file in UTF-16, which absolutely breaks node's string handling
+    // So we need to convert this blob to UTF-8
+
+    // We need to skip the first 2 bytes, which are the BOM
+    const arrayBuffer = await blob.slice(2).arrayBuffer();
+    const buffer = Buffer.from(arrayBuffer);
+    const utf8String = buffer.toString('utf16le');
+    return utf8String;
+}
+
+async function getLatestVersion() {
+    const requestResult = await fetch(constants.githubUrl);
+    if (!requestResult.ok) {
+        console.error("Failed to fetch releases");
+        console.error(requestResult);
+        process.exit(1);
+    };
+    let jsonResult = await requestResult.json();
+
+    jsonResult = jsonResult.filter((release) => !release.prerelease && !release.draft);
+    if (jsonResult.length == 0) {
+        console.error("No releases found");
+        process.exit(1);
+    }
+
+    return jsonResult[0].tag_name.replace(/^v/, '');
+}
+
+async function getSha256Sum(hashFileContent, targetFile) {
+    // The upstream file has a fomat like this:
+    // 0000000000000000000000000000000000000000000000000000000000000000 targetFile
+
+    let sha256 = hashFileContent.
+        split('\n').
+        map(line => line.replace("\r", "")). // Side-effect of the UTF-16 conversion, if the file was created from Windows
+        filter((line) => line.endsWith(targetFile))[0].
+        split(' ')[0];
+
+    return "sha256-" + Buffer.from(sha256, 'hex').toString('base64');
+}
+
+async function getSha256Sums(newVersion) {
+    // Upstream provides a file with the hashes of the files, but it's not in the SRI format, and it refers to the compressed tarball
+    // So let's just use nix-prefetch-url to get the hashes of the decompressed tarball, and `nix hash to-sri` to convert them to SRI format
+    const hashFileUrl = constants.sha256FileURL(newVersion);
+    const hashFileContent = await fetch(hashFileUrl).then((response) => response.blob());
+    const headerbuffer = await hashFileContent.slice(0, 2).arrayBuffer()
+    const header = Buffer.from(headerbuffer).toString('hex');
+
+    // We must detect if it's UTF-16 or UTF-8. If it's UTF-16, we must convert it to UTF-8, otherwise just use it as-is
+    const hashFileContentString = header == 'fffe' ?
+        await utf16ToUtf8(hashFileContent) :
+        await hashFileContent.text();
+
+    let x86_64;
+    let aarch64;
+    console.log("Getting new hashes");
+    let promises = [
+        getSha256Sum(hashFileContentString, constants.x86_64FileName(newVersion)).then((hash) => { x86_64 = hash; }),
+        getSha256Sum(hashFileContentString, constants.aarch64FileName(newVersion)).then((hash) => { aarch64 = hash; }),
+    ];
+    await Promise.all(promises);
+    return { x86_64, aarch64 };
+}
+
+async function updateFile(newVersion, sha256Sums, currentFile) {
+    // There is some assumptions in how the file is formatted, but nothing egregious
+
+    let newFile = currentFile.replace(/version = "(.*)";/, `version = "${newVersion}";`);
+    newFile = newFile.replace(/x86_64-linux\.hash = "(.*)";/, `x86_64-linux.hash = "${sha256Sums.x86_64}";`);
+    newFile = newFile.replace(/aarch64-linux\.hash = "(.*)";/, `aarch64-linux.hash = "${sha256Sums.aarch64}";`);
+
+    await fs.writeFile(constants.targetFile, newFile);
+};
+
+let currentFile = await fs.readFile(constants.targetFile, 'utf8');
+let currentVersion = currentFile.match(/version = "(.*)";/)[1];
+const newVersion = await getLatestVersion();
+if (currentVersion === newVersion) {
+    console.error("Already up to date");
+    process.exit(0);
+}
+console.log("New version: " + newVersion);
+const sha256Sums = await getSha256Sums(newVersion);
+console.log(sha256Sums)
+if (!sha256Sums.x86_64 || !sha256Sums.aarch64) {
+    console.error("Failed to find sha256 sums for the 2 files");
+    process.exit(1);
+}
+updateFile(newVersion, sha256Sums, currentFile);