Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mac Application launchers not symlinked on darwin #1341

Open
starcraft66 opened this issue Jun 18, 2020 · 241 comments
Open

Mac Application launchers not symlinked on darwin #1341

starcraft66 opened this issue Jun 18, 2020 · 241 comments
Labels
pinned Prevent marking as stale system: darwin

Comments

@starcraft66
Copy link
Member

Issue description

I noticed that when I install an application on macOS that includes a GUI launcher (emacs in my case) using nix-darwin, Emacs.app is symlinked into ~/Applications/Nix Apps so that things like Spotlight can find and launch Emacs without requiring me to have a dedicated terminal open to start emacs.

I think home-manager should adopt this behaviour and symlink its apps into ~/Applications/Home Manager Apps or something like that to greatly improve the experience of launching GUI apps on macOS/darwin.

Meta

Not sure what other useful information to add, this is more of a feature request than a bug.

@starcraft66
Copy link
Member Author

I should add that applications are already symlinked into ~/.nix-profile/Applications by nix-env(?) but this is not enough for them to be picked up by Spotlight. nix-darwin goes a step further by linking these apps into ~/Applications where Spotlight does search for apps.

https://github.com/LnL7/nix-darwin/blob/master/modules/system/applications.nix

@berbiche
Copy link
Member

berbiche commented Aug 28, 2020

Here's what I added to my configuration to make this work

EDIT : the example has been further simplified

{
  # Install MacOS applications to the user environment if the targetPlatform is Darwin
  home.file."Applications/home-manager".source = let
  apps = pkgs.buildEnv {
    name = "home-manager-applications";
    paths = config.home.packages;
    pathsToLink = "/Applications";
  };
  in mkIf pkgs.stdenv.targetPlatform.isDarwin "${apps}/Applications";
}

@berbiche berbiche mentioned this issue Aug 28, 2020
7 tasks
@berbiche
Copy link
Member

This task is now handled in my PR #1460

@jwiegley
Copy link
Contributor

jwiegley commented Sep 4, 2020

I'm seeing a conflict between the above code and nix-darwin:

Creating home file links in /Users/johnw
ln: failed to create symbolic link '/Users/johnw/Applications/Home Manager Apps': Permission denied

This is because ~/Applications is a symlink into my Nix store that cannot be modified, while it looks like home-manager is trying to make a new directory directly within ~/Applications.

@berbiche
Copy link
Member

berbiche commented Sep 4, 2020

You are right. The logic in nix-darwin to symlink applications to ~/Applications is here.

if [ ! -e ~/Applications -o -L ~/Applications ]; then
    ln -sfn ${cfg.build.applications}/Applications ~/Applications
elif [ ! -e ~/Applications/Nix\ Apps -o -L ~/Applications/Nix\ Apps ]; then
    ln -sfn ${cfg.build.applications}/Applications ~/Applications/Nix\ Apps
else
    echo "warning: ~/Applications and ~/Applications/Nix Apps are directories, skipping App linking..." >&2
fi

AFAIK there are many applications that create folders within ~/Applications so nix-darwin's logic seems wrong.
The first if should be removed altogether.

Maybe @LnL7 could provide more input?

@rycee
Copy link
Member

rycee commented Sep 4, 2020

@jwiegley Is it nix-darwin that takes ~/Applications or something else? I don't know macOS so I assumed that ~/Applications is a pre-existing directory for all users. I guess that assumption is wrong? Should I revert the d3aee54 commit?

@berbiche
Copy link
Member

berbiche commented Sep 4, 2020

On a brand new Mac ~/Applications already existed in my case

rycee added a commit that referenced this issue Sep 4, 2020
This disables the generation of the application directory until
conflicting behavior with nix-darwin is resolved.

See #1341 (comment)
@rycee
Copy link
Member

rycee commented Sep 4, 2020

I've disabled the feature in the above commit until we've come to an agreement with nix-darwin 🙂

