具有显式finalName的Maven将无法正常工作

1.背景

我的maven项目有很多模块和子模块,包含jarswars ,一切正常。 我也可以在服务器上部署它而没有任何问题。

我决定遵循这个maven命名转换 ,我正在使用project.nameproject.build.finalName进行一些测试以获得适当的名称。

我为根工件创建project.name所定义的模式是company-${project.artifactId} ,模块和子模块是${project.parent.name}-${project.artifactId}

  • 公司的任何伪影的任何-模块1
  • 公司的任何伪影的任何-模块2-任何-submodule1
  • 公司的任何伪影的任何-模块2-任何-submodule2

project.build.finalName的模式是${project.name}-${project.version}

  • 公司的任何伪影的任何-module1-1.0.jar
  • 公司的任何伪影的任何-模块2-任何-submodule1-2.0.jar
  • 公司的任何伪影的任何-模块2-任何-submodule2-3.0.war

但maven没有生成这些文件,而是给了我一个StackOverflowError

2.重现错误的示例

你可以从github克隆这个例子: https : //github.com/pauloleitemoreira/company-any-artifact

在github中,有一个master分支,它将重现此错误。 并且only-modules分支,这是一个使用${project.parent.name}来生成jar finalName的工作示例。

让我们考虑一个带有一个根pom工件,一个pom模块和一个子模块的maven项目。

 -any-artifact | |-any-module | |-any-submodule 

2.1任何神器

   4.0.0 com.company any-artifact pom 1.0-SNAPSHOT company-${project.artifactId}  any-module    ${project.name}-${project.version}   

2.2任何模块

   4.0.0  any-artifact com.company 1.0-SNAPSHOT  com.company.any-artifact any-module pom ${project.parent.name}-${project.artifactId}  any-submodule   

2.3 any-submodule

   4.0.0  any-module com.company.any-artifact 1.0-SNAPSHOT  com.company.any-artifact.any-module any-submodule ${project.parent.name}-${project.artifactId}  

3.问题

当尝试mvn clean install ,maven给了我一个StackOverflowError

 Exception in thread "main" java.lang.StackOverflowError at org.codehaus.plexus.util.StringUtils.isEmpty(StringUtils.java:177) at org.codehaus.plexus.util.introspection.ReflectionValueExtractor.evaluate(ReflectionValueExtractor.java:194) at org.codehaus.plexus.util.introspection.ReflectionValueExtractor.evaluate(ReflectionValueExtractor.java:163) at org.apache.maven.plugin.PluginParameterExpressionEvaluator.evaluate(PluginParameterExpressionEvaluator.java:266) at org.apache.maven.plugin.PluginParameterExpressionEvaluator.evaluate(PluginParameterExpressionEvaluator.java:143) at org.apache.maven.plugin.PluginParameterExpressionEvaluator.evaluate(PluginParameterExpressionEvaluator.java:174) at org.apache.maven.plugin.PluginParameterExpressionEvaluator.evaluate(PluginParameterExpressionEvaluator.java:143) at org.apache.maven.plugin.PluginParameterExpressionEvaluator.evaluate(PluginParameterExpressionEvaluator.java:429) at org.apache.maven.plugin.PluginParameterExpressionEvaluator.evaluate(PluginParameterExpressionEvaluator.java:143) at org.apache.maven.plugin.PluginParameterExpressionEvaluator.evaluate(PluginParameterExpressionEvaluator.java:174) at org.apache.maven.plugin.PluginParameterExpressionEvaluator.evaluate(PluginParameterExpressionEvaluator.java:143) at org.apache.maven.plugin.PluginParameterExpressionEvaluator.evaluate(PluginParameterExpressionEvaluator.java:429) at org.apache.maven.plugin.PluginParameterExpressionEvaluator.evaluate(PluginParameterExpressionEvaluator.java:143) at org.apache.maven.plugin.PluginParameterExpressionEvaluator.evaluate(PluginParameterExpressionEvaluator.java:174) at org.apache.maven.plugin.PluginParameterExpressionEvaluator.evaluate(PluginParameterExpressionEvaluator.java:143) 

重要的是要知道只有在我们使用子模块时才会发生错误。 如果我们使用根POM工件和jar模块创建项目,则不会发生错误。

4.问题

为什么只有在使用子模块时才会出现此错误?

