diff --git a/pkgs/misc/vscode-extensions/mktplcExtRefToFetchArgs.nix b/pkgs/misc/vscode-extensions/mktplcExtRefToFetchArgs.nix new file mode 100644 index 000000000000..a781250fc18b --- /dev/null +++ b/pkgs/misc/vscode-extensions/mktplcExtRefToFetchArgs.nix @@ -0,0 +1,8 @@ +{ publisher, name, version, sha256 ? "" }: +{ + url = "https://${publisher}.gallery.vsassets.io/_apis/public/gallery/publisher/${publisher}/extension/${name}/${version}/assetbyname/Microsoft.VisualStudio.Services.VSIXPackage"; + sha256 = sha256; + # The `*.vsix` file is in the end a simple zip file. Change the extension + # so that existing `unzip` hooks takes care of the unpacking. + name = "${publisher}-${name}.zip"; +} diff --git a/pkgs/misc/vscode-extensions/updateSettings.nix b/pkgs/misc/vscode-extensions/updateSettings.nix new file mode 100644 index 000000000000..a033229c5898 --- /dev/null +++ b/pkgs/misc/vscode-extensions/updateSettings.nix @@ -0,0 +1,39 @@ +# Updates the vscode setting file base on a nix expression +# should run from the workspace root. +{ writeShellScriptBin +, lib +, jq +}: +##User Input +{ settings ? {} +# if marked as true will create an empty json file if does not exists +, createIfDoesNotExists ? true +, vscodeSettingsFile ? ".vscode/settings.json" +, userSettingsFolder ? "" +, symlinkFromUserSetting ? false +}: +let + + updateVSCodeSettingsCmd = '' + ( + echo 'updateSettings.nix: Updating ${vscodeSettingsFile}...' + oldSettings=$(cat ${vscodeSettingsFile}) + echo $oldSettings' ${builtins.toJSON settings}' | ${jq}/bin/jq -s add > ${vscodeSettingsFile} + )''; + + createEmptySettingsCmd = ''mkdir -p .vscode && echo "{}" > ${vscodeSettingsFile}''; + fileName = builtins.baseNameOf vscodeSettingsFile; + symlinkFromUserSettingCmd = lib.optionalString symlinkFromUserSetting + '' && mkdir -p "${userSettingsFolder}" && ln -sfv "$(pwd)/${vscodeSettingsFile}" "${userSettingsFolder}/" ''; +in + + writeShellScriptBin ''vscodeNixUpdate-${lib.removeSuffix ".json" (fileName)}'' + (lib.optionalString (settings != {}) + (if createIfDoesNotExists then '' + [ ! -f "${vscodeSettingsFile}" ] && ${createEmptySettingsCmd} + ${updateVSCodeSettingsCmd} ${symlinkFromUserSettingCmd} + '' + else ''[ -f "${vscodeSettingsFile}" ] && ${updateVSCodeSettingsCmd} ${symlinkFromUserSettingCmd} + '' + ) + ) diff --git a/pkgs/misc/vscode-extensions/updateSettingsTest.nix b/pkgs/misc/vscode-extensions/updateSettingsTest.nix new file mode 100644 index 000000000000..097b9cad1661 --- /dev/null +++ b/pkgs/misc/vscode-extensions/updateSettingsTest.nix @@ -0,0 +1,6 @@ +with import {}; +callPackage (import ./updateSettings.nix) {} { + settings = { + a = "fdsdf"; + }; +} diff --git a/pkgs/misc/vscode-extensions/vscode-utils.nix b/pkgs/misc/vscode-extensions/vscode-utils.nix index 5d446b025d7a..743ae13a3e4a 100644 --- a/pkgs/misc/vscode-extensions/vscode-utils.nix +++ b/pkgs/misc/vscode-extensions/vscode-utils.nix @@ -1,16 +1,12 @@ -{ stdenv, lib, fetchurl, unzip }: - +{ stdenv, lib, buildEnv, writeShellScriptBin, fetchurl, vscode, unzip, jq }: let - mktplcExtRefToFetchArgs = ext: { - url = "https://${ext.publisher}.gallery.vsassets.io/_apis/public/gallery/publisher/${ext.publisher}/extension/${ext.name}/${ext.version}/assetbyname/Microsoft.VisualStudio.Services.VSIXPackage"; - sha256 = ext.sha256; - # The `*.vsix` file is in the end a simple zip file. Change the extension - # so that existing `unzip` hooks takes care of the unpacking. - name = "${ext.publisher}-${ext.name}.zip"; - }; + extendedPkgVersion = lib.getVersion vscode; + extendedPkgName = lib.removeSuffix "-${extendedPkgVersion}" vscode.name; + buildVscodeExtension = a@{ name, + namePrefix ? "${extendedPkgName}-extension-", src, # Same as "Unique Identifier" on the extension's web page. # For the moment, only serve as unique extension dir. @@ -24,16 +20,17 @@ let }: stdenv.mkDerivation ((removeAttrs a [ "vscodeExtUniqueId" ]) // { - name = "vscode-extension-${name}"; + name = namePrefix + name; inherit vscodeExtUniqueId; inherit configurePhase buildPhase dontPatchELF dontStrip; - installPrefix = "${vscodeExtUniqueId}"; + installPrefix = "share/${extendedPkgName}/extensions/${vscodeExtUniqueId}"; buildInputs = [ unzip ] ++ buildInputs; installPhase = '' + runHook preInstall mkdir -p "$out/$installPrefix" @@ -44,9 +41,8 @@ let }); - fetchVsixFromVscodeMarketplace = mktplcExtRef: - fetchurl((mktplcExtRefToFetchArgs mktplcExtRef)); + fetchurl((import ./mktplcExtRefToFetchArgs.nix mktplcExtRef)); buildVscodeMarketplaceExtension = a@{ name ? "", @@ -79,10 +75,25 @@ let extensionsFromVscodeMarketplace = mktplcExtRefList: builtins.map extensionFromVscodeMarketplace mktplcExtRefList; -in + vscodeWithConfiguration = import ./vscodeWithConfiguration.nix { + inherit lib extensionsFromVscodeMarketplace writeShellScriptBin; + vscodeDefault = vscode; + }; + + vscodeExts2nix = import ./vscodeExts2nix.nix { + inherit lib writeShellScriptBin; + vscodeDefault = vscode; + }; + + vscodeEnv = import ./vscodeEnv.nix { + inherit lib buildEnv writeShellScriptBin extensionsFromVscodeMarketplace jq; + vscodeDefault = vscode; + }; +in { inherit fetchVsixFromVscodeMarketplace buildVscodeExtension buildVscodeMarketplaceExtension extensionFromVscodeMarketplace - extensionsFromVscodeMarketplace; + extensionsFromVscodeMarketplace + vscodeWithConfiguration vscodeExts2nix vscodeEnv; } diff --git a/pkgs/misc/vscode-extensions/vscodeEnv.nix b/pkgs/misc/vscode-extensions/vscodeEnv.nix new file mode 100644 index 000000000000..6e4bb7b3ea88 --- /dev/null +++ b/pkgs/misc/vscode-extensions/vscodeEnv.nix @@ -0,0 +1,86 @@ +#Use vscodeWithConfiguration and vscodeExts2nix to create a vscode executable. When the executable exits, it updates the mutable extension file, which is imported when evaluated by Nix later. +{ lib +, buildEnv +, writeShellScriptBin +, extensionsFromVscodeMarketplace +, vscodeDefault +, jq +}: +##User input +{ vscode ? vscodeDefault +, nixExtensions ? [] +, vscodeExtsFolderName ? ".vscode-exts" +# will add to the command updateSettings (which will run on executing vscode) settings to override in settings.json file +, settings ? {} +, createSettingsIfDoesNotExists ? true +, launch ? {} +, createLaunchIfDoesNotExists ? true +# will add to the command updateKeybindings(which will run on executing vscode) keybindings to override in keybinding.json file +, keybindings ? {} +, createKeybindingsIfDoesNotExists ? true +, user-data-dir ? ''"''${TMP}''${name}"/vscode-data-dir'' +# if file exists will use it and import the extensions in it into this dervation else will use empty extensions list +# this file will be created/updated by vscodeExts2nix when vscode exists +, mutableExtensionsFile +}: +let + mutableExtensionsFilePath = toString mutableExtensionsFile; + mutableExtensions = if builtins.pathExists mutableExtensionsFile + then import mutableExtensionsFilePath else []; + vscodeWithConfiguration = import ./vscodeWithConfiguration.nix { + inherit lib writeShellScriptBin extensionsFromVscodeMarketplace; + vscodeDefault = vscode; + } + { + inherit nixExtensions mutableExtensions vscodeExtsFolderName user-data-dir; + }; + + updateSettings = import ./updateSettings.nix { inherit lib writeShellScriptBin jq; }; + userSettingsFolder = "${ user-data-dir }/User"; + + updateSettingsCmd = updateSettings { + settings = { + "extensions.autoCheckUpdates" = false; + "extensions.autoUpdate" = false; + "update.mode" = "none"; + } // settings; + inherit userSettingsFolder; + createIfDoesNotExists = createSettingsIfDoesNotExists; + symlinkFromUserSetting = (user-data-dir != ""); + }; + + updateLaunchCmd = updateSettings { + settings = launch; + createIfDoesNotExists = createLaunchIfDoesNotExists; + vscodeSettingsFile = ".vscode/launch.json"; + }; + + updateKeybindingsCmd = updateSettings { + settings = keybindings; + createIfDoesNotExists = createKeybindingsIfDoesNotExists; + vscodeSettingsFile = ".vscode/keybindings.json"; + inherit userSettingsFolder; + symlinkFromUserSetting = (user-data-dir != ""); + }; + + vscodeExts2nix = import ./vscodeExts2nix.nix { + inherit lib writeShellScriptBin; + vscodeDefault = vscodeWithConfiguration; + } + { + extensionsToIgnore = nixExtensions; + extensions = mutableExtensions; + }; + code = writeShellScriptBin "code" '' + ${updateSettingsCmd}/bin/vscodeNixUpdate-settings + ${updateLaunchCmd}/bin/vscodeNixUpdate-launch + ${updateKeybindingsCmd}/bin/vscodeNixUpdate-keybindings + ${vscodeWithConfiguration}/bin/code --wait "$@" + echo 'running vscodeExts2nix to update ${mutableExtensionsFilePath}...' + ${vscodeExts2nix}/bin/vscodeExts2nix > ${mutableExtensionsFilePath} + ''; +in +buildEnv { + name = "vscodeEnv"; + paths = [ code vscodeExts2nix updateSettingsCmd updateLaunchCmd updateKeybindingsCmd ]; +} diff --git a/pkgs/misc/vscode-extensions/vscodeEnvTest.nix b/pkgs/misc/vscode-extensions/vscodeEnvTest.nix new file mode 100644 index 000000000000..d7e586cab6ec --- /dev/null +++ b/pkgs/misc/vscode-extensions/vscodeEnvTest.nix @@ -0,0 +1,12 @@ +with import {}; +callPackage (import ./vscodeEnv.nix) { + extensionsFromVscodeMarketplace = vscode-utils.extensionsFromVscodeMarketplace; + vscodeDefault = vscode; +} { + mutableExtensionsFile = ./extensions.nix; + settings = { + a = "fdsdf"; + t = "test"; + }; +} + diff --git a/pkgs/misc/vscode-extensions/vscodeExts2nix.nix b/pkgs/misc/vscode-extensions/vscodeExts2nix.nix new file mode 100644 index 000000000000..afd176b4c5e4 --- /dev/null +++ b/pkgs/misc/vscode-extensions/vscodeExts2nix.nix @@ -0,0 +1,44 @@ +# based on the passed vscode will stdout a nix expression with the installed vscode extensions +{ lib +, vscodeDefault +, writeShellScriptBin +}: + +##User input +{ vscode ? vscodeDefault +, extensionsToIgnore ? [] +# will use those extensions to get sha256 if still exists when executed. +, extensions ? [] +}: +let + mktplcExtRefToFetchArgs = import ./mktplcExtRefToFetchArgs.nix; +in +writeShellScriptBin "vscodeExts2nix" '' + echo '[' + + for line in $(${vscode}/bin/code --list-extensions --show-versions \ + ${lib.optionalString (extensionsToIgnore != []) '' + | grep -v -i '^\(${lib.concatMapStringsSep "\\|" (e : ''${e.publisher}.${e.name}'') extensionsToIgnore}\)' + ''} + ) ; do + [[ $line =~ ([^.]*)\.([^@]*)@(.*) ]] + name=''${BASH_REMATCH[2]} + publisher=''${BASH_REMATCH[1]} + version=''${BASH_REMATCH[3]} + + extensions="${lib.concatMapStringsSep "." (e : ''${e.publisher}${e.name}@${e.sha256}'') extensions}" + reCurrentExt=$publisher$name"@([^.]*)" + if [[ $extensions =~ $reCurrentExt ]]; then + sha256=''${BASH_REMATCH[1]} + else + sha256=$( + nix-prefetch-url "${(mktplcExtRefToFetchArgs {publisher = ''"$publisher"''; name = ''"$name"''; version = ''"$version"'';}).url}" 2> /dev/null + ) + fi + + echo "{ name = \"''${name}\"; publisher = \"''${publisher}\"; version = \"''${version}\"; sha256 = \"''${sha256}\"; }" + done + + + echo ']' +'' diff --git a/pkgs/misc/vscode-extensions/vscodeWithConfiguration.nix b/pkgs/misc/vscode-extensions/vscodeWithConfiguration.nix new file mode 100644 index 000000000000..f15d14c74418 --- /dev/null +++ b/pkgs/misc/vscode-extensions/vscodeWithConfiguration.nix @@ -0,0 +1,54 @@ +# wrapper over vscode to control extensions per project (extensions folder will be created in execution path) +{ lib +, writeShellScriptBin +, extensionsFromVscodeMarketplace +, vscodeDefault +}: +## User input +{ vscode ? vscodeDefault +# extensions to be symlinked into the project's extensions folder +, nixExtensions ? [] +# extensions to be copied into the project's extensions folder +, mutableExtensions ? [] +, vscodeExtsFolderName ? ".vscode-exts" +, user-data-dir ? ''"''${TMP}vscodeWithConfiguration/vscode-data-dir"'' +}: +let + nixExtsDrvs = extensionsFromVscodeMarketplace nixExtensions; + mutExtsDrvs = extensionsFromVscodeMarketplace mutableExtensions; + mutableExtsPaths = lib.forEach mutExtsDrvs ( e: + { + origin = ''${e}/share/vscode/extensions/${e.vscodeExtUniqueId}''; + target = ''${vscodeExtsFolderName}/${e.vscodeExtUniqueId}-${(lib.findSingle (ext: ''${ext.publisher}.${ext.name}'' == e.vscodeExtUniqueId) "" "m" mutableExtensions ).version}''; + } + ); + + #removed not defined extensions + rmExtensions = lib.optionalString (nixExtensions++mutableExtensions != []) '' + find ${vscodeExtsFolderName} -mindepth 1 -maxdepth 1 ${ + lib.concatMapStringsSep " " (e : ''! -iname ${e.publisher}.${e.name} '') nixExtensions + + + lib.concatMapStringsSep " " (e : ''! -iname ${e.publisher}.${e.name}-${e.version} '') mutableExtensions + } -exec rm -rf {} \; + ''; + #copy mutable extension out of the nix store + cpExtensions = '' + ${lib.concatMapStringsSep "\n" (e : ''ln -sfn ${e}/share/vscode/extensions/* ${vscodeExtsFolderName}/'') nixExtsDrvs} + ${lib.concatMapStringsSep "\n" (ePath : '' + if [ ! -d ${ePath.target} ]; then + cp -a ${ePath.origin} ${ePath.target} + chmod -R u+rwx ${ePath.target} + fi + '') mutableExtsPaths} + ''; +in + writeShellScriptBin "code" '' + if ! [[ "$@" =~ "--list-extension" ]]; then + mkdir -p "${vscodeExtsFolderName}" + ${rmExtensions} + ${cpExtensions} + fi + ${vscode}/bin/code --extensions-dir "${vscodeExtsFolderName}" ${ + lib.optionalString (user-data-dir != "") ''--user-data-dir ${user-data-dir }'' + } "$@" + ''