001/* 002 * JDrupes Builder 003 * Copyright (C) 2025 Michael N. Lipp 004 * 005 * This program is free software: you can redistribute it and/or modify 006 * it under the terms of the GNU Affero General Public License as 007 * published by the Free Software Foundation, either version 3 of the 008 * License, or (at your option) any later version. 009 * 010 * This program is distributed in the hope that it will be useful, 011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 013 * GNU Affero General Public License for more details. 014 * 015 * You should have received a copy of the GNU Affero General Public License 016 * along with this program. If not, see <https://www.gnu.org/licenses/>. 017 */ 018 019package org.jdrupes.builder.mvnrepo; 020 021import java.io.ByteArrayOutputStream; 022import java.io.IOException; 023import java.nio.file.Files; 024import java.nio.file.Path; 025import java.util.Arrays; 026import java.util.Optional; 027import java.util.function.Consumer; 028import java.util.function.Supplier; 029import java.util.stream.Stream; 030import org.apache.maven.model.Model; 031import org.apache.maven.model.io.DefaultModelWriter; 032import org.jdrupes.builder.api.BuildException; 033import org.jdrupes.builder.api.Generator; 034import org.jdrupes.builder.api.Project; 035import static org.jdrupes.builder.api.Project.Properties.*; 036import org.jdrupes.builder.api.Resource; 037import org.jdrupes.builder.api.ResourceRequest; 038import org.jdrupes.builder.core.AbstractGenerator; 039import static org.jdrupes.builder.mvnrepo.MvnProperties.*; 040import static org.jdrupes.builder.mvnrepo.MvnRepoTypes.*; 041 042/// A [Generator] for POM files. The generator generates a maven 043/// [Model] with basic information. The group is set to the value 044/// of the property [MvnProperties#GroupId] if it is defined. The 045/// artifact id is set to the property [MvnProperties#ArtifactId] 046/// or the name of the project, if [MvnProperties#ArtifactId] is not 047/// defined. The version is set to the value of the property 048/// [Project.Properties#Version]. 049/// 050/// This basic model is passed to [#adaptPom] where it can be adapted. 051/// as the project requires. Finally, the model is written to the 052/// POM file 053/// 054public class PomFileGenerator extends AbstractGenerator { 055 056 /// The Constant GENERATED_BY. 057 public static final String GENERATED_BY = "Generated by JDrupes Builder"; 058 private Supplier<Path> destination 059 = () -> project().buildDirectory().resolve("publications/maven"); 060 private Consumer<Model> pomAdapter = _ -> { 061 }; // Do nothing> 062 063 /// Instantiates a new library generator. 064 /// 065 /// @param project the project 066 /// 067 public PomFileGenerator(Project project) { 068 super(project); 069 } 070 071 /// Returns the destination directory. Defaults to sub directory 072 /// `publications/maven` in the project's build directory 073 /// (see [Project#buildDirectory]). 074 /// 075 /// @return the destination 076 /// 077 public Path destination() { 078 return destination.get(); 079 } 080 081 /// Sets the destination directory. The [Path] is resolved against 082 /// the project's build directory (see [Project#buildDirectory]). 083 /// 084 /// @param destination the new destination 085 /// @return the jar generator 086 /// 087 public PomFileGenerator destination(Path destination) { 088 this.destination 089 = () -> project().buildDirectory().resolve(destination); 090 return this; 091 } 092 093 /// Sets the destination directory. 094 /// 095 /// @param destination the new destination 096 /// @return the jar generator 097 /// 098 public PomFileGenerator destination(Supplier<Path> destination) { 099 this.destination = destination; 100 return this; 101 } 102 103 @Override 104 protected <T extends Resource> Stream<T> 105 doProvide(ResourceRequest<T> requested) { 106 var pomPath = destination().resolve(Optional.ofNullable( 107 project().get(ArtifactId)).orElse(project().name()) + "-pom.xml"); 108 if (cleanup(requested, pomPath)) { 109 return Stream.empty(); 110 } 111 112 if (!requested.includes(PomFileType)) { 113 return Stream.empty(); 114 } 115 116 pomPath.getParent().toFile().mkdirs(); 117 var deps = project().newResource(MvnRepoDependenciesType).addAll( 118 project().supplied(new ResourceRequest<>( 119 MvnRepoDependenciesType))); 120 Model model = generatePom(); 121 122 // create, compare and maybe write model 123 pomPath.getParent().toFile().mkdirs(); 124 try { 125 // Create new in memory 126 ByteArrayOutputStream newPom = new ByteArrayOutputStream(); 127 new DefaultModelWriter().write(newPom, null, model); 128 newPom.close(); 129 130 // Read existing 131 var existingContent = pomPath.toFile().exists() 132 ? Files.readAllBytes(pomPath) 133 : new byte[0]; 134 135 // Compare and write 136 if (Arrays.equals(newPom.toByteArray(), existingContent)) { 137 log.fine(() -> "Existing " + project().rootProject() 138 .directory().relativize(pomPath) + " is up to date."); 139 } else { 140 log.fine(() -> "Updating " + project().rootProject() 141 .directory().relativize(pomPath) + "."); 142 Files.write(pomPath, newPom.toByteArray()); 143 } 144 } catch (IOException e) { 145 throw new BuildException(e); 146 } 147 148 @SuppressWarnings("unchecked") 149 var result = (Stream<T>) Stream 150 .of(project().newResource(PomFileType, pomPath)); 151 return result; 152 } 153 154 private Model generatePom() { 155 Model model = new Model(); 156 model.setModelVersion("4.0.0"); 157 var groupId = project().<String> get(GroupId); 158 if (groupId != null) { 159 model.setGroupId(groupId); 160 } 161 model.setArtifactId(Optional.ofNullable(project() 162 .<String> get(ArtifactId)).orElse(project().name())); 163 model.setVersion(project().get(Version)); 164 model.setName(project().name()); 165 166 // Adapt 167 pomAdapter.accept(model); 168 return model; 169 } 170 171 /// Allow derived classes to post process the generated POM. 172 /// 173 /// @param adaptor the adaptor 174 /// @return the pom file generator 175 /// 176 public PomFileGenerator adaptPom(Consumer<Model> adaptor) { 177 this.pomAdapter = adaptor; 178 return this; 179 } 180 181}