/*******************************************************************************
*
* Copyright (c) 2004-2009 Oracle Corporation.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
*
* Kohsuke Kawaguchi, Red Hat, Inc.
*
*
*******************************************************************************/
package hudson;
import hudson.remoting.Callable;
import hudson.remoting.VirtualChannel;
import hudson.util.CaseInsensitiveComparator;
import java.io.File;
import java.io.IOException;
import java.util.Map;
import java.util.TreeMap;
import java.util.Arrays;
import java.util.UUID;
import org.apache.commons.lang3.StringUtils;
/**
* Environment variables.
*
* <p> While all the platforms I tested (Linux 2.6, Solaris, and Windows XP)
* have the case sensitive environment variable table, Windows batch script
* handles environment variable in the case preserving but case
* <b>insensitive</b> way (that is, cmd.exe can get both FOO and foo as
* environment variables when it's launched, and the "set" command will display
* it accordingly, but "echo %foo%" results in echoing the value of "FOO", not
* "foo" — this is presumably caused by the behavior of the underlying
* Win32 API <tt>GetEnvironmentVariable</tt> acting in case insensitive way.)
* Windows users are also used to write environment variable case-insensitively
* (like %Path% vs %PATH%), and you can see many documents on the web that
* claims Windows environment variables are case insensitive.
*
* <p> So for a consistent cross platform behavior, it creates the least
* confusion to make the table case insensitive but case preserving.
*
* <p> In Hudson, often we need to build up "environment variable overrides" on
* master, then to execute the process on slaves. This causes a problem when
* working with variables like <tt>PATH</tt>. So to make this work, we introduce
* a special convention <tt>PATH+FOO</tt> — all entries that starts with
* <tt>PATH+</tt> are merged and prepended to the inherited <tt>PATH</tt>
* variable, on the process where a new process is executed.
*
* @author Kohsuke Kawaguchi
*/
public class EnvVars extends TreeMap<String, String> {
/**
* Environment property which will be exposed. Value - name of currently
* logged user.
*/
public static final String HUDSON_USER_ENV_KEY = "HUDSON_USER";
/**
* If this {@link EnvVars} object represents the whole environment variable
* set, not just a partial list used for overriding later, then we need to
* know the platform for which this env vars are targeted for, or else we
* won't konw how to merge variables properly.
*
* <p> So this property remembers that information.
*/
private Platform platform;
public EnvVars() {
super(CaseInsensitiveComparator.INSTANCE);
}
public EnvVars(Map<String, String> m) {
this();
putAll(m);
// because of the backward compatibility, some parts of Hudson passes
// EnvVars as Map<String,String> so downcasting is safer.
if (m instanceof EnvVars) {
EnvVars lhs = (EnvVars) m;
this.platform = lhs.platform;
}
}
public EnvVars(EnvVars m) {
// this constructor is so that in future we can get rid of the downcasting.
this((Map) m);
}
/**
* Builds an environment variables from an array of the form
* <tt>"key","value","key","value"...</tt>
*/
public EnvVars(String... keyValuePairs) {
this();
if (keyValuePairs.length % 2 != 0) {
throw new IllegalArgumentException(Arrays.asList(keyValuePairs).toString());
}
for (int i = 0; i < keyValuePairs.length; i += 2) {
put(keyValuePairs[i], keyValuePairs[i + 1]);
}
}
/**
* Overrides the current entry by the given entry.
*
* <p> Handles <tt>PATH+XYZ</tt> notation.
*/
public void override(String key, String value) {
if (value == null || value.length() == 0) {
remove(key);
return;
}
int idx = key.indexOf('+');
if (idx > 0) {
String realKey = key.substring(0, idx);
String v = get(realKey);
if (v == null) {
v = value;
} else {
// we might be handling environment variables for a slave that can have different path separator
// than the master, so the following is an attempt to get it right.
// it's still more error prone that I'd like.
char ch = platform == null ? File.pathSeparatorChar : platform.pathSeparator;
v = value + ch + v;
}
put(realKey, v);
return;
}
put(key, value);
}
/**
* Overrides all values in the map by the given map. See
* {@link #override(String, String)}.
*
* @return this
*/
public EnvVars overrideAll(Map<String, String> all) {
for (Map.Entry<String, String> e : all.entrySet()) {
override(e.getKey(), e.getValue());
}
return this;
}
/**
* Resolves environment variables against each other.
*/
public static void resolve(Map<String, String> env) {
for (Map.Entry<String, String> entry : env.entrySet()) {
entry.setValue(Util.replaceMacro(entry.getValue(), env));
}
}
@Override
public String put(String key, String value) {
if (value == null) {
throw new IllegalArgumentException("Null value not allowed as an environment variable: " + key);
}
return super.put(key, value);
}
/**
* Takes a string that looks like "a=b" and adds that to this map.
*/
public void addLine(String line) {
int sep = line.indexOf('=');
if (sep > 0) {
put(line.substring(0, sep), line.substring(sep + 1));
}
}
/**
* Expands the variables in the given string by using environment variables
* represented in 'this'.
*/
public String expand(String s) {
return Util.replaceMacro(s, this);
}
/**
* Creates a magic cookie that can be used as the model environment variable
* when we later kill the processes.
*/
public static EnvVars createCookie() {
return new EnvVars("HUDSON_COOKIE", UUID.randomUUID().toString());
}
/**
* Removes HUDSON_USER from EnvVars.
*/
public static void clearHudsonUserEnvVar() {
masterEnvVars.remove(HUDSON_USER_ENV_KEY);
}
/**
* Sets current logged user to env vars.
*
* @param userName logged user.
*/
public static void setHudsonUserEnvVar(String userName) {
if (!StringUtils.isEmpty(userName)) {
masterEnvVars.put(HUDSON_USER_ENV_KEY, userName);
}
}
/**
* Returns HUDSON_USER property value.
*
* @return property value or null if property is absent.
*/
public static String getHudsonUserEnvValue() {
return masterEnvVars.get(HUDSON_USER_ENV_KEY);
}
/**
* Obtains the environment variables of a remote peer.
*
* @param channel Can be null, in which case the map indicating "N/A" will
* be returned.
* @return A fresh copy that can be owned and modified by the caller.
*/
public static EnvVars getRemote(VirtualChannel channel) throws IOException, InterruptedException {
if (channel == null) {
return new EnvVars("N/A", "N/A");
}
return channel.call(new GetEnvVars());
}
private static final class GetEnvVars implements Callable<EnvVars, RuntimeException> {
public EnvVars call() {
return new EnvVars(EnvVars.masterEnvVars);
}
private static final long serialVersionUID = 1L;
}
/**
* Environmental variables that we've inherited.
*
* <p> Despite what the name might imply, this is the environment variable
* of the current JVM process. And therefore, it is Hudson master's
* environment variables only when you access this from the master.
*
* <p> If you access this field from slaves, then this is the environment
* variable of the slave agent.
*/
public static final Map<String, String> masterEnvVars = initMaster();
private static EnvVars initMaster() {
EnvVars vars = new EnvVars(System.getenv());
vars.platform = Platform.current();
if (Main.isUnitTest || Main.isDevelopmentMode) // if unit test is launched with maven debug switch,
// we need to prevent forked Maven processes from seeing it, or else
// they'll hang
{
vars.remove("MAVEN_OPTS");
}
return vars;
}
}