需求:

这个问题一度困扰了我好几个小时,我一开始使用maven-assembly-plugin构建tar分发包的时候,发现每次最终打包,都会在最外层有个包装层,比如我要构建的tar分发包的artifactId为abc ,那么最终打包完的tar文件总是内含abc目录,然后才是其他子目录sub1,sub2。而我们所希望的是当untar时候,能够直接出来的是子目录(sub1,sub2),而不是abc目录+abc目录里的子目录(/abc/sub1,/abc/sub2)的形式。

解决方案:

其实只要在assembly.xml中加上<includeBaseDirectory>元素,并且让其设为false就可以了,如下:

  1. <assembly>  
  2.   
  3.   <id>tarball</id>  
  4.   
  5.   <formats>  
  6.   
  7.     <format>tar</format>  
  8.   
  9.   </formats>  
  10.   
  11.     
  12.   
  13.   <!--fixed the wrapper folder issue-->  
  14.   
  15.   <includeBaseDirectory>false</includeBaseDirectory>  
  16.   
  17.    
  18.   
  19.   <fileSets>  
  20.   
  21.  ... 

关于这个参数的含义,可以参见maven-assembly-plugin的官网:

http://maven.apache.org/plugins/maven-assembly-plugin/assembly.html

 

深入分析:

为什么这样可以呢,我们可以对maven-assembly-plugin的源代码进行研究。

首先,当我们在pom.xml中使用maven-assembly-plugin并且在<descriptor>中配置了assembly.xml文件的位置时候:

  1. <build>  
  2.                  <plugins>  
  3.                           <plugin>  
  4.                                    <groupId>org.apache.maven.plugins</groupId>  
  5.                                    <artifactId>maven-assembly-plugin</artifactId>  
  6.                                    <version>2.2.1</version>  
  7.                                    <configuration>  
  8.                                             <appendAssemblyId>false</appendAssemblyId>  
  9.                                             <descriptors>  
  10.                                                     <descriptor>src/main/assembly/assembly.xml</descriptor>  
  11.                                             </descriptors>  
  12.                                    </configuration>  
  13.                                    <executions>  
  14.                                             <execution>  
  15.                                                     <id>make-assembly</id>  
  16.                                                     <phase>package</phase>  
  17.                                                     <goals>  
  18.                                                              <goal>single</goal>  
  19.                                                     </goals>  
  20.                                             </execution>  
  21.                                    </executions>  
  22.                           </plugin>  
  23.                          ..... 
  24.                  </plugins>  
  25.          </build>  

 

插件会去调用DefaultAssemblyReader的readAssemblies()方法,然后调用如下代码进行遍历<descriptors>元素:

  1. for ( int i = 0; i < descriptors.length; i++ ) 
  2.           { 
  3.               getLogger().info( "Reading assembly descriptor: " + descriptors[i] ); 
  4.               addAssemblyFromDescriptor( descriptors[i], locator, configSource, assemblies ); 
  5.           } 
  6.       } 

 

我们继续跟进到addAssemblyFromDescriptor方法,可以看出它其实是用来读取一个asssembly descriptor文件(也就是我们例子中的assembly.xml),忽略参数检查,它其实核心代码如下:

  1. private Assembly addAssemblyFromDescriptor( final String spec, final Locator locator, 
  2.                                                final AssemblerConfigurationSource configSource, 
  3.                                                final List<Assembly> assemblies ) 
  4.        throws AssemblyReadException, InvalidAssemblerConfigurationException 
  5.    { 
  6.       ... 
  7.        Reader r = null
  8.        try 
  9.        { 
  10.            // TODO use ReaderFactory.newXmlReader() when plexus-utils is upgraded to 1.4.5+ 
  11.            r = new InputStreamReader( location.getInputStream(), "UTF-8" ); 
  12.  
  13.            File dir = null
  14.            if ( location.getFile() != null ) 
  15.            { 
  16.                dir = location.getFile().getParentFile(); 
  17.            } 
  18.  
  19.            final Assembly assembly = readAssembly( r, spec, dir, configSource ); 
  20.  
  21.            assemblies.add( assembly ); 
  22.  
  23.            return assembly; 
  24.        } 
  25.      ... 
  26.    } 

所以这里可以看出,它最终在11行新建InputStreamReader,并在第19行读取assembly descriptor文件,最终读取的结果存储在Assembly对象模型中,而Assembly这个模型是有includeBaseDirectory这个成员变量的:

  1. /** 
  2.     * Set includes a base directory in the final archive. For 
  3.     * example, 
  4.     *             if you are creating an assembly named 
  5.     * "your-app", setting 
  6.     *             includeBaseDirectory to true will create an 
  7.     * archive that 
  8.     *             includes this base directory. If this option is 
  9.     * set to false 
  10.     *             the archive created will unzip its content to 
  11.     * the current 
  12.     *             directory. Default value is true. 
  13.     *  
  14.     * @param includeBaseDirectory 
  15.     */ 
  16.    public void setIncludeBaseDirectory( boolean includeBaseDirectory ) 
  17.    { 
  18.        this.includeBaseDirectory = includeBaseDirectory; 
  19.    } //-- void setIncludeBaseDirectory( boolean ) 

 

以上是解析assembly descriptor并且设置了includeBaseDirectory,现在我们来看下如何使用这个属性。很显然,在不看代码之前,我们很容易猜想到,它肯定影响了最终打包的行为,正如我们所期望的一样。

 