aakropotkin pushed a commit to aakropotkin/home-manager that referenced this issue Sep 9, 2020
This disables the generation of the application directory until
conflicting behavior with nix-darwin is resolved.

See nix-community#1341 (comment)
@Atemu
Copy link

Atemu commented Dec 18, 2020

I don't see a reason why this should also be disabled for Darwin users who don't use nix-darwin, could you make it an option instead?

@nuance
Copy link

nuance commented Jan 15, 2021

I might have something unusual about my setup, but I've found that symlinks don't get picked up by spotlight, so symlinking the app dir (or the individual apps) don't cause them to show up in my spotlight search (eg cmd-space + "emacs" only shows web results).

After some debugging, I found that spotlight ignores symlinks but will index aliases. Aliases are kind of awful to work with - you can't seem to create them from the CLI without pinging finder via AppleScript / osascript.

I've added the following to my config which seems to make things work:

  home.activation = {
    aliasApplications = lib.hm.dag.entryAfter [ "writeBoundary" ] ''
      app_folder=$(echo ~/Applications);
      for app in $(find "$genProfilePath/home-path/Applications" -type l); do
        $DRY_RUN_CMD rm -f $app_folder/$(basename $app)
        $DRY_RUN_CMD osascript -e "tell app \"Finder\"" -e "make new alias file at POSIX file \"$app_folder\" to POSIX file \"$app\"" -e "set name of result to \"$(basename $app)\"" -e "end tell"
      done
    '';
  };

(in the context of my dot files: https://github.com/nuance/dotfiles/blob/master/nix/environments/macos.nix#L11-L19)

Happy to make a PR for this or let someone else use this code if it looks reasonable.

@Atemu
Copy link

Atemu commented Jan 15, 2021

An alternative solution I have (sort of) working locally is to cp -r --symlink the .Apps or maybe even just symlink the Contents/ subdir but at least one app (Anki) has issues with that approach.

@wmertens
Copy link
Contributor

I don't have a Mac any more, but isn't it sufficient to symlink from ~/Applications/Nix Apps to ~/.nix-profile/Applications?

@Atemu
Copy link

Atemu commented Jan 19, 2021

It is not. As @nuance mentioned, Finder ignores symlink for whatever stupid reason.

Although you could try aliasing the whole directory instead of the indiviual apps perhaps.

@nuance
Copy link

nuance commented Jan 19, 2021 via email

@midchildan
Copy link
Contributor

I'm guessing that people who're having problems with Spotlight installed Nix using the post-Catalina installer. The newer installer creates the Nix store on a separate filesystem, which prevents files there from being indexed by Spotlight.

There are 2 known solutions for this:

  1. Make Spotlight index the Nix store and continue to use symlinks
  2. Create aliases to individual applications

However, there are problems with both one way or another. The same issue came up on Homebrew numerous times, and they never found a satisfactory answer.

@midchildan
Copy link
Contributor

With the symlink approach, things should work just as it did before. However, I haven't upgraded from Mojave, so I can't be 100% sure whether it would work on Catalina and later. But the problem is, Spotlight will pick up all applications from the Nix store and not only the ones in the current profile. This means it's very difficult to tell which one to choose when you have multiple profile generations in your Nix store:
symlink

With the alias approach, you won't have the same problem as long as the Nix store is out of reach of Spotlight. However, the alias won't be categorized under "Applications" and will get pushed far down the list:
alias

Comparing the two, the alias approach slightly looks more usable if it turns out to work reliably. There are past reports of the "right click → Open With" functionality not working when using the alias approach, but I haven't encountered that problem so far so it may have been resolved.

Looking at Homebrew's past struggles with aliases, the main issue seemed to be about keeping aliases in a healthy state between upgrades. Home Manager might not suffer from this problem because it can delete the Home Manager Apps directory and recreate the aliases from scratch on each upgrade. I don't know how reliable this will be when the aliases cross filesystem boundaries though.

@Atemu
Copy link

Atemu commented Feb 1, 2021

FWIW, I've been using the cp -rs approach for Emacs successfully and maybe it's just Anki for which it doesn't work (I don't manage any other apps with Nix).
I've got an experimental branch here but it might not work in some cases because --no-preserve=mode strips exec permission too. I wanted to find a way to only reset the write permission but you could also just chmod +w afterwards I guess.

