View Javadoc
1   package org.apache.maven.wrapper;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *  http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.io.IOException;
23  import java.io.InputStream;
24  import java.net.URI;
25  import java.nio.file.DirectoryStream;
26  import java.nio.file.FileVisitResult;
27  import java.nio.file.Files;
28  import java.nio.file.Path;
29  import java.nio.file.Paths;
30  import java.nio.file.SimpleFileVisitor;
31  import java.nio.file.StandardCopyOption;
32  import java.nio.file.attribute.BasicFileAttributes;
33  import java.nio.file.attribute.PosixFilePermission;
34  import java.nio.file.attribute.PosixFilePermissions;
35  import java.util.ArrayList;
36  import java.util.Enumeration;
37  import java.util.List;
38  import java.util.Locale;
39  import java.util.Set;
40  import java.util.zip.ZipEntry;
41  import java.util.zip.ZipException;
42  import java.util.zip.ZipFile;
43  
44  /**
45   * Maven distribution installer, eventually using a {@link Downloader} first.
46   *
47   * @author Hans Dockter
48   */
49  public class Installer
50  {
51      public static final Path DEFAULT_DISTRIBUTION_PATH = Paths.get( "wrapper", "dists" );
52  
53      private final Downloader download;
54  
55      private final PathAssembler pathAssembler;
56  
57      public Installer( Downloader download, PathAssembler pathAssembler )
58      {
59          this.download = download;
60          this.pathAssembler = pathAssembler;
61      }
62  
63      public Path createDist( WrapperConfiguration configuration )
64          throws Exception
65      {
66          URI distributionUrl = configuration.getDistribution();
67  
68          String mvnwRepoUrl = System.getenv( MavenWrapperMain.MVNW_REPOURL );
69          if ( mvnwRepoUrl != null && !mvnwRepoUrl.isEmpty() )
70          {
71              Logger.info( "Detected MVNW_REPOURL environment variable " + mvnwRepoUrl );
72              String mvnPath = distributionUrl.toURL().toString();
73              mvnPath = mvnPath.substring( mvnPath.indexOf( "org/apache/maven" ) );
74              distributionUrl = new URI( mvnwRepoUrl ).resolve( "/" ).resolve( mvnPath );
75          }
76  
77          boolean alwaysDownload = configuration.isAlwaysDownload();
78          boolean alwaysUnpack = configuration.isAlwaysUnpack();
79  
80          PathAssembler.LocalDistribution localDistribution = pathAssembler.getDistribution( configuration );
81          Path localZipFile = localDistribution.getZipFile();
82  
83          if ( alwaysDownload || alwaysUnpack || Files.notExists( localZipFile ) )
84          {
85              Logger.info( "Installing Maven distribution " + localDistribution.getDistributionDir().toAbsolutePath() );
86          }
87  
88          boolean downloaded = false;
89          if ( alwaysDownload || Files.notExists( localZipFile ) )
90          {
91              Logger.info( "Downloading " + distributionUrl );
92              Path tmpZipFile = localZipFile.resolveSibling( localZipFile.getFileName() + ".part" );
93              Files.deleteIfExists( tmpZipFile );
94              download.download( distributionUrl, tmpZipFile );
95              Files.move( tmpZipFile, localZipFile, StandardCopyOption.REPLACE_EXISTING );
96              downloaded = Files.exists( localZipFile );
97          }
98  
99          Path distDir = localDistribution.getDistributionDir();
100         List<Path> dirs = listDirs( distDir );
101 
102         if ( downloaded || alwaysUnpack || dirs.isEmpty() )
103         {
104             for ( Path dir : dirs )
105             {
106                 Logger.info( "Deleting directory " + dir.toAbsolutePath() );
107                 deleteDir( dir );
108             }
109             Logger.info( "Unzipping " + localZipFile.toAbsolutePath() + " to " + distDir.toAbsolutePath() );
110             unzip( localZipFile, distDir );
111             dirs = listDirs( distDir );
112             if ( dirs.isEmpty() )
113             {
114                 throw new RuntimeException( String.format( Locale.ROOT,
115                                                            "Maven distribution '%s' does not contain any directory."
116                                                                + " Expected to find exactly 1 directory.",
117                                                            distDir ) );
118             }
119             setExecutablePermissions( dirs.get( 0 ) );
120         }
121         if ( dirs.size() != 1 )
122         {
123             throw new RuntimeException( String.format( Locale.ROOT,
124                                                        "Maven distribution '%s' contains too many directories."
125                                                            + " Expected to find exactly 1 directory.",
126                                                        distDir ) );
127         }
128         return dirs.get( 0 );
129     }
130 
131     private List<Path> listDirs( Path distDir )
132         throws IOException
133     {
134         List<Path> dirs = new ArrayList<>();
135         if ( Files.exists( distDir ) )
136         {
137             try ( DirectoryStream<Path> dirStream = Files.newDirectoryStream( distDir ) )
138             {
139                 for ( Path file : dirStream )
140                 {
141                     if ( Files.isDirectory( file ) )
142                     {
143                         dirs.add( file );
144                     }
145                 }
146             }
147         }
148         return dirs;
149     }
150 
151     private void setExecutablePermissions( Path mavenHome )
152     {
153         if ( isWindows() )
154         {
155             return;
156         }
157         Path mavenCommand = mavenHome.resolve( "bin/mvn" );
158         try
159         {
160             Set<PosixFilePermission> perms = PosixFilePermissions.fromString( "rwxr-xr-x" );
161             Files.setPosixFilePermissions( mavenCommand, perms );
162         }
163         catch ( IOException e )
164         {
165             Logger.warn( "Could not set executable permissions for: " + mavenCommand.toAbsolutePath()
166                 + ". Please do this manually if you want to use Maven." );
167         }
168     }
169 
170     private boolean isWindows()
171     {
172         String osName = System.getProperty( "os.name" ).toLowerCase( Locale.US );
173         return osName.contains( "windows" );
174     }
175 
176     private void deleteDir( Path dirPath )
177         throws IOException
178     {
179         Files.walkFileTree( dirPath, new SimpleFileVisitor<Path>()
180         {
181             @Override
182             public FileVisitResult postVisitDirectory( Path dir, IOException exc )
183                 throws IOException
184             {
185                 Files.delete( dir );
186                 if ( exc != null )
187                 {
188                     throw exc;
189                 }
190                 return FileVisitResult.CONTINUE;
191             }
192 
193             @Override
194             public FileVisitResult visitFile( Path file, BasicFileAttributes attrs )
195                 throws IOException
196             {
197                 Files.delete( file );
198                 return FileVisitResult.CONTINUE;
199             }
200         } );
201     }
202 
203     public void unzip( Path zip, Path dest )
204         throws IOException
205     {
206         final Path destDir = dest.normalize();
207         try ( final ZipFile zipFile = new ZipFile( zip.toFile() ) )
208         {
209             final Enumeration<? extends ZipEntry> entries = zipFile.entries();
210 
211             while ( entries.hasMoreElements() )
212             {
213                 final ZipEntry entry = entries.nextElement();
214 
215                 Path fileEntry = destDir.resolve( entry.getName() ).normalize();
216                 if ( !fileEntry.startsWith( destDir ) )
217                 {
218                     throw new ZipException( "Zip includes an invalid entry: " + entry.getName() );
219                 }
220 
221                 if ( entry.isDirectory() )
222                 {
223                     continue;
224                 }
225 
226                 Files.createDirectories( fileEntry.getParent() );
227 
228                 try ( InputStream inStream = zipFile.getInputStream( entry ) )
229                 {
230                     Files.copy( inStream, fileEntry );
231                 }
232             }
233         }
234     }
235 }