所以,我们很轻松就找到了DefaultAssemblyArchiver的createArchive()方法,这个方法做了一系列打包的动作。

  1. public File createArchive( final Assembly assembly, final String fullName, final String format, 
  2.                                final AssemblerConfigurationSource configSource ) 
  3.         throws ArchiveCreationException, AssemblyFormattingException, InvalidAssemblerConfigurationException 
  4.     { 
  5.         validate( assembly ); 
  6.  
  7.         String filename = fullName; 
  8.         if ( !configSource.isIgnoreDirFormatExtensions() || !format.startsWith( "dir" ) ) 
  9.         { 
  10.             filename += "." + format; 
  11.         } 
  12.  
  13.         AssemblyFileUtils.verifyTempDirectoryAvailability( configSource.getTemporaryRootDirectory(), getLogger() ); 
  14.  
  15.         final File outputDirectory = configSource.getOutputDirectory(); 
  16.  
  17.         final File destFile = new File( outputDirectory, filename ); 
  18.  
  19.         try 
  20.         { 
  21.             final String finalName = configSource.getFinalName(); 
  22.             final String specifiedBasedir = assembly.getBaseDirectory(); 
  23.  
  24.             String basedir = finalName; 
  25.  
  26.             if ( specifiedBasedir != null ) 
  27.             { 
  28.                 basedir = 
  29.                     AssemblyFormatUtils.getOutputDirectory( specifiedBasedir, configSource.getProject(), null
  30.                                                             finalName, configSource ); 
  31.             } 
  32.  
  33.             final List<ContainerDescriptorHandler> containerHandlers = 
  34.                 selectContainerDescriptorHandlers( assembly.getContainerDescriptorHandlers(), configSource ); 
  35.  
  36.             final Archiver archiver = 
  37.                 createArchiver( format, assembly.isIncludeBaseDirectory(), basedir, configSource, containerHandlers ); 
  38.  
  39.             archiver.setDestFile( destFile ); 
  40.  
  41.             final AssemblyContext context = new DefaultAssemblyContext(); 
  42.  
  43.             dependencyResolver.resolve( assembly, configSource, context ); 
  44.  
  45.             for ( final Iterator<AssemblyArchiverPhase> phaseIterator = assemblyPhases.iterator(); phaseIterator.hasNext(); ) 
  46.             { 
  47.                 final AssemblyArchiverPhase phase = phaseIterator.next(); 
  48.  
  49.                 phase.execute( assembly, archiver, configSource, context ); 
  50.             } 
  51.  
  52.             archiver.createArchive(); 
  53.         } 
  54.    ... 
  55.  
  56.         return destFile; 
  57.     } 

从第36行可以看出,在最终创建archiver时候(createArchiver方法),它会吧assembly.isIncludeBaseDirectory()作为参数传递进去,我们看下如果这个参数设置为false的行为。

 

  1. protected Archiver createArchiver( final String format, final boolean includeBaseDir, final String finalName, 
  2.                                      final AssemblerConfigurationSource configSource, 
  3.                                      final List<ContainerDescriptorHandler> containerHandlers ) 
  4.       throws ArchiverException, NoSuchArchiverException 
  5.   { 
  6.       Archiver archiver; 
  7.       if ( format.startsWith( "tar" ) ) 
  8.       { 
  9.           archiver = createTarArchiver( format, configSource.getTarLongFileMode() ); 
  10.       } 
  11.       else if ( "war".equals( format ) ) 
  12.       { 
  13.           archiver = createWarArchiver(); 
  14.       } 
  15.       else 
  16.       { 
  17.           archiver = archiverManager.getArchiver( format ); 
  18.       } 
  19.  
  20.       final List<FileSelector> extraSelectors = new ArrayList<FileSelector>(); 
  21.       final List<ArchiveFinalizer> extraFinalizers = new ArrayList<ArchiveFinalizer>(); 
  22.       if ( archiver instanceof JarArchiver ) 
  23.       { 
  24.           extraSelectors.add( new JarSecurityFileSelector() ); 
  25.  
  26.           extraFinalizers.add( new ManifestCreationFinalizer( configSource.getProject(), 
  27.                                                               configSource.getJarArchiveConfiguration() ) ); 
  28.  
  29.       } 
  30.  
  31.       if ( configSource.getArchiverConfig() != null ) 
  32.       { 
  33.           configureArchiver( archiver, configSource ); 
  34.       } 
  35.  
  36.       String prefix = ""
  37.       if ( includeBaseDir ) 
  38.       { 
  39.           prefix = finalName; 
  40.       } 
  41.  
  42.       archiver = 
  43.           new AssemblyProxyArchiver( prefix, archiver, containerHandlers, extraSelectors, extraFinalizers, 
  44.                                      configSource.getWorkingDirectory(), getLogger(), configSource.isDryRun() ); 
  45.  
  46.       archiver.setUseJvmChmod( configSource.isUpdateOnly() ); 
  47.       archiver.setIgnorePermissions( configSource.isIgnorePermissions() ); 
  48.       archiver.setForced( !configSource.isUpdateOnly() ); 
  49.  
  50.       return archiver; 
  51.   } 

这里,从36到40行可以看出来,当includeBaseDirectory为true时候,prefix为artifact的finalName,如果为false,则设置为"",而这个finalName就是分发包的最外层。所以,当我们吧includeBaseDirectory设置为false,就没有包装层了。这个结果正和官网上对这个参数的说明一致。具体打包过程见AssemblyProxyArchiver,我就不展开了。