有什么建议可以解决我的问题吗? 我应该忘记它并按照我想要的模式手动为每个项目设置project.nameproject.build.fileName吗?

重要更新:

有些答案只是说使用&{parent.name} ,但它不起作用 。 请回答这个问题,请考虑使用Maven version 3.3.9测试您的解决方案。

Maven版本3.3.9

编辑 – 在错误发生时使用阶段向问题添加详细信息,在prepare-package阶段之前工作正常,但StackOverflow发生在项目的maven生命周期的package阶段。

您的问题的严格答案是${project.parent.name}不会作为模型插值过程的一部分进行解析。 反过来,你有一个StackOverflowError ,在一个完全不同的代码位置,即当…构建项目的最终JAR时。

第1部分:构建的模型是错误的

这是发生了什么。 当您在项目上启动Maven命令时,首先要做的是创建项目的有效模型。 这意味着读取您的POM文件,使用激活的配置文件进行推理,应用inheritance,对属性执行插值…所有这些都为您的项目构建最终的Maven模型。 这项工作由Maven Model Builder组件完成。

构建模型的过程非常复杂,许多步骤可能分为两个阶段,但我们在模型插值部分对此感兴趣的部分。 这是当Maven在模型中用计算值替换${...}表示的所有标记时。 它在注入配置文件后发生,并执行inheritance。 在那个时间点,由MavenProject对象表示的Maven项目尚不存在,只构建了它的Model 。 只有在您拥有完整模型之后,才可以从中开始构建Maven项目。

因此,当插值完成时,它仅仅根据POM文件中存在的信息进行推理,并且唯一有效的值是模型参考中提到的值。 (如果要查看源代码,则此替换由StringSearchModelInterpolator类执行。)值得注意的是,您会注意到模型中的元素包含父模型的名称。 Maven中的类Model实际上是使用Modello从源.mdo文件生成的,该源只为元素定义了groupIdartifactIdversionrelativePath (以及自定义id )。 这在文档中也可见。

所有这些的结果是,在执行模型插值后,将不会替换令牌${project.parent.name} 。 而且,从它构造的MavenProject将有一个包含${project.parent.name}替换的名称。 您可以在我们的示例项目的日志中看到这一点

 [INFO] Reactor Build Order: [INFO] [INFO] company-any-artifact [INFO] ${project.parent.name}-any-module [INFO] ${project.parent.name}-any-submodule 

这意味着Maven认为项目any-module的实际名称是${project.parent.name}-any-module

第2部分:奇怪的事情开始了

我们现在正处于反应堆中的所有项目都正确创建甚至编译的时候。 实际上,理论上一切都应该工作得很好,但项目本身只有完全不知名的名字。 但是你有一个奇怪的例子,它在使用maven-jar-plugin创建JAR时失败了。 使用以下日志在您的示例中构建失败:

 [INFO] --- maven-jar-plugin:2.4:jar (default-jar) @ any-submodule --- [INFO] ------------------------------------------------------------------------ [INFO] Reactor Summary: [INFO] [INFO] company-any-artifact ............................... SUCCESS [ 0.171 s] [INFO] ${project.parent.name}-any-module .................. SUCCESS [ 0.002 s] [INFO] ${project.parent.name}-any-submodule ............... FAILURE [ 0.987 s] [INFO] ------------------------------------------------------------------------ [INFO] BUILD FAILURE [INFO] ------------------------------------------------------------------------ 

这意味着在模型建立之后出现了问题。 原因是插件将项目名称作为参数注入 :

 /** * Name of the generated JAR. * * @parameter alias="jarName" expression="${jar.finalName}" default-value="${project.build.finalName}" * @required */ private String finalName; 

请注意project.build.finalName作为子模块生成的JAR名称的默认值。 这个注入和变量的插值是由另一个名为PluginParameterExpressionEvaluator类完成的。

