maven fmpp 插件开发说明
实际上已经有几个 fmpp maven 插件,但是不是很好用,dremio 自己包装了一个,然后fork 了dremio fmpp 插件的代码独立包装了一些
同时发布到github repo 中,方便使用
参考代码
- pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.rongfengliang</groupId>
<artifactId>fmpp-maven-plugin</artifactId>
<packaging>maven-plugin</packaging>
<version>1.0</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<commons-io.version>2.11.0</commons-io.version>
<guava.version>31.1-jre</guava.version>
<fmpp.version>0.9.16</fmpp.version>
<freemarker.version>2.3.31</freemarker.version>
</properties>
<dependencies>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>${guava.version}</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>${commons-io.version}</version>
</dependency>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-core</artifactId>
<version>3.3.9</version>
<scope>provided</scope>
<exclusions>
<exclusion>
<artifactId>commons-logging</artifactId>
<groupId>commons-logging</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-plugin-api</artifactId>
<version>3.3.9</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.maven.plugin-tools</groupId>
<artifactId>maven-plugin-annotations</artifactId>
<version>3.6.4</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>net.sourceforge.fmpp</groupId>
<artifactId>fmpp</artifactId>
<version>${fmpp.version}</version>
</dependency>
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>${freemarker.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-plugin-plugin</artifactId>
<version>3.6.2</version>
<configuration>
<goalPrefix>rongfengliang-fmpp</goalPrefix>
</configuration>
<executions>
<execution>
<id>default-descriptor</id>
<goals>
<goal>descriptor</goal>
</goals>
<phase>process-classes</phase>
</execution>
<execution>
<id>help-descriptor</id>
<goals>
<goal>helpmojo</goal>
</goals>
<phase>process-classes</phase>
</execution>
</executions>
</plugin>
</plugins>
</build>
<distributionManagement>
<repository>
<id>github</id>
<name>fmpp maven plugin</name>
<url>https://maven.pkg.github.com/rongfengliang/fmpp-maven-plugin</url>
</repository>
</distributionManagement>
</project>
- 核心插件代码
FMPPMojo.java 核心是使用fmpp 的api 进行构建,同时进行配置参数设置,运行的阶段是生成source
fmpp 包含了配置,模版以及输出目录,当然dremio 还扩展了支持data,实际上基于配置就可以加载数据
数据加载dremio 配置了自己的MavenDataLoader
package com.rongfengliang;
import com.google.common.base.Joiner;
import com.google.common.base.Stopwatch;
import fmpp.Engine;
import fmpp.ProgressListener;
import fmpp.progresslisteners.TerseConsoleProgressListener;
import fmpp.setting.Settings;
import fmpp.util.MiscUtil;
import org.apache.commons.io.FileUtils;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.project.MavenProject;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import static java.lang.String.format;
/**
* a maven plugin to run the freemarker generation incrementally
* (if output has not changed, the files are not touched)
*/
@Mojo(name = "generate", defaultPhase = LifecyclePhase.GENERATE_SOURCES)
public class FMPPMojo extends AbstractMojo {
/**
* Used to add new source directories to the build.
**/
@Parameter(defaultValue = "${project}", readonly = true, required = true)
private MavenProject project;
/**
* Where to find the FreeMarker template files.
*/
@Parameter(defaultValue = "src/main/resources/fmpp/templates/", required = true)
private File templates;
/**
* Where to write the generated files of the output files.
*/
@Parameter(defaultValue = "${project.build.directory}/generated-sources/fmpp/", required = true)
private File output;
/**
* Location of the FreeMarker config file.
*/
@Parameter(defaultValue = "src/main/resources/fmpp/config.fmpp", required = true)
private File config;
/**
* compilation scope to be added to ("compile" or "test")
*/
@Parameter(defaultValue = "compile", required = true)
private String scope;
@Parameter
private String data;
/**
* if maven properties are added as data
*/
@Parameter(defaultValue = "true", required = true)
private boolean addMavenDataLoader;
@Override
public void execute() throws MojoExecutionException, MojoFailureException {
if (project == null) {
throw new MojoExecutionException("This plugin can only be used inside a project.");
}
String outputPath = output.getAbsolutePath();
if ((!output.exists() && !output.mkdirs()) || !output.isDirectory()) {
throw new MojoFailureException("can not write to output dir: " + outputPath);
}
String templatesPath = templates.getAbsolutePath();
if (!templates.exists() || !templates.isDirectory()) {
throw new MojoFailureException("templates not found in dir: " + outputPath);
}
// add the output directory path to the project source directories
switch (scope) {
case "compile":
project.addCompileSourceRoot(outputPath);
break;
case "test":
project.addTestCompileSourceRoot(outputPath);
break;
default:
throw new MojoFailureException("scope must be compile or test");
}
final Stopwatch sw = Stopwatch.createStarted();
try {
getLog().info(format("Freemarker generation:\n scope: %s,\n config: %s,\n templates: %s",
scope, config.getAbsolutePath(), templatesPath));
final File tmp = Files.createTempDirectory("freemarker-tmp").toFile();
String tmpPath = tmp.getAbsolutePath();
final String tmpPathNormalized = tmpPath.endsWith(File.separator) ? tmpPath : tmpPath + File.separator;
Settings settings = new Settings(new File("."));
settings.set(Settings.NAME_SOURCE_ROOT, templatesPath);
settings.set(Settings.NAME_OUTPUT_ROOT, tmp.getAbsolutePath());
settings.load(config);
settings.addProgressListener(new TerseConsoleProgressListener());
settings.addProgressListener(new ProgressListener() {
@Override
public void notifyProgressEvent(
Engine engine, int event,
File src, int pMode,
Throwable error, Object param)
throws Exception {
if (event == EVENT_END_PROCESSING_SESSION) {
getLog().info(format("Freemarker generation took %dms", sw.elapsed(TimeUnit.MILLISECONDS)));
sw.reset();
Report report = moveIfChanged(tmp, tmpPathNormalized);
if (!tmp.delete()) {
throw new MojoFailureException(format("can not delete %s", tmp));
}
getLog().info(format("Incremental output update took %dms", sw.elapsed(TimeUnit.MILLISECONDS)));
getLog().info(format("new: %d", report.newFiles));
getLog().info(format("changed: %d", report.changedFiles));
getLog().info(format("unchanged: %d", report.unchangedFiles));
}
}
} );
List<String> dataValues = new ArrayList<>();
if (addMavenDataLoader) {
getLog().info("Adding maven data loader");
settings.setEngineAttribute(MavenDataLoader.MAVEN_DATA_ATTRIBUTE, new MavenDataLoader.MavenData(project));
dataValues.add(format("maven: %s()", MavenDataLoader.class.getName()));
}
if (data != null) {
dataValues.add(data);
}
if(!dataValues.isEmpty()) {
String dataString = Joiner.on(",").join(dataValues);
getLog().info("Setting data loader "+ dataString);
settings.add(Settings.NAME_DATA, dataString);
}
settings.execute();
} catch (Exception e) {
throw new MojoFailureException(MiscUtil.causeMessages(e), e);
}
}
private static final class Report {
private int changedFiles;
private int unchangedFiles;
private int newFiles;
Report(int changedFiles, int unchangedFiles, int newFiles) {
super();
this.changedFiles = changedFiles;
this.unchangedFiles = unchangedFiles;
this.newFiles = newFiles;
}
public Report() {
this(0, 0, 0);
}
void add(Report other) {
changedFiles += other.changedFiles;
unchangedFiles += other.unchangedFiles;
newFiles += other.newFiles;
}
public void addChanged() {
++ changedFiles;
}
public void addNew() {
++ newFiles;
}
public void addUnchanged() {
++ unchangedFiles;
}
}
private Report moveIfChanged(File root, String tmpPath) throws MojoFailureException, IOException {
Report report = new Report();
for (File file : root.listFiles()) {
if (file.isDirectory()) {
report.add(moveIfChanged(file, tmpPath));
if (!file.delete()) {
throw new MojoFailureException(format("can not delete %s", file));
}
} else {
String absPath = file.getAbsolutePath();
if (!absPath.startsWith(tmpPath)) {
throw new MojoFailureException(format("%s should start with %s", absPath, tmpPath));
}
String relPath = absPath.substring(tmpPath.length());
File outputFile = new File(output, relPath);
if (!outputFile.exists()) {
report.addNew();
} else if (!FileUtils.contentEquals(file, outputFile)) {
getLog().info(format("%s has changed", relPath));
if (!outputFile.delete()) {
throw new MojoFailureException(format("can not delete %s", outputFile));
}
report.addChanged();
} else {
report.addUnchanged();
}
if (!outputFile.exists()) {
File parentDir = outputFile.getParentFile();
if (parentDir.exists() && !parentDir.isDirectory()) {
throw new MojoFailureException(format("can not move %s to %s as %s is not a dir", file, outputFile, parentDir));
}
if (!parentDir.exists() && !parentDir.mkdirs()) {
throw new MojoFailureException(format("can not move %s to %s as dir %s can not be created", file, outputFile, parentDir));
}
FileUtils.moveFile(file, outputFile);
} else {
if (!file.delete()) {
throw new MojoFailureException(format("can not delete %s", file));
}
}
}
}
return report;
}
}
- 构建以及发布
直接基于了github repo 进行发布,配置好账户token 就可以了
mvn clean package deploy
说明
最近在研究dremio sql 解析部分,使用到了fmpp 工具,为了方便使用,基于dremio 的源码,自己构建了一个
参考资料
https://github.com/rongfengliang/fmpp-maven-plugin
https://github.com/dremio/dremio-oss/tree/master/tools/fmpp-maven-plugin
https://fmpp.sourceforge.net/manual.html
https://fmpp.sourceforge.net/writefrontend.html