PageRenderTime 54ms CodeModel.GetById 14ms RepoModel.GetById 1ms app.codeStats 0ms

/amps-maven-plugin/src/main/java/com/atlassian/maven/plugins/amps/MavenGoals.java

https://bitbucket.org/atlassian/amps
Java | 1049 lines | 794 code | 120 blank | 135 comment | 54 complexity | 5e7008797b8a4b14832e17480ccc82b7 MD5 | raw file
Possible License(s): Apache-2.0, BSD-3-Clause
  1. package com.atlassian.maven.plugins.amps;
  2. import aQute.bnd.osgi.Constants;
  3. import com.atlassian.maven.plugins.amps.minifier.MinifierParameters;
  4. import com.atlassian.maven.plugins.amps.minifier.ResourcesMinifier;
  5. import com.atlassian.maven.plugins.amps.util.AmpsCreatePluginPrompter;
  6. import com.atlassian.maven.plugins.amps.util.CreatePluginProperties;
  7. import com.atlassian.maven.plugins.amps.util.MojoExecutorWrapper;
  8. import com.atlassian.maven.plugins.amps.util.VersionUtils;
  9. import com.googlecode.htmlcompressor.compressor.XmlCompressor;
  10. import org.apache.commons.io.FileUtils;
  11. import org.apache.commons.io.filefilter.FileFilterUtils;
  12. import org.apache.commons.io.filefilter.IOFileFilter;
  13. import org.apache.commons.lang3.StringUtils;
  14. import org.apache.maven.archetype.common.DefaultPomManager;
  15. import org.apache.maven.archetype.common.MavenJDOMWriter;
  16. import org.apache.maven.archetype.common.util.Format;
  17. import org.apache.maven.model.FileSet;
  18. import org.apache.maven.model.Model;
  19. import org.apache.maven.model.Plugin;
  20. import org.apache.maven.plugin.MojoExecutionException;
  21. import org.apache.maven.plugin.logging.Log;
  22. import org.apache.maven.project.MavenProject;
  23. import org.codehaus.plexus.components.interactivity.PrompterException;
  24. import org.codehaus.plexus.util.IOUtil;
  25. import org.codehaus.plexus.util.xml.Xpp3Dom;
  26. import org.jdom.Document;
  27. import org.jdom.input.SAXBuilder;
  28. import org.twdata.maven.mojoexecutor.MojoExecutor.Element;
  29. import org.twdata.maven.mojoexecutor.MojoExecutor.ExecutionEnvironment;
  30. import javax.annotation.Nonnull;
  31. import javax.annotation.Nullable;
  32. import java.io.File;
  33. import java.io.FileInputStream;
  34. import java.io.FileOutputStream;
  35. import java.io.FilenameFilter;
  36. import java.io.IOException;
  37. import java.io.InputStream;
  38. import java.io.OutputStreamWriter;
  39. import java.io.UncheckedIOException;
  40. import java.io.Writer;
  41. import java.nio.charset.Charset;
  42. import java.nio.file.Files;
  43. import java.nio.file.Path;
  44. import java.nio.file.Paths;
  45. import java.util.ArrayList;
  46. import java.util.Collection;
  47. import java.util.HashSet;
  48. import java.util.List;
  49. import java.util.Map;
  50. import java.util.Map.Entry;
  51. import java.util.Properties;
  52. import java.util.Set;
  53. import java.util.TreeSet;
  54. import java.util.jar.Manifest;
  55. import java.util.regex.Matcher;
  56. import java.util.stream.Stream;
  57. import static com.atlassian.maven.plugins.amps.BannedDependencies.getBannedElements;
  58. import static com.atlassian.maven.plugins.amps.product.manager.BaseUrlUtils.getBaseUrl;
  59. import static com.atlassian.maven.plugins.amps.util.FileUtils.file;
  60. import static java.lang.String.format;
  61. import static java.nio.charset.StandardCharsets.UTF_8;
  62. import static java.nio.file.Files.createDirectories;
  63. import static java.nio.file.Files.createTempDirectory;
  64. import static java.nio.file.Files.walk;
  65. import static java.util.Map.Entry.comparingByKey;
  66. import static java.util.Objects.requireNonNull;
  67. import static java.util.stream.Collectors.groupingBy;
  68. import static java.util.stream.Collectors.joining;
  69. import static java.util.stream.Collectors.mapping;
  70. import static java.util.stream.Collectors.toCollection;
  71. import static java.util.stream.Collectors.toMap;
  72. import static org.apache.commons.io.FileUtils.deleteDirectory;
  73. import static org.apache.commons.lang3.StringUtils.isNotBlank;
  74. import static org.twdata.maven.mojoexecutor.MojoExecutor.artifactId;
  75. import static org.twdata.maven.mojoexecutor.MojoExecutor.configuration;
  76. import static org.twdata.maven.mojoexecutor.MojoExecutor.element;
  77. import static org.twdata.maven.mojoexecutor.MojoExecutor.goal;
  78. import static org.twdata.maven.mojoexecutor.MojoExecutor.groupId;
  79. import static org.twdata.maven.mojoexecutor.MojoExecutor.name;
  80. import static org.twdata.maven.mojoexecutor.MojoExecutor.plugin;
  81. import static org.twdata.maven.mojoexecutor.MojoExecutor.version;
  82. /**
  83. * Executes specific maven goals
  84. */
  85. public class MavenGoals {
  86. /**
  87. * Defines a Failsafe/Surefire {@code %regex} pattern which can be used to exclude/include integration tests.
  88. * <p>
  89. * Pattern handling for excludes and includes changed in Surefire 2.22, and patterns that don't start with
  90. * "**&#47;" are now prefixed with it automatically. That means "it/**" becomes "**&#47;it/**", which matches
  91. * any test with an "it" package anywhere, not just at the root. Regexes are <i>not</i> automatically prefixed,
  92. * so using a regex pattern here allows us to continue only matching tests with an "it" package at the root.
  93. * <p>
  94. * <b>Warning</b>: Surefire's documentation states that all slashes are forward, even on Windows. However,
  95. * <a href="https://github.com/bturner/surefire-slashes">a simple test</a> proves that that's not the case.
  96. * <a href="https://issues.apache.org/jira/browse/SUREFIRE-1599">SUREFIRE-1599</a> has been created to track
  97. * this mismatch between the documentation and the implementation.
  98. *
  99. * @since 8.0
  100. */
  101. public static final String REGEX_INTEGRATION_TESTS = "%regex[it[/\\\\].*]";
  102. private static final String ABSTRACT_CLASSES = "**/Abstract*";
  103. private static final String INNER_CLASSES = "**/*$*";
  104. private static final String REPORTS_DIRECTORY = "reportsDirectory";
  105. private final MavenContext ctx;
  106. private final Log log;
  107. private final MojoExecutorWrapper mojoExecutorWrapper;
  108. public MavenGoals(final MavenContext ctx, final MojoExecutorWrapper mojoExecutorWrapper) {
  109. this.ctx = requireNonNull(ctx);
  110. this.log = ctx.getLog();
  111. this.mojoExecutorWrapper = requireNonNull(mojoExecutorWrapper);
  112. }
  113. private ExecutionEnvironment executionEnvironment() {
  114. return ctx.getExecutionEnvironment();
  115. }
  116. public MavenProject getContextProject() {
  117. return ctx.getProject();
  118. }
  119. public void executeAmpsRecursively(final String ampsVersion, final String ampsGoal, final Xpp3Dom configuration)
  120. throws MojoExecutionException {
  121. mojoExecutorWrapper.executeWithMergedConfig(
  122. plugin(
  123. groupId("com.atlassian.maven.plugins"),
  124. artifactId("amps-maven-plugin"),
  125. version(ampsVersion)
  126. ),
  127. goal(ampsGoal),
  128. configuration,
  129. executionEnvironment());
  130. }
  131. public void createPlugin(final String productId, AmpsCreatePluginPrompter createPrompter) throws MojoExecutionException {
  132. CreatePluginProperties props = null;
  133. Properties systemProps = System.getProperties();
  134. if (systemProps.containsKey("groupId")
  135. && systemProps.containsKey("artifactId")
  136. && systemProps.containsKey("version")
  137. && systemProps.containsKey("package")) {
  138. props = new CreatePluginProperties(systemProps.getProperty("groupId"),
  139. systemProps.getProperty("artifactId"), systemProps.getProperty("version"),
  140. systemProps.getProperty("package"), systemProps.getProperty("useOsgiJavaConfig", "N"));
  141. }
  142. if (props == null) {
  143. try {
  144. props = createPrompter.prompt();
  145. } catch (PrompterException e) {
  146. throw new MojoExecutionException("Unable to gather properties", e);
  147. }
  148. }
  149. if (props != null) {
  150. ExecutionEnvironment execEnv = executionEnvironment();
  151. Properties userProperties = execEnv.getMavenSession().getUserProperties();
  152. userProperties.setProperty("groupId", props.getGroupId());
  153. userProperties.setProperty("artifactId", props.getArtifactId());
  154. userProperties.setProperty("version", props.getVersion());
  155. userProperties.setProperty("package", props.getThePackage());
  156. userProperties.setProperty("useOsgiJavaConfig", props.getUseOsgiJavaConfigInMavenInvocationFormat());
  157. mojoExecutorWrapper.executeWithMergedConfig(
  158. ctx.getPlugin("org.apache.maven.plugins", "maven-archetype-plugin"),
  159. goal("generate"),
  160. configuration(
  161. element(name("archetypeGroupId"), "com.atlassian.maven.archetypes"),
  162. element(name("archetypeArtifactId"), (productId.equals("all") ? "" : productId + "-") + "plugin-archetype"),
  163. element(name("archetypeVersion"), VersionUtils.getVersion()),
  164. element(name("interactiveMode"), "false")
  165. ),
  166. execEnv);
  167. /*
  168. The problem is if plugin is sub of multiple module project, then the pom file will be add parent section.
  169. When add parent section to module pom file, maven use the default Format with lineEnding is \r\n.
  170. This step add \r\n character as the line ending.
  171. Call the function below to remove cr (\r) character
  172. */
  173. correctCrlf(props.getArtifactId());
  174. File pluginDir = new File(ctx.getProject().getBasedir(), props.getArtifactId());
  175. if (pluginDir.exists()) {
  176. File src = new File(pluginDir, "src");
  177. File test = new File(src, "test");
  178. File java = new File(test, "java");
  179. String packagePath = props.getThePackage().replaceAll("\\.", Matcher.quoteReplacement(File.separator));
  180. File packageFile = new File(java, packagePath);
  181. File packageUT = new File(packageFile, "ut");
  182. File packageIT = new File(packageFile, "it");
  183. File ut = new File(new File(java, "ut"), packagePath);
  184. File it = new File(new File(java, "it"), packagePath);
  185. if (packageFile.exists()) {
  186. try {
  187. if (packageUT.exists()) {
  188. FileUtils.copyDirectory(packageUT, ut);
  189. }
  190. if (packageIT.exists()) {
  191. FileUtils.copyDirectory(packageIT, it);
  192. }
  193. IOFileFilter filter = FileFilterUtils.and(FileFilterUtils.notFileFilter(FileFilterUtils.nameFileFilter("it")), FileFilterUtils.notFileFilter(FileFilterUtils.nameFileFilter("ut")));
  194. com.atlassian.maven.plugins.amps.util.FileUtils.cleanDirectory(java, filter);
  195. } catch (IOException e) {
  196. //for now just ignore
  197. }
  198. }
  199. }
  200. }
  201. }
  202. /**
  203. * Helper function to scan all generated folder and list all pom.xml files that need to be re-write to remove \r
  204. */
  205. private void correctCrlf(String artifactId) {
  206. if (ctx != null && ctx.getProject() != null
  207. && ctx.getProject().getBasedir() != null && ctx.getProject().getBasedir().exists()) {
  208. File outputDirectoryFile = new File(ctx.getProject().getBasedir(), artifactId);
  209. if (outputDirectoryFile.exists()) {
  210. FilenameFilter pomFilter = (dir, name) -> "pom.xml".equals(name);
  211. File[] pomFiles = outputDirectoryFile.listFiles(pomFilter);
  212. DefaultPomManager pomManager = new DefaultPomManager();
  213. for (File pom : pomFiles) {
  214. processCorrectCrlf(pomManager, pom);
  215. }
  216. }
  217. }
  218. }
  219. /**
  220. * Helper function to re-write pom.xml file with lineEnding \n instead of \r\n
  221. */
  222. protected void processCorrectCrlf(DefaultPomManager pomManager, File pom) {
  223. InputStream inputStream = null;
  224. Writer outputStreamWriter = null;
  225. final Model model;
  226. try {
  227. model = pomManager.readPom(pom);
  228. String fileEncoding = StringUtils.isEmpty(model.getModelEncoding()) ? model.getModelEncoding() : "UTF-8";
  229. inputStream = new FileInputStream(pom);
  230. SAXBuilder builder = new SAXBuilder();
  231. Document doc = builder.build(inputStream);
  232. // The cdata parts of the pom are not preserved from initial to target
  233. MavenJDOMWriter writer = new MavenJDOMWriter();
  234. outputStreamWriter = new OutputStreamWriter(new FileOutputStream(pom), fileEncoding);
  235. Format form = Format.getRawFormat().setEncoding(fileEncoding);
  236. form.setLineSeparator("\n");
  237. writer.write(model, doc, outputStreamWriter, form);
  238. } catch (Exception e) {
  239. log.error("Have exception when try correct line ending.", e);
  240. } finally {
  241. IOUtil.close(inputStream);
  242. IOUtil.close(outputStreamWriter);
  243. }
  244. }
  245. public void copyBundledDependencies() throws MojoExecutionException {
  246. mojoExecutorWrapper.executeWithMergedConfig(
  247. ctx.getPlugin("org.apache.maven.plugins", "maven-dependency-plugin"),
  248. goal("copy-dependencies"),
  249. configuration(
  250. // Here, runtime means only compile- and runtime-scoped dependencies
  251. element(name("includeScope"), "runtime"),
  252. element(name("includeTypes"), "jar"),
  253. element(name("outputDirectory"), "${project.build.outputDirectory}/META-INF/lib")
  254. ),
  255. executionEnvironment()
  256. );
  257. }
  258. /**
  259. * Uses the Enforcer plugin to check that the current project has no platform modules in {@code compile} scope,
  260. * even transitively, i.e. it will not bundle any such artifacts.
  261. *
  262. * @param banExcludes any dependencies to be allowed, in the form {@code groupId:artifactId[:version][:type]}
  263. * @throws MojoExecutionException if something goes wrong
  264. */
  265. public void validateBannedDependencies(final Set<String> banExcludes) throws MojoExecutionException {
  266. log.info("validate banned dependencies");
  267. mojoExecutorWrapper.executeWithMergedConfig(
  268. plugin(
  269. groupId("org.apache.maven.plugins"),
  270. artifactId("maven-enforcer-plugin"),
  271. version("3.0.0-M3")
  272. ),
  273. goal("enforce"),
  274. configuration(
  275. element(name("rules"),
  276. element(name("bannedDependencies"),
  277. element(name("searchTransitive"), "true"),
  278. element(name("message"), "make sure platform artifacts are not bundled into plugin"),
  279. element(name("excludes"), getBannedElements(banExcludes).toArray(new Element[0])))
  280. )
  281. ),
  282. executionEnvironment()
  283. );
  284. }
  285. public void copyTestBundledDependencies(List<ProductArtifact> testBundleExcludes) throws MojoExecutionException {
  286. StringBuilder sb = new StringBuilder();
  287. for (ProductArtifact artifact : testBundleExcludes) {
  288. log.info("excluding artifact from test jar: " + artifact.getArtifactId());
  289. sb.append(",").append(artifact.getArtifactId());
  290. }
  291. String customExcludes = sb.toString();
  292. mojoExecutorWrapper.executeWithMergedConfig(
  293. ctx.getPlugin("org.apache.maven.plugins", "maven-dependency-plugin"),
  294. goal("copy-dependencies"),
  295. configuration(
  296. element(name("excludeArtifactIds"), "junit" + customExcludes),
  297. element(name("useSubDirectoryPerScope"), "true"),
  298. element(name("outputDirectory"), "${project.build.directory}/testlibs")
  299. ),
  300. executionEnvironment()
  301. );
  302. File targetDir = new File(ctx.getProject().getBuild().getDirectory());
  303. File testlibsDir = new File(targetDir, "testlibs");
  304. File compileLibs = new File(testlibsDir, "compile");
  305. File testLibs = new File(testlibsDir, "test");
  306. File testClassesDir = new File(ctx.getProject().getBuild().getTestOutputDirectory());
  307. File metainfDir = new File(testClassesDir, "META-INF");
  308. File libDir = new File(metainfDir, "lib");
  309. try {
  310. compileLibs.mkdirs();
  311. testLibs.mkdirs();
  312. libDir.mkdirs();
  313. FileUtils.copyDirectory(compileLibs, libDir);
  314. FileUtils.copyDirectory(testLibs, libDir);
  315. } catch (IOException e) {
  316. throw new MojoExecutionException("unable to copy test libs", e);
  317. }
  318. }
  319. public void copyTestBundledDependenciesExcludingTestScope(List<ProductArtifact> testBundleExcludes) throws MojoExecutionException {
  320. StringBuilder sb = new StringBuilder();
  321. for (ProductArtifact artifact : testBundleExcludes) {
  322. log.info("excluding artifact from test jar: " + artifact.getArtifactId());
  323. sb.append(",").append(artifact.getArtifactId());
  324. }
  325. String customExcludes = sb.toString();
  326. mojoExecutorWrapper.executeWithMergedConfig(
  327. ctx.getPlugin("org.apache.maven.plugins", "maven-dependency-plugin"),
  328. goal("copy-dependencies"),
  329. configuration(
  330. // Here, runtime means only compile- and runtime-scoped dependencies
  331. element(name("includeScope"), "runtime"),
  332. element(name("includeTypes"), "jar"),
  333. element(name("excludeArtifactIds"), "junit" + customExcludes),
  334. element(name("outputDirectory"), "${project.build.testOutputDirectory}/META-INF/lib")
  335. ),
  336. executionEnvironment()
  337. );
  338. }
  339. private void extractDependencies(final Xpp3Dom configuration) throws MojoExecutionException {
  340. // Save a copy of the given config (it's mutated when we do the first extraction)
  341. final Xpp3Dom copyOfConfiguration = new Xpp3Dom(configuration);
  342. // Do the extraction they asked for ...
  343. doExtractDependencies(configuration);
  344. // ... but check whether that caused any files to be overwritten
  345. warnAboutOverwrites(copyOfConfiguration);
  346. }
  347. private void warnAboutOverwrites(final Xpp3Dom configuration) throws MojoExecutionException {
  348. final Path tempDirectory = createTempDirectoryForOverwriteDetection();
  349. configuration.getChild("outputDirectory").setValue(tempDirectory.toString());
  350. configuration.addChild(element("useSubDirectoryPerArtifact", "true").toDom());
  351. // We set these overWrite flags so that Maven will allow each dependency to be unpacked again (see MDEP-586)
  352. configuration.addChild(element("overWriteReleases", "true").toDom());
  353. configuration.addChild(element("overWriteSnapshots", "true").toDom());
  354. configuration.addChild(element("silent", "true").toDom());
  355. doExtractDependencies(configuration);
  356. checkForOverwrites(tempDirectory);
  357. try {
  358. deleteDirectory(tempDirectory.toFile());
  359. } catch (final IOException ignored) {
  360. // Ignore; it's in the temp folder anyway
  361. }
  362. }
  363. private void checkForOverwrites(final Path dependencyDirectory) {
  364. try (final Stream<Path> fileStream = walk(dependencyDirectory)) {
  365. // Map all dependency files to the artifacts that contain them
  366. final Map<Path, Set<Path>> artifactsByPath = fileStream
  367. .filter(Files::isRegularFile)
  368. .map(dependencyDirectory::relativize)
  369. .collect(groupingBy(MavenGoals::tail, mapping(MavenGoals::head, toCollection(TreeSet::new))));
  370. // Find any clashes
  371. final Map<Path, Set<Path>> clashes = artifactsByPath.entrySet().stream()
  372. .filter(e -> e.getValue().size() > 1)
  373. .collect(toMap(Entry::getKey, Entry::getValue));
  374. if (!clashes.isEmpty()) {
  375. logWarnings(clashes);
  376. }
  377. } catch (IOException e) {
  378. throw new UncheckedIOException(e);
  379. }
  380. }
  381. private void logWarnings(final Map<Path, Set<Path>> clashes) {
  382. log.warn("Extracting your plugin's dependencies caused the following file(s) to overwrite each other:");
  383. clashes.entrySet().stream()
  384. .sorted(comparingByKey())
  385. .forEach(e -> log.warn(format("-- %s from %s", e.getKey(), e.getValue())));
  386. log.warn("To prevent this, set <extractDependencies> to false in your AMPS configuration");
  387. }
  388. private static Path head(final Path path) {
  389. return path.subpath(0, 1);
  390. }
  391. private static Path tail(final Path path) {
  392. return path.subpath(1, path.getNameCount());
  393. }
  394. private Path createTempDirectoryForOverwriteDetection() {
  395. final Path targetDirectory = Paths.get(ctx.getProject().getBuild().getDirectory());
  396. try {
  397. createDirectories(targetDirectory);
  398. return createTempDirectory(targetDirectory, "amps-overwrite-detection-");
  399. } catch (final IOException e) {
  400. throw new UncheckedIOException(e);
  401. }
  402. }
  403. private void doExtractDependencies(final Xpp3Dom configuration) throws MojoExecutionException {
  404. mojoExecutorWrapper.executeWithMergedConfig(
  405. ctx.getPlugin("org.apache.maven.plugins", "maven-dependency-plugin"),
  406. goal("unpack-dependencies"),
  407. configuration,
  408. executionEnvironment()
  409. );
  410. }
  411. public void extractBundledDependencies() throws MojoExecutionException {
  412. extractDependencies(configuration(
  413. // Here, runtime means only compile- and runtime-scoped dependencies
  414. element(name("includeScope"), "runtime"),
  415. element(name("includeTypes"), "jar"),
  416. element(name("excludes"), "atlassian-plugin.xml, module-info.class, META-INF/MANIFEST.MF, META-INF/*.DSA, META-INF/*.SF"),
  417. element(name("outputDirectory"), "${project.build.outputDirectory}")
  418. ));
  419. }
  420. public void extractTestBundledDependenciesExcludingTestScope(List<ProductArtifact> testBundleExcludes)
  421. throws MojoExecutionException {
  422. StringBuilder sb = new StringBuilder();
  423. for (ProductArtifact artifact : testBundleExcludes) {
  424. sb.append(",").append(artifact.getArtifactId());
  425. }
  426. String customExcludes = sb.toString();
  427. extractDependencies(configuration(
  428. // Here, runtime means only compile- and runtime-scoped dependencies
  429. element(name("includeScope"), "runtime"),
  430. element(name("includeTypes"), "jar"),
  431. element(name("excludeArtifactIds"), "junit" + customExcludes),
  432. element(name("excludes"), "atlassian-plugin.xml, module-info.class, META-INF/MANIFEST.MF, META-INF/*.DSA, META-INF/*.SF"),
  433. element(name("outputDirectory"), "${project.build.testOutputDirectory}")
  434. ));
  435. }
  436. public void extractTestBundledDependencies(List<ProductArtifact> testBundleExcludes) throws MojoExecutionException {
  437. StringBuilder sb = new StringBuilder();
  438. for (ProductArtifact artifact : testBundleExcludes) {
  439. sb.append(",").append(artifact.getArtifactId());
  440. }
  441. String customExcludes = sb.toString();
  442. extractDependencies(configuration(
  443. element(name("excludeArtifactIds"), "junit" + customExcludes),
  444. element(name("includeTypes"), "jar"),
  445. element(name("useSubDirectoryPerScope"), "true"),
  446. element(name("excludes"), "atlassian-plugin.xml, module-info.class, META-INF/MANIFEST.MF, META-INF/*.DSA, META-INF/*.SF"),
  447. element(name("outputDirectory"), "${project.build.directory}/testlibs")
  448. ));
  449. File targetDir = new File(ctx.getProject().getBuild().getDirectory());
  450. File testlibsDir = new File(targetDir, "testlibs");
  451. File compileLibs = new File(testlibsDir, "compile");
  452. File testLibs = new File(testlibsDir, "test");
  453. File testClassesDir = new File(ctx.getProject().getBuild().getTestOutputDirectory());
  454. try {
  455. compileLibs.mkdirs();
  456. testLibs.mkdirs();
  457. FileUtils.copyDirectory(compileLibs, testClassesDir, FileFilterUtils.notFileFilter(FileFilterUtils.nameFileFilter("META-INF")));
  458. FileUtils.copyDirectory(testLibs, testClassesDir, FileFilterUtils.notFileFilter(FileFilterUtils.nameFileFilter("META-INF")));
  459. } catch (IOException e) {
  460. throw new MojoExecutionException("unable to copy test libs", e);
  461. }
  462. }
  463. public void compressResources(boolean compressJs, boolean compressCss, boolean useClosureForJs, Charset cs, Map<String, String> closureOptions) throws MojoExecutionException {
  464. MinifierParameters minifierParameters = new MinifierParameters(compressJs,
  465. compressCss,
  466. useClosureForJs,
  467. cs,
  468. log,
  469. closureOptions
  470. );
  471. new ResourcesMinifier(minifierParameters).minify(ctx.getProject().getBuild().getResources(), ctx.getProject().getBuild().getOutputDirectory());
  472. }
  473. public void filterPluginDescriptor() throws MojoExecutionException {
  474. mojoExecutorWrapper.executeWithMergedConfig(
  475. ctx.getPlugin("org.apache.maven.plugins", "maven-resources-plugin"),
  476. goal("copy-resources"),
  477. configuration(
  478. element(name("encoding"), "UTF-8"),
  479. element(name("resources"),
  480. element(name("resource"),
  481. element(name("directory"), "src/main/resources"),
  482. element(name("filtering"), "true"),
  483. element(name("includes"),
  484. element(name("include"), "atlassian-plugin.xml"))
  485. )
  486. ),
  487. element(name("outputDirectory"), "${project.build.outputDirectory}")
  488. ),
  489. executionEnvironment()
  490. );
  491. XmlCompressor compressor = new XmlCompressor();
  492. File pluginXmlFile = new File(ctx.getProject().getBuild().getOutputDirectory(), "atlassian-plugin.xml");
  493. if (pluginXmlFile.exists()) {
  494. try {
  495. String source = FileUtils.readFileToString(pluginXmlFile, UTF_8);
  496. String min = compressor.compress(source);
  497. FileUtils.writeStringToFile(pluginXmlFile, min, UTF_8);
  498. } catch (IOException e) {
  499. throw new MojoExecutionException("IOException while minifying plugin XML file", e);
  500. }
  501. }
  502. }
  503. public void filterTestPluginDescriptor() throws MojoExecutionException {
  504. mojoExecutorWrapper.executeWithMergedConfig(
  505. ctx.getPlugin("org.apache.maven.plugins", "maven-resources-plugin"),
  506. goal("copy-resources"),
  507. configuration(
  508. element(name("encoding"), "UTF-8"),
  509. element(name("resources"),
  510. element(name("resource"),
  511. element(name("directory"), "src/test/resources"),
  512. element(name("filtering"), "true"),
  513. element(name("includes"),
  514. element(name("include"), "atlassian-plugin.xml"))
  515. )
  516. ),
  517. element(name("outputDirectory"), "${project.build.testOutputDirectory}")
  518. ),
  519. executionEnvironment()
  520. );
  521. }
  522. /**
  523. * Runs the current project's unit tests via the Surefire plugin. Excludes tests whose names match the pattern
  524. * {@value #REGEX_INTEGRATION_TESTS} and inner classes.
  525. *
  526. * @param systemProperties see SureFire's systemPropertyVariables parameter
  527. * @param excludedGroups see SureFire's excludedGroups parameter
  528. * @param category see SureFire's category parameter
  529. * @throws MojoExecutionException if something goes wrong
  530. */
  531. public void runUnitTests(
  532. final Map<String, Object> systemProperties, final String excludedGroups, final String category)
  533. throws MojoExecutionException {
  534. final Element systemProps = convertPropsToElements(systemProperties);
  535. final Xpp3Dom config = configuration(
  536. systemProps,
  537. element(name("excludes"),
  538. element(name("exclude"), REGEX_INTEGRATION_TESTS),
  539. element(name("exclude"), "**/*$*")),
  540. element(name("excludedGroups"), excludedGroups)
  541. );
  542. if (isNotBlank(category)) {
  543. config.addChild(groupsElement(category));
  544. }
  545. final Plugin surefirePlugin =
  546. ctx.getPlugin("org.apache.maven.plugins", "maven-surefire-plugin");
  547. log.info("Surefire " + surefirePlugin.getVersion() + " test configuration:");
  548. log.info(config.toString());
  549. mojoExecutorWrapper.executeWithMergedConfig(
  550. surefirePlugin,
  551. goal("test"),
  552. config,
  553. executionEnvironment()
  554. );
  555. }
  556. /**
  557. * Copies the given WAR artifact (typically the webapp for a product) to the given target directory, as
  558. * {@code <productId>-original.war}.
  559. *
  560. * @param artifact the WAR artifact to copy
  561. * @param targetDirectory the destination directory
  562. * @param productId the AMPS product ID to use as the base name of the destination file
  563. * @return the destination file
  564. * @throws MojoExecutionException if execution fails
  565. */
  566. @Nonnull
  567. public File copyWebappWar(final ProductArtifact artifact, final File targetDirectory, final String productId)
  568. throws MojoExecutionException {
  569. final File destinationFile = new File(targetDirectory, productId + "-original.war");
  570. mojoExecutorWrapper.executeWithMergedConfig(
  571. ctx.getPlugin("org.apache.maven.plugins", "maven-dependency-plugin"),
  572. goal("copy"),
  573. configuration(
  574. element(name("artifactItems"),
  575. element(name("artifactItem"),
  576. element(name("groupId"), artifact.getGroupId()),
  577. element(name("artifactId"), artifact.getArtifactId()),
  578. element(name("type"), "war"),
  579. element(name("version"), artifact.getVersion()),
  580. element(name("destFileName"), destinationFile.getName()))),
  581. element(name("outputDirectory"), targetDirectory.getPath())
  582. ),
  583. executionEnvironment()
  584. );
  585. return destinationFile;
  586. }
  587. public void unpackWebappWar(final File targetDirectory, final ProductArtifact artifact)
  588. throws MojoExecutionException {
  589. mojoExecutorWrapper.executeWithMergedConfig(
  590. ctx.getPlugin("org.apache.maven.plugins", "maven-dependency-plugin"),
  591. goal("unpack"),
  592. configuration(
  593. element(name("artifactItems"),
  594. element(name("artifactItem"),
  595. element(name("groupId"), artifact.getGroupId()),
  596. element(name("artifactId"), artifact.getArtifactId()),
  597. element(name("type"), "war"),
  598. element(name("version"), artifact.getVersion()))),
  599. element(name("outputDirectory"), targetDirectory.getPath()),
  600. //Overwrite needs to be enabled or unpack won't unpack multiple copies
  601. //of the same dependency GAV, _even to different output directories_
  602. element(name("overWriteReleases"), "true"),
  603. element(name("overWriteSnapshots"), "true"),
  604. //Use the JVM's chmod; it's faster than forking
  605. element(name("useJvmChmod"), "true")
  606. ),
  607. executionEnvironment()
  608. );
  609. }
  610. /**
  611. * Copies {@code artifacts} to the {@code outputDirectory}. Artifacts are looked up in this order:
  612. *
  613. * <ol>
  614. * <li>in the Maven reactor</li>
  615. * <li>in the Maven repositories</li>
  616. * </ol>
  617. * <p>
  618. * This can't be used in a goal that happens before the <em>package</em> phase, as artifacts in the reactor will not
  619. * be packaged (and therefore copyable) until this phase.
  620. *
  621. * @param outputDirectory the directory to copy artifacts to
  622. * @param artifacts the list of artifact to copy to the given directory
  623. */
  624. public void copyPlugins(final File outputDirectory, final List<ProductArtifact> artifacts)
  625. throws MojoExecutionException {
  626. for (ProductArtifact artifact : artifacts) {
  627. final MavenProject artifactReactorProject = getReactorProjectForArtifact(artifact);
  628. if (artifactReactorProject != null) {
  629. log.debug(artifact + " will be copied from reactor project " + artifactReactorProject);
  630. final File artifactFile = artifactReactorProject.getArtifact().getFile();
  631. if (artifactFile == null) {
  632. log.warn("The plugin " + artifact + " is in the reactor but not the file hasn't been attached. Skipping.");
  633. } else {
  634. log.debug("Copying " + artifactFile + " to " + outputDirectory);
  635. try {
  636. FileUtils.copyFile(artifactFile, new File(outputDirectory, artifactFile.getName()));
  637. } catch (IOException e) {
  638. throw new MojoExecutionException("Could not copy " + artifact + " to " + outputDirectory, e);
  639. }
  640. }
  641. } else {
  642. mojoExecutorWrapper.executeWithMergedConfig(
  643. ctx.getPlugin("org.apache.maven.plugins", "maven-dependency-plugin"),
  644. goal("copy"),
  645. configuration(
  646. element(name("artifactItems"),
  647. element(name("artifactItem"),
  648. element(name("groupId"), artifact.getGroupId()),
  649. element(name("artifactId"), artifact.getArtifactId()),
  650. element(name("version"), artifact.getVersion()))),
  651. element(name("outputDirectory"), outputDirectory.getPath())
  652. ),
  653. executionEnvironment());
  654. }
  655. }
  656. }
  657. private MavenProject getReactorProjectForArtifact(ProductArtifact artifact) {
  658. for (final MavenProject project : ctx.getReactor()) {
  659. if (project.getGroupId().equals(artifact.getGroupId())
  660. && project.getArtifactId().equals(artifact.getArtifactId())
  661. && project.getVersion().equals(artifact.getVersion())) {
  662. return project;
  663. }
  664. }
  665. return null;
  666. }
  667. private Plugin bndPlugin() {
  668. final Plugin bndPlugin = ctx.getPlugin("org.apache.felix", "maven-bundle-plugin");
  669. log.info("using maven-bundle-plugin v" + bndPlugin.getVersion());
  670. return bndPlugin;
  671. }
  672. public static String getReportsDirectory(
  673. final File targetDirectory, final String testGroupId, final String containerId) {
  674. return targetDirectory.getAbsolutePath() + "/" + testGroupId + "/" + containerId + "/surefire-reports";
  675. }
  676. /**
  677. * Runs the plugin's integration tests by invoking {@code maven-failsafe-plugin:integration-test}.
  678. *
  679. * @param reportsDirectory the directory in which to write the test reports
  680. * @param includes the filename patterns of the tests to be run, e.g. "&#8727;&#8727;/IT&#8727;.java"
  681. * @param excludes the filename patterns of the tests <em>not</em> to be run
  682. * @param systemProperties the System properties to pass to the JUnit tests
  683. * @param category if specified, only classes/methods/etc decorated with one of these groups/categories/tags will be
  684. * included in test run. In JUnit4, this affects tests annotated with the
  685. * {@link org.junit.experimental.categories.Category Category} annotation.
  686. * @param debug see the {@code debugForkedProcess} parameter of the {@code failsafe:integration-test} goal
  687. * @throws MojoExecutionException if something goes wrong, or any tests fail
  688. */
  689. public void runIntegrationTests(
  690. final String reportsDirectory, final List<String> includes, final List<String> excludes,
  691. final Map<String, Object> systemProperties, final String category, @Nullable final String debug)
  692. throws MojoExecutionException {
  693. final Xpp3Dom integrationTestConfig =
  694. getIntegrationTestConfig(includes, excludes, systemProperties, category, debug, reportsDirectory);
  695. final Plugin failsafePlugin =
  696. ctx.getPlugin("org.apache.maven.plugins", "maven-failsafe-plugin");
  697. log.info("Failsafe " + failsafePlugin.getVersion() + " integration-test configuration:");
  698. log.info(integrationTestConfig.toString());
  699. mojoExecutorWrapper.executeWithMergedConfig(
  700. failsafePlugin,
  701. goal("integration-test"),
  702. integrationTestConfig,
  703. executionEnvironment()
  704. );
  705. }
  706. /**
  707. * Runs {@code maven-failsafe-plugin:verify}.
  708. *
  709. * @param reportsDirectory the directory containing the test reports
  710. * @throws MojoExecutionException if something goes wrong or a test failed
  711. */
  712. public void runVerify(final String reportsDirectory)
  713. throws MojoExecutionException {
  714. mojoExecutorWrapper.executeWithMergedConfig(
  715. ctx.getPlugin("org.apache.maven.plugins", "maven-failsafe-plugin"),
  716. goal("verify"),
  717. configuration(element(name(REPORTS_DIRECTORY), reportsDirectory)),
  718. executionEnvironment()
  719. );
  720. }
  721. private Xpp3Dom getIntegrationTestConfig(
  722. final List<String> includes, final List<String> excludes, final Map<String, Object> systemProperties,
  723. final String category, final @Nullable String debug, final String reportsDirectory) {
  724. final Element[] includeElements = includes.stream()
  725. .map(include -> element(name("include"), include))
  726. .toArray(Element[]::new);
  727. systemProperties.put(REPORTS_DIRECTORY, reportsDirectory);
  728. final Element systemPropsElement = convertPropsToElements(systemProperties);
  729. final Xpp3Dom config = configuration(
  730. element(name("includes"), includeElements),
  731. element(name("excludes"), getTestExcludes(excludes)),
  732. systemPropsElement,
  733. element(name(REPORTS_DIRECTORY), reportsDirectory)
  734. );
  735. if (debug != null) {
  736. config.addChild(element(name("debugForkedProcess"), debug).toDom());
  737. }
  738. if (isNotBlank(category)) {
  739. config.addChild(groupsElement(category));
  740. }
  741. return config;
  742. }
  743. private Element[] getTestExcludes(final List<String> userSpecifiedExcludes) {
  744. final Set<String> allExcludes = new HashSet<>(userSpecifiedExcludes);
  745. allExcludes.add(ABSTRACT_CLASSES);
  746. allExcludes.add(INNER_CLASSES);
  747. return allExcludes.stream()
  748. .map(exclude -> element(name("exclude"), exclude))
  749. .toArray(Element[]::new);
  750. }
  751. private Xpp3Dom groupsElement(final String category) {
  752. return element(name("groups"), category).toDom();
  753. }
  754. /**
  755. * Converts a map of System properties to a Maven {@code systemPropertyVariables} config element.
  756. */
  757. private Element convertPropsToElements(final Map<String, Object> systemProperties) {
  758. final List<Element> properties = new ArrayList<>();
  759. for (Entry<String, Object> entry : systemProperties.entrySet()) {
  760. log.info("adding system property to configuration: " + entry.getKey() + "::" + entry.getValue());
  761. properties.add(element(name(entry.getKey()), entry.getValue().toString()));
  762. }
  763. return element(name("systemPropertyVariables"), properties.toArray(new Element[0]));
  764. }
  765. /**
  766. * Installs a P2 plugin into a running Atlassian product.
  767. *
  768. * @param pdkParams describes the intended host product and the plugin to be uploaded
  769. * @throws MojoExecutionException if the installation fails
  770. */
  771. public void installPlugin(final PdkParams pdkParams) throws MojoExecutionException {
  772. final String baseUrl = getBaseUrl(pdkParams.getServer(), pdkParams.getPort(), pdkParams.getContextPath());
  773. // We delegate the plugin installation to https://bitbucket.org/atlassian/atlassian-pdk-maven-plugin
  774. mojoExecutorWrapper.executeWithMergedConfig(
  775. ctx.getPlugin("com.atlassian.maven.plugins", "atlassian-pdk"),
  776. goal("install"),
  777. configuration(
  778. element(name("pluginFile"), pdkParams.getPluginFile()),
  779. element(name("username"), pdkParams.getUsername()),
  780. element(name("password"), pdkParams.getPassword()),
  781. element(name("serverUrl"), baseUrl),
  782. element(name("pluginKey"), pdkParams.getPluginKey())
  783. ),
  784. executionEnvironment()
  785. );
  786. }
  787. public void installIdeaPlugin() throws MojoExecutionException {
  788. // See https://github.com/atlassian/maven-cli-plugin/blob/master/maven/src/main/java/org/twdata/maven/cli/IdeaMojo.java
  789. mojoExecutorWrapper.executeWithMergedConfig(
  790. ctx.getPlugin("org.twdata.maven", "maven-cli-plugin"),
  791. goal("idea"),
  792. configuration(),
  793. executionEnvironment()
  794. );
  795. }
  796. /**
  797. * Downloads the ZIP version of the specified artifact into the given directory.
  798. *
  799. * @param targetDirectory the destination directory
  800. * @param artifact the artifact to copy (as {@code <type>zip</type>})
  801. * @param localName the name to give the copy
  802. * @return the destination ZIP file
  803. * @throws MojoExecutionException if execution fails
  804. */
  805. public File copyZip(final File targetDirectory, final ProductArtifact artifact, final String localName)
  806. throws MojoExecutionException {
  807. final File artifactZip = new File(targetDirectory, localName);
  808. mojoExecutorWrapper.executeWithMergedConfig(
  809. ctx.getPlugin("org.apache.maven.plugins", "maven-dependency-plugin"),
  810. goal("copy"),
  811. configuration(
  812. element(name("artifactItems"),
  813. element(name("artifactItem"),
  814. element(name("groupId"), artifact.getGroupId()),
  815. element(name("artifactId"), artifact.getArtifactId()),
  816. element(name("type"), "zip"),
  817. element(name("version"), artifact.getVersion()),
  818. element(name("destFileName"), artifactZip.getName()))),
  819. element(name("outputDirectory"), artifactZip.getParent())
  820. ),
  821. executionEnvironment()
  822. );
  823. return artifactZip;
  824. }
  825. public void generateBundleManifest(final Map<String, String> instructions, final Map<String, String> basicAttributes) throws MojoExecutionException {
  826. final List<Element> instlist = new ArrayList<>();
  827. for (final Entry<String, String> entry : instructions.entrySet()) {
  828. instlist.add(element(entry.getKey(), entry.getValue()));
  829. }
  830. if (!instructions.containsKey(Constants.IMPORT_PACKAGE)) {
  831. instlist.add(element(Constants.IMPORT_PACKAGE, "*;resolution:=optional"));
  832. // BND will expand the wildcard to a list of actually-used packages, but this tells it to mark
  833. // them all as optional
  834. }
  835. for (final Entry<String, String> entry : basicAttributes.entrySet()) {
  836. instlist.add(element(entry.getKey(), entry.getValue()));
  837. }
  838. mojoExecutorWrapper.executeWithMergedConfig(
  839. bndPlugin(),
  840. goal("manifest"),
  841. configuration(
  842. element(name("supportedProjectTypes"),
  843. element(name("supportedProjectType"), "jar"),
  844. element(name("supportedProjectType"), "bundle"),
  845. element(name("supportedProjectType"), "war"),
  846. element(name("supportedProjectType"), "atlassian-plugin")),
  847. element(name("instructions"), instlist.toArray(new Element[0]))
  848. ),
  849. executionEnvironment()
  850. );
  851. }
  852. public void generateTestBundleManifest(final Map<String, String> instructions, final Map<String, String> basicAttributes) throws MojoExecutionException {
  853. final List<Element> instlist = new ArrayList<>();
  854. for (final Entry<String, String> entry : instructions.entrySet()) {
  855. instlist.add(element(entry.getKey(), entry.getValue()));
  856. }
  857. if (!instructions.containsKey(Constants.IMPORT_PACKAGE)) {
  858. instlist.add(element(Constants.IMPORT_PACKAGE, "*;resolution:=optional"));
  859. // BND will expand the wildcard to a list of actually-used packages, but this tells it to mark
  860. // them all as optional
  861. }
  862. for (final Entry<String, String> entry : basicAttributes.entrySet()) {
  863. instlist.add(element(entry.getKey(), entry.getValue()));
  864. }
  865. mojoExecutorWrapper.executeWithMergedConfig(
  866. bndPlugin(),
  867. goal("manifest"),
  868. configuration(
  869. element(name("manifestLocation"), "${project.build.testOutputDirectory}/META-INF"),
  870. element(name("supportedProjectTypes"),
  871. element(name("supportedProjectType"), "jar"),
  872. element(name("supportedProjectType"), "bundle"),
  873. element(name("supportedProjectType"), "war"),
  874. element(name("supportedProjectType"), "atlassian-plugin")),
  875. element(name("instructions"), instlist.toArray(new Element[0]))
  876. ),
  877. executionEnvironment()
  878. );
  879. }
  880. public void generateMinimalManifest(final Map<String, String> basicAttributes) throws MojoExecutionException {
  881. File metaInf = file(ctx.getProject().getBuild().getOutputDirectory(), "META-INF");
  882. if (!metaInf.exists()) {
  883. metaInf.mkdirs();
  884. }
  885. File mf = file(ctx.getProject().getBuild().getOutputDirectory(), "META-INF", "MANIFEST.MF");
  886. Manifest m = new Manifest();
  887. m.getMainAttributes().putValue("Manifest-Version", "1.0");
  888. for (Entry<String, String> entry : basicAttributes.entrySet()) {
  889. m.getMainAttributes().putValue(entry.getKey(), entry.getValue());
  890. }
  891. try (FileOutputStream fos = new FileOutputStream(mf)) {
  892. m.write(fos);
  893. } catch (IOException e) {
  894. throw new MojoExecutionException("Unable to create manifest", e);
  895. }
  896. }
  897. public void generateTestMinimalManifest(final Map<String, String> basicAttributes) throws MojoExecutionException {
  898. File metaInf = file(ctx.getProject().getBuild().getTestOutputDirectory(), "META-INF");
  899. if (!metaInf.exists()) {
  900. metaInf.mkdirs();
  901. }
  902. File mf = file(ctx.getProject().getBuild().getTestOutputDirectory(), "META-INF", "MANIFEST.MF");
  903. Manifest m = new Manifest();
  904. m.getMainAttributes().putValue("Manifest-Version", "1.0");
  905. for (Entry<String, String> entry : basicAttributes.entrySet()) {
  906. m.getMainAttributes().putValue(entry.getKey(), entry.getValue());
  907. }
  908. try (FileOutputStream fos = new FileOutputStream(mf)) {
  909. m.write(fos);
  910. } catch (IOException e) {
  911. throw new MojoExecutionException("Unable to create manifest", e);
  912. }
  913. }
  914. /**
  915. * JARs up the current project.
  916. *
  917. * @param manifestExists whether there is a {@code target/classes/META-INF/MANIFEST.MF} file to include in the JAR.
  918. * @throws MojoExecutionException if the operation fails
  919. */
  920. public void jarWithOptionalManifest(final boolean manifestExists) throws MojoExecutionException {
  921. Element[] archive = new Element[0];
  922. if (manifestExists) {
  923. archive = new Element[]{element(name("manifestFile"), "${project.build.outputDirectory}/META-INF/MANIFEST.MF")};
  924. }
  925. mojoExecutorWrapper.executeWithMergedConfig(
  926. ctx.getPlugin("org.apache.maven.plugins", "maven-jar-plugin"),
  927. goal("jar"),
  928. configuration(
  929. element(n