blob: 3d1423b0f8fb57f4131f9e317fe01167d3a1f05f [file] [log] [blame]
// 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() {}
}