Add .tox/ to .gitignore
[odlparent.git] / karaf / karaf-maven-plugin / src / main / java / org / apache / karaf / tooling / AssemblyMojo.java
1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one
3  * or more contributor license agreements.  See the NOTICE file
4  * distributed with this work for additional information
5  * regarding copyright ownership.  The ASF licenses this file
6  * to you under the Apache License, Version 2.0 (the
7  * "License"); you may not use this file except in compliance
8  * with the License.  You may obtain a copy of the License at
9  *
10  *  http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing,
13  * software distributed under the License is distributed on an
14  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15  * KIND, either express or implied.  See the License for the
16  * specific language governing permissions and limitations
17  * under the License.
18  */
19 package org.apache.karaf.tooling;
20
21 import java.io.File;
22 import java.io.FileInputStream;
23 import java.io.InputStream;
24 import java.nio.file.Files;
25 import java.nio.file.attribute.PosixFilePermissions;
26 import java.util.ArrayList;
27 import java.util.HashMap;
28 import java.util.List;
29 import java.util.Map;
30
31 import org.apache.karaf.profile.assembly.Builder;
32 import org.apache.karaf.tooling.utils.IoUtils;
33 import org.apache.karaf.tooling.utils.MavenUtil;
34 import org.apache.karaf.tooling.utils.MojoSupport;
35 import org.apache.karaf.tools.utils.model.KarafPropertyEdits;
36 import org.apache.karaf.tools.utils.model.io.stax.KarafPropertyInstructionsModelStaxReader;
37 import org.apache.maven.artifact.Artifact;
38 import org.apache.maven.plugin.MojoExecutionException;
39 import org.apache.maven.plugin.MojoFailureException;
40 import org.apache.maven.plugins.annotations.LifecyclePhase;
41 import org.apache.maven.plugins.annotations.Mojo;
42 import org.apache.maven.plugins.annotations.Parameter;
43 import org.apache.maven.plugins.annotations.ResolutionScope;
44
45 import java.io.File;
46 import java.io.FileInputStream;
47 import java.io.InputStream;
48 import java.nio.file.Files;
49 import java.nio.file.attribute.PosixFilePermissions;
50 import java.util.ArrayList;
51 import java.util.List;
52
53 /**
54  * Creates a customized Karaf distribution by installing features and setting up
55  * configuration files. The plugin gets features from feature.xml files and KAR
56  * archives declared as dependencies or as files configured with the
57  * featureRespositories parameter. It picks up other files, such as config files,
58  * from ${project.build.directory}/classes. Thus, a file in src/main/resources/etc
59  * will be copied by the resource plugin to ${project.build.directory}/classes/etc,
60  * and then added to the assembly by this goal.
61  * <br>
62  */
63 @Mojo(name = "assembly", defaultPhase = LifecyclePhase.PACKAGE, requiresDependencyResolution = ResolutionScope.RUNTIME, threadSafe = true)
64 public class AssemblyMojo extends MojoSupport {
65
66     /**
67      * Base directory used to overwrite resources in generated assembly after the build (resource directory).
68      */
69     @Parameter(defaultValue = "${project.basedir}/src/main/resources/assembly")
70     protected File sourceDirectory;
71
72     /**
73      * Base directory used to copy the resources during the build (working directory).
74      */
75     @Parameter(defaultValue = "${project.build.directory}/assembly")
76     protected File workDirectory;
77
78     /**
79      * Features configuration file (etc/org.apache.karaf.features.cfg).
80      */
81     @Parameter(defaultValue = "${project.build.directory}/assembly/etc/org.apache.karaf.features.cfg")
82     protected File featuresCfgFile;
83
84     /**
85      * startup.properties file.
86      */
87     @Parameter(defaultValue = "${project.build.directory}/assembly/etc/startup.properties")
88     protected File startupPropertiesFile;
89
90     /**
91      * Directory used during build to construction the Karaf system repository.
92      */
93     @Parameter(defaultValue="${project.build.directory}/assembly/system")
94     protected File systemDirectory;
95
96     /**
97      * default start level for bundles in features that don't specify it.
98      */
99     @Parameter
100     protected int defaultStartLevel = 30;
101
102     @Parameter
103     private List<String> startupRepositories;
104     @Parameter
105     private List<String> bootRepositories;
106     @Parameter
107     private List<String> installedRepositories;
108
109     /**
110      * List of features from runtime-scope features xml and kars to be installed into system and listed in startup.properties.
111      */
112     @Parameter
113     private List<String> startupFeatures;
114
115     /**
116      * List of features from runtime-scope features xml and kars to be installed into system repo and listed in features service boot features.
117      */
118     @Parameter
119     private List<String> bootFeatures;
120
121     /**
122      * List of features from runtime-scope features xml and kars to be installed into system repo and not mentioned elsewhere.
123      */
124     @Parameter
125     private List<String> installedFeatures;
126
127     @Parameter
128     private List<String> blacklistedFeatures;
129
130     @Parameter
131     private List<String> startupBundles;
132     @Parameter
133     private List<String> bootBundles;
134     @Parameter
135     private List<String> installedBundles;
136     @Parameter
137     private List<String> blacklistedBundles;
138     
139     @Parameter
140     private String profilesUri;
141
142     @Parameter
143     private List<String> bootProfiles;
144
145     @Parameter
146     private List<String> startupProfiles;
147
148     @Parameter
149     private List<String> installedProfiles;
150
151     @Parameter
152     private List<String> blacklistedProfiles;
153
154     @Parameter
155     private Builder.BlacklistPolicy blacklistPolicy = Builder.BlacklistPolicy.Discard;
156
157     /**
158      * Ignore the dependency attribute (dependency="[true|false]") on bundle
159      */
160     @Parameter(defaultValue = "false")
161     protected boolean ignoreDependencyFlag;
162
163     /**
164      * Additional feature repositories
165      */
166     @Parameter
167     protected List<String> featureRepositories;
168
169     @Parameter
170     protected List<String> libraries;
171
172     /**
173      * Use reference: style urls in startup.properties
174      */
175     @Parameter(defaultValue = "false")
176     protected boolean useReferenceUrls;
177
178     /**
179      * Include project build output directory in the assembly
180      */
181     @Parameter(defaultValue = "true")
182     protected boolean includeBuildOutputDirectory;
183
184     @Parameter
185     protected boolean installAllFeaturesByDefault = true;
186
187     @Parameter
188     protected Builder.KarafVersion karafVersion = Builder.KarafVersion.v4x;
189
190     /**
191      * Specify the version of Java SE to be assumed for osgi.ee.
192      */
193     @Parameter(defaultValue = "1.7")
194     protected String javase;
195
196     /**
197      * Specify an XML file that instructs this goal to apply edits to
198      * one or more standard Karaf property files.
199      * The contents of this file are documented in detail on
200      * <a href="karaf-property-instructions-model.html">this page</a>.
201      * This allows you to
202      * customize these files without making copies in your resources
203      * directories. Here's a simple example:
204      * {@literal
205     <pre>
206       <property-edits xmlns="http://karaf.apache.org/tools/property-edits/1.0.0">
207          <edits>
208           <edit>
209             <file>config.properties</file>
210             <operation>put</operation>
211             <key>karaf.framework</key>
212             <value>equinox</value>
213           </edit>
214           <edit>
215             <file>config.properties</file>
216             <operation>extend</operation>
217             <key>org.osgi.framework.system.capabilities</key>
218             <value>my-magic-capability</value>
219           </edit>
220           <edit>
221             <file>config.properties</file>
222             <operation prepend='true'>extend</operation>
223             <key>some-other-list</key>
224             <value>my-value-goes-first</value>
225             </edit>
226          </edits>
227       </property-edits>
228      </pre>
229     }
230      */
231     @Parameter(defaultValue = "${project.basedir}/src/main/karaf/assembly-property-edits.xml")
232     protected String propertyFileEdits;
233
234     @Override
235     public void execute() throws MojoExecutionException, MojoFailureException {
236         try {
237             doExecute();
238         }
239         catch (MojoExecutionException | MojoFailureException e) {
240             throw e;
241         }
242         catch (Exception e) {
243             throw new MojoExecutionException("Unable to build assembly", e);
244         }
245     }
246
247     protected void doExecute() throws Exception {
248         startupRepositories = nonNullList(startupRepositories);
249         bootRepositories = nonNullList(bootRepositories);
250         installedRepositories = nonNullList(installedRepositories);
251         startupBundles = nonNullList(startupBundles);
252         bootBundles = nonNullList(bootBundles);
253         installedBundles = nonNullList(installedBundles);
254         blacklistedBundles = nonNullList(blacklistedBundles);
255         startupFeatures = nonNullList(startupFeatures);
256         bootFeatures = nonNullList(bootFeatures);
257         installedFeatures = nonNullList(installedFeatures);
258         blacklistedFeatures = nonNullList(blacklistedFeatures);
259         startupProfiles = nonNullList(startupProfiles);
260         bootProfiles = nonNullList(bootProfiles);
261         installedProfiles = nonNullList(installedProfiles);
262         blacklistedProfiles = nonNullList(blacklistedProfiles);
263
264         if (!startupProfiles.isEmpty() || !bootProfiles.isEmpty() || !installedProfiles.isEmpty()) {
265             if (profilesUri == null) {
266                 throw new IllegalArgumentException("profilesDirectory must be specified");
267             }
268         }
269
270         if (featureRepositories != null && !featureRepositories.isEmpty()) {
271             getLog().warn("Use of featureRepositories is deprecated, use startupRepositories, bootRepositories or installedRepositories instead");
272             startupRepositories.addAll(featureRepositories);
273             bootRepositories.addAll(featureRepositories);
274             installedRepositories.addAll(featureRepositories);
275         }
276
277         StringBuilder remote = new StringBuilder();
278         for (Object obj : project.getRemoteProjectRepositories()) {
279             if (remote.length() > 0) {
280                 remote.append(",");
281             }
282             remote.append(invoke(obj, "getUrl"));
283             remote.append("@id=").append(invoke(obj, "getId"));
284             if (!((Boolean) invoke(getPolicy(obj, false), "isEnabled"))) {
285                 remote.append("@noreleases");
286             }
287             if ((Boolean) invoke(getPolicy(obj, true), "isEnabled")) {
288                 remote.append("@snapshots");
289             }
290         }
291         getLog().info("Using repositories: " + remote.toString());
292
293         Builder builder = Builder.newInstance();
294         builder.offline(mavenSession.isOffline());
295         builder.localRepository(localRepo.getBasedir());
296         builder.mavenRepositories(remote.toString());
297         builder.javase(javase);
298
299         // Set up blacklisted items
300         builder.blacklistBundles(blacklistedBundles);
301         builder.blacklistFeatures(blacklistedFeatures);
302         builder.blacklistProfiles(blacklistedProfiles);
303         builder.blacklistPolicy(blacklistPolicy);
304
305         if (propertyFileEdits != null) {
306             File file = new File(propertyFileEdits);
307             if (file.exists()) {
308                 KarafPropertyEdits edits;
309                 try (InputStream editsStream = new FileInputStream(propertyFileEdits)) {
310                     KarafPropertyInstructionsModelStaxReader kipmsr = new KarafPropertyInstructionsModelStaxReader();
311                     edits = kipmsr.read(editsStream, true);
312                 }
313                 builder.propertyEdits(edits);
314             }
315         }
316
317         // creating system directory
318         getLog().info("Creating work directory");
319         builder.homeDirectory(workDirectory.toPath());
320         IoUtils.deleteRecursive(workDirectory);
321         workDirectory.mkdirs();
322
323         List<String> startupKars = new ArrayList<>();
324         List<String> bootKars = new ArrayList<>();
325         List<String> installedKars = new ArrayList<>();
326
327         // Loading kars and features repositories
328         getLog().info("Loading kar and features repositories dependencies");
329         for (Artifact artifact : project.getDependencyArtifacts()) {
330             Builder.Stage stage;
331             switch (artifact.getScope()) {
332             case "compile":
333                 stage = Builder.Stage.Startup;
334                 break;
335             case "runtime":
336                 stage = Builder.Stage.Boot;
337                 break;
338             case "provided":
339                 stage = Builder.Stage.Installed;
340                 break;
341             default:
342                 continue;
343             }
344             if ("kar".equals(artifact.getType())) {
345                 String uri = artifactToMvn(artifact);
346                 switch (stage) {
347                 case Startup:   startupKars.add(uri); break;
348                 case Boot:      bootKars.add(uri); break;
349                 case Installed: installedKars.add(uri); break;
350                 }
351             } else if ("features".equals(artifact.getClassifier())) {
352                 String uri = artifactToMvn(artifact);
353                 switch (stage) {
354                 case Startup:   startupRepositories.add(uri); break;
355                 case Boot:      bootRepositories.add(uri); break;
356                 case Installed: installedRepositories.add(uri); break;
357                 }
358             } else if ("jar".equals(artifact.getType()) || "bundle".equals(artifact.getType())) {
359                 String uri = artifactToMvn(artifact);
360                 switch (stage) {
361                 case Startup:   startupBundles.add(uri); break;
362                 case Boot:      bootBundles.add(uri); break;
363                 case Installed: installedBundles.add(uri); break;
364                 }
365             }
366         }
367
368         builder.karafVersion(karafVersion)
369                .useReferenceUrls(useReferenceUrls)
370                .defaultAddAll(installAllFeaturesByDefault)
371                .ignoreDependencyFlag(ignoreDependencyFlag);
372         if (profilesUri != null) {
373             builder.profilesUris(profilesUri);
374         }
375         if (libraries != null) {
376             builder.libraries(libraries.toArray(new String[libraries.size()]));
377         }
378         // Startup
379         builder.defaultStage(Builder.Stage.Startup)
380                .kars(toArray(startupKars))
381                .repositories(startupFeatures.isEmpty() && startupProfiles.isEmpty() && installAllFeaturesByDefault, toArray(startupRepositories))
382                .features(toArray(startupFeatures))
383                .bundles(toArray(startupBundles))
384                .profiles(toArray(startupProfiles));
385         // Boot
386         builder.defaultStage(Builder.Stage.Boot)
387                 .kars(toArray(bootKars))
388                 .repositories(bootFeatures.isEmpty() && bootProfiles.isEmpty() && installAllFeaturesByDefault, toArray(bootRepositories))
389                 .features(toArray(bootFeatures))
390                 .bundles(toArray(bootBundles))
391                 .profiles(toArray(bootProfiles));
392         // Installed
393         builder.defaultStage(Builder.Stage.Installed)
394                 .kars(toArray(installedKars))
395                 .repositories(installedFeatures.isEmpty() && installedProfiles.isEmpty() && installAllFeaturesByDefault, toArray(installedRepositories))
396                 .features(toArray(installedFeatures))
397                 .bundles(toArray(installedBundles))
398                 .profiles(toArray(installedProfiles));
399
400         // Generate the assembly
401         builder.generateAssembly();
402
403         // Include project classes content
404         if (includeBuildOutputDirectory)
405             IoUtils.copyDirectory(new File(project.getBuild().getOutputDirectory()), workDirectory);
406
407         // Overwrite assembly dir contents
408         if (sourceDirectory.exists())
409             IoUtils.copyDirectory(sourceDirectory, workDirectory);
410
411         // Chmod the bin/* scripts
412         File[] files = new File(workDirectory, "bin").listFiles();
413         if( files!=null ) {
414             for (File file : files) {
415                 if( !file.getName().endsWith(".bat") ) {
416                     try {
417                         Files.setPosixFilePermissions(file.toPath(), PosixFilePermissions.fromString("rwxr-xr-x"));
418                     } catch (Throwable ignore) {
419                         // we tried our best, perhaps the OS does not support posix file perms.
420                     }
421                 }
422             }
423         }
424     }
425
426     private Object invoke(Object object, String getter) throws MojoExecutionException {
427         try {
428             return object.getClass().getMethod(getter).invoke(object);
429         } catch (Exception e) {
430             throw new MojoExecutionException("Unable to build remote repository from " + object.toString(), e);
431         }
432     }
433
434     private Object getPolicy(Object object, boolean snapshots) throws MojoExecutionException {
435         return invoke(object, "getPolicy", new Class[] { Boolean.TYPE }, new Object[] { snapshots });
436     }
437
438     private Object invoke(Object object, String getter, Class[] types, Object[] params) throws MojoExecutionException {
439         try {
440             return object.getClass().getMethod(getter, types).invoke(object, params);
441         } catch (Exception e) {
442             throw new MojoExecutionException("Unable to build remote repository from " + object.toString(), e);
443         }
444     }
445
446     private String artifactToMvn(Artifact artifact) throws MojoExecutionException {
447         String uri;
448
449         String groupId = artifact.getGroupId();
450         String artifactId = artifact.getArtifactId();
451         String version = artifact.getBaseVersion();
452         String type = artifact.getArtifactHandler().getExtension();
453         String classifier = artifact.getClassifier();
454
455         if (MavenUtil.isEmpty(classifier)) {
456             if ("jar".equals(type)) {
457                 uri = String.format("mvn:%s/%s/%s", groupId, artifactId, version);
458             } else {
459                 uri = String.format("mvn:%s/%s/%s/%s", groupId, artifactId, version, type);
460             }
461         } else {
462             uri = String.format("mvn:%s/%s/%s/%s/%s", groupId, artifactId, version, type, classifier);
463         }
464         return uri;
465     }
466
467     private String[] toArray(List<String> strings) {
468         return strings.toArray(new String[strings.size()]);
469     }
470
471     private List<String> nonNullList(List<String> list) {
472         return list == null ? new ArrayList<String>() : list;
473     }
474
475 }