要点: 我想知道如何构建包含多个Maven项目的Maven应用程序,其中包含从只读源和所有输出到给定(临时)文件夹的所有输入,而不会破坏IDE(Netbeans)支持。所提议的解决方案都不适用于我,所以我要求详细解释我的问题尽可能好。
详细信息: 很长一段时间以来,我试图将我的Maven构建集成到我们的生产性建筑环境中,这有很多要求:
这类似于Out-of-tree build with maven. Is it possible?以及与maven构建目录相关的其他问题,但不幸的是,所提出的解决方案都不适用于此。
我有"中央存储库"通过" file:
" URL以及"本地存储库"在(临时)构建目录下面,但我没有找到任何方法来拥有专家#34; target"构建目录下的目录,不会破坏任何其他内容。
我有几个共享一些库的应用程序。对于每个图书馆,我都有自己的父母POM" (违反DRY原则,因为我找不到更好的方法)。每个都包含应用程序和特定于环境的设置,例如distributionManagement存储库路径,该路径使用${env.variable}
定义,以允许构建工具注入系统和应用程序特定值。提供默认值是为了让使用Netbeans的开发人员感到满意。
不幸的是,这对构建目录不起作用。
我可以在Maven: How to change path to target directory from command line?中设置构建目录。然后,由一个父POM构建的所有类文件(所有库和应用程序)将被放入同一个目录中。 Maven可以运行一次并运行 - 但在以后的运行中它将失败!至少单元测试失败,因为Maven(surefire)将找到所有项目的所有测试,而不仅仅是Maven当前正在处理的测试。因此,它会在构建库A时尝试从应用程序运行单元测试(在我的情况下失败,因为它还需要库B,而库A中没有作为依赖项提供)。
我也试过像"-DbuildDirectory=$BUILDDIR/maven-target/\${project.name}"
这样的结构。这适用于编译源,但同样不适用于测试。尽管Maven(3.1.1)在存储文件时正确评估${project.name}
,但它将字面(!)传递给类路径,因此在编译单元测试时,java没有找到测试对象("错误:找不到符号")。将符号链接${project.name}
设置为正确的名称当然只适用于一个项目(例如库A),因为我发现无法从pom.xml中更改它。
目前我最接近的方法是使用全局目标目录并在每次构建之前清理它,但显然这在使用此构建系统进行开发时很糟糕(在没有IDE的情况下逐步工作)。
现在我甚至考虑使用填充值从构建系统生成pom.xml文件,并将所有源复制到构建树。这应该适用于我们定义的发布版本,但在正常开发期间(使用IDE)会感到不舒服。
有没有诀窍,或者这对Maven来说真的不可能吗?
已编辑以回答问题
非常感谢您花时间处理我的问题/帮助请求!我尽可能清楚地回答。
队友建议增加所有这些的动力:
即使互联网发生变化,也应该可以在十年或二十年内复制任何版本。
为什么需要更改" target"?
的值
由于源树是只读的,因此Maven无法在其默认位置创建目录。可写的构建目录可用,但在源代码树之外。
Maven不会写入/ src,所以没问题
"源树"没有引用" src"构建过程的源/输入中的文件夹,但是整个结构 - 包括POM文件,资源甚至二进制库(如果需要)。所以包含src的目录也包含pom.xml,属于只读的VCS控制"源树"。
您能举例说明一些与标准项目布局不兼容的事情吗?
这种只读源树的一个不同示例可能是:在CD-R上刻录整个项目结构(带有库的应用程序)并确保" mvn编译测试包部署"的工作原理。
(只读源树需求的一个动机是确保构建过程不会意外(或有意)操纵源代码,这可能导致重复次数自动更改构建工件,从而破坏可重复性。)
使用存储库管理器
据我所知,存储库管理器是一种通常在主机上运行的复杂服务。我认为使用文件URL而不是这种复杂的服务可以减少依赖关系。请注意,整个存储库必须是版本控制的,因此无论如何自动更新都不能工作,但据我所知,存储库管理器的优势。
你使用依赖jar&#39>
是的,当然。库和应用程序依赖于几个JAR。甚至Maven依赖于几个JAR,例如编译器插件。
如果您的请求声明您需要自己构建所有依赖项?
实际上,我担心情况会是这样,是的,我很清楚,不幸的是,这将是巨大的努力。即使使用Debian软件包,这也很难实现,并且他们付出了很多努力来实现它。不幸的是,目前我还没有看到建立这些图书馆的现实机会。
" ...所有构建工件必须位于构建目录下面#34; - 您能详细解释一下吗?
构建过程应该创建给定构建目录下的所有文件,例如/tmp/$USER/build/$PROJECTNAME/$PROJECTVERSION/$APPLICATION/$VARIANT/
。构建系统可以在其下创建任意结构,但不应该在此之外更改任何内容。此树中的文件列表定义为输出(构建结果)并从那里获取。通常会将某些二进制文件或ZIP文件复制到版本控制系统。
有些人称之为"树木构建","构建源代码"或类似的。
定义良好的工具链
我们的想法是拥有一个虚拟机映像(在版本控制下),为每个构建创建一个动态的"克隆"其中,安装所需的工具链(来自版本控制),例如构建工具,Maven安装及其存储库,可以访问要构建的源的特定版本(例如,以只读ClearCase视图的形式) ),运行构建脚本入口点脚本,收集创建的输出(例如一些" release.zip"" buildlogs.zip")最后丢弃整个虚拟机,包括其所有内容。当然工具链可以完全预先安装在图像中,但实际上并没有这样做(是的,我们也使用Docker)。
对于影响分析,了解谁在使用哪个库非常重要?
是的,这肯定是非常的。但是,消息来源应该从中抽象出来,并且通常会这样做。例如,JUnit的作者不要'知道每个人都在使用它。我们喜欢同时在几个项目中使用库,所以我们不能提到父POM,因为有多个父POM。
我希望这次我能够更好地解释。
编辑#2回答新问题
再次感谢您抽出宝贵的时间来完成这篇长篇文章!我希望这次能够解释最后遗漏的部分,并说清楚。我认为有些问题似乎是对要求的评论。我担心我们会陷入讨论中。
在maven中,您有一个目录结构,如:
root +- pom.xml +- src
根目录受版本控制。所以我不理解以下内容:
"源树"没有引用" src"内的文件夹 构建过程的源/输入,但整个结构 -
root
+- pom.xml
+- src
+-- ...
+- target
+-- ...
我添加了target
"结构以更好地说明问题。如您所说,根目录受版本控制,因此是只读的。因此,root/pom.xml
,root/src
和root
本身是只读的。
当Maven尝试创建root/target
时,会收到错误(Read-only file system
),因为root
及其所有子文件夹都是只读的。
这里甚至父文件夹都受版本控制,如果是ClearCase,还有更多的其他只读父目录,但我认为这并不重要。
我们正在谈论工件的terra字节....它们必须备份但不检查版本控制。
这超出了范围,但我尽力回答。 我现在正在处理的一个ClearCase实例具有2.2 TB,所以tera字节完全正确。例如,如果标准要求版本之间的所有更改都可追溯,则备份可能还不够。但是我认为这超出了我的问题范围。
文件库存储库运行不正常,需要复制到您使用它的每台计算机上,这是一种安装到许多计算机的网络文件系统的麻烦。存储库管理器基于http(s)工作,其处理起来要简单得多..
我认为这超出了范围,与我的问题无关,但无论如何我都试着回答。 问题是,为了重现性,您需要将此http(s)的所有内容存档一段时间,并保持其运行。有历史,因为它可能需要重现一个五岁的州。当然,互联网上可能无法提供所需的软件包(请记住Codehaus.org吗?)。具有挑战性的任务。
存储库管理器更加简单并保持约定
不幸的是,它不符合要求,或者需要很高的价格(管理额外的虚拟机以及存储包的版本控制)。
自己构建所有依赖项我认为这根本不值得付出努力。我没有真正的优势
我认为这些要求并不少见(https://en.wikipedia.org/wiki/Source_code_escrow),但我担心我们会脱离主题并开始讨论要求。
如果JUnit团队可以从maven中心访问请求(可以这样做),他们可以看到谁正在使用JUnit
我认为它偏离主题,但JUnit开发人员应该如何能够了解所有"我的"使用JUnit的项目?
与父母的关系在哪里?
对不起,如果我引起混淆,这只是一个旁注。我认为这超出了我的提问范围。不过,我会尽力回答。
为了避免冗余,Maven中的一种方法是使用父POM并从中继承。例如,您可以在父POM中定义distributionManagement
并使库POM继承它。如果您有项目特定的distributionManagement
要求,则需要项目特定的父POM文件。如果两个项目共享一个相同的库,那么两个项目POM中的哪一个应该由库继承(作为父项)?在聚合器POM中设置distributionManagement
不起作用,因为它没有传播。
编辑#3解释只读
你好@JF Meier
谢谢你的评论。
只读意味着无法写入,因此Maven无法创建目标目录。因此,javac无法存储类文件和编译中止。
也许它太抽象了,所以让我举一个完整的实例:
cat 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>test</groupId>
<artifactId>mavenproject1</artifactId>
<version>1.0-SNAPSHOT</version>
</project>
cat src/main/java/Hello.java
:
public class HelloWorld {
public static void main(String[] args) { System.out.println("Hello, World!"); }
}
现在复制&amp;从贝壳粘贴(我剪了几条无聊的线条):
steffen@node1:/view/dev_test_project_v1/vobs/playground/steffen/minimal_pom $ mvn compile
[...]
[ERROR] Failure executing javac, but could not parse the error:
javac: directory not found: /view/dev_test_project_v1/vobs/playground/steffen/minimal_pom/target/classes
这是一个问题。我必须纠正一个小细节,它不会显示Read-only file sytem
错误,我认为是一个错误,但在strace中我们可以看到它:
21192 mkdir("/view/dev_test_project_v1/vobs/playground/steffen/minimal_pom/target/classes", 0777) = -1 ENOENT (No such file or directory)
21192 mkdir("/view/dev_test_project_v1/vobs/playground/steffen/minimal_pom/target", 0777) = -1 EROFS (Read-only file system)
21192 mkdir("/view/dev_test_project_v1/vobs/playground/steffen/minimal_pom/target", 0777) = -1 EROFS (Read-only file system)
这会导致javac: directory not found
错误。
它是只读的:
steffen@node1:/view/dev_test_project_v1/vobs/playground/steffen/minimal_pom $ mkdir -p target/classes
mkdir: cannot create directory ‘target’: Read-only file system
mkdir工具正确显示错误消息。
答案 0 :(得分:1)
不是答案..
首先,您需要无法访问互联网。好的,这可以通过使用存储库管理器来完成。这是企业环境中Maven和其他构建工具的默认设置。
你使用依赖jar(库?)?
如果您的请求声明您需要自己构建所有依赖项?这将是一项巨大的努力,因此意味着您需要拥有组织内所有库的所有源代码,并且您需要维护它们的所有更新/更改。(极大的努力)。
我不了解您的要求:
源树是只读的
如果您有版本控制系统,通常存储库具有身份验证,这意味着您可以控制某人是否能够提交(写入)...
..所有构建工件必须位于构建目录下..
你能详细解释一下吗?
以下我可以理解:
定义良好的工具链(在版本控制下或在只读虚拟机中)
通常使用您使用的工具链定义环境(当然应该检查版本控制)。
图书馆不知道哪个应用程序使用它们(即&#34;没有父POM&#34;)
父母的使用并不意味着图书馆不知道谁在使用图书馆?但除此之外,我不明白这个要求?对于影响分析,了解谁在使用哪个库非常重要?
你提到你已经通过文件协议配置了中央仓库,我无法推荐......此外target
目录是Maven的惯例......我没有看到任何优势改变这个?有什么问题?
答案 1 :(得分:1)
因此,根据您的更新,我在此处添加了另一个更新:
为什么需要更改“目标”的值?
因为源树是只读的,所以Maven无法创建 目录在其默认位置。可写的构建目录是 可用,但在源代码树之外。
您似乎误解了target
目录的想法,或者您误解了Maven的工作方式。
target
目录旨在包含 NOT 检查到源代码管理中的所有生成/编译的内容。或者换句话说,target
目录在签入期间默认被忽略,并且永远不会被检查到版本控制中。
Maven不会写入/ src,所以没问题
在maven中,您有一个目录结构,如:
root
+- pom.xml
+- src
+-- ...
根目录受版本控制。所以我不明白以下几点:
“source tree”不引用其中的“src”文件夹 构建过程的源/输入,但整个结构 - 如果需要,包括POM文件,资源甚至二进制库。所以 包含src的目录也包含pom.xml并且属于 只读VCS控制的“源树”。
来到下一点:
据我所知,存储库管理器通常是一项复杂的服务 在主机上运行。我认为使用文件URL而不是这么复杂 service使依赖项保持较小。请注意整个 存储库必须受版本控制,因此需要自动更新 反正一定不行,但据我所知,是一个优点 存储库管理器。
整个存储库应该受版本控制。听起来你没有在企业环境中使用真实版本的经验。我们正在讨论工件的terra字节....它们必须备份但不检查版本控制。
原因很简单,因为每个工件都是由它的坐标唯一定义的groupId,artifactId,version。
除了你提出的论点,设置的复杂性比你想象的要简单。除此之外,文件库存储库不能很好地工作,需要复制到您使用它的每台机器上,这是一种麻烦,有一种安装到许多机器的网络文件系统。存储库管理器基于http(s)工作,其处理起来要简单得多..
来到下一点:
构建过程应该创建给定构建下的所有文件 目录,例如 的/ tmp / $ USER /建设/ $ PROJECTNAME / $ PROJECTVERSION / $应用/ $ VARIANT /.
Maven在target
目录中执行此操作..
构建系统可以在其下创建任意结构,但是 不应该在此之外改变任何东西。其中的文件列表 此树被定义为输出(构建结果)并从那里获取。 通常会将某些二进制文件或ZIP文件复制到版本控制 系统。有人称之为“树外构建”,“构建源代码”或 类似。
正如我之前提到的产生的工件,我不建议将它们置于版本控制之下,因为存储库管理器更简单并保持约定......
回到你自己构建所有依赖关系的观点,我认为这根本不值得付出努力。我没有真正的优势...特别是因为您可以从Maven存储库(或您组织内部自己的存储库管理器)中使用基于正确坐标的所有工件... ...
下一点:
是的,这肯定是非常的。但是,消息来源应该是抽象的 从它,通常做。例如,JUnit的作者不知道 每个人都在使用它。
如果JUnit团队可以从maven中心访问请求(可以这样做),他们可以看到谁正在使用JUnit。通过使用存储库管理器的访问日志并提取用法,这在组织内部更加简单信息...
我不明白的是:
我们喜欢在几个库中使用库 项目同时进行,所以我们不能提到父POM,因为 有多个父POM。
这在Maven中称为简单依赖。与父母pom的关系在哪里?你似乎误解了/或者不明白Maven是如何工作的?
答案 2 :(得分:0)
让我在khmarbaise的回答中添加以下内容。
我不会在十年内看到你的&#34;可重现的&#34;标准Maven结构的问题。你只是:
版本控制系统中的源不会更改,每次都会完全相同。您只需要确保只在依赖项中使用发行版本(而不是SNAPSHOT版本),这样Nexus / Artifactory每次都会为您提供相同的工件。
答案 3 :(得分:0)
如果其他人面临类似的要求,我会以一个例子的形式描述我的解决方案。我理解并乐意接受大多数人肯定不希望这个或根本不需要&#34;树外&#34;用Maven构建。这对于像我这样没有其他选择的极少数人来说。
所以这不适合&#34;普通的Maven构建&#34;,但仅适用于问题中描述的特定要求(没有互联网访问,除了/ tmp文件夹之外的所有内容都是只读的)。这样的配置可以是从VW模板运行的虚拟机中的构建系统,由Jenkins作业动态实例化。
关于ClearCase的一个词
此方法不是特定于ClearCase的,但该示例使用ClearCase路径。
以下示例使用挂载到
toolchain_view
的只读ClearCase视图/view/toolchain_view/
(包含Maven,所有Java包和其他构建工具)和只读源代码视图(包含应用程序)可在下面&#34; / view / steffen_hellojava_source_view /&#34;。而不是&#34; / view / toolchain_view /&#34;,有人可以使用&#34; / opt / vendor / name / toolchain / version /&#34;或者使用任何其他版本控制系统,因此这种方法不是特定于ClearCase的。对于那些不了解ClearCase的人:该视图的行为类似于NFS文件系统,其中服务器根据版本描述选择文件内容。文件内容本身位于名为Versioned Object Base(VOB)的数据库中。使用所谓的配置规范(CS),某人可以定义哪个内容版本以选择规则的形式显示哪个文件(元素)(例如通过LABEL选择)。例如:
---[ toolchain-1_4_7_0.cs ]---------------------------------------->8======= element /vobs/playvob/toolchain/... TOOLCHAIN_VERSION_1_4_7_0 # TOOLCHAIN_VERSION_1_4_7_0 automatically also selects: # element /vobs/playvob/toolchain/3p/maven/... TOOLCHAIN_MAVEN_3_1_1_0 # element /vobs/playvob/toolchain/3p/maven-repo/... TOOLCHAIN_MAVENREPO_1_0_1_0 # element /vobs/playvob/toolchain/3p/java/... TOOLCHAIN_JAVA_1_7_0_U60 =======8<-------------------------------------------------------------------
现在是一个ClearCase视图,例如&#34; toolchain_view&#34;可以创建和配置使用这些选择规则
cleartool -tag toolchain_view -setcs toolchain-1_4_7_0.cs
。在示例中,它以/view/toolchain_view/
挂载,前缀为元素(文件)路径。
现在我们需要将Maven配置为
/view/toolchain_view/vobs/playvob/toolchain/3p/maven-repo/
仅用作中央存储库/tmp
和target
以下的/tmp
目录前两个可以在Maven设置文件中配置,但显然最后一个似乎需要在POM文件中进行特定更改。
这里摘录了Maven的--global-settings
文件。我把它压缩了一下。必不可少的是两个中央存储库使用file:///
URL,并将其强制执行为唯一的镜像:
<!-- Automatically generated by toolchain creator scripts.
This sets the maven-repo version to the correct value, for example
toolchain version 1.4.7.0 defines Maven repo version 1.0.1. -->
<settings ...>
<localRepository>${env.MAVEN_LOCAL_REPO}</localRepository>
<profiles>
<profile>
<id>1</id>
<activation><activeByDefault>true</activeByDefault></activation>
<repositories><repository>
<id>3rdparty</id><name>third party repo</name>
<url>file:///view/toolchain_view/vobs/playvob/toolchain/3p/maven-repo/</url>
<snapshots><enabled>true</enabled><updatePolicy>never</updatePolicy></snapshots>
<releases><enabled>true</enabled><updatePolicy>never</updatePolicy></releases>
</repository></repositories>
<pluginRepositories><pluginRepository>
<id>3rdparty</id><name>third party repo</name>
<url>file:///view/toolchain_view/vobs/playvob/toolchain/3p/maven-repo/</url>
<snapshots><enabled>true</enabled><updatePolicy>never</updatePolicy></snapshots>
<releases><enabled>true</enabled><updatePolicy>never</updatePolicy></releases>
</pluginRepository></pluginRepositories>
</profile>
</profiles>
<!-- Unfortunately **required** for file-based "central repository": -->
<mirrors><mirror>
<id>dehe_repo_1.0.1</id><name>Vendor Name Central 1.0.1</name>
<url>file:///view/toolchain_view/vobs/playvob/toolchain/3p/maven-repo/</url>
<mirrorOf>*,!3rdparty</mirrorOf>
</mirror></mirrors>
</settings>
为了简化部署,我们可以使用包含版本号的路径,例如/opt/vendor/name/toolchain/1.4.7.0/mvn_settings.xml
,这些路径可以由自己的Debian软件包提供。
通过设置环境变量将localRepository
指向特定于构建的(临时)目录,允许在所有内容都是只读时进行构建,但临时目录除外,这是&#34; out-of-tree&# 34;生成。
使用IDE(Netbeans)构建时,我们也可以使用此设置文件,但通常不这样做会更舒服。但是,在这种情况下,有人必须注意不要意外添加依赖项。如果这些未包含在固定的Maven Repo中,则构建系统上的编译将会中断。
为了支持树外构建,我们还需要将target
文件夹移出只读源树(通常它们是在pom.xml
和src
旁边创建的Java包目录,它本身受版本控制,因此在这里是只读的)。这是通过使用两个属性实现的:buildDirectory
和deployDirectory
。默认的buildDirectory是普通的target
文件夹,因此当不设置buildDirectory时,Maven会正常构建。这很好,因为我们不需要IDE(Netbeans)的特定POM文件。
Surefire生成单元测试报告,当然也需要转到我们的构建目录。
<project>
...
<properties>
<project.skipTests>false</project.skipTests>
<project.testFailureIgnore>false</project.testFailureIgnore>
<buildDirectory>${project.basedir}/target</buildDirectory>
<deployDirectory>defaultDeploy</deployDirectory>
</properties>
...
<build>
<!-- https://stackoverflow.com/a/3908061 -->
<directory>${buildDirectory}/hellojava</directory>
<!-- https://stackoverflow.com/a/6733858/9095109 -->
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.12.4</version>
<configuration>
<skipTests>${project.skipTests}</skipTests>
<testFailureIgnore>${project.testFailureIgnore}</testFailureIgnore>
<workingDirectory>${project.build.directory}/test-run</workingDirectory>
</configuration>
</plugin>
</plugins>
</build>
...
<distributionManagement>
<repository>
<id>build-integration</id>
<name>Deployment Repository</name>
<url>file:///${deployDirectory}</url>
</repository>
</distributionManagement>
...
</project>
现在必须通过命令行参数设置此POM文件中属性的值,并且必须配置MAVEN_LOCAL_REPO
,例如:
#!/bin/bash
# Normally in a wrapper which is automatically generated by toolchain creator.
# The path to the pom.xml and build directories then of course are parameters.
export JAVA_HOME="/view/toolchain_view/vobs/playvob/toolchain/3p/java/"
export PATH="/view/toolchain_view/vobs/playvob/toolchain/bin:$PATH"
# or: export PATH="/opt/vendor/name/toolchain/1.4.7.0/bin:$PATH"
: ${MAVEN_LOCAL_REPO:="$HOME/.m2/repository"}
: ${MAVEN_OPTS:="-XX:PermSize=64m -XX:MaxPermSize=192m"}
export MAVEN_LOCAL_REPO="/tmp/steffen-build/hellojava/mavenbuild/repo"
/view/toolchain_view/vobs/playvob/toolchain/3p/maven/bin/mvn -e \
--global-settings /view/toolchain_view/vobs/playvob/toolchain/mvn_settings.xml \
-DbuildDirectory=/tmp/steffen-build/hellojava/mavenbuild/target/ \
-DdeployDirectory=/tmp/steffen-build/hellojava/mavenbuild/output/ \
-Dproject.skipTests \
-f /view/steffen_hellojava_source_view/hellojava/pom.xml \
compile package deploy
现在/tmp
可以是&#34;只读系统上的RAM磁盘&#34;。所有工件都写在专用构建目录下。我们可以在DVD或只读NFS存档服务器上拥有工具链和完整的源代码树,并且仍然可以编译它,而无需访问Internet。即使Maven Central已被重命名或其他任何内容,这仍应在20年内有效。
当然,包装器脚本可以隐藏所有细节。就我而言,它们集成在基于cmake的构建系统中,顶级构建目录由Jenkins作业配置。
对于原始问题的每个要求,我们可以检查这种方法是否符合它:
file:///
/view
树MAVEN_LOCAL_REPO
/view/
或/opt
,所有版本都是&#34;固定&#34; (固定)因此满足了所有(非常具体的)输入要求。好。所以这实现了没有Internet的树外构建。