Programming
Setting environment variables via launchdconf no longer works in OS X YosemiteEl CapitanmacOS SierraMojave
Navigating the ever-evolving landscape of macOS can present unique challenges, especially when fundamental system behaviors change. For many developers and system administrators, a common point of frustration arose with OS X Yosemite, continuing through El Capitan, macOS Sierra, and Mojave: the traditional method of setting environment variables via launchd.conf simply stopped working as expected. This shift wasn’t a bug, but rather a deliberate security enhancement by Apple, designed to tighten the operating system’s integrity and limit the scope of system-wide modifications. Understanding this change is crucial for modern macOS development and system administration.
The deprecation of launchd.conf as a reliable mechanism for global environment variables forced a re-evaluation of how applications and processes inherit their execution context. Historically, a simple entry in this file would propagate variables to all processes managed by launchd. However, with the introduction of System Integrity Protection (SIP) and other security measures, this method became increasingly ineffective. This article will delve into why the old approach failed, and more importantly, provide robust, modern solutions for setting environment variables on macOS versions Yosemite and beyond, ensuring your applications and scripts behave as intended.
Why launchd.conf Became Obsolete for Environment Variables
The cessation of launchd.conf’s functionality for global environment variables wasn’t an arbitrary decision, but a consequence of Apple’s ongoing commitment to system security, specifically with the introduction of System Integrity Protection (SIP) in OS X El Capitan. SIP, also known as “rootless” mode, restricts the root user’s ability to write to certain system directories and files. This protective layer ensures that critical system files, including those managed by launchd, remain untampered with, preventing malware and malicious actors from gaining deep system access.
Before SIP, launchd.conf was a central configuration file read by launchd (the system’s service management daemon) during system startup. Any environment variables defined within it would theoretically be inherited by all processes launched by launchd. However, this global reach also presented a security vulnerability. If an attacker could modify launchd.conf, they could inject malicious paths or libraries into virtually every process, compromising the entire system. SIP mitigates this risk by making /etc/launchd.conf a protected file, meaning even with root privileges, direct modification or parsing by launchd for environment variables is severely restricted or ignored altogether.
This fundamental change means that while the file might still exist, its utility for universally setting environment variables across all processes has been effectively nullified for modern macOS versions like Yosemite, El Capitan, Sierra, Mojave, Catalina, Big Sur, Monterey, Ventura, and Sonoma. Developers and system administrators must now adopt more granular and secure methods, tailored to specific contexts—whether for terminal sessions, GUI applications, or background services—to ensure their environment variables are correctly recognized and applied.
Modern Approaches for Global and Persistent Environment Variables
Given that setting environment variables via launchd.conf no longer works in OS X Yosemite/El Capitan/macOS Sierra/Mojave and later, new strategies are essential. The modern approach involves distinguishing between variables needed for graphical applications (GUI apps) and those primarily for terminal sessions, as well as considering their persistence.
For GUI applications and system-wide processes, the most robust method for setting persistent environment variables is through launchd property list (.plist) files. Specifically, variables can be defined within user-specific launchd agents. These agents are loaded when a user logs in, and they can be configured to set environment variables that will then be available to all applications launched within that user’s session.
Featured Snippet: To set persistent environment variables for all GUI applications and user-level processes on macOS (Yosemite and later), create a .plist file in ~/Library/LaunchAgents/. This file should define an environment variable dictionary within its properties, which launchd will load upon user login, making the variables accessible to all subsequently launched applications in that user’s session.
Creating a .plist file in ~/Library/LaunchAgents/ is the recommended way to ensure variables are available to GUI applications. Here’s how you can do it:
- Open a text editor and create a new file named something like
com.yourusername.environment.plist. - Add the following XML content, replacing
YOUR_VARIABLEandyour_valuewith your actual variable name and its value: ```Label com.yourusername.environment ProgramArguments /bin/launchctl setenv YOUR_VARIABLE your_value RunAtLoad - Save this file in
~/Library/LaunchAgents/. - Load the agent using the command:
launchctl load ~/Library/LaunchAgents/com.yourusername.environment.plist. For changes to take effect system-wide (or for a new user session), a logout/login or reboot might be required.
This method ensures that when your user session starts, launchctl setenv is executed, making the variables available for GUI applications and other processes within your user environment. It’s a reliable alternative since launchd.conf environment variables are no longer globally effective.
Managing Environment Variables for Terminal Sessions
While the .plist method effectively handles environment variables for GUI applications and the broader user session, terminal-specific environment variables are typically managed differently. Command-line users often require variables for specific tools, paths, or configurations that are primarily relevant within the shell environment. The most common and recommended approach for terminal sessions involves configuring shell profile scripts.
macOS defaults to Zsh (Z Shell) for new user accounts since macOS Catalina, while older versions primarily used Bash (Bourne Again Shell). Both shells offer specific configuration files that are sourced (executed) when a new terminal session starts. These files allow Question & Answer :
It looks like the launchd.conf does not load my environment variable anymore. Has anyone else noticed that?
Is there another solution to permanently set environment variables?
Create an environment.plist file in ~/Library/LaunchAgents/ with this content:
<?xml version="1.0" encoding="UTF-8"?> <plist version="1.0"> <dict> <key>Label</key> <string>my.startup</string> <key>ProgramArguments</key> <array> <string>sh</string> <string>-c</string> <string> launchctl setenv PRODUCTS_PATH /Users/mortimer/Projects/my_products launchctl setenv ANDROID_NDK_HOME /Applications/android-ndk launchctl setenv PATH $PATH:/Applications/gradle/bin </string> </array> <key>RunAtLoad</key> <true/> </dict> </plist>
You can add many launchctl commands inside the <string></string> block.
The plist will activate after system reboot. You can also use launchctl load ~/Library/LaunchAgents/environment.plist to launch it immediately.
[Edit]
The same solution works in El Capitan too.
Xcode 7.0+ doesn’t evaluate environment variables by default. The old behaviour can be enabled with this command:
defaults write com.apple.dt.Xcode UseSanitizedBuildSystemEnvironment -bool NO
[Edit]
There a couple of situations where this doesn’t quite work. If the computer is restarted and “Reopen windows when logging back in” is selected, the reopened windows may not see the variables (Perhaps they are opened before the agent is run). Also, if you log in via ssh, the variables will not be set (so you’ll need to set them in ~/.bash_profile). Finally, this doesn’t seem to work for PATH on El Capitan and Sierra. That needs to be set via ’launchctl config user path …’ and in /etc/paths.