diff --git a/home-manager/default.nix b/home-manager/default.nix
index 943d61e81..394f0e7f2 100644
--- a/home-manager/default.nix
+++ b/home-manager/default.nix
@@ -15,9 +15,7 @@ in
pkgs.stdenv.mkDerivation {
name = "home-manager";
- phases = [ "installPhase" ];
-
- installPhase = ''
+ buildCommand = ''
install -v -D -m755 ${./home-manager} $out/bin/home-manager
substituteInPlace $out/bin/home-manager \
diff --git a/modules/files.nix b/modules/files.nix
index e294f97f0..c61560c5b 100644
--- a/modules/files.nix
+++ b/modules/files.nix
@@ -6,8 +6,17 @@ with import ./lib/dag.nix { inherit lib; };
let
cfg = config.home.file;
+
homeDirectory = config.home.homeDirectory;
+ fileType = (import lib/file-type.nix {
+ inherit homeDirectory lib pkgs;
+ }).fileType;
+
+ # A symbolic link whose target path matches this pattern will be
+ # considered part of a Home Manager generation.
+ homeFilePattern = "${builtins.storeDir}/*-home-manager-files/*";
+
in
{
@@ -15,50 +24,7 @@ in
home.file = mkOption {
description = "Attribute set of files to link into the user home.";
default = {};
- type = types.loaOf (types.submodule (
- { name, config, ... }: {
- options = {
- target = mkOption {
- type = types.str;
- apply = removePrefix (homeDirectory + "/");
- description = ''
- Path to target file relative to HOME.
- '';
- };
-
- text = mkOption {
- default = null;
- type = types.nullOr types.lines;
- description = "Text of the file.";
- };
-
- source = mkOption {
- type = types.path;
- description = ''
- Path of the source file. The file name must not start
- with a period since Nix will not allow such names in
- the Nix store.
-
- This may refer to a directory.
- '';
- };
-
- mode = mkOption {
- type = types.str;
- default = "444";
- description = "The permissions to apply to the file.";
- };
- };
-
- config = {
- target = mkDefault name;
- source = mkIf (config.text != null) (
- let name' = "user-etc-" + baseNameOf name;
- in mkDefault (pkgs.writeText name' config.text)
- );
- };
- })
- );
+ type = fileType "HOME" homeDirectory;
};
home-files = mkOption {
@@ -83,11 +49,23 @@ in
})
];
+ warnings =
+ let
+ badFiles =
+ map (f: f.target)
+ (filter (f: f.mode != null)
+ (attrValues cfg));
+ badFilesStr = toString badFiles;
+ in
+ mkIf (badFiles != []) [
+ ("The 'mode' field is deprecated for 'home.file', "
+ + "use 'executable' instead: ${badFilesStr}")
+ ];
+
# This verifies that the links we are about to create will not
# overwrite an existing file.
home.activation.checkLinkTargets = dagEntryBefore ["writeBoundary"] (
let
- pattern = "-home-manager-files/";
check = pkgs.writeText "check" ''
. ${./lib-bash/color-echo.sh}
@@ -97,7 +75,7 @@ in
relativePath="''${sourcePath#$newGenFiles/}"
targetPath="$HOME/$relativePath"
if [[ -e "$targetPath" \
- && ! "$(readlink "$targetPath")" =~ "${pattern}" ]] ; then
+ && ! "$(readlink "$targetPath")" == ${homeFilePattern} ]] ; then
errorEcho "Existing file '$targetPath' is in the way"
collision=1
fi
@@ -123,8 +101,6 @@ in
home.activation.linkGeneration = dagEntryAfter ["writeBoundary"] (
let
- pattern = "-home-manager-files/";
-
link = pkgs.writeText "link" ''
newGenFiles="$1"
shift
@@ -147,7 +123,7 @@ in
targetPath="$HOME/$relativePath"
if [[ -e "$newGenFiles/$relativePath" ]] ; then
$VERBOSE_ECHO "Checking $targetPath: exists"
- elif [[ ! "$(readlink "$targetPath")" =~ "${pattern}" ]] ; then
+ elif [[ ! "$(readlink "$targetPath")" == ${homeFilePattern} ]] ; then
warnEcho "Path '$targetPath' not link into Home Manager generation. Skipping delete."
else
$VERBOSE_ECHO "Checking $targetPath: gone (deleting)"
@@ -210,30 +186,57 @@ in
home-files = pkgs.stdenv.mkDerivation {
name = "home-manager-files";
- phases = [ "installPhase" ];
+ # Symlink directories and files that have the right execute bit.
+ # Copy files that need their execute bit changed or use the
+ # deprecated 'mode' option.
+ buildCommand = ''
+ mkdir -p $out
- installPhase =
- "mkdir -p $out\n" +
- concatStringsSep "\n" (
- mapAttrsToList (n: v:
- ''
- target="$(realpath -m "$out/${v.target}")"
+ function insertFile() {
+ local source="$1"
+ local relTarget="$2"
+ local executable="$3"
+ local mode="$4" # For backwards compatibility.
- # Target file must be within $HOME.
- if [[ ! "$target" =~ "$out" ]] ; then
- echo "Error installing file '${v.target}' outside \$HOME" >&2
- exit 1
- fi
+ # Figure out the real absolute path to the target.
+ local target
+ target="$(realpath -m "$out/$relTarget")"
- if [ -d "${v.source}" ]; then
- mkdir -p "$(dirname "$out/${v.target}")"
- ln -s "${v.source}" "$target"
+ # Target path must be within $HOME.
+ if [[ ! $target =~ $out ]] ; then
+ echo "Error installing file '$relTarget' outside \$HOME" >&2
+ exit 1
+ fi
+
+ mkdir -p "$(dirname "$target")"
+ if [[ -d $source ]]; then
+ ln -s "$source" "$target"
+ elif [[ $mode ]]; then
+ install -m "$mode" "$source" "$target"
+ else
+ [[ -x $source ]] && isExecutable=1 || isExecutable=""
+ if [[ $executable == symlink || $isExecutable == $executable ]]; then
+ ln -s "$source" "$target"
+ else
+ cp "$source" "$target"
+ if [[ $executable ]]; then
+ chmod +x "$target"
else
- install -D -m${v.mode} "${v.source}" "$target"
+ chmod -x "$target"
fi
- ''
- ) cfg
- );
+ fi
+ fi
+ }
+ '' + concatStrings (
+ mapAttrsToList (n: v: ''
+ insertFile "${v.source}" \
+ "${v.target}" \
+ "${if v.executable == null
+ then "symlink"
+ else builtins.toString v.executable}" \
+ "${builtins.toString v.mode}"
+ '') cfg
+ );
};
};
}
diff --git a/modules/home-environment.nix b/modules/home-environment.nix
index 892c20cc8..323294a37 100644
--- a/modules/home-environment.nix
+++ b/modules/home-environment.nix
@@ -265,7 +265,7 @@ in
pkgs.nix
];
- sf = pkgs.writeText "activation-script" ''
+ activationScript = pkgs.writeScript "activation-script" ''
#!${pkgs.stdenv.shell}
set -eu
@@ -283,10 +283,10 @@ in
pkgs.stdenv.mkDerivation {
name = "home-manager-generation";
- phases = [ "installPhase" ];
+ buildCommand = ''
+ mkdir -p $out
- installPhase = ''
- install -D -m755 ${sf} $out/activate
+ cp ${activationScript} $out/activate
substituteInPlace $out/activate \
--subst-var-by GENERATION_DIR $out
diff --git a/modules/lib/file-type.nix b/modules/lib/file-type.nix
new file mode 100644
index 000000000..ebdcb7741
--- /dev/null
+++ b/modules/lib/file-type.nix
@@ -0,0 +1,105 @@
+{ homeDirectory, lib, pkgs }:
+
+with lib;
+
+let
+
+ # Figures out a valid Nix store name for the given path.
+ storeFileName = path:
+ let
+ # All characters that are considered safe. Note "-" is not
+ # included to avoid "-" followed by digit being interpreted as a
+ # version.
+ safeChars =
+ [ "+" "." "_" "?" "=" ]
+ ++ lowerChars
+ ++ upperChars
+ ++ stringToCharacters "0123456789";
+
+ empties = l: genList (x: "") (length l);
+
+ unsafeInName = stringToCharacters (
+ replaceStrings safeChars (empties safeChars) path
+ );
+
+ safeName = replaceStrings unsafeInName (empties unsafeInName) path;
+ in
+ "home_file_" + safeName;
+
+in
+
+{
+ # Constructs a type suitable for a `home.file` like option. The
+ # target path may be either absolute or relative, in which case it
+ # is relative the `basePath` argument (which itself must be an
+ # absolute path).
+ #
+ # Arguments:
+ # - basePathDesc docbook compatible description of the base path
+ # - basePath the file base path
+ fileType = basePathDesc: basePath: types.loaOf (types.submodule (
+ { name, config, ... }: {
+ options = {
+ target = mkOption {
+ type = types.str;
+ apply = p:
+ let
+ absPath = if hasPrefix "/" p then p else "${basePath}/${p}";
+ in
+ removePrefix (homeDirectory + "/") absPath;
+ description = ''
+ Path to target file relative to ${basePathDesc}.
+ '';
+ };
+
+ text = mkOption {
+ default = null;
+ type = types.nullOr types.lines;
+ description = "Text of the file.";
+ };
+
+ source = mkOption {
+ type = types.path;
+ description = ''
+ Path of the source file. The file name must not start
+ with a period since Nix will not allow such names in
+ the Nix store.
+
+ This may refer to a directory.
+ '';
+ };
+
+ mode = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ The permissions to apply to the file.
+
+ DEPRECATED: use home.file.<name?>.executable
+ instead.
+ '';
+ };
+
+ executable = mkOption {
+ type = types.nullOr types.bool;
+ default = null;
+ description = ''
+ Set the execute bit. If null, defaults to the mode
+ of the source file or to false
+ for files created through the text option.
+ '';
+ };
+ };
+
+ config = {
+ target = mkDefault name;
+ source = mkIf (config.text != null) (
+ mkDefault (pkgs.writeTextFile {
+ inherit (config) executable text;
+ name = storeFileName name;
+ })
+ );
+ };
+ }
+ ));
+}
diff --git a/modules/misc/news.nix b/modules/misc/news.nix
index ac969f160..44b9d7f59 100644
--- a/modules/misc/news.nix
+++ b/modules/misc/news.nix
@@ -425,6 +425,23 @@ in
A new window manager module is available: 'xsession.windowManager.i3'.
'';
}
+
+ {
+ time = "2017-11-06T13:23:17+00:00";
+ condition = any (f: f.mode != null) (attrValues config.home.file);
+ message = ''
+ The
+
+ home.file..mode
+
+ option is now deprecated. Please use
+
+ home.file..executable
+
+ instead. The 'mode' option will be completely removed
+ December 6, 2017.
+ '';
+ }
];
};
}
diff --git a/modules/misc/xdg.nix b/modules/misc/xdg.nix
index 8dcf1fba4..8f47ac5dc 100644
--- a/modules/misc/xdg.nix
+++ b/modules/misc/xdg.nix
@@ -6,55 +6,10 @@ let
cfg = config.xdg;
- fileType = basePathDesc: basePath: (types.loaOf (types.submodule (
- { name, config, ... }: {
- options = {
- target = mkOption {
- type = types.str;
- apply = p: "${basePath}/${p}";
- description = ''
- Path to target file relative to ${basePathDesc}.
- '';
- };
-
- text = mkOption {
- default = null;
- type = types.nullOr types.lines;
- description = "Text of the file.";
- };
-
- source = mkOption {
- type = types.path;
- description = ''
- Path of the source file. The file name must not start
- with a period since Nix will not allow such names in
- the Nix store.
-
- This may refer to a directory.
- '';
- };
-
- executable = mkOption {
- type = types.bool;
- default = false;
- description = "Whether the file should be executable.";
- };
- };
-
- config = {
- target = mkDefault name;
- source = mkIf (config.text != null) (
- let
- file = pkgs.writeTextFile {
- inherit (config) text executable;
- name = "user-etc-" + baseNameOf name;
- };
- in
- mkDefault file
- );
- };
- }
- )));
+ fileType = (import ../lib/file-type.nix {
+ inherit (config.home) homeDirectory;
+ inherit lib pkgs;
+ }).fileType;
defaultCacheHome = "${config.home.homeDirectory}/.cache";
defaultConfigHome = "${config.home.homeDirectory}/.config";
@@ -81,7 +36,7 @@ in
};
configFile = mkOption {
- type = fileType "xdg.configHome" cfg.configHome;
+ type = fileType "xdg.configHome" cfg.configHome;
default = {};
description = ''
Attribute set of files to link into the user's XDG
@@ -126,13 +81,7 @@ in
})
{
- home.file =
- let
- f = n: v: {
- inherit (v) source target;
- mode = if v.executable then "777" else "444";
- };
- in mapAttrsToList f cfg.configFile;
+ home.file = cfg.configFile;
}
];
}
diff --git a/modules/xsession.nix b/modules/xsession.nix
index d51bb6b48..9f06e76be 100644
--- a/modules/xsession.nix
+++ b/modules/xsession.nix
@@ -105,7 +105,7 @@ in
'';
home.file.".xsession" = {
- mode = "555";
+ executable = true;
text = ''
if [[ ! -v HM_XPROFILE_SOURCED ]]; then
. ~/.xprofile