40
40
import java .time .format .FormatStyle ;
41
41
import java .util .ArrayList ;
42
42
import java .util .Arrays ;
43
- import java .util .Collection ;
44
43
import java .util .Collections ;
45
44
import java .util .HashMap ;
46
45
import java .util .HashSet ;
47
46
import java .util .List ;
47
+ import java .util .ListIterator ;
48
48
import java .util .Map ;
49
49
import java .util .Objects ;
50
50
import java .util .Optional ;
@@ -86,8 +86,8 @@ final class BundleSupport {
86
86
Map <Path , Path > pathCanonicalizations = new HashMap <>();
87
87
Map <Path , Path > pathSubstitutions = new HashMap <>();
88
88
89
- private final List <String > buildArgs ;
90
- private Collection <String > updatedBuildArgs ;
89
+ private final List <String > nativeImageArgs ;
90
+ private List <String > updatedNativeImageArgs ;
91
91
92
92
boolean loadBundle ;
93
93
boolean writeBundle ;
@@ -110,9 +110,13 @@ final class BundleSupport {
110
110
static final String BUNDLE_OPTION = "--bundle" ;
111
111
static final String BUNDLE_FILE_EXTENSION = ".nib" ;
112
112
113
- private enum BundleOptionVariants {
113
+ enum BundleOptionVariants {
114
114
create (),
115
- apply ()
115
+ apply ();
116
+
117
+ String optionName () {
118
+ return BUNDLE_OPTION + "-" + this ;
119
+ }
116
120
}
117
121
118
122
static BundleSupport create (NativeImage nativeImage , String bundleArg , NativeImage .ArgumentQueue args ) {
@@ -121,10 +125,6 @@ static BundleSupport create(NativeImage nativeImage, String bundleArg, NativeIma
121
125
"Bundle support is still experimental and needs to be unlocked with '" + UNLOCK_BUNDLE_SUPPORT_OPTION + "'. The unlock option must precede '" + bundleArg + "'." );
122
126
}
123
127
124
- if (!nativeImage .userConfigProperties .isEmpty ()) {
125
- throw NativeImage .showError ("Bundle support cannot be combined with " + NativeImage .CONFIG_FILE_ENV_VAR_KEY + " environment variable use." );
126
- }
127
-
128
128
try {
129
129
String variant = bundleArg .substring (BUNDLE_OPTION .length () + 1 );
130
130
String bundleFilename = null ;
@@ -133,31 +133,31 @@ static BundleSupport create(NativeImage nativeImage, String bundleArg, NativeIma
133
133
variant = variantParts [0 ];
134
134
bundleFilename = variantParts [1 ];
135
135
}
136
- String applyOptionStr = BUNDLE_OPTION + "-" + BundleOptionVariants .apply ;
137
- String createOptionStr = BUNDLE_OPTION + "-" + BundleOptionVariants .create ;
136
+ String applyOptionName = BundleOptionVariants .apply . optionName () ;
137
+ String createOptionName = BundleOptionVariants .create . optionName () ;
138
138
BundleSupport bundleSupport ;
139
139
switch (BundleOptionVariants .valueOf (variant )) {
140
140
case apply :
141
141
if (nativeImage .useBundle ()) {
142
142
if (nativeImage .bundleSupport .loadBundle ) {
143
- throw NativeImage .showError (String .format ("native-image allows option %s to be specified only once." , applyOptionStr ));
143
+ throw NativeImage .showError (String .format ("native-image allows option %s to be specified only once." , applyOptionName ));
144
144
}
145
145
if (nativeImage .bundleSupport .writeBundle ) {
146
- throw NativeImage .showError (String .format ("native-image option %s is not allowed to be used after option %s." , applyOptionStr , createOptionStr ));
146
+ throw NativeImage .showError (String .format ("native-image option %s is not allowed to be used after option %s." , applyOptionName , createOptionName ));
147
147
}
148
148
}
149
149
if (bundleFilename == null ) {
150
- throw NativeImage .showError (String .format ("native-image option %s requires a bundle file argument. E.g. %s=bundle-file.nib." , applyOptionStr , applyOptionStr ));
150
+ throw NativeImage .showError (String .format ("native-image option %s requires a bundle file argument. E.g. %s=bundle-file.nib." , applyOptionName , applyOptionName ));
151
151
}
152
152
bundleSupport = new BundleSupport (nativeImage , bundleFilename );
153
153
/* Inject the command line args from the loaded bundle in-place */
154
- List <String > buildArgs = bundleSupport .getBuildArgs ();
154
+ List <String > buildArgs = bundleSupport .getNativeImageArgs ();
155
155
for (int i = buildArgs .size () - 1 ; i >= 0 ; i --) {
156
156
args .push (buildArgs .get (i ));
157
157
}
158
158
nativeImage .showVerboseMessage (nativeImage .isVerbose (), BUNDLE_INFO_MESSAGE_PREFIX + "Inject args: '" + String .join (" " , buildArgs ) + "'" );
159
159
/* Snapshot args after in-place expansion (includes also args after this one) */
160
- bundleSupport .updatedBuildArgs = args .snapshot ();
160
+ bundleSupport .updatedNativeImageArgs = args .snapshot ();
161
161
break ;
162
162
case create :
163
163
if (nativeImage .useBundle ()) {
@@ -209,7 +209,7 @@ private BundleSupport(NativeImage nativeImage) {
209
209
} catch (IOException e ) {
210
210
throw NativeImage .showError ("Unable to create bundle directory layout" , e );
211
211
}
212
- this .buildArgs = Collections . unmodifiableList ( nativeImage .config . getBuildArgs () );
212
+ this .nativeImageArgs = nativeImage .getNativeImageArgs ( );
213
213
}
214
214
215
215
private BundleSupport (NativeImage nativeImage , String bundleFilenameArg ) {
@@ -279,18 +279,25 @@ private BundleSupport(NativeImage nativeImage, String bundleFilenameArg) {
279
279
} catch (IOException e ) {
280
280
throw NativeImage .showError ("Failed to read bundle-file " + pathSubstitutionsFile , e );
281
281
}
282
+ Path environmentFile = stageDir .resolve ("environment.json" );
283
+ try (Reader reader = Files .newBufferedReader (environmentFile )) {
284
+ new EnvironmentParser (nativeImage .imageBuilderEnvironment ).parseAndRegister (reader );
285
+ } catch (IOException e ) {
286
+ throw NativeImage .showError ("Failed to read bundle-file " + environmentFile , e );
287
+ }
288
+
282
289
Path buildArgsFile = stageDir .resolve ("build.json" );
283
290
try (Reader reader = Files .newBufferedReader (buildArgsFile )) {
284
291
List <String > buildArgsFromFile = new ArrayList <>();
285
292
new BuildArgsParser (buildArgsFromFile ).parseAndRegister (reader );
286
- buildArgs = Collections .unmodifiableList (buildArgsFromFile );
293
+ nativeImageArgs = Collections .unmodifiableList (buildArgsFromFile );
287
294
} catch (IOException e ) {
288
- throw NativeImage .showError ("Failed to read bundle-file " + pathSubstitutionsFile , e );
295
+ throw NativeImage .showError ("Failed to read bundle-file " + buildArgsFile , e );
289
296
}
290
297
}
291
298
292
- public List <String > getBuildArgs () {
293
- return buildArgs ;
299
+ public List <String > getNativeImageArgs () {
300
+ return nativeImageArgs ;
294
301
}
295
302
296
303
Path recordCanonicalization (Path before , Path after ) {
@@ -409,8 +416,8 @@ private Path substitutePath(Path origPath, Path destinationDir) {
409
416
return origPath ;
410
417
}
411
418
412
- // TODO Report error if overlapping dir-trees are passed in
413
- // TODO add .endsWith(ClasspathUtils.cpWildcardSubstitute) handling (copy whole directory)
419
+ // TODO: Report error if overlapping dir-trees are passed in
420
+
414
421
String origFileName = origPath .getFileName ().toString ();
415
422
int extensionPos = origFileName .lastIndexOf ('.' );
416
423
String baseName ;
@@ -440,6 +447,16 @@ private Path substitutePath(Path origPath, Path destinationDir) {
440
447
return substitutedPath ;
441
448
}
442
449
450
+ Path originalPath (Path substitutedPath ) {
451
+ Path relativeSubstitutedPath = rootDir .relativize (substitutedPath );
452
+ for (Map .Entry <Path , Path > entry : pathSubstitutions .entrySet ()) {
453
+ if (entry .getValue ().equals (relativeSubstitutedPath )) {
454
+ return entry .getKey ();
455
+ }
456
+ }
457
+ return null ;
458
+ }
459
+
443
460
private void copyFiles (Path source , Path target , boolean overwrite ) {
444
461
nativeImage .showVerboseMessage (nativeImage .isVVerbose (), "> Copy files from " + source + " to " + target );
445
462
if (Files .isDirectory (source )) {
@@ -567,39 +584,36 @@ private Path writeBundle() {
567
584
} catch (IOException e ) {
568
585
throw NativeImage .showError ("Failed to write bundle-file " + pathSubstitutionsFile , e );
569
586
}
587
+ Path environmentFile = stageDir .resolve ("environment.json" );
588
+ try (JsonWriter writer = new JsonWriter (environmentFile )) {
589
+ /* Printing as list with defined sort-order ensures useful diffs are possible */
590
+ JsonPrinter .printCollection (writer , nativeImage .imageBuilderEnvironment .entrySet (), Map .Entry .comparingByKey (), BundleSupport ::printEnvironmentVariable );
591
+ } catch (IOException e ) {
592
+ throw NativeImage .showError ("Failed to write bundle-file " + environmentFile , e );
593
+ }
570
594
571
595
Path buildArgsFile = stageDir .resolve ("build.json" );
572
596
try (JsonWriter writer = new JsonWriter (buildArgsFile )) {
573
- ArrayList <String > cleanBuildArgs = new ArrayList <>();
574
- for (String buildArg : updatedBuildArgs != null ? updatedBuildArgs : buildArgs ) {
575
- if (buildArg .equals (UNLOCK_BUNDLE_SUPPORT_OPTION )) {
576
- continue ;
577
- }
578
- if (buildArg .startsWith (BUNDLE_OPTION )) {
579
- continue ;
580
- }
581
- if (buildArg .startsWith (nativeImage .oHPath )) {
582
- continue ;
583
- }
584
- if (buildArg .equals (CmdLineOptionHandler .VERBOSE_OPTION )) {
585
- continue ;
586
- }
587
- if (buildArg .equals (CmdLineOptionHandler .DRY_RUN_OPTION )) {
588
- continue ;
589
- }
590
- if (buildArg .startsWith ("-Dllvm.bin.dir=" )) {
591
- Optional <String > existing = nativeImage .config .getBuildArgs ().stream ().filter (arg -> arg .startsWith ("-Dllvm.bin.dir=" )).findFirst ();
592
- if (existing .isPresent () && !existing .get ().equals (buildArg )) {
593
- throw NativeImage .showError ("Bundle native-image argument '" + buildArg + "' conflicts with existing '" + existing .get () + "'." );
597
+ List <String > equalsNonBundleOptions = List .of (UNLOCK_BUNDLE_SUPPORT_OPTION , CmdLineOptionHandler .VERBOSE_OPTION , CmdLineOptionHandler .DRY_RUN_OPTION );
598
+ List <String > startsWithNonBundleOptions = List .of (BUNDLE_OPTION , DefaultOptionHandler .ADD_ENV_VAR_OPTION , nativeImage .oHPath );
599
+ ArrayList <String > bundleArgs = new ArrayList <>(updatedNativeImageArgs != null ? updatedNativeImageArgs : nativeImageArgs );
600
+ ListIterator <String > bundleArgsIterator = bundleArgs .listIterator ();
601
+ while (bundleArgsIterator .hasNext ()) {
602
+ String arg = bundleArgsIterator .next ();
603
+ if (equalsNonBundleOptions .contains (arg ) || startsWithNonBundleOptions .stream ().anyMatch (arg ::startsWith )) {
604
+ bundleArgsIterator .remove ();
605
+ } else if (arg .startsWith ("-Dllvm.bin.dir=" )) {
606
+ Optional <String > existing = nativeImage .config .getBuildArgs ().stream ().filter (a -> a .startsWith ("-Dllvm.bin.dir=" )).findFirst ();
607
+ if (existing .isPresent () && !existing .get ().equals (arg )) {
608
+ throw NativeImage .showError ("Bundle native-image argument '" + arg + "' conflicts with existing '" + existing .get () + "'." );
594
609
}
595
- continue ;
610
+ bundleArgsIterator . remove () ;
596
611
}
597
- cleanBuildArgs .add (buildArg );
598
612
}
599
613
/* Printing as list with defined sort-order ensures useful diffs are possible */
600
- JsonPrinter .printCollection (writer , cleanBuildArgs , null , BundleSupport ::printBuildArg );
614
+ JsonPrinter .printCollection (writer , bundleArgs , null , BundleSupport ::printBuildArg );
601
615
} catch (IOException e ) {
602
- throw NativeImage .showError ("Failed to write bundle-file " + pathSubstitutionsFile , e );
616
+ throw NativeImage .showError ("Failed to write bundle-file " + buildArgsFile , e );
603
617
}
604
618
605
619
bundleProperties .write ();
@@ -639,7 +653,7 @@ private static Manifest createManifest() {
639
653
private static final String substitutionMapDstField = "dst" ;
640
654
641
655
private static void printPathMapping (Map .Entry <Path , Path > entry , JsonWriter w ) throws IOException {
642
- w .append ('{' ).quote (substitutionMapSrcField ).append (" : " ).quote (entry .getKey ());
656
+ w .append ('{' ).quote (substitutionMapSrcField ).append (':' ).quote (entry .getKey ());
643
657
w .append (',' ).quote (substitutionMapDstField ).append (':' ).quote (entry .getValue ());
644
658
w .append ('}' );
645
659
}
@@ -648,6 +662,18 @@ private static void printBuildArg(String entry, JsonWriter w) throws IOException
648
662
w .quote (entry );
649
663
}
650
664
665
+ private static final String environmentKeyField = "key" ;
666
+ private static final String environmentValueField = "val" ;
667
+
668
+ private static void printEnvironmentVariable (Map .Entry <String , String > entry , JsonWriter w ) throws IOException {
669
+ if (entry .getValue () == null ) {
670
+ throw NativeImage .showError ("Storing environment variable '" + entry .getKey () + "' in bundle requires to have its value defined." );
671
+ }
672
+ w .append ('{' ).quote (environmentKeyField ).append (':' ).quote (entry .getKey ());
673
+ w .append (',' ).quote (environmentValueField ).append (':' ).quote (entry .getValue ());
674
+ w .append ('}' );
675
+ }
676
+
651
677
private static final class PathMapParser extends ConfigurationParser {
652
678
653
679
private final Map <Path , Path > pathMap ;
@@ -674,6 +700,33 @@ public void parseAndRegister(Object json, URI origin) {
674
700
}
675
701
}
676
702
703
+ private static final class EnvironmentParser extends ConfigurationParser {
704
+
705
+ private final Map <String , String > environment ;
706
+
707
+ private EnvironmentParser (Map <String , String > environment ) {
708
+ super (true );
709
+ environment .clear ();
710
+ this .environment = environment ;
711
+ }
712
+
713
+ @ Override
714
+ public void parseAndRegister (Object json , URI origin ) {
715
+ for (var rawEntry : asList (json , "Expected a list of environment variable objects" )) {
716
+ var entry = asMap (rawEntry , "Expected a environment variable object" );
717
+ Object envVarKeyString = entry .get (environmentKeyField );
718
+ if (envVarKeyString == null ) {
719
+ throw new JSONParserException ("Expected " + environmentKeyField + "-field in environment variable object" );
720
+ }
721
+ Object envVarValueString = entry .get (environmentValueField );
722
+ if (envVarValueString == null ) {
723
+ throw new JSONParserException ("Expected " + environmentValueField + "-field in environment variable object" );
724
+ }
725
+ environment .put (envVarKeyString .toString (), envVarValueString .toString ());
726
+ }
727
+ }
728
+ }
729
+
677
730
private static final class BuildArgsParser extends ConfigurationParser {
678
731
679
732
private final List <String > args ;
0 commit comments