All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.tencent.process.EnvVars Maven / Gradle / Ivy

/*
 * The MIT License
 *
 * Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Red Hat, Inc.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

package com.tencent.process;

import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.UUID;
import java.util.logging.Logger;

// copy from jenkins 96a66619b55b3b78b86817798fe36e58b2798cd4

/**
 * Environment variables.
 *
 * 

* 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 insensitive 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 {@code GetEnvironmentVariable} 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. * *

* So for a consistent cross platform behavior, it creates the least confusion to make the table * case insensitive but case preserving. * *

* In Jenkins, often we need to build up "environment variable overrides" * on the controller, then to execute the process on agents. This causes a problem * when working with variables like {@code PATH}. So to make this work, * we introduce a special convention {@code PATH+FOO} — all entries * that starts with {@code PATH+} are merged and prepended to the inherited * {@code PATH} variable, on the process where a new process is executed. * * @author Kohsuke Kawaguchi */ public class EnvVars extends TreeMap { private static final long serialVersionUID = 4320331661987259022L; private static Logger LOGGER = Logger.getLogger(EnvVars.class.getName()); /** * 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 know * how to merge variables properly. * *

* So this property remembers that information. */ private Platform platform; /** * Gets the platform for which these env vars targeted. * @since 2.144 * @return The platform. */ public Platform getPlatform() { return platform; } /** * Sets the platform for which these env vars target. * @since 2.144 * @param platform the platform to set. */ public void setPlatform(Platform platform) { this.platform = platform; } public EnvVars() { super(String.CASE_INSENSITIVE_ORDER); } public EnvVars(Map m) { this(); putAll(m); // because of the backward compatibility, some parts of Jenkins passes // EnvVars as Map so downcasting is safer. if (m instanceof EnvVars) { EnvVars lhs = (EnvVars) m; this.platform = lhs.platform; } } @SuppressWarnings("CopyConstructorMissesField") // does not set #platform, see its Javadoc 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 {@code "key","value","key","value"...} */ 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. * *

* Handles {@code PATH+XYZ} notation. */ public void override(String key, String value) { if (value == null || value.isEmpty()) { 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 agent that can have different path separator // than the controller, 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 all) { for (Map.Entry e : all.entrySet()) { override(e.getKey(), e.getValue()); } return this; } /** * Calculates the order to override variables. * * Sort variables with topological sort with their reference graph. * * This is package accessible for testing purpose. */ static class OverrideOrderCalculator { /** * Extract variables referred directly from a variable. */ private static class TraceResolver implements VariableResolver { private final Comparator comparator; public Set referredVariables; TraceResolver(Comparator comparator) { this.comparator = comparator; clear(); } public void clear() { referredVariables = new TreeSet<>(comparator); } @Override public String resolve(String name) { referredVariables.add(name); return ""; } } private static class VariableReferenceSorter extends CyclicGraphDetector { // map from a variable to a set of variables that variable refers. private final Map> refereeSetMap; VariableReferenceSorter(Map> refereeSetMap) { this.refereeSetMap = refereeSetMap; } @Override protected Iterable getEdges(String n) { // return variables referred from the variable. if (!refereeSetMap.containsKey(n)) { // there is a case a non-existing variable is referred... return Collections.emptySet(); } return refereeSetMap.get(n); } } private final Comparator comparator; private final EnvVars target; private final Map overrides; private Map> refereeSetMap; private List orderedVariableNames; OverrideOrderCalculator(EnvVars target, Map overrides) { comparator = target.comparator(); this.target = target; this.overrides = overrides; scan(); } public List getOrderedVariableNames() { return orderedVariableNames; } // Cut the reference to the variable in a cycle. private void cutCycleAt(String referee, List cycle) { // cycle contains variables in referrer-to-referee order. // This should not be negative, for the first and last one is same. int refererIndex = cycle.lastIndexOf(referee) - 1; assert refererIndex >= 0; String referrer = cycle.get(refererIndex); boolean removed = refereeSetMap.get(referrer).remove(referee); assert removed; LOGGER.warning(String.format("Cyclic reference detected: %s", String.join(" -> ", cycle))); LOGGER.warning(String.format("Cut the reference %s -> %s", referrer, referee)); } // Cut the variable reference in a cycle. private void cutCycle(List cycle) { // if an existing variable is contained in that cycle, // cut the cycle with that variable: // existing: // PATH=/usr/bin // overriding: // PATH1=/usr/local/bin:${PATH} // PATH=/opt/something/bin:${PATH1} // then consider reference PATH1 -> PATH can be ignored. for (String referee : cycle) { if (target.containsKey(referee)) { cutCycleAt(referee, cycle); return; } } // if not, cut the reference to the first one. cutCycleAt(cycle.get(0), cycle); } /** * Scan all variables and list all referring variables. */ public void scan() { refereeSetMap = new TreeMap<>(comparator); List extendingVariableNames = new ArrayList<>(); TraceResolver resolver = new TraceResolver(comparator); for (Map.Entry entry : overrides.entrySet()) { if (entry.getKey().indexOf('+') > 0) { // XYZ+AAA variables should be always processed in last. extendingVariableNames.add(entry.getKey()); continue; } resolver.clear(); Util.replaceMacro(entry.getValue(), resolver); // Variables directly referred from the current scanning variable. Set refereeSet = resolver.referredVariables; // Ignore self reference. refereeSet.remove(entry.getKey()); refereeSetMap.put(entry.getKey(), refereeSet); } VariableReferenceSorter sorter; while (true) { sorter = new VariableReferenceSorter(refereeSetMap); try { sorter.run(refereeSetMap.keySet()); } catch (CyclicGraphDetector.CycleDetectedException e) { // cyclic reference found. // cut the cycle and retry. @SuppressWarnings("unchecked") List cycle = e.cycle; cutCycle(cycle); continue; } break; } // When A refers B, the last appearance of B always comes after // the last appearance of A. List reversedDuplicatedOrder = new ArrayList<>(sorter.getSorted()); Collections.reverse(reversedDuplicatedOrder); orderedVariableNames = new ArrayList<>(overrides.size()); for (String key : reversedDuplicatedOrder) { if (overrides.containsKey(key) && !orderedVariableNames.contains(key)) { orderedVariableNames.add(key); } } Collections.reverse(orderedVariableNames); orderedVariableNames.addAll(extendingVariableNames); } } /** * Overrides all values in the map by the given map. Expressions in values will be expanded. * See {@link #override(String, String)}. * @return {@code this} */ public EnvVars overrideExpandingAll(Map all) { for (String key : new OverrideOrderCalculator(this, all).getOrderedVariableNames()) { override(key, expand(all.get(key))); } return this; } /** * Resolves environment variables against each other. */ public static void resolve(Map env) { for (Map.Entry entry : env.entrySet()) { entry.setValue(Util.replaceMacro(entry.getValue(), env)); } } /** * Convenience message * @since 1.485 **/ public String get(String key, String defaultValue) { String v = get(key); if (v == null) v = defaultValue; return v; } @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); } /** * Add a key/value but only if the value is not-null. Otherwise no-op. * @since 1.556 */ public void putIfNotNull(String key, String value) { if (value != null) put(key, value); } /** * Add entire map but filter null values out. * @since 2.214 */ public void putAllNonNull(Map map) { map.forEach(this::putIfNotNull); } /** * 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()); } // /** // * 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 extends MasterToSlaveCallable { // @Override // public EnvVars call() { // return new EnvVars(EnvVars.masterEnvVars); // } // // private static final long serialVersionUID = 1L; // } // // /** // * Environmental variables that we've inherited. // * // *

// * Despite what the name might imply, this is the environment variable // * of the current JVM process. And therefore, it is the Jenkins controller's // * environment variables only when you access this from the controller. // * // *

// * If you access this field from agents, then this is the environment // * variable of the agent. // */ // public static final Map 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; // } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy