Skip to content

Commit 0031a8e

Browse files
author
Marcelo Vanzin
committed
Review feedback.
1 parent c12d84b commit 0031a8e

File tree

12 files changed

+191
-176
lines changed

12 files changed

+191
-176
lines changed

core/src/main/scala/org/apache/spark/deploy/SparkSubmitArguments.scala

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ import org.apache.spark.util.Utils
3333
* The env argument is used for testing.
3434
*/
3535
private[spark] class SparkSubmitArguments(args: Seq[String], env: Map[String, String] = sys.env)
36-
extends SparkSubmitArgumentsParser {
36+
extends SparkSubmitArgumentsParser {
3737
var master: String = null
3838
var deployMode: String = null
3939
var executorMemory: String = null
@@ -397,6 +397,8 @@ private[spark] class SparkSubmitArguments(args: Seq[String], env: Map[String, St
397397
}
398398

399399
/**
400+
* Handle unrecognized command line options.
401+
*
400402
* The first unrecognized option is treated as the "primary resource". Everything else is
401403
* treated as application arguments.
402404
*/
@@ -419,7 +421,6 @@ private[spark] class SparkSubmitArguments(args: Seq[String], env: Map[String, St
419421
childArgs ++= extra
420422
}
421423

422-
423424
private def printUsageAndExit(exitCode: Int, unknownParam: Any = null): Unit = {
424425
val outStream = SparkSubmit.printStream
425426
if (unknownParam != null) {

core/src/main/scala/org/apache/spark/launcher/SparkSubmitArgumentsParser.scala

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,7 @@ package org.apache.spark.launcher
1919

2020
/**
2121
* This class makes SparkSubmitOptionParser visible for Spark code outside of the `launcher`
22-
* package.
22+
* package, since Java doesn't have a feature similar to `private[spark]`, and we don't want
23+
* that class to be public.
2324
*/
24-
private[spark] abstract class SparkSubmitArgumentsParser extends SparkSubmitOptionParser {
25-
26-
}
25+
private[spark] abstract class SparkSubmitArgumentsParser extends SparkSubmitOptionParser

core/src/main/scala/org/apache/spark/launcher/WorkerCommandBuilder.scala

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,9 @@ import scala.collection.JavaConversions._
2525
import org.apache.spark.deploy.Command
2626

2727
/**
28-
* This class is used by CommandUtils. It uses some package-private APIs in SparkLauncher and so
29-
* needs to live in the same package as the rest of the library.
28+
* This class is used by CommandUtils. It uses some package-private APIs in SparkLauncher, and since
29+
* Java doesn't have a feature similar to `private[spark]`, and we don't want that class to be
30+
* public, needs to live in the same package as the rest of the library.
3031
*/
3132
private[spark] class WorkerCommandBuilder(sparkHome: String, memoryMb: Int, command: Command)
3233
extends AbstractCommandBuilder {
@@ -38,7 +39,7 @@ private[spark] class WorkerCommandBuilder(sparkHome: String, memoryMb: Int, comm
3839
val cmd = buildJavaCommand(command.classPathEntries.mkString(File.pathSeparator))
3940
cmd.add(s"-Xms${memoryMb}M")
4041
cmd.add(s"-Xmx${memoryMb}M")
41-
command.javaOpts.foreach { cmd.add }
42+
command.javaOpts.foreach(cmd.add)
4243
addPermGenSizeOpt(cmd)
4344
addOptionString(cmd, getenv("SPARK_JAVA_OPTS"))
4445
cmd

launcher/src/main/java/org/apache/spark/launcher/AbstractCommandBuilder.java

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
import static org.apache.spark.launcher.CommandBuilderUtils.*;
3737

3838
/**
39-
* Abstract command builder that defines common functionality for all builders.
39+
* Abstract Spark command builder that defines common functionality.
4040
*/
4141
abstract class AbstractCommandBuilder {
4242

@@ -56,20 +56,16 @@ abstract class AbstractCommandBuilder {
5656
final Map<String, String> conf;
5757

5858
public AbstractCommandBuilder() {
59-
this(Collections.<String, String>emptyMap());
60-
}
61-
62-
public AbstractCommandBuilder(Map<String, String> env) {
6359
this.appArgs = new ArrayList<String>();
64-
this.childEnv = new HashMap<String, String>(env);
60+
this.childEnv = new HashMap<String, String>();
6561
this.conf = new HashMap<String, String>();
6662
this.files = new ArrayList<String>();
6763
this.jars = new ArrayList<String>();
6864
this.pyFiles = new ArrayList<String>();
6965
}
7066

7167
/**
72-
* Builds the command like to execute.
68+
* Builds the command to execute.
7369
*
7470
* @param env A map containing environment variables for the child process. It may already contain
7571
* entries defined by the user (such as SPARK_HOME, or those defined by the
@@ -78,6 +74,16 @@ public AbstractCommandBuilder(Map<String, String> env) {
7874
*/
7975
abstract List<String> buildCommand(Map<String, String> env) throws IOException;
8076

77+
/**
78+
* Builds a list of arguments to run java.
79+
*
80+
* This method finds the java executable to use and appends JVM-specific options for running a
81+
* class with Spark in the classpath. It also loads options from the "java-opts" file in the
82+
* configuration directory being used.
83+
*
84+
* Callers should still add at least the class to run, as well as any arguments to pass to the
85+
* class.
86+
*/
8187
List<String> buildJavaCommand(String extraClassPath) throws IOException {
8288
List<String> cmd = new ArrayList<String>();
8389
if (javaHome == null) {
@@ -241,7 +247,7 @@ List<String> buildClassPath(String appClassPath) throws IOException {
241247
/**
242248
* Adds entries to the classpath.
243249
*
244-
* @param cp List where to appended the new classpath entries.
250+
* @param cp List to which the new entries are appended.
245251
* @param entries New classpath entries (separated by File.pathSeparator).
246252
*/
247253
private void addToClassPath(List<String> cp, String entries) {
@@ -268,18 +274,15 @@ String getScalaVersion() {
268274
String sparkHome = getSparkHome();
269275
File scala210 = new File(sparkHome, "assembly/target/scala-2.10");
270276
File scala211 = new File(sparkHome, "assembly/target/scala-2.11");
271-
if (scala210.isDirectory() && scala211.isDirectory()) {
272-
checkState(false,
273-
"Presence of build for both scala versions (2.10 and 2.11) detected.\n" +
274-
"Either clean one of them or set SPARK_SCALA_VERSION in your environment.");
275-
} else if (scala210.isDirectory()) {
277+
checkState(!scala210.isDirectory() || !scala211.isDirectory(),
278+
"Presence of build for both scala versions (2.10 and 2.11) detected.\n" +
279+
"Either clean one of them or set SPARK_SCALA_VERSION in your environment.");
280+
if (scala210.isDirectory()) {
276281
return "2.10";
277282
} else {
278283
checkState(scala211.isDirectory(), "Cannot find any assembly build directories.");
279284
return "2.11";
280285
}
281-
282-
throw new IllegalStateException("Should not reach here.");
283286
}
284287

285288
String getSparkHome() {

launcher/src/main/java/org/apache/spark/launcher/CommandBuilderUtils.java

Lines changed: 64 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,10 @@ static String join(String sep, Iterable<String> elements) {
6464
return sb.toString();
6565
}
6666

67-
/** Returns the first value mapped to the given key in the given maps. */
68-
static String find(String key, Map<?, ?>... maps) {
67+
/**
68+
* Returns the first non-empty value mapped to the given key in the given maps, or null otherwise.
69+
*/
70+
static String firstNonEmptyValue(String key, Map<?, ?>... maps) {
6971
for (Map<?, ?> map : maps) {
7072
String value = (String) map.get(key);
7173
if (!isEmpty(value)) {
@@ -75,7 +77,7 @@ static String find(String key, Map<?, ?>... maps) {
7577
return null;
7678
}
7779

78-
/** Returns the first non-empty, non-null string in the given list. */
80+
/** Returns the first non-empty, non-null string in the given list, or null otherwise. */
7981
static String firstNonEmpty(String... candidates) {
8082
for (String s : candidates) {
8183
if (!isEmpty(s)) {
@@ -106,8 +108,8 @@ static boolean isWindows() {
106108
}
107109

108110
/**
109-
* Updates the user environment to contain the merged value of "envKey" after appending
110-
* the given path list.
111+
* Updates the user environment, appending the given pathList to the existing value of the given
112+
* environment variable (or setting it if it hasn't yet been set).
111113
*/
112114
static void mergeEnvPathList(Map<String, String> userEnv, String envKey, String pathList) {
113115
if (!isEmpty(pathList)) {
@@ -117,8 +119,8 @@ static void mergeEnvPathList(Map<String, String> userEnv, String envKey, String
117119
}
118120

119121
/**
120-
* Parse a string as if it were a list of arguments, in the way that a shell would.
121-
* This tries to follow the way bash parses strings. For example:
122+
* Parse a string as if it were a list of arguments, following bash semantics.
123+
* For example:
122124
*
123125
* Input: "\"ab cd\" efgh 'i \" j'"
124126
* Output: [ "ab cd", "efgh", "i \" j" ]
@@ -130,6 +132,8 @@ static List<String> parseOptionString(String s) {
130132
boolean inSingleQuote = false;
131133
boolean inDoubleQuote = false;
132134
boolean escapeNext = false;
135+
136+
// This is needed to detect when a quoted empty string is used as an argument ("" or '').
133137
boolean hasData = false;
134138

135139
for (int i = 0; i < s.length(); i++) {
@@ -161,7 +165,7 @@ static List<String> parseOptionString(String s) {
161165
}
162166
break;
163167
default:
164-
if (inSingleQuote || inDoubleQuote || !Character.isWhitespace(c)) {
168+
if (!Character.isWhitespace(c) || inSingleQuote || inDoubleQuote) {
165169
opt.appendCodePoint(c);
166170
} else {
167171
opts.add(opt.toString());
@@ -225,4 +229,56 @@ static void checkState(boolean check, String msg, Object... args) {
225229
}
226230
}
227231

232+
/**
233+
* Quote a command argument for a command to be run by a Windows batch script, if the argument
234+
* needs quoting. Arguments only seem to need quotes in batch scripts if they have whitespace.
235+
*
236+
* For example:
237+
* original single argument: ab "cde" fgh
238+
* quoted: "ab ""cde"" fgh"
239+
*/
240+
static String quoteForBatchScript(String arg) {
241+
boolean needsQuotes = false;
242+
for (int i = 0; i < arg.length(); i++) {
243+
if (Character.isWhitespace(arg.codePointAt(i)) || arg.codePointAt(i) == '"') {
244+
needsQuotes = true;
245+
break;
246+
}
247+
}
248+
if (!needsQuotes) {
249+
return arg;
250+
}
251+
StringBuilder quoted = new StringBuilder();
252+
quoted.append("\"");
253+
for (int i = 0; i < arg.length(); i++) {
254+
int cp = arg.codePointAt(i);
255+
if (cp == '\"') {
256+
quoted.append("\"");
257+
}
258+
quoted.appendCodePoint(cp);
259+
}
260+
quoted.append("\"");
261+
return quoted.toString();
262+
}
263+
264+
/**
265+
* Quotes a string so that it can be used in a command string and be parsed back into a single
266+
* argument by python's "shlex.split()" function.
267+
*
268+
* Basically, just add simple escapes. E.g.:
269+
* original single argument : ab "cd" ef
270+
* after: "ab \"cd\" ef"
271+
*/
272+
static String quoteForPython(String s) {
273+
StringBuilder quoted = new StringBuilder().append('"');
274+
for (int i = 0; i < s.length(); i++) {
275+
int cp = s.codePointAt(i);
276+
if (cp == '"' || cp == '\\') {
277+
quoted.appendCodePoint('\\');
278+
}
279+
quoted.appendCodePoint(cp);
280+
}
281+
return quoted.append('"').toString();
282+
}
283+
228284
}

launcher/src/main/java/org/apache/spark/launcher/Main.java

Lines changed: 5 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -75,9 +75,10 @@ public static void main(String[] argsArray) throws Exception {
7575
}
7676

7777
if (isWindows()) {
78-
System.out.println(prepareForWindows(cmd, env));
78+
System.out.println(prepareWindowsCommand(cmd, env));
7979
} else {
80-
List<String> bashCmd = prepareForBash(cmd, env);
80+
// In bash, use NULL as the arg separator since it cannot be used in an argument.
81+
List<String> bashCmd = prepareBashCommand(cmd, env);
8182
for (String c : bashCmd) {
8283
System.out.print(c);
8384
System.out.print('\0');
@@ -95,7 +96,7 @@ public static void main(String[] argsArray) throws Exception {
9596
* The command is executed using "cmd /c" and formatted in single line, since that's the
9697
* easiest way to consume this from a batch script (see spark-class2.cmd).
9798
*/
98-
private static String prepareForWindows(List<String> cmd, Map<String, String> childEnv) {
99+
private static String prepareWindowsCommand(List<String> cmd, Map<String, String> childEnv) {
99100
StringBuilder cmdline = new StringBuilder("cmd /c \"");
100101
for (Map.Entry<String, String> e : childEnv.entrySet()) {
101102
cmdline.append(String.format("set %s=%s", e.getKey(), e.getValue()));
@@ -113,7 +114,7 @@ private static String prepareForWindows(List<String> cmd, Map<String, String> ch
113114
* Prepare the command for execution from a bash script. The final command will have commands to
114115
* set up any needed environment variables needed by the child process.
115116
*/
116-
private static List<String> prepareForBash(List<String> cmd, Map<String, String> childEnv) {
117+
private static List<String> prepareBashCommand(List<String> cmd, Map<String, String> childEnv) {
117118
if (childEnv.isEmpty()) {
118119
return cmd;
119120
}
@@ -128,34 +129,6 @@ private static List<String> prepareForBash(List<String> cmd, Map<String, String>
128129
return newCmd;
129130
}
130131

131-
/**
132-
* Quote a command argument for a command to be run by a Windows batch script, if the argument
133-
* needs quoting. Arguments only seem to need quotes in batch scripts if they have whitespace.
134-
*/
135-
private static String quoteForBatchScript(String arg) {
136-
boolean needsQuotes = false;
137-
for (int i = 0; i < arg.length(); i++) {
138-
if (Character.isWhitespace(arg.codePointAt(i))) {
139-
needsQuotes = true;
140-
break;
141-
}
142-
}
143-
if (!needsQuotes) {
144-
return arg;
145-
}
146-
StringBuilder quoted = new StringBuilder();
147-
quoted.append("\"");
148-
for (int i = 0; i < arg.length(); i++) {
149-
int cp = arg.codePointAt(i);
150-
if (cp == '\"') {
151-
quoted.append("\"");
152-
}
153-
quoted.appendCodePoint(cp);
154-
}
155-
quoted.append("\"");
156-
return quoted.toString();
157-
}
158-
159132
/**
160133
* Internal launcher used when command line parsing fails. This will behave differently depending
161134
* on the platform:

launcher/src/main/java/org/apache/spark/launcher/SparkClassCommandBuilder.java

Lines changed: 0 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -89,9 +89,6 @@ public List<String> buildCommand(Map<String, String> env) throws IOException {
8989
toolsDir.getAbsolutePath(), className);
9090

9191
javaOptsKeys.add("SPARK_JAVA_OPTS");
92-
} else {
93-
// Any classes not explicitly listed above are submitted using SparkSubmit.
94-
return createSparkSubmitCommand(env);
9592
}
9693

9794
List<String> cmd = buildJavaCommand(extraClassPath);
@@ -108,48 +105,4 @@ public List<String> buildCommand(Map<String, String> env) throws IOException {
108105
return cmd;
109106
}
110107

111-
private List<String> createSparkSubmitCommand(Map<String, String> env) throws IOException {
112-
final List<String> sparkSubmitArgs = new ArrayList<String>();
113-
final List<String> appArgs = new ArrayList<String>();
114-
115-
// Parse the command line and special-case the HELP command line argument, allowing it to be
116-
// propagated to the app being launched.
117-
SparkSubmitOptionParser parser = new SparkSubmitOptionParser() {
118-
119-
@Override
120-
protected boolean handle(String opt, String value) {
121-
if (opt.equals(HELP)) {
122-
appArgs.add(opt);
123-
} else {
124-
sparkSubmitArgs.add(opt);
125-
sparkSubmitArgs.add(value);
126-
}
127-
return true;
128-
}
129-
130-
@Override
131-
protected boolean handleUnknown(String opt) {
132-
appArgs.add(opt);
133-
return true;
134-
}
135-
136-
@Override
137-
protected void handleExtraArgs(List<String> extra) {
138-
appArgs.addAll(extra);
139-
}
140-
141-
};
142-
143-
parser.parse(classArgs);
144-
sparkSubmitArgs.add(parser.CLASS);
145-
sparkSubmitArgs.add(className);
146-
147-
SparkSubmitCommandBuilder builder = new SparkSubmitCommandBuilder(true, sparkSubmitArgs);
148-
builder.appResource = "spark-internal";
149-
for (String arg: appArgs) {
150-
builder.appArgs.add(arg);
151-
}
152-
return builder.buildCommand(env);
153-
}
154-
155108
}

0 commit comments

Comments
 (0)