那么这会发生什么:

  • any-submodule上的JAR插件注入项目的最终名称,名为${project.parent.name}-any-submodule
  • 感谢父项目的inheritance,以及最顶层POM项目中的声明,它inheritance了${project.name}-${project.version}
  • Maven现在尝试为any-submodule插入${project.name}
  • 由于第1部分,这解析为${project.parent.name}-any-submodule
  • Maven现在尝试为any-submodule插入${project.parent.name} 。 这样可以正常工作:构建MavenProject并在项目实例上调用getParent() ,返回具体的Maven父项目。 因此, ${project.parent.name}将尝试解析any-module的名称,实际上是${project.parent.name}-any-module
  • Maven现在尝试插入${project.parent.name}-any-module但仍然在any-submodule项目实例上 。 对于PluginParameterExpressionEvaluator ,用于评估标记的根"project"未更改。
  • Maven现在尝试在any-submodule上插入${project.parent.name} ,它再次正常工作并返回${project.parent.name}-any-module
  • Maven现在尝试在any-submodule上插入${project.parent.name} …它可以工作并返回${project.parent.name}-any-module因此它会尝试计算${project.parent.name} 。 ..

你可以在这里看到无休止的递归,这会产生StackOverflowError 。 这是PluginParameterExpressionEvaluator的错误吗? 这一点尚不清楚:它的原因是模型值首先没有被正确替换。 从理论上讲,它可以处理评估${project.parent}的特殊情况,并创建一个处理此父项目的新PluginParameterExpressionEvaluator ,而不是始终处理当前项目。 如果您对此有强烈的兴趣 ,请随时创建一个JIRA问题 。

第3部分:没有子模块的原因

通过以上所述,您现在可以推断出它在这种情况下的工作原理。 让我们了解Maven需要做什么来评估最终名称,因为必须在Maven Jar插件中注入:

  • any-module上的JAR插件注入项目的最终名称,名为${project.parent.name}-any-module
  • 感谢来自父项目的inheritance,以及最顶层POM项目中的声明,它inheritance了${project.name}-${project.version}
  • Maven现在尝试为any-module插入${project.name}
  • 这解析为${project.parent.name}-any-module ,与之前相同。
  • Maven现在尝试为any-module插入${project.parent.name} 。 就像以前一样,这可以正常工作:构建MavenProject并在项目实例上调用getParent() ,返回具体的Maven父项目。 因此, ${project.parent.name}将尝试解析any-artifact的名称,这实际上是company-any-artifact
  • 插值成功并停止。

而且你没有任何错误。

正如我在回答project.parent.name和parent.name之间的差异时所述,并在pom.xml中使用了finalName

我们先来看看基础知识:

如POM参考文献中所述 :

finalName :这是捆绑项目最终构建时的名称(没有文件扩展名,例如:my-project-1.0.jar)。 它默认为$ {artifactId} – $ {version}。

name :除了artifactId之外,项目往往具有会话名称。

所以这两者有不同的用途。

  • name纯粹是信息性的,主要用于生成的文档和构建日志。 它不是在任何其他地方inheritance或使用的。 它是一个人类可读的字符串,因此可以包含任何字符,即文件名中不允许的空格或字符。 所以,这是有效的: My Turbo Project on Speed! 。 这显然至少是一个神器的可疑文件名。

  • 如上所述, finalName是生成的工件的名称。 它inheritance的,因此它通常应该依赖于属性。 只有两个非常有用的选项是默认的${artifactId}-${version}和无版本的${artifactId} 。 其他一切都会导致混淆(例如一个名为foo的项目,创建一个神器bar.jar )。 实际上,我的涡轮增压项目! 是有效的,因为这是一个有效的文件名,但实际上,像这样的文件名往往是无法使用的(例如,尝试在bash中添加包含!的文件名)


那么,至于Stackoverflow发生的原因:

  • name不是inheritance的
  • 插值期间也不评估project.parent.name ,因为该名称是少数几个完全不可见的属性之一
  • parent.name实际上用于在较旧的Maven版本中工作,但更多是由于一个错误(在不使用前导project情况下,它也被弃用以访问属性)。
  • 缺少的属性不是插值的,即保持在模型中
  • 因此,在any-submodule有效pom中, finalName的值是(尝试使用mvn help:effective-pom ): ${project.parent.name}-any-submodule

到目前为止这么糟糕。 现在出现了StackOverflow的原因

Maven有一个名为后期插值的附加function,可以在插件参数实际使用时对其进行评估。 这允许pluing使用不属于模型的属性,但是由生命周期早期的插件生成(这允许插件为最终名称提供git修订)。

那么会发生什么:

编辑 :使错误的实际原因更清楚(见注释):

  • 评估jar插件的finalName: @Parameter( defaultValue = "${project.build.finalName}", readonly = true )
  • PluginParameterExpressionEvaluator启动并尝试评估最终名称( ${project.parent.name}-any-submodule ,其中包含属性表达式$ {project.parent.name}。
  • 评估者询问模型,模型又返回父项目的名称,即: ${project.parent.name}-any-module
  • 因此,求值程序尝试解决此问题,它返回${project.parent.name}-any-module (再次),因为属性总是针对当前项目解析 ,循环再次开始。
  • 抛出StackOverflowError。

怎么解决这个问题

可悲的是,你做不到。

您需要为每个项目明确指定name (以及artifactId )。 没有解决方法。

然后,你可以finalName依赖它。 不过我会建议反对它(参见我对p..xml中project.parent.name和parent.name的使用以及对finalName的使用的回答 )

更改最终名称的问题是本地构建工件的名称和存储库中的名称会有所不同,因此本地工件被命名为any-artifact-any-module-any-submodule.jar ,但工件您的存储库中的名称仍然是any-submodule.jar

建议

  • 如果你真的需要区分那么好,请改为更改artifactId: artifact-anymodule-anysubmodule
  • 不要使用短划线作为短名称来区分结构的级别。
  • 提示:模块的路径仍然可以是anymodule模块,不需要是模块的实际artifactId!
  • 虽然我们在这里:使用name来实现它的目的,因此你可以考虑更具视觉吸引力的东西(因为这是构建日志中出现的名称): Artifact :: AnyModule :: AnySubModule
  • 实际上,使用非常简短的groovy脚本自动创建名称条目非常容易。
  • 您还可以编写强制规则来强制执行artifactIds的命名

这是属性inheritance的问题。
尝试使用${parent.name}而不是${project.parent.name}
请看: 在父POM中声明的项目名称未在模块过滤的web.xml中展开 。

— UPDATE —

Benjamin Bentmann(maven committier)说:“总的来说, ${project.parent.*}forms的表达是一种不好的做法,因为它们依赖于某种构建状态,并且通常不会在整个POM中起作用,从而产生意外”。

https://issues.apache.org/jira/browse/MNG-5126?jql=text%20~%20%22parent%20name%22

也许你应该考虑使用${project.parent.*}是一个好方法。

有趣! 我开始克隆回购并重现错误。 我将不胜感激任何可以从下面提到的帮助我调试问题的步骤中获取的线索 –

  1. Maven生命周期阶段问题发生的阶段是生命周期的package阶段。 意思是mvn package重现了项目的问题。

  2. 经过错误中的堆栈跟踪线。 了解其表达评估失败的地方 –

     @Override public Object evaluate( String expr ) throws ExpressionEvaluationException { return evaluate( expr, null ); // Line 143 } 
  3. 它也不是导致它的finalName属性。 由于指定相同${artifactId}-${version}的默认值${artifactId}-${version}可以使用相同的项目配置。

  4. 然后尝试将any-submodule的包装更改为

     pom 

    并且错误消失了。 包装为jarwar等的含义表达式评估不同,导致溢出。

  5. 修改any-moduleany-submodule pom.xml内容我可以肯定地说,这是在评估表达式时引起递归的project.parent.name并导致堆栈溢出(如何? – 是我的意思还在寻找 ..)。 另外,改变

    ${project.parent.name}-${project.artifactId}

    ${parent.name}-${project.artifactId}

    在我没有得到错误但生成的jar类型的意义上对我有用 –

    ${parent.name}-any-module-any-submodule-1.0-SNAPSHOT.jar

    ${parent.name}-any-submodule-1.0-SNAPSHOT分别带有变化。

  6. 根据要求寻找解决方案,我正在寻找你正在使用的递归的尾部。

注意 – 仍在努力寻找适合此问题的解决方案。

将company-any-artifact中的pom.xml更改为以下内容,它将起作用。

   4.0.0 com.company any-artifact pom 1.0-SNAPSHOT ${project.groupId}  any-module    ${project.groupId}-${project.version}   

将子模块中的pom.xml更改为以下

   4.0.0  any-artifact com.company 1.0-SNAPSHOT  com.company.any-artifact any-module pom   any-submodule   ${project.parent.name}-${project.artifactId}   

将子模块pom.xml更改为以下

   4.0.0  any-module com.company.any-artifact 1.0-SNAPSHOT  com.company.any-artifact.any-module any-submodule   company-${project.parent.name}-${project.artifactId}-${project.version}   

然后输出是: company-any-module-any-submodule-1.0-SNAPSHOT