If it turns out that doesn't work with many other apps, we could also resort to just copying the .apps. (hard- or reflinks would be cross-device unfortunately.)

@midchildan
Copy link
Contributor

midchildan commented Feb 2, 2021

I've just tried the cp -rs approach, but it appears spotlight won't index those too. The apps would appear on Launchpad and you can launch them alright, but it won't appear on spotlight searches.

Here are the steps I followed to check:

  1. Remove all instances of MacVim from paths indexed by Spotlight
  2. Download the official DMG for MacVim
  3. Open the DMG image
  4. Run nix run nixpkgs.coreutils -c cp -rs /Volumes/MacVim/MacVim.app ~/Applications
  5. Open Spotlight (Cmd + space) and search "MacVim"

MacVim.app is symlinked from the DMG image to simulate the post-Catalina situation in which the Nix store resides on a separate filesystem.

Copying the apps could be tricky as well because if I understand correctly, apps built in nixpkgs have no guarantee of being relocatable.

@Atemu
Copy link

Atemu commented Feb 2, 2021

Yeah doing it with MacVim doesn't work for me either. Weird that it does work for Emacs though.

@nicknovitski
Copy link
Member

Currently I copy the applications, and it's working for me so far. cp -fHRL resolves all symlinks, and though the copied apps may have references to files in the nix store, those won't be garbage collected as long as the "original" applications are still in the current profile.

  home.activation = {
    copyApplications = let
      apps = pkgs.buildEnv {
        name = "home-manager-applications";
        paths = config.home.packages;
        pathsToLink = "/Applications";
      };
    in lib.hm.dag.entryAfter [ "writeBoundary" ] ''
      baseDir="$HOME/Applications/Home Manager Apps"
      if [ -d "$baseDir" ]; then
        rm -rf "$baseDir"
      fi
      mkdir -p "$baseDir"
      for appFile in ${apps}/Applications/*; do
        target="$baseDir/$(basename "$appFile")"
        $DRY_RUN_CMD cp ''${VERBOSE_ARG:+-v} -fHRL "$appFile" "$baseDir"
        $DRY_RUN_CMD chmod ''${VERBOSE_ARG:+-v} -R +w "$target"
      done
    '';
  };

I think if home manager were to change to handle this use case, it might add an option for copying targets rather than linking them, but how could it do this safely? It would need to be able to overwrite targets that already exist.

@Atemu
Copy link

Atemu commented Feb 15, 2021

Like this?

https://github.com/Atemu/home-manager/blob/darwin-copy-apps-fully-wip/modules/targets/darwin.nix

I'm not sure how well reverting back to a symlink could work but atm HM just says the dir is in the way of the symlink and needs to be removed first which I think is fine.

malte-v pushed a commit to malte-v/home-manager that referenced this issue Feb 24, 2021
This disables the generation of the application directory until
conflicting behavior with nix-darwin is resolved.

See nix-community#1341 (comment)
@stale
Copy link

stale bot commented May 16, 2021

Thank you for your contribution! I marked this issue as stale due to inactivity. If this remains inactive for another 7 days, I will close this issue. Please read the relevant sections below before commenting.

If you are the original author of the issue

  • If this is resolved, please consider closing it so that the maintainers know not to focus on this.
  • If this might still be an issue, but you are not interested in promoting its resolution, please consider closing it while encouraging others to take over and reopen an issue if they care enough.
  • If you know how to solve the issue, please consider submitting a Pull Request that addresses this issue.

If you are not the original author of the issue

  • If you are also experiencing this issue, please add details of your situation to help with the debugging process.
  • If you know how to solve the issue, please consider submitting a Pull Request that addresses this issue.

Memorandum on closing issues

If you have nothing of substance to add, please refrain from commenting and allow the bot close the issue. Also, don't be afraid to manually close an issue, even if it holds valuable information.

Closed issues stay in the system for people to search, read, cross-reference, or even reopen--nothing is lost! Closing obsolete issues is an important way to help maintainers focus their time and effort.

@stale stale bot added the status: stale label May 16, 2021
@jcszymansk
Copy link

install Firefox via home-manager on MacOS considering that all Firefox packages in nixpkgs only support linux?

@Samuel-Martineau As for Firefox, you may use this project which provides current binaries of several editions thereof; you can also <shameless_plug>use my nixcasks</shameless_plug> which provides many Homebrew Casks as Nix derivations, Firefox is among them (I use LibreWolf via nixcasks, and it works, so FF will probably too).

@pperanich
Copy link

pperanich commented Dec 26, 2023

I have re-implemented the functionailty of the Lisp utility written by @hraban in bash. Not sure if this furthers the possibility of including in home-manager or if licensing issues still preclude this.

#!/bin/bash

# Ensure you have these tools installed: jq, plutil, rsync, mktemp, grep, cut, realpath

copyable_app_props=(
	"CFBundleDevelopmentRegion"
	"CFBundleDocumentTypes"
	"CFBundleGetInfoString"
	"CFBundleIconFile"
	"CFBundleIdentifier"
	"CFBundleInfoDictionaryVersion"
	"CFBundleName"
	"CFBundleShortVersionString"
	"CFBundleURLTypes"
	"NSAppleEventsUsageDescription"
	"NSAppleScriptEnabled"
	"NSDesktopFolderUsageDescription"
	"NSDocumentsFolderUsageDescription"
	"NSDownloadsFolderUsageDescription"
	"NSPrincipalClass"
	"NSRemovableVolumesUsageDescription"
	"NSServices"
	"UTExportedTypeDeclarations"
)

dry_run=${DRY_RUN:-false}
debug_sh=${DEBUGSH:-false}

# Function definitions
rootp() {
	[[ "$(id -u)" == "0" ]]
}

sh_cmd() {
	if $dry_run; then
		echo "Dry-run: $*"
	else
		if $debug_sh; then set -x; fi
		"$@"
		if $debug_sh; then set +x; fi
	fi
}

sync_icons() {
	from=$1
	to=$2
	from_resources="${from}/Contents/Resources/"
	to_resources="${to}/Contents/Resources/"

	find "$to_resources" -name "*.icns" -delete
	rsync --include "*.icns" --exclude "*" --recursive "$from_resources" "$to_resources"
}

copy_paths() {
	from=$1
	to=$2
	paths=("${@:3}")

	keys=$(jq -n '$ARGS.positional' --args "${paths[@]}")
	jqfilter="to_entries |[.[]| select(.key as \$item| \$keys | index(\$item) >= 0) ] | from_entries"

	temp_dir=$(mktemp -d)
	trap 'rm -rf "$temp_dir"' EXIT

	pushd $temp_dir >/dev/null

	cp "$from" "orig"
	chmod u+w "orig"

	cp "$to" "bare-wrapper"
	chmod u+w "bare-wrapper"

	plutil -convert json -- "orig"
	plutil -convert json -- "bare-wrapper"
	jq --argjson keys "$keys" "$jqfilter" <"orig" >"filtered"
	cat "bare-wrapper" "filtered" | jq -s add >"final"
	plutil -convert xml1 -- "final"

	cp "final" "$to"
	popd >/dev/null
}

sync_dock() {
	# Make sure all environment variables are cleared that might affect dockutil
	unset SUDO_USER

	# Array of applications to sync
	declare -a apps=("$@")

	# Iterate through each provided app
	for app_path in "${apps[@]}"; do
		if [[ -d "$app_path" ]]; then
			# Extract the name of the app from the path
			app_name=$(basename "$app_path")
			app_name=${app_name%.*} # Remove the '.app' extension
			resolved_path=$(realpath "$app_path")

			# Find the current Dock item for the app, if it exists
			current_dock_item=$(dockutil --list --no-restart | grep "/$app_name.app" | awk -F"\t" '{print $1}')

			if [[ -n "$current_dock_item" ]]; then
				# The app is currently in the Dock, attempt to replace it
				echo "Updating $app_name in Dock..."
				if $dry_run; then
					echo "Dry-run: dockutil --add \"$resolved_path\" --replacing \"$current_dock_item\""
				else
					dockutil --add "$resolved_path" --replacing "$current_dock_item" --no-restart
				fi
			else
				# The app is not in the Dock; you might choose to add it or do nothing
				echo "$app_name is not currently in the Dock."
			fi
		else
			echo "Warning: Provided path '$app_path' is not valid."
		fi
	done

	# Restart the Dock to apply changes
	if ! $dry_run; then
		killall Dock
	else
		echo "Dry-run: would now restart Dock."
	fi
}

mktrampoline() {
	app=$1
	trampoline=$2

	if [[ ! -d $app ]]; then
		echo "app path is not directory."
		return 1
	fi

	cmd="do shell script \"open '$app'\""
	sh_cmd /usr/bin/osacompile -o "$trampoline" -e "$cmd"
	sync_icons "$app" "$trampoline"
	copy_paths "$(realpath "$app/Contents/Info.plist")" "$(realpath "$trampoline/Contents/Info.plist")" "${copyable_app_props[@]}"
}

sync_trampolines() {
	[[ ! -d $1 ]] && echo "Source directory does not exist" && return 1
	[[ ! -d $2 ]] && mkdir -p "$2"

	apps=("$1"/*.app)

	for app in "${apps[@]}"; do
		trampoline="${2}/$(basename "$app")"
		mktrampoline "$app" "$trampoline"
	done
	sync_dock "${apps[@]}"
}

# Main switch case to process commands
case $1 in
mktrampoline)
	mktrampoline "$2" "$3"
	;;
sync-trampolines)
	sync_trampolines "$2" "$3"
	;;
*)
	echo "Usage: $0 {mktrampoline|sync-trampolines}"
	exit 1
	;;
esac

@pperanich
Copy link

I've cleaned the script up a bit and added a home-manager module.

# Utilities not in nixpkgs.
plutil="/usr/bin/plutil"
killall="/usr/bin/killall"
osacompile="/usr/bin/osacompile"

copyable_app_props=(
	"CFBundleDevelopmentRegion"
	"CFBundleDocumentTypes"
	"CFBundleGetInfoString"
	"CFBundleIconFile"
	"CFBundleIdentifier"
	"CFBundleInfoDictionaryVersion"
	"CFBundleName"
	"CFBundleShortVersionString"
	"CFBundleURLTypes"
	"NSAppleEventsUsageDescription"
	"NSAppleScriptEnabled"
	"NSDesktopFolderUsageDescription"
	"NSDocumentsFolderUsageDescription"
	"NSDownloadsFolderUsageDescription"
	"NSPrincipalClass"
	"NSRemovableVolumesUsageDescription"
	"NSServices"
	"UTExportedTypeDeclarations"
)

function sync_icons() {
	local from="$1"
	local to="$2"
	from_resources="$from/Contents/Resources/"
	to_resources="$to/Contents/Resources/"

	find "$to_resources" -name "*.icns" -delete
	rsync --include "*.icns" --exclude "*" --recursive "$from_resources" "$to_resources"
}

function copy_paths() {
	local from="$1"
	local to="$2"
	local paths=("${@:3}")

	keys=$(jq -n '$ARGS.positional' --args "${paths[@]}")
	jqfilter="to_entries |[.[]| select(.key as \$item| \$keys | index(\$item) >= 0) ] | from_entries"

	temp_dir=$(mktemp -d)
	trap 'rm -rf "$temp_dir"' EXIT

	pushd $temp_dir >/dev/null

	cp "$from" "orig"
	chmod u+w "orig"

	cp "$to" "bare-wrapper"
	chmod u+w "bare-wrapper"

	$plutil -convert json -- "orig"
	$plutil -convert json -- "bare-wrapper"
	jq --argjson keys "$keys" "$jqfilter" <"orig" >"filtered"
	cat "bare-wrapper" "filtered" | jq -s add >"final"
	$plutil -convert xml1 -- "final"

	cp "final" "$to"
	popd >/dev/null
}

function sync_dock() {
	# Make sure all environment variables are cleared that might affect dockutil
	unset SUDO_USER

	# Array of applications to sync
	declare -a apps=("$@")

	# Iterate through each provided app
	for app_path in "${apps[@]}"; do
		if [ -d "$app_path" ]; then
			# Extract the name of the app from the path
			app_name=$(basename "$app_path")
			app_name=${app_name%.*} # Remove the '.app' extension
			resolved_path=$(realpath "$app_path")

			# Find the current Dock item for the app, if it exists
			current_dock_item=$(dockutil --list --no-restart | grep "$app_name.app" | awk -F "\t" '{print $1}' || echo "")

			if [ -n "$current_dock_item" ]; then
				# The app is currently in the Dock, attempt to replace it
				echo "Updating $app_name in Dock..."
				dockutil --add "$resolved_path" --replacing "$current_dock_item" --no-restart
			else
				# The app is not in the Dock; you might choose to add it or do nothing
				echo "$app_name is not currently in the Dock."
			fi
		else
			echo "Warning: Provided path $app_path is not valid."
		fi
	done

	# Restart the Dock to apply changes
	$killall Dock
}

function mktrampoline() {
	local app="$1"
	local trampoline="$2"

	if [[ ! -d $app ]]; then
		echo "app path is not directory."
		return 1
	fi

	cmd="do shell script \"open '$app'\""
	$osacompile -o "$trampoline" -e "$cmd"
	sync_icons "$app" "$trampoline"
	copy_paths "$(realpath "$app/Contents/Info.plist")" "$(realpath "$trampoline/Contents/Info.plist")" "${copyable_app_props[@]}"
}

function sync_trampolines() {
	[[ ! -d "$1" ]] && echo "Source directory does not exist" && return 1

	if [[ -d "$2" ]]; then
		rm -rf "$2"
	fi
	mkdir -p "$2"

	apps=("$1"/*.app)

	for app in "${apps[@]}"; do
		trampoline="$2/$(basename "$app")"
		mktrampoline "$app" "$trampoline"
	done
	sync_dock "${apps[@]}"
}
{ config, lib, pkgs, ... }:

with lib;

{
  config = mkIf pkgs.stdenv.hostPlatform.isDarwin {
    # Install MacOS applications to the user Applications folder. Also update Docked applications
    home.extraActivationPath = with pkgs; [
      rsync
      dockutil
      gawk
    ];
    home.activation.trampolineApps = hm.dag.entryAfter [ "writeBoundary" ] ''
      ${builtins.readFile ./lib-bash/trampoline-apps.sh}
      fromDir="$HOME/Applications/Home Manager Apps"
      toDir="$HOME/Applications/Home Manager Trampolines"
      sync_trampolines "$fromDir" "$toDir"
    '';
  };
}

@epetousis
Copy link

epetousis commented Mar 10, 2024

An alternative solution I have (sort of) working locally is to cp -r --symlink the .Apps or maybe even just symlink the Contents/ subdir but at least one app (Anki) has issues with that approach.

@Atemu what was Anki complaining about when aliasing the Contents folder? I set up an activation script to do this just now; it seems far simpler than creating trampoline apps, and Anki, Firefox and Emacs all seem to be working great for me.

Edit: while the apps do run, code signing breaks. I'm happy with this tradeoff on my system, but it might not be valid for everyone.

@magikid
Copy link

magikid commented Apr 1, 2024

I'm using the aliasing approach and needed to make a small change to get apps with spaces in their names working properly. I update the IFS to not care about spaces.

        home.activation = {
            aliasApplications = lib.hm.dag.entryAfter [ "writeBoundary" ] ''
                ORIGIFS=$IFS
                IFS=$'\n\t'
                app_folder=$(echo ~/Applications/Home\ Manager\ Trampolines);
                for app in $(find "$genProfilePath/home-path/Applications" -type l); do
                    echo "Linking $app to $app_folder"
                    $DRY_RUN_CMD rm -f $app_folder/$(basename $app)
                    $DRY_RUN_CMD /usr/bin/osascript -e "tell app \"Finder\"" -e "make new alias file at POSIX file \"$app_folder\" to POSIX file \"$app\"" -e "set name of result to \"$(basename $app)\"" -e "end tell"
                done
                IFS=$ORIGIFS
            '';
        };

mjrusso added a commit to mjrusso/nixos-config that referenced this issue Apr 3, 2024
@pshirshov
Copy link

pshirshov commented Apr 10, 2024

Better version with mkalias:

  # don't forget to add 'com.apple.alias-file' without quotes into alfred's Extras
  home.activation = {
    aliasHomeManagerApplications = lib.hm.dag.entryAfter [ "writeBoundary" ] ''
      app_folder="${config.home.homeDirectory}/Applications/Home Manager Trampolines"
      rm -rf "$app_folder"
      mkdir -p "$app_folder"
      for app in $(find "$genProfilePath/home-path/Applications" -type l); do
          app_target="$app_folder/$(basename $app)"
          real_app="$(readlink $app)"
          echo "mkalias \"$real_app\" \"$app_target\"" >&2
          $DRY_RUN_CMD ${pkgs.mkalias}/bin/mkalias "$real_app" "$app_target"
      done
    '';
  };

@Tobix99
Copy link

Tobix99 commented Apr 11, 2024

@pshirshov I had some problems with your script with VS Code (because of the spaces in the "file name")

I updated it to this:

home.activation = {
  aliasHomeManagerApplications = lib.hm.dag.entryAfter [ "writeBoundary" ] ''
    app_folder="${config.home.homeDirectory}/Applications/Home Manager Trampolines"
    rm -rf "$app_folder"
    mkdir -p "$app_folder"
    find "$genProfilePath/home-path/Applications" -type l -print | while read -r app; do
        app_target="$app_folder/$(basename "$app")"
        real_app="$(readlink "$app")"
        echo "mkalias \"$real_app\" \"$app_target\"" >&2
        $DRY_RUN_CMD ${pkgs.mkalias}/bin/mkalias "$real_app" "$app_target"
    done
  '';
};

@pshirshov
Copy link

Anyway, linking is broken. It's better to just make copies: LnL7/nix-darwin#214 (comment)

@hraban
Copy link
Contributor

hraban commented Apr 11, 2024

Anyway, linking is broken. It's better to just make copies: LnL7/nix-darwin#214 (comment)

Copying has been discussed upthread and it's less reliable in the wild than creating a link of some kind (whether symlink, trapoline, alias, ... is up for debate). There isn't a clear winner so far.

augustomelo added a commit to augustomelo/dotfiles that referenced this issue May 3, 2024
@remi-gelinas
Copy link

Is this still actually an issue? I haven't done anything specific in my config to link application files, for home-manager or nix-darwin, and both seem to be linking application bundles correctly

@hraban
Copy link
Contributor

hraban commented May 28, 2024

Is this still actually an issue? I haven't done anything specific in my config to link application files, for home-manager or nix-darwin, and both seem to be linking application bundles correctly

Spotlight and Launchpad (usually) don't pick up on apps inside a symlink inside the application directory. Support for Spotlight is nice when you want keyboard-only navigation without any external tools, just ⌘-space <first few letters of app name> RET

@remi-gelinas
Copy link

Spotlight and Launchpad (usually) don't pick up on apps inside a symlink inside the application directory. Support for Spotlight is nice when you want keyboard-only navigation without any external tools, just ⌘-space RET

Right - but is that something HM can actually fix, or is that a spotlight issue?

@pshirshov
Copy link

There are reasonable workarounds.

@hraban
Copy link
Contributor

hraban commented May 28, 2024

Spotlight and Launchpad (usually) don't pick up on apps inside a symlink inside the application directory. Support for Spotlight is nice when you want keyboard-only navigation without any external tools, just ⌘-space RET

Right - but is that something HM can actually fix, or is that a spotlight issue?

That's what this thread is about :) welcome to the jungle.

@krad246
Copy link

krad246 commented May 28, 2024

From my experience your solution has been working fine @hraban . Ideally yeah just to keep a single source of truth you'd want a mount or some kind of symlink, etc. but trampolining it feels pretty unobtrusive as it stands. I haven't noticed any weird stuff.

@remi-gelinas
Copy link

Interesting... So for example, Raycast can find the symlinks that Home Manager and nix-darwin create by default. If I understand correctly, Spotlight normally can't see those, and requires extra trampolines? With @hraban's solution for aliasing, will Spotlight find those trampolines?

@rrittsteiger
Copy link

Symlinks don't work from my experience. Aliases are the way to go. From Apples point of view, I would assume, that this is not a bug but a feature. Would be great if there would be a possibility in HM to fix this.

@arichtman
Copy link

Someone pointed me at this - sharing in case it helps someone
https://github.com/hraban/mac-app-util

@AntonyBlakey
Copy link

AntonyBlakey commented Aug 5, 2024

I have found that the following is required for me. A couple of changes from the most recent above:

  • run the script after "linkGeneration", otherwise home manager hasn't created it's own links, and you will be using the previous generation I think.
  • Modify the find because home manager creates nested symlinks, and you want to pick up .app directories as the targets.
  • On my machine at least, home manager uses home-files not home-path
# Need to create aliases because Launchbar doesn't look through symlinks.
home.activation.link-apps = lib.hm.dag.entryAfter [ "linkGeneration" ] ''
  new_nix_apps="${config.home.homeDirectory}/Applications/Nix"
  rm -rf "$new_nix_apps"
  mkdir -p "$new_nix_apps"
  find -H -L "$genProfilePath/home-files/Applications" -name "*.app" -type d -print | while read -r app; do
    real_app=$(readlink -f "$app")
    app_name=$(basename "$app")
    target_app="$new_nix_apps/$app_name"
    echo "Alias '$real_app' to '$target_app'"
    ${pkgs.mkalias}/bin/mkalias "$real_app" "$target_app"
  done
'';

@AntonyBlakey
Copy link

run the script after "linkGeneration", otherwise home manager hasn't created it's own links, and you will be using the previous generation I think.

I have just discovered that you should replace $genProfilePath with $newGenPath in the find, and then you can leave the sequencing to be after "writeBarrier". This is more correct.

@nvmd
Copy link

nvmd commented Aug 8, 2024

I've tried @AntonyBlakey's solution. Using find to find “real” targets seems like a nice idea.

The minor issue I had is that it may also pick up and link *.apps that are not intended for an end-user. For example, with UTM it makes an alias for an internal QEMULauncher.app:

Alias '/nix/store/hsvx9x41c15py99bqnmdg29gkkrlb0fm-utm-4.5.3/Applications/UTM.app' to '/Users/<user>/Applications/Nix/UTM.app'
Alias '/nix/store/hsvx9x41c15py99bqnmdg29gkkrlb0fm-utm-4.5.3/Applications/UTM.app/Contents/XPCServices/QEMUHelper.xpc/Contents/MacOS/QEMULauncher.app' to '/Users/<user>/Applications/Nix/QEMULauncher.app'

Specifying the -maxdepth 1 argument for find (before -name) fixes that. However, I'm unsure if 1 is the universally correct value, though. Or if can it be reliably predetermined like that for all cases.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
pinned Prevent marking as stale system: darwin
Projects
None yet
Development

No branches or pull requests