需求:
这个问题一度困扰了我好几个小时,我一开始使用maven-assembly-plugin构建tar分发包的时候,发现每次最终打包,都会在最外层有个包装层,比如我要构建的tar分发包的artifactId为abc ,那么最终打包完的tar文件总是内含abc目录,然后才是其他子目录sub1,sub2。而我们所希望的是当untar时候,能够直接出来的是子目录(sub1,sub2),而不是abc目录+abc目录里的子目录(/abc/sub1,/abc/sub2)的形式。
解决方案:
其实只要在assembly.xml中加上
- <assembly>
- <id>tarballid>
- <formats>
- <format>tarformat>
- formats>
- <includeBaseDirectory>falseincludeBaseDirectory>
- <fileSets>
- ...
关于这个参数的含义,可以参见maven-assembly-plugin的官网:
http://maven.apache.org/plugins/maven-assembly-plugin/assembly.html
深入分析:
为什么这样可以呢,我们可以对maven-assembly-plugin的源代码进行研究。
首先,当我们在pom.xml中使用maven-assembly-plugin并且在
- <build>
- <plugins>
- <plugin>
- <groupId>org.apache.maven.pluginsgroupId>
- <artifactId>maven-assembly-pluginartifactId>
- <version>2.2.1version>
- <configuration>
- <appendAssemblyId>falseappendAssemblyId>
- <descriptors>
- <descriptor>src/main/assembly/assembly.xmldescriptor>
- descriptors>
- configuration>
- <executions>
- <execution>
- <id>make-assemblyid>
- <phase>packagephase>
- <goals>
- <goal>singlegoal>
- goals>
- execution>
- executions>
- plugin>
- .....
- plugins>
- build>
插件会去调用DefaultAssemblyReader的readAssemblies()方法,然后调用如下代码进行遍历
- for ( int i = 0; i < descriptors.length; i++ )
- {
- getLogger().info( "Reading assembly descriptor: " + descriptors[i] );
- addAssemblyFromDescriptor( descriptors[i], locator, configSource, assemblies );
- }
- }
我们继续跟进到addAssemblyFromDescriptor方法,可以看出它其实是用来读取一个asssembly descriptor文件(也就是我们例子中的assembly.xml),忽略参数检查,它其实核心代码如下:
- private Assembly addAssemblyFromDescriptor( final String spec, final Locator locator,
- final AssemblerConfigurationSource configSource,
- final List<Assembly> assemblies )
- throws AssemblyReadException, InvalidAssemblerConfigurationException
- {
- ...
- Reader r = null;
- try
- {
- // TODO use ReaderFactory.newXmlReader() when plexus-utils is upgraded to 1.4.5+
- r = new InputStreamReader( location.getInputStream(), "UTF-8" );
- File dir = null;
- if ( location.getFile() != null )
- {
- dir = location.getFile().getParentFile();
- }
- final Assembly assembly = readAssembly( r, spec, dir, configSource );
- assemblies.add( assembly );
- return assembly;
- }
- ...
- }
所以这里可以看出,它最终在11行新建InputStreamReader,并在第19行读取assembly descriptor文件,最终读取的结果存储在Assembly对象模型中,而Assembly这个模型是有includeBaseDirectory这个成员变量的:
- /**
- * Set includes a base directory in the final archive. For
- * example,
- * if you are creating an assembly named
- * "your-app", setting
- * includeBaseDirectory to true will create an
- * archive that
- * includes this base directory. If this option is
- * set to false
- * the archive created will unzip its content to
- * the current
- * directory. Default value is true.
- *
- * @param includeBaseDirectory
- */
- public void setIncludeBaseDirectory( boolean includeBaseDirectory )
- {
- this.includeBaseDirectory = includeBaseDirectory;
- } //-- void setIncludeBaseDirectory( boolean )
以上是解析assembly descriptor并且设置了includeBaseDirectory,现在我们来看下如何使用这个属性。很显然,在不看代码之前,我们很容易猜想到,它肯定影响了最终打包的行为,正如我们所期望的一样。
所以,我们很轻松就找到了DefaultAssemblyArchiver的createArchive()方法,这个方法做了一系列打包的动作。
- public File createArchive( final Assembly assembly, final String fullName, final String format,
- final AssemblerConfigurationSource configSource )
- throws ArchiveCreationException, AssemblyFormattingException, InvalidAssemblerConfigurationException
- {
- validate( assembly );
- String filename = fullName;
- if ( !configSource.isIgnoreDirFormatExtensions() || !format.startsWith( "dir" ) )
- {
- filename += "." + format;
- }
- AssemblyFileUtils.verifyTempDirectoryAvailability( configSource.getTemporaryRootDirectory(), getLogger() );
- final File outputDirectory = configSource.getOutputDirectory();
- final File destFile = new File( outputDirectory, filename );
- try
- {
- final String finalName = configSource.getFinalName();
- final String specifiedBasedir = assembly.getBaseDirectory();
- String basedir = finalName;
- if ( specifiedBasedir != null )
- {
- basedir =
- AssemblyFormatUtils.getOutputDirectory( specifiedBasedir, configSource.getProject(), null,
- finalName, configSource );
- }
- final List
containerHandlers = - selectContainerDescriptorHandlers( assembly.getContainerDescriptorHandlers(), configSource );
- final Archiver archiver =
- createArchiver( format, assembly.isIncludeBaseDirectory(), basedir, configSource, containerHandlers );
- archiver.setDestFile( destFile );
- final AssemblyContext context = new DefaultAssemblyContext();
- dependencyResolver.resolve( assembly, configSource, context );
- for ( final Iterator
phaseIterator = assemblyPhases.iterator(); phaseIterator.hasNext(); ) - {
- final AssemblyArchiverPhase phase = phaseIterator.next();
- phase.execute( assembly, archiver, configSource, context );
- }
- archiver.createArchive();
- }
- ...
- return destFile;
- }
从第36行可以看出,在最终创建archiver时候(createArchiver方法),它会吧assembly.isIncludeBaseDirectory()作为参数传递进去,我们看下如果这个参数设置为false的行为。
- protected Archiver createArchiver( final String format, final boolean includeBaseDir, final String finalName,
- final AssemblerConfigurationSource configSource,
- final List
containerHandlers ) - throws ArchiverException, NoSuchArchiverException
- {
- Archiver archiver;
- if ( format.startsWith( "tar" ) )
- {
- archiver = createTarArchiver( format, configSource.getTarLongFileMode() );
- }
- else if ( "war".equals( format ) )
- {
- archiver = createWarArchiver();
- }
- else
- {
- archiver = archiverManager.getArchiver( format );
- }
- final List
extraSelectors = new ArrayList(); - final List
extraFinalizers = new ArrayList(); - if ( archiver instanceof JarArchiver )
- {
- extraSelectors.add( new JarSecurityFileSelector() );
- extraFinalizers.add( new ManifestCreationFinalizer( configSource.getProject(),
- configSource.getJarArchiveConfiguration() ) );
- }
- if ( configSource.getArchiverConfig() != null )
- {
- configureArchiver( archiver, configSource );
- }
- String prefix = "";
- if ( includeBaseDir )
- {
- prefix = finalName;
- }
- archiver =
- new AssemblyProxyArchiver( prefix, archiver, containerHandlers, extraSelectors, extraFinalizers,
- configSource.getWorkingDirectory(), getLogger(), configSource.isDryRun() );
- archiver.setUseJvmChmod( configSource.isUpdateOnly() );
- archiver.setIgnorePermissions( configSource.isIgnorePermissions() );
- archiver.setForced( !configSource.isUpdateOnly() );
- return archiver;
- }
这里,从36到40行可以看出来,当includeBaseDirectory为true时候,prefix为artifact的finalName,如果为false,则设置为"",而这个finalName就是分发包的最外层。所以,当我们吧includeBaseDirectory设置为false,就没有包装层了。这个结果正和官网上对这个参数的说明一致。具体打包过程见AssemblyProxyArchiver,我就不展开了。