| // Copyright 2014 The Bazel Authors. All rights reserved. | |
| // | |
| // Licensed under the Apache License, Version 2.0 (the "License"); | |
| // you may not use this file except in compliance with the License. | |
| // You may obtain a copy of the License at | |
| // | |
| // http://www.apache.org/licenses/LICENSE-2.0 | |
| // | |
| // Unless required by applicable law or agreed to in writing, software | |
| // distributed under the License is distributed on an "AS IS" BASIS, | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| // See the License for the specific language governing permissions and | |
| // limitations under the License. | |
| package com.google.devtools.build.lib.util; | |
| import static java.util.Map.Entry.comparingByKey; | |
| import com.google.common.collect.Ordering; | |
| import java.util.Comparator; | |
| import java.util.List; | |
| import java.util.Map; | |
| import javax.annotation.Nullable; | |
| /** Utils for emitting scripts cross-platform. */ | |
| public class ScriptUtil { | |
| private static final ScriptEmitter SCRIPT_EMITTER = | |
| OS.getCurrent() == OS.WINDOWS ? new WindowsScriptEmitter() : new LinuxScriptEmitter(); | |
| static void emitBeginIsolate(StringBuilder message) { | |
| SCRIPT_EMITTER.emitBeginIsolate(message); | |
| } | |
| static void emitEndIsolate(StringBuilder message) { | |
| SCRIPT_EMITTER.emitEndIsolate(message); | |
| } | |
| /** Emits command to change directories. */ | |
| public static void emitChangeDirectory(StringBuilder message, String cwd) { | |
| SCRIPT_EMITTER.emitChangeDirectory(message, cwd); | |
| } | |
| /** Emits the "env" prefix, setting and unsetting the provided environment variables. */ | |
| public static void emitEnvPrefix( | |
| StringBuilder message, | |
| boolean ignoreEnvironment, | |
| Map<String, String> setEnv, | |
| @Nullable List<String> unsetEnv) { | |
| SCRIPT_EMITTER.emitEnvPrefix(message, ignoreEnvironment); | |
| if (unsetEnv != null) { | |
| for (String name : Ordering.natural().sortedCopy(unsetEnv)) { | |
| message.append(" "); | |
| SCRIPT_EMITTER.emitUnsetEnvVar(message, name); | |
| } | |
| } | |
| Comparator<Map.Entry<String, String>> mapEntryComparator = comparingByKey(); | |
| for (Map.Entry<String, String> entry : | |
| Ordering.from(mapEntryComparator).sortedCopy(setEnv.entrySet())) { | |
| message.append(" "); | |
| SCRIPT_EMITTER.emitSetEnvVar(message, entry.getKey(), entry.getValue()); | |
| } | |
| } | |
| /** | |
| * Formats the command element and adds it to the message. | |
| * | |
| * @param isBinary is true if the `commandElement` is the binary to be executed | |
| */ | |
| public static void emitCommandElement( | |
| StringBuilder message, String commandElement, boolean isBinary) { | |
| SCRIPT_EMITTER.emitCommandElement(message, commandElement, isBinary); | |
| } | |
| /** Emits the prefix for "exec"-ing a command. */ | |
| public static void emitExec(StringBuilder message) { | |
| SCRIPT_EMITTER.emitExec(message); | |
| } | |
| private interface ScriptEmitter { | |
| void emitBeginIsolate(StringBuilder message); | |
| void emitEndIsolate(StringBuilder message); | |
| void emitChangeDirectory(StringBuilder message, String cwd); | |
| void emitEnvPrefix(StringBuilder message, boolean ignoreEnvironment); | |
| void emitSetEnvVar(StringBuilder message, String name, String value); | |
| void emitUnsetEnvVar(StringBuilder message, String name); | |
| void emitCommandElement(StringBuilder message, String commandElement, boolean isBinary); | |
| void emitExec(StringBuilder message); | |
| } | |
| private static final class LinuxScriptEmitter implements ScriptEmitter { | |
| @Override | |
| public void emitBeginIsolate(StringBuilder message) { | |
| message.append("("); | |
| } | |
| @Override | |
| public void emitEndIsolate(StringBuilder message) { | |
| message.append(")"); | |
| } | |
| @Override | |
| public void emitChangeDirectory(StringBuilder message, String cwd) { | |
| message.append("cd ").append(ShellEscaper.escapeString(cwd)).append(" && \\\n "); | |
| } | |
| @Override | |
| public void emitEnvPrefix(StringBuilder message, boolean ignoreEnvironment) { | |
| message.append(ignoreEnvironment ? "env - \\\n " : "env \\\n "); | |
| } | |
| @Override | |
| public void emitSetEnvVar(StringBuilder message, String name, String value) { | |
| message | |
| .append(ShellEscaper.escapeString(name)) | |
| .append('=') | |
| .append(ShellEscaper.escapeString(value)) | |
| .append(" \\\n "); | |
| } | |
| @Override | |
| public void emitUnsetEnvVar(StringBuilder message, String name) { | |
| // Only the short form of --unset is supported on macOS. | |
| message.append("-u ").append(ShellEscaper.escapeString(name)).append(" \\\n "); | |
| } | |
| @Override | |
| public void emitCommandElement(StringBuilder message, String commandElement, boolean isBinary) { | |
| message.append(ShellEscaper.escapeString(commandElement)); | |
| } | |
| @Override | |
| public void emitExec(StringBuilder message) { | |
| message.append("exec "); | |
| } | |
| } | |
| // TODO(bazel-team): (2010) Add proper escaping. We can't use ShellUtils.shellEscape() as it is | |
| // incompatible with CMD.EXE syntax, but something else might be needed. | |
| private static final class WindowsScriptEmitter implements ScriptEmitter { | |
| @Override | |
| public void emitBeginIsolate(StringBuilder message) { | |
| // TODO(bazel-team): Implement this. | |
| } | |
| @Override | |
| public void emitEndIsolate(StringBuilder message) { | |
| // TODO(bazel-team): Implement this. | |
| } | |
| @Override | |
| public void emitChangeDirectory(StringBuilder message, String cwd) { | |
| message.append("cd ").append("/d ").append(cwd).append("\n"); | |
| } | |
| @Override | |
| public void emitEnvPrefix(StringBuilder message, boolean ignoreEnvironment) {} | |
| @Override | |
| public void emitSetEnvVar(StringBuilder message, String name, String value) { | |
| message.append("SET ").append(name).append('=').append(value).append("\n "); | |
| } | |
| @Override | |
| public void emitUnsetEnvVar(StringBuilder message, String name) { | |
| message.append("SET ").append(name).append('=').append("\n "); | |
| } | |
| @Override | |
| public void emitCommandElement(StringBuilder message, String commandElement, boolean isBinary) { | |
| // Replace the forward slashes with back slashes if the `commandElement` is the binary path | |
| message.append(isBinary ? commandElement.replace('/', '\\') : commandElement); | |
| } | |
| @Override | |
| public void emitExec(StringBuilder message) { | |
| // TODO(bazel-team): Implement this if possible for greater efficiency. | |
| } | |
| } | |
| private ScriptUtil() {} | |
| } |