Clojure CLI, the bash script, determines the user config directory looking at
the CLJ_CONFIG
env variable. If unset, fallbacks to $HOME/.clojure
, and
creates a default deps.edn
file if you don't have one:
# Determine user config directory
if [[ -n "$CLJ_CONFIG" ]]; then
config_dir="$CLJ_CONFIG"
elif [[ -n "$XDG_CONFIG_HOME" ]]; then
config_dir="$XDG_CONFIG_HOME/clojure"
else
config_dir="$HOME/.clojure"
fi
# If user config directory does not exist, create it
if [[ ! -d "$config_dir" ]]; then
mkdir -p "$config_dir"
fi
if [[ ! -e "$config_dir/deps.edn" ]]; then
cp "$install_dir/example-deps.edn" "$config_dir/deps.edn"
fi
if [ "$install_dir/tools.edn" -nt "$config_dir/tools/tools.edn" ]; then
mkdir -p "$config_dir/tools"
cp "$install_dir/tools.edn" "$config_dir/tools/tools.edn"
fi
Babashka mimics that behaviour:
(defn get-config-dir
"Retrieves configuration directory.
First tries `CLJ_CONFIG` env var, then `$XDG_CONFIG_HOME/clojure`, then ~/.clojure."
[]
(or (*getenv-fn* "CLJ_CONFIG")
(when-let [xdg-config-home (*getenv-fn* "XDG_CONFIG_HOME")]
(.getPath (io/file xdg-config-home "clojure")))
(.getPath (io/file (home-dir) ".clojure"))))
But there is a subtle difference, Babashka doesn't take the $HOME
value, but
(System/getProperty "user.home")
:
(defn- home-dir []
(if windows?
;; workaround for https://github.com/oracle/graal/issues/1630
(*getenv-fn* "userprofile")
(System/getProperty "user.home")))
Why is that difference? I guess because the Java version, at least in theory, is
system independent (it works on Windows). Or maybe because Clojure code base
also invokes (System/getProperty "user.home")
, and Babashka mimics Clojure:
(defn user-deps-path
"Use the same logic as clj to calculate the location of the user deps.edn.
Note that it's possible no file may exist at this location."
[]
(let [config-env (System/getenv "CLJ_CONFIG")
xdg-env (System/getenv "XDG_CONFIG_HOME")
home (System/getProperty "user.home")
config-dir (cond config-env config-env
xdg-env (str xdg-env File/separator "clojure")
:else (str home File/separator ".clojure"))]
(str config-dir File/separator "deps.edn")))
(defn ^:private local-repo-path
"Helper to form the path to the default local repo - use `@cached-local-repo` for
caching delayed value"
[]
(.getAbsolutePath (jio/file (System/getProperty "user.home") ".m2" "repository")))
And I guess Clojure does it because the call is system independent. Or maybe to mimic Maven, I don't really know.
Now the question is, why in Java System.getProperty("user.home");
doesn't
return the same value as $HOME
? And why does it return a different value in
Linux vs MacOS?
If we looking at JDK source code, we can see that getProperty
property is
implemented in C. On Linux, does a call to getpwuid()
, which gets the HOME
directory from /etc/passwd
:
/* user properties */
{
struct passwd *pwent = getpwuid(getuid());
sprops.user_name = pwent ? strdup(pwent->pw_name) : "?";
#ifdef MACOSX
setUserHome(&sprops);
#else
sprops.user_home = pwent ? strdup(pwent->pw_dir) : NULL;
#endif
if (sprops.user_home == NULL || sprops.user_home[0] == '\0' ||
sprops.user_home[1] == '\0') {
// If the OS supplied home directory is not defined or less than two characters long
// $HOME is the backup source for the home directory, if defined
char* user_home = getenv("HOME");
if ((user_home != NULL) && (user_home[0] != '\0')) {
sprops.user_home = user_home;
} else {
sprops.user_home = "?";
}
In the Nix sandbox environment, in the /etc/passwd
, the user home is set to
/build
, which is writable.
But on MacOS, JDK invokes NSHomeDirectory() to find the user home directory:
/*
* Method for fetching the user.home path and storing it in the property list.
* For signed .apps running in the Mac App Sandbox, user.home is set to the
* app's sandbox container.
*/
void setUserHome(java_props_t *sprops) {
if (sprops == NULL) { return; }
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
sprops->user_home = createUTF8CString((CFStringRef)NSHomeDirectory());
[pool drain];
}
Which returns /var/empty
in the Nix sandbox, and the sandbox user doesn't have
access to that directory. And Babashka fails to create the default deps.edn
file.
One last thing to mention, GraalVM System.getProperty
implementation is
different from OpenJDK, but as you can see, GraalVM mimics JDK:
/*
* Initialization code is adapted from the JDK native code that initializes the system
* properties, as found in src/solaris/native/java/lang/java_props_md.c
*/
@Override
protected String userNameValue() {
Pwd.passwd pwent = Pwd.getpwuid(Unistd.getuid());
return pwent.isNull() ? "?" : CTypeConversion.toJavaString(pwent.pw_name());
}
@Override
protected String userHomeValue() {
Pwd.passwd pwent = Pwd.getpwuid(Unistd.getuid());
return pwent.isNull() ? "?" : CTypeConversion.toJavaString(pwent.pw_dir());
}