package xapi.mojo.api; import com.google.common.base.Preconditions; import org.apache.maven.execution.MavenSession; import org.apache.maven.model.Dependency; import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.MojoFailureException; import org.apache.maven.plugin.descriptor.PluginDescriptor; import org.apache.maven.plugins.annotations.Component; import org.apache.maven.plugins.annotations.Parameter; import org.apache.maven.project.DefaultProjectBuildingRequest; import org.apache.maven.project.MavenProject; import org.apache.maven.project.MavenProjectHelper; import org.apache.maven.project.ProjectBuilder; import org.apache.maven.project.ProjectBuildingException; import org.apache.maven.project.ProjectBuildingRequest; import org.apache.maven.project.ProjectBuildingResult; import org.apache.maven.project.ProjectDependenciesResolver; import org.codehaus.plexus.component.repository.ComponentDependency; import org.eclipse.aether.artifact.DefaultArtifact; import org.eclipse.aether.repository.WorkspaceReader; import xapi.dev.X_Dev; import xapi.inject.impl.SingletonProvider; import xapi.log.X_Log; import xapi.log.api.LogLevel; import xapi.mojo.gwt.MavenServiceMojo; import xapi.mvn.X_Maven; import xapi.time.X_Time; import xapi.util.X_Debug; import xapi.util.X_GC; import xapi.util.X_Namespace; import xapi.util.X_String; import xapi.util.X_Util; import javax.annotation.Generated; import javax.inject.Provider; import javax.tools.JavaCompiler; import javax.tools.ToolProvider; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.net.URL; import java.util.ArrayList; import java.util.Arrays; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; /** * @requiresProject true */ public abstract class AbstractXapiMojo extends AbstractMojo { @Component private ProjectBuilder builder; @Component private MavenProjectHelper projectHelper; /** * The project the mojo is being executed upon */ @Component private MavenProject project; @Component private WorkspaceReader workspace; @Component() private ProjectDependenciesResolver resolver; /** * The directory in which to place generated resources */ @Parameter(property = "xapi.gen.dir", defaultValue = "target/generated-sources/xapi") private String generateDirectory; /** * The version of xapi to use when programatically adding xapi artifacts */ @Parameter(property = "xapi.version", defaultValue = X_Namespace.XAPI_VERSION) private String xapiVersion; /** * The loglevel to use by default */ @Parameter(property = "xapi.log.level", defaultValue = "WARN") private String xapiLogLevel; /** * The runtime platform this mojo execution is targeting. */ @Parameter(property = "xapi.platform", defaultValue = "jre") private String platform; /** * Base file from which all relative uris are resolved */ @Parameter(property = "source.root", defaultValue = "${project.basedir}") private File sourceRoot; @Parameter(property = "xapi.source.artifacts") private SourceDependency[] sourceDependencies; @Parameter(property = "assertions") private Boolean assertions; /** * The Maven Session Object, injected by plexus * */ @Component private MavenSession session; @Component private PluginDescriptor plugin; /** * A target project to use for dynamically building MavenProjects from other * poms. This value can be a simple string as relative from your * ${source.root} directory. * * So long as you stick with the pom.xml naming convention, that is. * * You may also provide an absolute path name, or even a fully qualified * artifact ID to load from local repo. * * Artifact id must contain at least the following groupId:artifactId:version * is the exact format provided. * * You may optionally include a classifier, as * groupId:artifactId:classifier:version * */ @Parameter(property = "target.project", defaultValue = "${project.basedir}") private String targetProject; @Parameter(property = "output.directory", defaultValue = "${project.build.directory}/generated-sources/xapi") private String outputDirectory; /** * Additional source roots to use when looking up source artifacts. * * These files should point to the parent pom from which you want to start searching. */ @Parameter(property = "source.project.roots") private List<File> sourceProjectRoots; public ProjectBuilder getBuilder() { return builder; } public ProjectBuildingRequest getBuildingRequest() { return session.getProjectBuildingRequest(); } public MavenProjectHelper getProjectHelper() { return projectHelper; } public PluginDescriptor getPluginDescriptor() { return plugin; } public String getPlatform() { return platform; } public MavenProject getProject() { X_Log.trace(getClass(), "project", project,"dependencies",project.getDependencies()); return project; } public File getGenerateDirectory() { return generateDirectoryProvider.get(); } public MavenSession getSession() { return session; } public File getSourceRoot() { return sourceRoot; } public String getXapiVersion() { return xapiVersion; } @Override @SuppressWarnings("unchecked") public void execute() throws MojoExecutionException, MojoFailureException { try { X_Log.logLevel(LogLevel.valueOf(xapiLogLevel)); } catch (Throwable e){getLog().warn("Could not set xapi.log.level to "+xapiLogLevel, e);} MavenServiceMojo.init(this); doExecute(); X_GC.deepDestroy(Class.class.cast(getClass()), this); } protected abstract void doExecute() throws MojoExecutionException, MojoFailureException; private Provider<Map<String, MavenProject>> localWorkspace = new SingletonProvider<Map<String, MavenProject>>() { @Override protected Map<String, MavenProject> initialValue() { final MavenProject root = X_Maven.getRootArtifact(getProject()); final Iterable<MavenProject> children = X_Maven.getAllChildren( root, getBuilder(), getBuildingRequest() ); final Map<String, MavenProject> map = new LinkedHashMap<String, MavenProject>(); for (MavenProject child : children) { map.put(child.getGroupId()+":"+child.getArtifactId(), child); } if (sourceProjectRoots != null) { boolean warnOnce = true; for (File sourceProjectRoot : sourceProjectRoots) { if (sourceProjectRoot.isDirectory()) { sourceProjectRoot = new File(sourceProjectRoot, "pom.xml"); } if (sourceProjectRoot.exists()) { try { final ProjectBuildingResult sourceProject = builder.build(sourceProjectRoot, getBuildingRequest()); for (MavenProject child : X_Maven.getAllChildren( sourceProject.getProject(), getBuilder(), getBuildingRequest() )) { String key = child.getGroupId()+":"+child.getArtifactId(); if (map.containsKey(key)) { final File existing = map.get(key).getFile(); if (existing.equals(child.getFile())) { if (warnOnce) { warnOnce = false; X_Log.warn(getClass(), "Sourcepath ", existing, "is already added."); X_Log.warn(getClass(), "You do not have to add source paths that are in the same maven project as the plugin is executed"); } } else { X_Log.warn(getClass(), "Duplicate artifact id found: ", key, "Choosing", existing, "over", child.getFile()); } } else { map.put(key, child); } } } catch (ProjectBuildingException e) { X_Log.warn(getClass(), "Unable to build source project root ", sourceProjectRoot, e); } } else { X_Log.warn(getClass(), "Unable to find source project root ", sourceProjectRoot); } } } return map; } }; private final Provider<File> generateDirectoryProvider = new SingletonProvider<File>() { @Override protected File initialValue() { if (outputDirectory != null) { File f = new File(outputDirectory); if (f.exists()) { return f; } } if (targetProject == null) { return new File(getSourceRoot(), generateDirectory); } X_Log.error(targetProjectDirectory.get(), generateDirectory); return new File(targetProjectDirectory.get(), generateDirectory); }; }; private final Provider<File> targetProjectDirectory = new SingletonProvider<File>() { @Override protected File initialValue() { Preconditions .checkNotNull( targetProject, "You must supply a ${target.project} configuration property " + "in order to use any service methods which depend upon #getTargetPom()."); boolean endsWithXml = targetProject.endsWith(".xml"); // first, check for absolute file. File targetFile = new File(targetProject); try { targetFile = targetFile.getCanonicalFile(); } catch (IOException ignored) { } if (endsWithXml && targetFile.exists()) { targetFile = targetFile.getParentFile(); } if (targetFile.isDirectory()) { return targetFile; } try { // okay, no absolute file. Now check relative to source root. targetFile = new File(sourceRoot.getCanonicalFile(), targetProject); if (targetFile.exists()) { return targetFile.getParentFile(); } } catch (IOException ignored) { } // Assume maven artifact String[] bits = targetProject.split(":", -1); if (bits.length < 2) { throw new AssertionError( "The target.project you supplied, " + targetProject + ", is neither the " + "location of a pom file (*.xml)," + " nor a maven artifact (groupId:artifactId:extension:version)"); } DefaultArtifact artifact; if (bits.length == 2) { Preconditions .checkArgument( bits[0].equals(X_Namespace.XAPI_GROUP_ID), "Unless your target artifact, " + targetProject + " begins with group Id, " + X_Namespace.XAPI_GROUP_ID + ", you must supply, at the very least, groupId:artifactId:version"); artifact = new DefaultArtifact(targetProject + ":" + X_Namespace.XAPI_VERSION); } else { artifact = new DefaultArtifact(targetProject); } // Check workspace first, since that is the most useful place for use to // resolve artifacts if (workspace != null) { File result = workspace.findArtifact(artifact); X_Log.info(getClass(), "Searching for target project directory from",result ,"derived from artifact",artifact); if (result != null) { return result.getParentFile().getParentFile(); } // If we couldn't find the artifact directly from the workspace, we need to // look up the pom tree to the root, build all modules, and find the correct artifact. MavenProject root = getSession().getCurrentProject(); while (root.getParent() != null) { root = root.getParent(); } try { DefaultProjectBuildingRequest req = new DefaultProjectBuildingRequest(session.getProjectBuildingRequest()); req.setProject(root); req.setResolveDependencies(true); List<File> poms = new ArrayList<File>(); poms.add(new File(root.getBasedir(), "pom.xml")); List<ProjectBuildingResult> res = builder.build(poms,true,req); for (ProjectBuildingResult proj : res) { String ident = proj.getProject().getArtifact().getId(); if (ident.startsWith(targetProject)) { if (ident.equals(targetProject)||ident.startsWith(targetProject+":")) { return proj.getProject().getBasedir(); } } } } catch (ProjectBuildingException e) { e.printStackTrace(); } } throw new RuntimeException( "Could not find pom file for " + targetProject + "; if you wish to target a project outside your " + "maven workspace, you must specify the full location of the project pom" + " (using groupId:artifactId only works if the given project " + "is accessible to WorkspaceReader)"); } }; public File getTargetProjectDirectory() { return targetProjectDirectory.get(); } public final SingletonProvider<JavaCompiler> compiler = new SingletonProvider<JavaCompiler>() { @Override protected JavaCompiler initialValue() { return initCompiler(); }; }; public final SingletonProvider<String[]> compileClasspath = new SingletonProvider<String[]>() { @Override protected String[] initialValue() { URL[] cp = X_Maven.compileScopeUrls(getProject(), getSession()); return X_Dev.toStrings(cp); }; }; public MavenProject findInWorkspace(String groupId, String artifactId) { Map<String, MavenProject> projects = localWorkspace.get(); return projects.get(groupId+":"+artifactId); } public void compile(final String javaName, final String source, boolean overwrite, String... additionalClasspath) { File file = saveModel(javaName, source, overwrite); prepareCompile(file, javaName, source, overwrite, additionalClasspath).run(); } public File saveModel(final String javaName, final String source, boolean overwrite) { File genDir = getGenerateDirectory(); X_Log.trace(getClass(),"Preparing compile", genDir); genDir.mkdirs(); String sourceName; if (javaName.endsWith(".java")) { sourceName = javaName.substring(0, source.length() - 5); } else { sourceName = javaName; } File f = new File(genDir, sourceName.replace('.', File.separatorChar) + ".java"); try { f.getParentFile().mkdirs(); if (!f.createNewFile()) { if (overwrite) { f.delete(); f.createNewFile(); } else { throw new RuntimeException("Source file " + f + " exists, but overwrite was false."); } } FileWriter writer = new FileWriter(f); try { writer.write(source); } finally { writer.close(); } } catch (Exception e) { X_Log.error("Unable to save generated file", javaName, "to", f, e); throw X_Debug.rethrow(e); } return f; } public Runnable prepareCompile(File srcFile,final String javaName, final String source, boolean overwrite, String... additionalClasspath) { File genDir = getGenerateDirectory(); String[] cp = compileClasspath.get(); if (additionalClasspath.length > 0) { String[] clone = Arrays.copyOf(additionalClasspath, additionalClasspath.length + cp.length); System.arraycopy(cp, 0, clone, additionalClasspath.length, cp.length); cp = clone; } String[] args = new String[] { "-sourcepath", genDir.getAbsolutePath() + File.separator, "-classpath", X_String.join(File.pathSeparator, cp), "-d", getProject().getBuild().getDirectory() + File.separator + "classes", "-proc:none", srcFile.getAbsolutePath() }; final String[] finalArgs = Boolean.TRUE.equals(assertions) ? X_Util.pushOnto(args, "-ea") : args; X_Log.log(getClass(), logLevel(), "Compile arguments", finalArgs); return new Runnable() { @Override public void run() { int result; try { result = compiler.get().run(null, null, null, finalArgs); } catch (Exception e) { X_Log.error("Cannot compile", javaName, ":\n", source, e); throw X_Debug.rethrow(e); } if (result != 0) { throw new RuntimeException("Unable to compile generated source(" + result + ")" + "\nClass: " + javaName + "\nSource: " + source); } } }; } protected LogLevel logLevel() { if (xapiLogLevel == null) { return LogLevel.INFO; } return LogLevel.valueOf(xapiLogLevel); } protected JavaCompiler initCompiler() { return ToolProvider.getSystemJavaCompiler(); } public String findArtifact(String groupId, String artifactId, String extension, String version) { if (extension == null) { extension = "jar"; } // WorkspaceReader workspace = getSession().getRepositorySession() // .getWorkspaceReader(); File artifact = workspace.findArtifact(new DefaultArtifact(groupId, artifactId, extension, version)); if (artifact != null) { return artifact.getAbsolutePath(); } return null; } public String generatedAnnotation() { return "@"+Generated.class.getName()+"(" + "\n value=\"" + getClass().getName()+ "\", " + "\n comments=\"Generated by xapi:annogen\"," + "\n date=\"" +X_Time.timestamp()+"\"" + ")"; } public String guessVersion(String groupId, String backup) { if (getPluginDescriptor() != null) { for (ComponentDependency dep : getPluginDescriptor().getDependencies()) { if (dep.getGroupId().equals(groupId)) { return dep.getVersion(); } } } if (getProject() != null) { for (Dependency dep : getProject().getDependencies()) { if (dep.getGroupId().equals(groupId)) { return dep.getVersion(); } } } return backup; } public SourceDependency[] getSourceDependencies() { return sourceDependencies; } public void setSourceDependencies(SourceDependency[] sourceDependencies) { this.sourceDependencies = sourceDependencies; } public boolean hasSourceDependencies() { return sourceDependencies != null && sourceDependencies.length > 0; } }