Skip to content

Commit 27c55a1

Browse files
Add @argFile option to the Native Image Driver
1 parent 7324aa0 commit 27c55a1

File tree

3 files changed

+224
-1
lines changed

3 files changed

+224
-1
lines changed

substratevm/src/com.oracle.svm.driver/resources/Help.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@ Usage: native-image [options] class [imagename] [options]
77
(to build an image for a class)
88
or native-image [options] -jar jarfile [imagename] [options]
99
(to build an image for a jar file)
10+
1011
where options include:
12+
13+
@argument files one or more argument files containing options
1114
-cp <class search path of directories and zip/jar files>
1215
-classpath <class search path of directories and zip/jar files>
1316
--class-path <class search path of directories and zip/jar files>

substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/DefaultOptionHandler.java

Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,12 @@
2525
package com.oracle.svm.driver;
2626

2727
import java.io.File;
28+
import java.io.IOException;
29+
import java.nio.charset.StandardCharsets;
2830
import java.nio.file.Files;
2931
import java.nio.file.Path;
3032
import java.nio.file.Paths;
33+
import java.util.ArrayList;
3134
import java.util.Arrays;
3235
import java.util.List;
3336
import java.util.regex.Pattern;
@@ -58,6 +61,7 @@ class DefaultOptionHandler extends NativeImage.OptionHandler<NativeImage> {
5861
}
5962

6063
boolean useDebugAttach = false;
64+
boolean disableAtFiles = false;
6165

6266
private static void singleArgumentCheck(ArgumentQueue args, String arg) {
6367
if (!args.isEmpty()) {
@@ -210,6 +214,10 @@ public boolean consume(ArgumentQueue args) {
210214
nativeImage.showNewline();
211215
System.exit(0);
212216
return true;
217+
case "--disable-@files":
218+
args.poll();
219+
disableAtFiles = true;
220+
return true;
213221
}
214222

215223
String debugAttach = "--debug-attach";
@@ -296,9 +304,221 @@ public boolean consume(ArgumentQueue args) {
296304
}
297305
return true;
298306
}
307+
if (headArg.startsWith("@") && !disableAtFiles) {
308+
args.poll();
309+
headArg = headArg.substring(1);
310+
Path argFile = Paths.get(headArg);
311+
NativeImage.NativeImageArgsProcessor processor = nativeImage.new NativeImageArgsProcessor(argFile.toString());
312+
readArgFile(argFile).forEach(processor::accept);
313+
List<String> leftoverArgs = processor.apply(false);
314+
if (leftoverArgs.size() > 0) {
315+
NativeImage.showError("Found unrecognized options while parsing argument file '" + argFile + "':\n" + String.join("\n", leftoverArgs));
316+
}
317+
return true;
318+
}
299319
return false;
300320
}
301321

