3
3
import com .netgrif .maven .plugin .module .assembly .AssemblyDescriptorBuilder ;
4
4
import com .netgrif .maven .plugin .module .assembly .DependencySetBuilder ;
5
5
import com .netgrif .maven .plugin .module .parameters .SimpleArtifact ;
6
+ import io .github .classgraph .ClassGraph ;
7
+ import io .github .classgraph .ClassInfo ;
8
+ import io .github .classgraph .ScanResult ;
6
9
import org .apache .maven .artifact .Artifact ;
7
10
import org .apache .maven .execution .MavenSession ;
11
+ import org .apache .maven .model .Developer ;
8
12
import org .apache .maven .plugin .AbstractMojo ;
9
13
import org .apache .maven .plugin .BuildPluginManager ;
10
14
import org .apache .maven .plugin .MojoExecutionException ;
17
21
import org .apache .maven .shared .dependency .graph .DependencyNode ;
18
22
import org .eclipse .sisu .Nullable ;
19
23
20
- import java .io .File ;
21
- import java .io .IOException ;
24
+ import java .io .*;
22
25
import java .nio .file .Files ;
23
26
import java .util .ArrayList ;
24
27
import java .util .HashSet ;
25
28
import java .util .List ;
26
29
import java .util .Set ;
30
+ import java .util .*;
31
+ import java .util .jar .Attributes ;
32
+ import java .util .jar .JarEntry ;
33
+ import java .util .jar .JarInputStream ;
34
+ import java .util .jar .JarOutputStream ;
35
+ import java .util .jar .Manifest ;
36
+ import java .util .stream .Collectors ;
27
37
28
38
import static org .twdata .maven .mojoexecutor .MojoExecutor .*;
29
39
38
48
requiresDependencyResolution = ResolutionScope .COMPILE_PLUS_RUNTIME )
39
49
public class BuildModuleMojo extends AbstractMojo {
40
50
41
- private final Log log = getLog ();
51
+ private Log log () {
52
+ return getLog ();
53
+ }
42
54
43
55
@ Component
44
56
private MavenProject project ;
@@ -64,6 +76,9 @@ public class BuildModuleMojo extends AbstractMojo {
64
76
@ Parameter (property = "singleOutput" )
65
77
private boolean singleOutput = true ;
66
78
79
+ @ Parameter (property = "customManifestOutputJar" , defaultValue = "false" )
80
+ private boolean customManifestOutputJar ;
81
+
67
82
@ Override
68
83
public void execute () throws MojoExecutionException {
69
84
ProjectBuildingRequest buildingRequest = new DefaultProjectBuildingRequest (session .getProjectBuildingRequest ());
@@ -76,21 +91,30 @@ public void execute() throws MojoExecutionException {
76
91
SimpleArtifact hostAppArtifact = getHostAppArtifact ();
77
92
78
93
if (hostAppArtifact == null ) {
79
- log .warn ("Packaging module without host application" );
94
+ log () .warn ("Packaging module without host application" );
80
95
} else {
81
96
hostDep = findDependency (hostAppArtifact .getGroupId (), hostAppArtifact .getArtifactId (), hostAppArtifact .getVersion (), rootNode );
82
97
if (hostDep == null || hostDep .getArtifact () == null )
83
98
throw new HostApplicationNotFoundException ("Cannot find host app artifact as dependency: " + hostAppArtifact );
84
- if (log .isDebugEnabled ())
85
- log .debug ("Host app dependency: " + hostDep .getArtifact ());
99
+ if (log () .isDebugEnabled ())
100
+ log () .debug ("Host app dependency: " + hostDep .getArtifact ());
86
101
hostAppDependencies = flattenDependencies (hostDep );
87
- log .info ("Dependencies aggregated " + hostAppDependencies .size () + " from host app" );
102
+ log () .info ("Dependencies aggregated " + hostAppDependencies .size () + " from host app" );
88
103
hostAppDependencies .forEach (d -> {
89
- if (log .isDebugEnabled ())
90
- log .debug (d .getArtifact ().toString ());
104
+ if (log () .isDebugEnabled ())
105
+ log () .debug (d .getArtifact ().toString ());
91
106
});
92
107
}
93
108
109
+ try {
110
+ createJarWithCustomManifest ();
111
+
112
+ generateSpringFactoriesToOutputDir ();
113
+
114
+ } catch (IOException ioEx ) {
115
+ log ().error ("Failed to create JAR with custom manifest" , ioEx );
116
+ throw new MojoExecutionException ("Failed to create JAR with custom manifest" , ioEx );
117
+ }
94
118
95
119
// Build assembly descriptor
96
120
AssemblyDescriptorBuilder assembly = new AssemblyDescriptorBuilder ();
@@ -103,16 +127,16 @@ public void execute() throws MojoExecutionException {
103
127
File targetDir = new File (project .getBuild ().getDirectory () + File .separator + "assembly" );
104
128
try {
105
129
Files .createDirectories (targetDir .toPath ());
106
- log .info ("Successfully ensured target directory exists: " + targetDir .getAbsolutePath ());
130
+ log () .info ("Successfully ensured target directory exists: " + targetDir .getAbsolutePath ());
107
131
} catch (IOException e ) {
108
132
throw new RuntimeException ("Could not create target directory for package assembly: " + targetDir , e );
109
133
}
110
134
if (!targetDir .getParentFile ().canWrite ()) {
111
135
throw new RuntimeException ("Cannot write to parent directory: " + targetDir .getParent ());
112
136
}
113
137
File descriptor = assembly .build (targetDir .getPath ());
114
- if (log .isDebugEnabled ())
115
- log .debug ("maven-assembly-plugin descriptor saved on path: " + descriptor .getAbsolutePath ());
138
+ if (log () .isDebugEnabled ())
139
+ log () .debug ("maven-assembly-plugin descriptor saved on path: " + descriptor .getAbsolutePath ());
116
140
117
141
// Executes maven-assembly-plugin to build the resulting package
118
142
executeMojo (
@@ -134,10 +158,139 @@ public void execute() throws MojoExecutionException {
134
158
}
135
159
}
136
160
161
+ private void createJarWithCustomManifest () throws IOException {
162
+ File sourceJar = new File (project .getBuild ().getDirectory (), project .getBuild ().getFinalName () + ".jar" );
163
+ if (!sourceJar .exists ()) {
164
+ log ().warn ("Original JAR file not found: " + sourceJar .getAbsolutePath ());
165
+ return ;
166
+ }
167
+
168
+ Manifest manifest = buildCustomManifest ();
169
+
170
+ if (!customManifestOutputJar ) {
171
+ File tempJar = new File (sourceJar .getParent (), sourceJar .getName () + ".tmp" );
172
+ copyJarExcludingManifest (sourceJar , tempJar , manifest );
173
+
174
+ if (!sourceJar .delete ()) {
175
+ throw new IOException ("Could not delete original JAR before renaming temp JAR!" );
176
+ }
177
+ if (!tempJar .renameTo (sourceJar )) {
178
+ throw new IOException ("Could not rename temp JAR to original name!" );
179
+ }
180
+ } else {
181
+ File outJar = new File (project .getBuild ().getDirectory (), project .getBuild ().getFinalName () + "-with-manifest.jar" );
182
+ copyJarExcludingManifest (sourceJar , outJar , manifest );
183
+ log ().info ("Created JAR with custom manifest: " + outJar .getAbsolutePath ());
184
+ }
185
+ }
186
+
187
+ private void copyJarExcludingManifest (File inputJar , File outputJar , Manifest manifest ) throws IOException {
188
+ try (
189
+ JarInputStream jarIn = new JarInputStream (new FileInputStream (inputJar ));
190
+ JarOutputStream jarOut = new JarOutputStream (new FileOutputStream (outputJar ), manifest )
191
+ ) {
192
+ JarEntry entry ;
193
+ byte [] buffer = new byte [8192 ];
194
+ while ((entry = jarIn .getNextJarEntry ()) != null ) {
195
+ String name = entry .getName ();
196
+ if ("META-INF/MANIFEST.MF" .equalsIgnoreCase (name )) {
197
+ continue ;
198
+ }
199
+ jarOut .putNextEntry (new JarEntry (name ));
200
+ int bytesRead ;
201
+ while ((bytesRead = jarIn .read (buffer )) != -1 ) {
202
+ jarOut .write (buffer , 0 , bytesRead );
203
+ }
204
+ jarOut .closeEntry ();
205
+ }
206
+ }
207
+ }
208
+
209
+ private Manifest buildCustomManifest () throws IOException {
210
+ File sourceJar = new File (project .getBuild ().getDirectory (), project .getBuild ().getFinalName () + ".jar" );
211
+ Manifest manifest ;
212
+ try (JarInputStream jarIn = new JarInputStream (new FileInputStream (sourceJar ))) {
213
+ manifest = jarIn .getManifest ();
214
+ if (manifest == null ) {
215
+ manifest = new Manifest ();
216
+ }
217
+ }
218
+ Attributes mainAttrs = manifest .getMainAttributes ();
219
+ if (mainAttrs .getValue (Attributes .Name .MANIFEST_VERSION ) == null ) {
220
+ mainAttrs .put (Attributes .Name .MANIFEST_VERSION , "1.0" );
221
+ }
222
+
223
+ List <Developer > developers = project .getDevelopers () != null ? project .getDevelopers () : Collections .emptyList ();
224
+
225
+ Map <String , String > customAttributes = new LinkedHashMap <>();
226
+ putIfNotNull (customAttributes , "Netgrif-Name" , project .getName ());
227
+ putIfNotNull (customAttributes , "Netgrif-Version" , project .getVersion ());
228
+ putIfNotNull (customAttributes , "Netgrif-Url" , project .getUrl ());
229
+ putIfNotNull (customAttributes , "Netgrif-Description" , project .getDescription ());
230
+ putIfNotNull (customAttributes , "Netgrif-GroupId" , project .getGroupId ());
231
+ putIfNotNull (customAttributes , "Netgrif-ArtifactId" , project .getArtifactId ());
232
+
233
+ if (project .getScm () != null ) {
234
+ putIfNotNull (customAttributes , "Netgrif-SCM-Connection" , project .getScm ().getConnection ());
235
+ putIfNotNull (customAttributes , "Netgrif-SCM-URL" , project .getScm ().getUrl ());
236
+ }
237
+
238
+ if (project .getLicenses () != null && !project .getLicenses ().isEmpty ()) {
239
+ putIfNotNull (customAttributes , "Netgrif-License" , project .getLicenses ().getFirst ());
240
+ }
241
+
242
+ if (project .getOrganization () != null ) {
243
+ putIfNotNull (customAttributes , "Netgrif-Organization" , project .getOrganization ().getName ());
244
+ putIfNotNull (customAttributes , "Netgrif-OrganizationUrl" , project .getOrganization ().getUrl ());
245
+ }
246
+
247
+ if (project .getIssueManagement () != null ) {
248
+ putIfNotNull (customAttributes , "Netgrif-IssueSystem" , project .getIssueManagement ().getSystem ());
249
+ putIfNotNull (customAttributes , "Netgrif-IssueUrl" , project .getIssueManagement ().getUrl ());
250
+ }
251
+
252
+ putIfNotNull (customAttributes , "Netgrif-BuildJdk" , System .getProperty ("java.version" ));
253
+
254
+ customAttributes .put ("Netgrif-BuildTime" , java .time .ZonedDateTime .now ().toString ());
255
+
256
+ String authors = developers .stream ()
257
+ .map (dev -> {
258
+ String name = dev .getName ();
259
+ String email = dev .getEmail ();
260
+ String org = dev .getOrganization ();
261
+ if (name == null || name .isBlank ()) {
262
+ return null ;
263
+ }
264
+ StringBuilder sb = new StringBuilder (name );
265
+ if (org != null && !org .isBlank ()) {
266
+ sb .append (" (" ).append (org ).append (")" );
267
+ }
268
+ if (email != null && !email .isBlank ()) {
269
+ sb .append (" <" ).append (email ).append (">" );
270
+ }
271
+ return sb .toString ();
272
+ })
273
+ .filter (Objects ::nonNull )
274
+ .collect (Collectors .joining ("\n " ));
275
+
276
+ if (!authors .isBlank ()) {
277
+ putIfNotNull (customAttributes , "Netgrif-Author" , authors );
278
+ }
279
+ customAttributes .forEach (mainAttrs ::putValue );
280
+
281
+ return manifest ;
282
+ }
283
+
284
+ private void putIfNotNull (Map <String , String > map , String key , Object value ) {
285
+ if (value != null && !String .valueOf (value ).isBlank ()) {
286
+ map .put (key , String .valueOf (value ));
287
+ }
288
+ }
289
+
137
290
private SimpleArtifact getHostAppArtifact () {
138
291
SimpleArtifact hostAppArtifact = null ;
139
292
if (hostApp == null && (naeVersion == null || naeVersion .isEmpty ())) {
140
- log .warn ("Host application was not found. Packaging module without host application" );
293
+ log () .warn ("Host application was not found. Packaging module without host application" );
141
294
return null ;
142
295
}
143
296
if (naeVersion != null && !naeVersion .isEmpty ()) {
@@ -191,8 +344,8 @@ public void separateDescriptor(AssemblyDescriptorBuilder assembly, @Nullable Dep
191
344
if (hostAppDependencies != null ) {
192
345
hostAppDependencies .forEach (depSet ::exclude );
193
346
}
194
- if (log .isDebugEnabled ())
195
- log .debug ("Excluding extra dependencies: " + excludes );
347
+ if (log () .isDebugEnabled ())
348
+ log () .debug ("Excluding extra dependencies: " + excludes );
196
349
excludes .forEach (depSet ::exclude );
197
350
}
198
351
@@ -225,9 +378,45 @@ public void singleDescriptor(AssemblyDescriptorBuilder assembly, @Nullable Depen
225
378
if (hostAppDependencies != null ) {
226
379
hostAppDependencies .forEach (depSet ::exclude );
227
380
}
228
- if (log .isDebugEnabled ())
229
- log .debug ("Excluding extra dependencies: " + excludes );
381
+ if (log () .isDebugEnabled ())
382
+ log () .debug ("Excluding extra dependencies: " + excludes );
230
383
excludes .forEach (depSet ::exclude );
231
384
}
232
385
386
+ private void generateSpringFactoriesToOutputDir () throws IOException {
387
+ String outputDir = project .getBuild ().getOutputDirectory ();
388
+ File springMetaInf = new File (outputDir , "META-INF/spring" );
389
+ if (!springMetaInf .exists ()) {
390
+ springMetaInf .mkdirs ();
391
+ }
392
+ List <String > configClasses = new ArrayList <>();
393
+ try (ScanResult scanResult = new ClassGraph ()
394
+ .overrideClasspath (outputDir )
395
+ .enableAnnotationInfo ()
396
+ .enableClassInfo ()
397
+ .scan ()) {
398
+ for (ClassInfo ci : scanResult .getAllClasses ()) {
399
+ if (ci .hasAnnotation ("org.springframework.context.annotation.Configuration" )
400
+ || ci .hasAnnotation ("org.springframework.boot.autoconfigure.AutoConfiguration" )) {
401
+ configClasses .add (ci .getName ());
402
+ }
403
+ }
404
+ }
405
+
406
+ if (configClasses .isEmpty ()) {
407
+ return ;
408
+ }
409
+ Collections .sort (configClasses );
410
+
411
+ File importsFile = new File (springMetaInf , "org.springframework.boot.autoconfigure.AutoConfiguration.imports" );
412
+ try (PrintWriter writer = new PrintWriter (new FileWriter (importsFile ))) {
413
+ for (String className : configClasses ) {
414
+ log ().info ("Adding to imports: " + className );
415
+ writer .println (className );
416
+ }
417
+ }
418
+ log ().info ("Generated " + importsFile .getAbsolutePath () + " with auto-configuration: " + configClasses .size () + " entries" );
419
+ }
420
+
421
+
233
422
}
0 commit comments