322+
// Ported from JDK11's java.base/share/native/libjli/args.c
323+
enum PARSER_STATE {
324+
FIND_NEXT,
325+
IN_COMMENT,
326+
IN_QUOTE,
327+
IN_ESCAPE,
328+
SKIP_LEAD_WS,
329+
IN_TOKEN
330+
}
331+
332+
class CTX_ARGS {
333+
PARSER_STATE state;
334+
int cptr;
335+
int eob;
336+
char quoteChar;
337+
List<String> parts;
338+
String options;
339+
}
340+
341+
// Ported from JDK11's java.base/share/native/libjli/args.c
342+
private List<String> readArgFile(Path file) {
343+
List<String> arguments = new ArrayList<>();
344+
// Use of the at sign (@) to recursively interpret files isn't supported.
345+
arguments.add("--disable-@files");
346+
347+
String options = null;
348+
try {
349+
options = new String(Files.readAllBytes(file), StandardCharsets.UTF_8);
350+
} catch (IOException e) {
351+
NativeImage.showError("Error reading argument file", e);
352+
}
353+
354+
CTX_ARGS ctx = new CTX_ARGS();
355+
ctx.state = PARSER_STATE.FIND_NEXT;
356+
ctx.parts = new ArrayList<>(4);
357+
ctx.quoteChar = '"';
358+
ctx.cptr = 0;
359+
ctx.eob = options.length();
360+
ctx.options = options;
361+
362+
String token = nextToken(ctx);
363+
while (token != null) {
364+
arguments.add(token);
365+
token = nextToken(ctx);
366+
}
367+
368+
// remaining partial token
369+
if (ctx.state == PARSER_STATE.IN_TOKEN || ctx.state == PARSER_STATE.IN_QUOTE) {
370+
if (ctx.parts.size() != 0) {
371+
token = String.join("", ctx.parts);
372+
arguments.add(token);
373+
}
374+
}
375+
return arguments;
376+
}
377+
378+
// Ported from JDK11's java.base/share/native/libjli/args.c
379+
@SuppressWarnings("fallthrough")
380+
private static String nextToken(CTX_ARGS ctx) {
381+
int nextc = ctx.cptr;
382+
int eob = ctx.eob;
383+
int anchor = nextc;
384+
String token;
385+
386+
for (; nextc < eob; nextc++) {
387+
char ch = ctx.options.charAt(nextc);
388+
389+
// Skip white space characters
390+
if (ctx.state == PARSER_STATE.FIND_NEXT || ctx.state == PARSER_STATE.SKIP_LEAD_WS) {
391+
while (ch == ' ' || ch == '\n' || ch == '\r' || ch == '\t' || ch == '\f') {
392+
nextc++;
393+
if (nextc >= eob) {
394+
return null;
395+
}
396+
ch = ctx.options.charAt(nextc);
397+
}
398+
ctx.state = (ctx.state == PARSER_STATE.FIND_NEXT) ? PARSER_STATE.IN_TOKEN : PARSER_STATE.IN_QUOTE;
399+
anchor = nextc;
400+
// Deal with escape sequences
401+
} else if (ctx.state == PARSER_STATE.IN_ESCAPE) {
402+
// concatenation directive
403+
if (ch == '\n' || ch == '\r') {
404+
ctx.state = PARSER_STATE.SKIP_LEAD_WS;
405+
} else {
406+
// escaped character
407+
char[] escaped = new char[2];
408+
escaped[1] = '\0';
409+
switch (ch) {
410+
case 'n':
411+
escaped[0] = '\n';
412+
break;
413+
case 'r':
414+
escaped[0] = '\r';
415+
break;
416+
case 't':
417+
escaped[0] = '\t';
418+
break;
419+
case 'f':
420+
escaped[0] = '\f';
421+
break;
422+
default:
423+
escaped[0] = ch;
424+
break;
425+
}
426+
ctx.parts.add(String.valueOf(escaped));
427+
ctx.state = PARSER_STATE.IN_QUOTE;
428+
}
429+
// anchor to next character
430+
anchor = nextc + 1;
431+
continue;
432+
// ignore comment to EOL
433+
} else if (ctx.state == PARSER_STATE.IN_COMMENT) {
434+
while (ch != '\n' && ch != '\r') {
435+
nextc++;
436+
if (nextc >= eob) {
437+
return null;
438+
}
439+
ch = ctx.options.charAt(nextc);
440+
}
441+
anchor = nextc + 1;
442+
ctx.state = PARSER_STATE.FIND_NEXT;
443+
continue;
444+
}
445+
446+
assert (ctx.state != PARSER_STATE.IN_ESCAPE);
447+
assert (ctx.state != PARSER_STATE.FIND_NEXT);
448+
assert (ctx.state != PARSER_STATE.SKIP_LEAD_WS);
449+
assert (ctx.state != PARSER_STATE.IN_COMMENT);
450+
451+
switch (ch) {
452+
case ' ':
453+
case '\t':
454+
case '\f':
455+
if (ctx.state == PARSER_STATE.IN_QUOTE) {
456+
continue;
457+
}
458+
// fall through
459+
case '\n':
460+
case '\r':
461+
if (ctx.parts.size() == 0) {
462+
token = ctx.options.substring(anchor, nextc);
463+
} else {
464+
ctx.parts.add(ctx.options.substring(anchor, nextc));
465+
token = String.join("", ctx.parts);
466+
ctx.parts = new ArrayList<>();
467+
}
468+
ctx.cptr = nextc + 1;
469+
ctx.state = PARSER_STATE.FIND_NEXT;
470+
return token;
471+
case '#':
472+
if (ctx.state == PARSER_STATE.IN_QUOTE) {
473+
continue;
474+
}
475+
ctx.state = PARSER_STATE.IN_COMMENT;
476+
anchor = nextc + 1;
477+
break;
478+
case '\\':
479+
if (ctx.state != PARSER_STATE.IN_QUOTE) {
480+
continue;
481+
}
482+
ctx.parts.add(ctx.options.substring(anchor, nextc));
483+
ctx.state = PARSER_STATE.IN_ESCAPE;
484+
// anchor after backslash character
485+
anchor = nextc + 1;
486+
break;
487+
case '\'':
488+
case '"':
489+
if (ctx.state == PARSER_STATE.IN_QUOTE && ctx.quoteChar != ch) {
490+
// not matching quote
491+
continue;
492+
}
493+
// partial before quote
494+
if (anchor != nextc) {
495+
ctx.parts.add(ctx.options.substring(anchor, nextc));
496+
}
497+
// anchor after quote character
498+
anchor = nextc + 1;
499+
if (ctx.state == PARSER_STATE.IN_TOKEN) {
500+
ctx.quoteChar = ch;
501+
ctx.state = PARSER_STATE.IN_QUOTE;
502+
} else {
503+
ctx.state = PARSER_STATE.IN_TOKEN;
504+
}
505+
break;
506+
default:
507+
break;
508+
}
509+
}
510+
511+
assert (nextc == eob);
512+
// Only need partial token, not comment or whitespaces
513+
if (ctx.state == PARSER_STATE.IN_TOKEN || ctx.state == PARSER_STATE.IN_QUOTE) {
514+
if (anchor < nextc) {
515+
// not yet return until end of stream, we have part of a token.
516+
ctx.parts.add(ctx.options.substring(anchor, nextc));
517+
}
518+
}
519+
return null;
520+
}
521+
302522
private void processClasspathArgs(String cpArgs) {
303523
for (String cp : cpArgs.split(File.pathSeparator, Integer.MAX_VALUE)) {
304524
/* Conform to `java` command empty cp entry handling. */

substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/NativeImage.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -815,7 +815,7 @@ protected NativeImage(BuildConfiguration config) {
815815
/* Discover supported MacroOptions */
816816
optionRegistry = new MacroOption.Registry();
817817

818-
/* Default handler needs to be fist */
818+
/* Default handler needs to be first */
819819
defaultOptionHandler = new DefaultOptionHandler(this);
820820
registerOptionHandler(defaultOptionHandler);
821821
apiOptionHandler = new APIOptionHandler(this);

0 commit comments

Comments
 (0)