From a850495296876350bbc23d0e94b96afa5e905e19 Mon Sep 17 00:00:00 2001 From: bdemers Date: Mon, 4 Mar 2013 11:21:40 -0500 Subject: [PATCH] A copy of the gradle wrapper ported for use with maven Lightly touched to better mesh with current maven builds. (some of which my not have been necessary in hind sight) Test it out with this build, run: "./mvnw clean install" at the root of this repo This will download, install, and run maven 3.0.5 (as configured in maven/wrapper/maven-wrapper.properties) NOTE: windows batch file is NOT tested TODO: * Possibly extract these bits out into something both gradle and maven (and others) can use * Port gradle IT's to this build * See if anyone is interested in this project --- maven-wrapper/.gitignore | 3 + maven-wrapper/maven/wrapper/maven-wrapper.jar | Bin 0 -> 53177 bytes .../maven/wrapper/maven-wrapper.properties | 1 + maven-wrapper/mvnw | 170 +++++ maven-wrapper/mvnw.bat | 189 +++++ maven-wrapper/pom.xml | 55 ++ .../maven/wrapper/BootstrapMainStarter.java | 57 ++ .../maven/wrapper/DefaultDownloader.java | 137 ++++ .../org/apache/maven/wrapper/Downloader.java | 28 + .../org/apache/maven/wrapper/Installer.java | 233 ++++++ .../maven/wrapper/MavenWrapperMain.java | 152 ++++ .../apache/maven/wrapper/PathAssembler.java | 146 ++++ .../wrapper/SystemPropertiesHandler.java | 73 ++ .../maven/wrapper/WrapperConfiguration.java | 109 +++ .../apache/maven/wrapper/WrapperExecutor.java | 177 +++++ .../cli/AbstractCommandLineConverter.java | 44 ++ ...bstractPropertiesCommandLineConverter.java | 60 ++ .../cli/CommandLineArgumentException.java | 35 + .../wrapper/cli/CommandLineConverter.java | 36 + .../maven/wrapper/cli/CommandLineOption.java | 132 ++++ .../maven/wrapper/cli/CommandLineParser.java | 675 ++++++++++++++++++ .../maven/wrapper/cli/ParsedCommandLine.java | 133 ++++ .../wrapper/cli/ParsedCommandLineOption.java | 52 ++ ...ProjectPropertiesCommandLineConverter.java | 40 ++ .../SystemPropertiesCommandLineConverter.java | 39 + .../apache/maven/wrapper/DownloaderTest.java | 49 ++ .../apache/maven/wrapper/InstallerTest.java | 196 +++++ .../maven/wrapper/PathAssemblerTest.java | 145 ++++ .../wrapper/SystemPropertiesHandlerTest.java | 60 ++ .../maven/wrapper/WrapperExecutorTest.java | 192 +++++ .../apache/maven/wrapper/wrapper.properties | 5 + 31 files changed, 3423 insertions(+) create mode 100644 maven-wrapper/.gitignore create mode 100644 maven-wrapper/maven/wrapper/maven-wrapper.jar create mode 100644 maven-wrapper/maven/wrapper/maven-wrapper.properties create mode 100755 maven-wrapper/mvnw create mode 100644 maven-wrapper/mvnw.bat create mode 100644 maven-wrapper/pom.xml create mode 100644 maven-wrapper/src/main/java/org/apache/maven/wrapper/BootstrapMainStarter.java create mode 100644 maven-wrapper/src/main/java/org/apache/maven/wrapper/DefaultDownloader.java create mode 100644 maven-wrapper/src/main/java/org/apache/maven/wrapper/Downloader.java create mode 100644 maven-wrapper/src/main/java/org/apache/maven/wrapper/Installer.java create mode 100644 maven-wrapper/src/main/java/org/apache/maven/wrapper/MavenWrapperMain.java create mode 100644 maven-wrapper/src/main/java/org/apache/maven/wrapper/PathAssembler.java create mode 100644 maven-wrapper/src/main/java/org/apache/maven/wrapper/SystemPropertiesHandler.java create mode 100644 maven-wrapper/src/main/java/org/apache/maven/wrapper/WrapperConfiguration.java create mode 100644 maven-wrapper/src/main/java/org/apache/maven/wrapper/WrapperExecutor.java create mode 100644 maven-wrapper/src/main/java/org/apache/maven/wrapper/cli/AbstractCommandLineConverter.java create mode 100644 maven-wrapper/src/main/java/org/apache/maven/wrapper/cli/AbstractPropertiesCommandLineConverter.java create mode 100644 maven-wrapper/src/main/java/org/apache/maven/wrapper/cli/CommandLineArgumentException.java create mode 100644 maven-wrapper/src/main/java/org/apache/maven/wrapper/cli/CommandLineConverter.java create mode 100644 maven-wrapper/src/main/java/org/apache/maven/wrapper/cli/CommandLineOption.java create mode 100644 maven-wrapper/src/main/java/org/apache/maven/wrapper/cli/CommandLineParser.java create mode 100644 maven-wrapper/src/main/java/org/apache/maven/wrapper/cli/ParsedCommandLine.java create mode 100644 maven-wrapper/src/main/java/org/apache/maven/wrapper/cli/ParsedCommandLineOption.java create mode 100644 maven-wrapper/src/main/java/org/apache/maven/wrapper/cli/ProjectPropertiesCommandLineConverter.java create mode 100644 maven-wrapper/src/main/java/org/apache/maven/wrapper/cli/SystemPropertiesCommandLineConverter.java create mode 100644 maven-wrapper/src/test/java/org/apache/maven/wrapper/DownloaderTest.java create mode 100644 maven-wrapper/src/test/java/org/apache/maven/wrapper/InstallerTest.java create mode 100644 maven-wrapper/src/test/java/org/apache/maven/wrapper/PathAssemblerTest.java create mode 100644 maven-wrapper/src/test/java/org/apache/maven/wrapper/SystemPropertiesHandlerTest.java create mode 100644 maven-wrapper/src/test/java/org/apache/maven/wrapper/WrapperExecutorTest.java create mode 100644 maven-wrapper/src/test/resources/org/apache/maven/wrapper/wrapper.properties diff --git a/maven-wrapper/.gitignore b/maven-wrapper/.gitignore new file mode 100644 index 0000000000..114ff4f92a --- /dev/null +++ b/maven-wrapper/.gitignore @@ -0,0 +1,3 @@ +.classpath +.project +.settings \ No newline at end of file diff --git a/maven-wrapper/maven/wrapper/maven-wrapper.jar b/maven-wrapper/maven/wrapper/maven-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..593b7af78891f3d7a78e1cc9881ccae8740a05bd GIT binary patch literal 53177 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 O29X3hE;L#n{`-GQW|*h| literal 0 HcmV?d00001 diff --git a/maven-wrapper/maven/wrapper/maven-wrapper.properties b/maven-wrapper/maven/wrapper/maven-wrapper.properties new file mode 100644 index 0000000000..10dcf5fd65 --- /dev/null +++ b/maven-wrapper/maven/wrapper/maven-wrapper.properties @@ -0,0 +1 @@ +distributionUrl=https://repository.apache.org/content/repositories/releases/org/apache/maven/apache-maven/3.0.5/apache-maven-3.0.5-bin.zip \ No newline at end of file diff --git a/maven-wrapper/mvnw b/maven-wrapper/mvnw new file mode 100755 index 0000000000..c03152e1bf --- /dev/null +++ b/maven-wrapper/mvnw @@ -0,0 +1,170 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven2 Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xnoagent -Djava.compiler=NONE -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + if [ -z "$JAVA_VERSION" ] ; then + JAVA_VERSION="CurrentJDK" + fi + if [ -z "$JAVA_HOME" ] ; then + JAVA_HOME=/System/Library/Frameworks/JavaVM.framework/Versions/${JAVA_VERSION}/Home + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Migwn, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" + # TODO classpath? +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" -a ! "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + javaExecutable="`readlink -f \"$javaExecutable\"`" + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`which java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." + echo " We cannot execute $JAVACMD" + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` +fi + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + -classpath "./maven/wrapper/maven-wrapper.jar" \ + ${WRAPPER_LAUNCHER} "$@" diff --git a/maven-wrapper/mvnw.bat b/maven-wrapper/mvnw.bat new file mode 100644 index 0000000000..ab2822dd77 --- /dev/null +++ b/maven-wrapper/mvnw.bat @@ -0,0 +1,189 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven2 Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xnoagent -Djava.compiler=NONE -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM enable echoing my setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" +:skipRcPre + +set ERROR_CODE=0 + +@REM set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" @setlocal +if "%OS%"=="WINNT" @setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo ERROR: JAVA_HOME not found in your environment. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto chkMHome + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory. +echo JAVA_HOME = "%JAVA_HOME%" +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation +echo. +goto error + +:chkMHome +if not "%M2_HOME%"=="" goto valMHome + +if "%OS%"=="Windows_NT" SET "M2_HOME=%~dp0.." +if "%OS%"=="WINNT" SET "M2_HOME=%~dp0.." +if not "%M2_HOME%"=="" goto valMHome + +echo. +echo ERROR: M2_HOME not found in your environment. +echo Please set the M2_HOME variable in your environment to match the +echo location of the Maven installation +echo. +goto error + +:valMHome + +:stripMHome +if not "_%M2_HOME:~-1%"=="_\" goto checkMBat +set "M2_HOME=%M2_HOME:~0,-1%" +goto stripMHome + +:checkMBat +if exist "%M2_HOME%\bin\mvn.bat" goto init + +echo. +echo ERROR: M2_HOME is set to an invalid directory. +echo M2_HOME = "%M2_HOME%" +echo Please set the M2_HOME variable in your environment to match the +echo location of the Maven installation +echo. +goto error +@REM ==== END VALIDATION ==== + +:init +@REM Decide how to startup depending on the version of windows + +@REM -- Windows NT with Novell Login +if "%OS%"=="WINNT" goto WinNTNovell + +@REM -- Win98ME +if NOT "%OS%"=="Windows_NT" goto Win9xArg + +:WinNTNovell + +@REM -- 4NT shell +if "%@eval[2+2]" == "4" goto 4NTArgs + +@REM -- Regular WinNT shell +set MAVEN_CMD_LINE_ARGS=%* +goto endInit + +@REM The 4NT Shell from jp software +:4NTArgs +set MAVEN_CMD_LINE_ARGS=%$ +goto endInit + +:Win9xArg +@REM Slurp the command line arguments. This loop allows for an unlimited number +@REM of agruments (up to the command line limit, anyway). +set MAVEN_CMD_LINE_ARGS= +:Win9xApp +if %1a==a goto endInit +set MAVEN_CMD_LINE_ARGS=%MAVEN_CMD_LINE_ARGS% %1 +shift +goto Win9xApp + +@REM Reaching here means variables are defined and arguments have been captured +:endInit +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" + +@REM -- Regular WinNT shell +set WRAPPER_JAR="".\maven\wrapper\maven-wrapper.jar"" +goto runm2 + +@REM Start MAVEN2 +:runm2 +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain +%MAVEN_JAVA_EXE% %MAVEN_OPTS% -classpath %WRAPPER_JAR% %WRAPPER_LAUNCHER% %MAVEN_CMD_LINE_ARGS% +if ERRORLEVEL 1 goto error +goto end + +:error +if "%OS%"=="Windows_NT" @endlocal +if "%OS%"=="WINNT" @endlocal +set ERROR_CODE=1 + +:end +@REM set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" goto endNT +if "%OS%"=="WINNT" goto endNT + +@REM For old DOS remove the set variables from ENV - we assume they were not set +@REM before we started - at least we don't leave any baggage around +set MAVEN_JAVA_EXE= +set MAVEN_CMD_LINE_ARGS= +goto postExec + +:endNT +@endlocal & set ERROR_CODE=%ERROR_CODE% + +:postExec + +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" +:skipRcPost + +@REM pause the batch file if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%" == "on" pause + +if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% + +cmd /C exit /B %ERROR_CODE% + + diff --git a/maven-wrapper/pom.xml b/maven-wrapper/pom.xml new file mode 100644 index 0000000000..4293959e9a --- /dev/null +++ b/maven-wrapper/pom.xml @@ -0,0 +1,55 @@ + + 4.0.0 + + maven-parent + org.apache.maven + 23 + + maven-wapper + 0.0.1-SNAPSHOT + Maven Wrapper + + + + + junit + junit + 4.11 + test + + + org.hamcrest + hamcrest-all + 1.3 + test + + + org.mockito + mockito-all + 1.9.5 + test + + + + commons-lang + commons-lang + 2.6 + test + + + commons-io + commons-io + 2.4 + test + + + + + ant + ant + 1.7.0 + test + + + + \ No newline at end of file diff --git a/maven-wrapper/src/main/java/org/apache/maven/wrapper/BootstrapMainStarter.java b/maven-wrapper/src/main/java/org/apache/maven/wrapper/BootstrapMainStarter.java new file mode 100644 index 0000000000..98efa97321 --- /dev/null +++ b/maven-wrapper/src/main/java/org/apache/maven/wrapper/BootstrapMainStarter.java @@ -0,0 +1,57 @@ +/* + * Copyright 2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.maven.wrapper; + +import java.io.File; +import java.lang.reflect.Method; +import java.net.URL; +import java.net.URLClassLoader; + +/** + * @author Hans Dockter + */ +public class BootstrapMainStarter +{ + public void start( String[] args, File mavenHome ) + throws Exception + { + File mavenJar = findLauncherJar( mavenHome ); + URLClassLoader contextClassLoader = + new URLClassLoader( new URL[] { mavenJar.toURI().toURL() }, ClassLoader.getSystemClassLoader().getParent() ); + Thread.currentThread().setContextClassLoader( contextClassLoader ); + Class mainClass = contextClassLoader.loadClass( "org.codehaus.plexus.classworlds.launcher.Launcher" ); + + System.setProperty( "maven.home", mavenHome.getAbsolutePath() ); + System.setProperty( "classworlds.conf", new File( mavenHome, "/bin/m2.conf" ).getAbsolutePath() ); + + Method mainMethod = mainClass.getMethod( "main", String[].class ); + mainMethod.invoke( null, new Object[] { args } ); + } + + private File findLauncherJar( File mavenHome ) + { + for ( File file : new File( mavenHome, "boot" ).listFiles() ) + { + if ( file.getName().matches( "plexus-classworlds-.*\\.jar" ) ) + { + return file; + } + } + throw new RuntimeException( + String.format( "Could not locate the Maven launcher JAR in Maven distribution '%s'.", + mavenHome ) ); + } +} diff --git a/maven-wrapper/src/main/java/org/apache/maven/wrapper/DefaultDownloader.java b/maven-wrapper/src/main/java/org/apache/maven/wrapper/DefaultDownloader.java new file mode 100644 index 0000000000..738911ce05 --- /dev/null +++ b/maven-wrapper/src/main/java/org/apache/maven/wrapper/DefaultDownloader.java @@ -0,0 +1,137 @@ +/* + * Copyright 2007-2009 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.maven.wrapper; + +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.Authenticator; +import java.net.PasswordAuthentication; +import java.net.URI; +import java.net.URL; +import java.net.URLConnection; + +/** + * @author Hans Dockter + */ +public class DefaultDownloader + implements Downloader +{ + private static final int PROGRESS_CHUNK = 20000; + + private static final int BUFFER_SIZE = 10000; + + private final String applicationName; + + private final String applicationVersion; + + public DefaultDownloader( String applicationName, String applicationVersion ) + { + this.applicationName = applicationName; + this.applicationVersion = applicationVersion; + configureProxyAuthentication(); + } + + private void configureProxyAuthentication() + { + if ( System.getProperty( "http.proxyUser" ) != null ) + { + Authenticator.setDefault( new SystemPropertiesProxyAuthenticator() ); + } + } + + public void download( URI address, File destination ) + throws Exception + { + if ( destination.exists() ) + { + return; + } + destination.getParentFile().mkdirs(); + + downloadInternal( address, destination ); + } + + private void downloadInternal( URI address, File destination ) + throws Exception + { + OutputStream out = null; + URLConnection conn; + InputStream in = null; + try + { + URL url = address.toURL(); + out = new BufferedOutputStream( new FileOutputStream( destination ) ); + conn = url.openConnection(); + final String userAgentValue = calculateUserAgent(); + conn.setRequestProperty( "User-Agent", userAgentValue ); + in = conn.getInputStream(); + byte[] buffer = new byte[BUFFER_SIZE]; + int numRead; + long progressCounter = 0; + while ( ( numRead = in.read( buffer ) ) != -1 ) + { + progressCounter += numRead; + if ( progressCounter / PROGRESS_CHUNK > 0 ) + { + System.out.print( "." ); + progressCounter = progressCounter - PROGRESS_CHUNK; + } + out.write( buffer, 0, numRead ); + } + } + finally + { + System.out.println( "" ); + if ( in != null ) + { + in.close(); + } + if ( out != null ) + { + out.close(); + } + } + } + + private String calculateUserAgent() + { + String appVersion = applicationVersion; + + String javaVendor = System.getProperty( "java.vendor" ); + String javaVersion = System.getProperty( "java.version" ); + String javaVendorVersion = System.getProperty( "java.vm.version" ); + String osName = System.getProperty( "os.name" ); + String osVersion = System.getProperty( "os.version" ); + String osArch = System.getProperty( "os.arch" ); + return String.format( "%s/%s (%s;%s;%s) (%s;%s;%s)", applicationName, appVersion, osName, osVersion, osArch, + javaVendor, javaVersion, javaVendorVersion ); + } + + private static class SystemPropertiesProxyAuthenticator + extends Authenticator + { + @Override + protected PasswordAuthentication getPasswordAuthentication() + { + return new PasswordAuthentication( System.getProperty( "http.proxyUser" ), + System.getProperty( "http.proxyPassword", "" ).toCharArray() ); + } + } +} diff --git a/maven-wrapper/src/main/java/org/apache/maven/wrapper/Downloader.java b/maven-wrapper/src/main/java/org/apache/maven/wrapper/Downloader.java new file mode 100644 index 0000000000..2b22cdb871 --- /dev/null +++ b/maven-wrapper/src/main/java/org/apache/maven/wrapper/Downloader.java @@ -0,0 +1,28 @@ +/* + * Copyright 2007-2009 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.maven.wrapper; + +import java.io.File; +import java.net.URI; + +/** + * @author Hans Dockter + */ +public interface Downloader +{ + void download( URI address, File destination ) + throws Exception; +} diff --git a/maven-wrapper/src/main/java/org/apache/maven/wrapper/Installer.java b/maven-wrapper/src/main/java/org/apache/maven/wrapper/Installer.java new file mode 100644 index 0000000000..6540545b33 --- /dev/null +++ b/maven-wrapper/src/main/java/org/apache/maven/wrapper/Installer.java @@ -0,0 +1,233 @@ +/* + * Copyright 2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.maven.wrapper; + +import java.io.BufferedOutputStream; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.net.URI; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.Formatter; +import java.util.List; +import java.util.Locale; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +/** + * @author Hans Dockter + */ +public class Installer +{ + public static final String DEFAULT_DISTRIBUTION_PATH = "wrapper/dists"; + + private final Downloader download; + + private final PathAssembler pathAssembler; + + public Installer( Downloader download, PathAssembler pathAssembler ) + { + this.download = download; + this.pathAssembler = pathAssembler; + } + + public File createDist( WrapperConfiguration configuration ) + throws Exception + { + URI distributionUrl = configuration.getDistribution(); + boolean alwaysDownload = configuration.isAlwaysDownload(); + boolean alwaysUnpack = configuration.isAlwaysUnpack(); + + PathAssembler.LocalDistribution localDistribution = pathAssembler.getDistribution( configuration ); + + File localZipFile = localDistribution.getZipFile(); + boolean downloaded = false; + if ( alwaysDownload || !localZipFile.exists() ) + { + File tmpZipFile = new File( localZipFile.getParentFile(), localZipFile.getName() + ".part" ); + tmpZipFile.delete(); + System.out.println( "Downloading " + distributionUrl ); + download.download( distributionUrl, tmpZipFile ); + tmpZipFile.renameTo( localZipFile ); + downloaded = true; + } + + File distDir = localDistribution.getDistributionDir(); + List dirs = listDirs( distDir ); + + if ( downloaded || alwaysUnpack || dirs.isEmpty() ) + { + for ( File dir : dirs ) + { + System.out.println( "Deleting directory " + dir.getAbsolutePath() ); + deleteDir( dir ); + } + System.out.println( "Unzipping " + localZipFile.getAbsolutePath() + " to " + distDir.getAbsolutePath() ); + unzip( localZipFile, distDir ); + dirs = listDirs( distDir ); + if ( dirs.isEmpty() ) + { + throw new RuntimeException( + String.format( "Maven distribution '%s' does not contain any directories. Expected to find exactly 1 directory.", + distributionUrl ) ); + } + setExecutablePermissions( dirs.get( 0 ) ); + } + if ( dirs.size() != 1 ) + { + throw new RuntimeException( + String.format( "Maven distribution '%s' contains too many directories. Expected to find exactly 1 directory.", + distributionUrl ) ); + } + return dirs.get( 0 ); + } + + private List listDirs( File distDir ) + { + List dirs = new ArrayList(); + if ( distDir.exists() ) + { + for ( File file : distDir.listFiles() ) + { + if ( file.isDirectory() ) + { + dirs.add( file ); + } + } + } + return dirs; + } + + private void setExecutablePermissions( File mavenHome ) + { + if ( isWindows() ) + { + return; + } + File mavenCommand = new File( mavenHome, "bin/mvn" ); + String errorMessage = null; + try + { + ProcessBuilder pb = new ProcessBuilder( "chmod", "755", mavenCommand.getCanonicalPath() ); + Process p = pb.start(); + if ( p.waitFor() == 0 ) + { + System.out.println( "Set executable permissions for: " + mavenCommand.getAbsolutePath() ); + } + else + { + BufferedReader is = new BufferedReader( new InputStreamReader( p.getInputStream() ) ); + Formatter stdout = new Formatter(); + String line; + while ( ( line = is.readLine() ) != null ) + { + stdout.format( "%s%n", line ); + } + errorMessage = stdout.toString(); + } + } + catch ( IOException e ) + { + errorMessage = e.getMessage(); + } + catch ( InterruptedException e ) + { + errorMessage = e.getMessage(); + } + if ( errorMessage != null ) + { + System.out.println( "Could not set executable permissions for: " + mavenCommand.getAbsolutePath() ); + System.out.println( "Please do this manually if you want to use maven." ); + } + } + + private boolean isWindows() + { + String osName = System.getProperty( "os.name" ).toLowerCase( Locale.US ); + if ( osName.indexOf( "windows" ) > -1 ) + { + return true; + } + return false; + } + + private boolean deleteDir( File dir ) + { + if ( dir.isDirectory() ) + { + String[] children = dir.list(); + for ( int i = 0; i < children.length; i++ ) + { + boolean success = deleteDir( new File( dir, children[i] ) ); + if ( !success ) + { + return false; + } + } + } + + // The directory is now empty so delete it + return dir.delete(); + } + + public void unzip( File zip, File dest ) + throws IOException + { + Enumeration entries; + ZipFile zipFile; + + zipFile = new ZipFile( zip ); + + entries = zipFile.entries(); + + while ( entries.hasMoreElements() ) + { + ZipEntry entry = (ZipEntry) entries.nextElement(); + + if ( entry.isDirectory() ) + { + ( new File( dest, entry.getName() ) ).mkdirs(); + continue; + } + + copyInputStream( zipFile.getInputStream( entry ), + new BufferedOutputStream( new FileOutputStream( new File( dest, entry.getName() ) ) ) ); + } + zipFile.close(); + } + + public void copyInputStream( InputStream in, OutputStream out ) + throws IOException + { + byte[] buffer = new byte[1024]; + int len; + + while ( ( len = in.read( buffer ) ) >= 0 ) + { + out.write( buffer, 0, len ); + } + + in.close(); + out.close(); + } + +} diff --git a/maven-wrapper/src/main/java/org/apache/maven/wrapper/MavenWrapperMain.java b/maven-wrapper/src/main/java/org/apache/maven/wrapper/MavenWrapperMain.java new file mode 100644 index 0000000000..86c1f06625 --- /dev/null +++ b/maven-wrapper/src/main/java/org/apache/maven/wrapper/MavenWrapperMain.java @@ -0,0 +1,152 @@ +/* + * Copyright 2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.maven.wrapper; + +import java.io.File; +import java.io.InputStream; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Map; +import java.util.Properties; + +import org.apache.maven.wrapper.cli.CommandLineParser; +import org.apache.maven.wrapper.cli.SystemPropertiesCommandLineConverter; + +/** + * @author Hans Dockter + */ +public class MavenWrapperMain +{ + public static final String DEFAULT_MAVEN_USER_HOME = System.getProperty( "user.home" ) + "/.m2"; + + public static final String MAVEN_USER_HOME_PROPERTY_KEY = "maven.user.home"; + + public static final String MAVEN_USER_HOME_ENV_KEY = "MAVEN_USER_HOME"; + + public static void main( String[] args ) + throws Exception + { + File wrapperJar = wrapperJar(); + File propertiesFile = wrapperProperties( wrapperJar ); + File rootDir = rootDir( wrapperJar ); + + Properties systemProperties = System.getProperties(); + systemProperties.putAll( parseSystemPropertiesFromArgs( args ) ); + + addSystemProperties( rootDir ); + + WrapperExecutor wrapperExecutor = WrapperExecutor.forWrapperPropertiesFile( propertiesFile, System.out ); + wrapperExecutor.execute( args, new Installer( new DefaultDownloader( "mvnw", wrapperVersion() ), + new PathAssembler( mavenUserHome() ) ), new BootstrapMainStarter() ); + } + + private static Map parseSystemPropertiesFromArgs( String[] args ) + { + SystemPropertiesCommandLineConverter converter = new SystemPropertiesCommandLineConverter(); + CommandLineParser commandLineParser = new CommandLineParser(); + converter.configure( commandLineParser ); + commandLineParser.allowUnknownOptions(); + return converter.convert( commandLineParser.parse( args ) ); + } + + private static void addSystemProperties( File rootDir ) + { + System.getProperties().putAll( SystemPropertiesHandler.getSystemProperties( new File( mavenUserHome(), + "maven.properties" ) ) ); + System.getProperties().putAll( SystemPropertiesHandler.getSystemProperties( new File( rootDir, + "maven.properties" ) ) ); + } + + private static File rootDir( File wrapperJar ) + { + return wrapperJar.getParentFile().getParentFile().getParentFile(); + } + + private static File wrapperProperties( File wrapperJar ) + { + return new File( wrapperJar.getParent(), wrapperJar.getName().replaceFirst( "\\.jar$", ".properties" ) ); + } + + private static File wrapperJar() + { + URI location; + try + { + location = MavenWrapperMain.class.getProtectionDomain().getCodeSource().getLocation().toURI(); + } + catch ( URISyntaxException e ) + { + throw new RuntimeException( e ); + } + if ( !location.getScheme().equals( "file" ) ) + { + throw new RuntimeException( + String.format( "Cannot determine classpath for wrapper Jar from codebase '%s'.", + location ) ); + } + return new File( location.getPath() ); + } + + static String wrapperVersion() + { + try + { + InputStream resourceAsStream = + MavenWrapperMain.class.getResourceAsStream( "/META-INF/maven/org.apache.maven/maven-wapper/pom.properties" ); + if ( resourceAsStream == null ) + { + throw new RuntimeException( "No maven properties found." ); + } + Properties mavenProperties = new Properties(); + try + { + mavenProperties.load( resourceAsStream ); + String version = mavenProperties.getProperty( "version" ); + if ( version == null ) + { + throw new RuntimeException( "No version number specified in build receipt resource." ); + } + return version; + } + finally + { + resourceAsStream.close(); + } + } + catch ( Exception e ) + { + throw new RuntimeException( "Could not determine wrapper version.", e ); + } + } + + private static File mavenUserHome() + { + String mavenUserHome = System.getProperty( MAVEN_USER_HOME_PROPERTY_KEY ); + if ( mavenUserHome != null ) + { + return new File( mavenUserHome ); + } + else if ( ( mavenUserHome = System.getenv( MAVEN_USER_HOME_ENV_KEY ) ) != null ) + { + return new File( mavenUserHome ); + } + else + { + return new File( DEFAULT_MAVEN_USER_HOME ); + } + } +} diff --git a/maven-wrapper/src/main/java/org/apache/maven/wrapper/PathAssembler.java b/maven-wrapper/src/main/java/org/apache/maven/wrapper/PathAssembler.java new file mode 100644 index 0000000000..03dd0c0c12 --- /dev/null +++ b/maven-wrapper/src/main/java/org/apache/maven/wrapper/PathAssembler.java @@ -0,0 +1,146 @@ +/* + * Copyright 2007-2008 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.maven.wrapper; + +import java.io.File; +import java.math.BigInteger; +import java.net.URI; +import java.security.MessageDigest; + +/** + * @author Hans Dockter + */ +public class PathAssembler +{ + public static final String MAVEN_USER_HOME_STRING = "MAVEN_USER_HOME"; + + public static final String PROJECT_STRING = "PROJECT"; + + private File mavenUserHome; + + public PathAssembler() + { + } + + public PathAssembler( File mavenUserHome ) + { + this.mavenUserHome = mavenUserHome; + } + + /** + * Determines the local locations for the distribution to use given the supplied configuration. + */ + public LocalDistribution getDistribution( WrapperConfiguration configuration ) + { + String baseName = getDistName( configuration.getDistribution() ); + String distName = removeExtension( baseName ); + String rootDirName = rootDirName( distName, configuration ); + File distDir = + new File( getBaseDir( configuration.getDistributionBase() ), configuration.getDistributionPath() + "/" + + rootDirName ); + File distZip = + new File( getBaseDir( configuration.getZipBase() ), configuration.getZipPath() + "/" + rootDirName + "/" + + baseName ); + return new LocalDistribution( distDir, distZip ); + } + + private String rootDirName( String distName, WrapperConfiguration configuration ) + { + String urlHash = getMd5Hash( configuration.getDistribution().toString() ); + return String.format( "%s/%s", distName, urlHash ); + } + + private String getMd5Hash( String string ) + { + try + { + MessageDigest messageDigest = MessageDigest.getInstance( "MD5" ); + byte[] bytes = string.getBytes(); + messageDigest.update( bytes ); + return new BigInteger( 1, messageDigest.digest() ).toString( 32 ); + } + catch ( Exception e ) + { + throw new RuntimeException( "Could not hash input string.", e ); + } + } + + private String removeExtension( String name ) + { + int p = name.lastIndexOf( "." ); + if ( p < 0 ) + { + return name; + } + return name.substring( 0, p ); + } + + private String getDistName( URI distUrl ) + { + String path = distUrl.getPath(); + int p = path.lastIndexOf( "/" ); + if ( p < 0 ) + { + return path; + } + return path.substring( p + 1 ); + } + + private File getBaseDir( String base ) + { + if ( base.equals( MAVEN_USER_HOME_STRING ) ) + { + return mavenUserHome; + } + else if ( base.equals( PROJECT_STRING ) ) + { + return new File( System.getProperty( "user.dir" ) ); + } + else + { + throw new RuntimeException( "Base: " + base + " is unknown" ); + } + } + + public class LocalDistribution + { + private final File distZip; + + private final File distDir; + + public LocalDistribution( File distDir, File distZip ) + { + this.distDir = distDir; + this.distZip = distZip; + } + + /** + * Returns the location to install the distribution into. + */ + public File getDistributionDir() + { + return distDir; + } + + /** + * Returns the location to install the distribution ZIP file to. + */ + public File getZipFile() + { + return distZip; + } + } +} diff --git a/maven-wrapper/src/main/java/org/apache/maven/wrapper/SystemPropertiesHandler.java b/maven-wrapper/src/main/java/org/apache/maven/wrapper/SystemPropertiesHandler.java new file mode 100644 index 0000000000..69fefff22e --- /dev/null +++ b/maven-wrapper/src/main/java/org/apache/maven/wrapper/SystemPropertiesHandler.java @@ -0,0 +1,73 @@ +/* + * Copyright 2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.maven.wrapper; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * @author Hans Dockter + */ +public class SystemPropertiesHandler +{ + + public static Map getSystemProperties( File propertiesFile ) + { + Map propertyMap = new HashMap(); + if ( !propertiesFile.isFile() ) + { + return propertyMap; + } + Properties properties = new Properties(); + try + { + FileInputStream inStream = new FileInputStream( propertiesFile ); + try + { + properties.load( inStream ); + } + finally + { + inStream.close(); + } + } + catch ( IOException e ) + { + throw new RuntimeException( "Error when loading properties file=" + propertiesFile, e ); + } + + Pattern pattern = Pattern.compile( "systemProp\\.(.*)" ); + for ( Object argument : properties.keySet() ) + { + Matcher matcher = pattern.matcher( argument.toString() ); + if ( matcher.find() ) + { + String key = matcher.group( 1 ); + if ( key.length() > 0 ) + { + propertyMap.put( key, properties.get( argument ).toString() ); + } + } + } + return propertyMap; + } +} diff --git a/maven-wrapper/src/main/java/org/apache/maven/wrapper/WrapperConfiguration.java b/maven-wrapper/src/main/java/org/apache/maven/wrapper/WrapperConfiguration.java new file mode 100644 index 0000000000..22ee7be6fc --- /dev/null +++ b/maven-wrapper/src/main/java/org/apache/maven/wrapper/WrapperConfiguration.java @@ -0,0 +1,109 @@ +/* + * Copyright 2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.maven.wrapper; + +import java.net.URI; + +public class WrapperConfiguration +{ + public static final String ALWAYS_UNPACK_ENV = "MAVEN_WRAPPER_ALWAYS_UNPACK"; + + public static final String ALWAYS_DOWNLOAD_ENV = "MAVEN_WRAPPER_ALWAYS_DOWNLOAD"; + + private boolean alwaysUnpack = Boolean.parseBoolean( System.getenv( ALWAYS_UNPACK_ENV ) ); + + private boolean alwaysDownload = Boolean.parseBoolean( System.getenv( ALWAYS_DOWNLOAD_ENV ) ); + + private URI distribution; + + private String distributionBase = PathAssembler.MAVEN_USER_HOME_STRING; + + private String distributionPath = Installer.DEFAULT_DISTRIBUTION_PATH; + + private String zipBase = PathAssembler.MAVEN_USER_HOME_STRING; + + private String zipPath = Installer.DEFAULT_DISTRIBUTION_PATH; + + public boolean isAlwaysDownload() + { + return alwaysDownload; + } + + public void setAlwaysDownload( boolean alwaysDownload ) + { + this.alwaysDownload = alwaysDownload; + } + + public boolean isAlwaysUnpack() + { + return alwaysUnpack; + } + + public void setAlwaysUnpack( boolean alwaysUnpack ) + { + this.alwaysUnpack = alwaysUnpack; + } + + public URI getDistribution() + { + return distribution; + } + + public void setDistribution( URI distribution ) + { + this.distribution = distribution; + } + + public String getDistributionBase() + { + return distributionBase; + } + + public void setDistributionBase( String distributionBase ) + { + this.distributionBase = distributionBase; + } + + public String getDistributionPath() + { + return distributionPath; + } + + public void setDistributionPath( String distributionPath ) + { + this.distributionPath = distributionPath; + } + + public String getZipBase() + { + return zipBase; + } + + public void setZipBase( String zipBase ) + { + this.zipBase = zipBase; + } + + public String getZipPath() + { + return zipPath; + } + + public void setZipPath( String zipPath ) + { + this.zipPath = zipPath; + } +} diff --git a/maven-wrapper/src/main/java/org/apache/maven/wrapper/WrapperExecutor.java b/maven-wrapper/src/main/java/org/apache/maven/wrapper/WrapperExecutor.java new file mode 100644 index 0000000000..ef95020507 --- /dev/null +++ b/maven-wrapper/src/main/java/org/apache/maven/wrapper/WrapperExecutor.java @@ -0,0 +1,177 @@ +/* + * Copyright 2007-2008 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.maven.wrapper; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Properties; + +/** + * @author Hans Dockter + */ +public class WrapperExecutor +{ + public static final String DISTRIBUTION_URL_PROPERTY = "distributionUrl"; + + public static final String DISTRIBUTION_BASE_PROPERTY = "distributionBase"; + + public static final String ZIP_STORE_BASE_PROPERTY = "zipStoreBase"; + + public static final String DISTRIBUTION_PATH_PROPERTY = "distributionPath"; + + public static final String ZIP_STORE_PATH_PROPERTY = "zipStorePath"; + + private final Properties properties; + + private final File propertiesFile; + + private final Appendable warningOutput; + + private final WrapperConfiguration config = new WrapperConfiguration(); + + public static WrapperExecutor forProjectDirectory( File projectDir, Appendable warningOutput ) + { + return new WrapperExecutor( new File( projectDir, "maven/wrapper/maven-wrapper.properties" ), new Properties(), + warningOutput ); + } + + public static WrapperExecutor forWrapperPropertiesFile( File propertiesFile, Appendable warningOutput ) + { + if ( !propertiesFile.exists() ) + { + throw new RuntimeException( String.format( "Wrapper properties file '%s' does not exist.", propertiesFile ) ); + } + return new WrapperExecutor( propertiesFile, new Properties(), warningOutput ); + } + + WrapperExecutor( File propertiesFile, Properties properties, Appendable warningOutput ) + { + this.properties = properties; + this.propertiesFile = propertiesFile; + this.warningOutput = warningOutput; + if ( propertiesFile.exists() ) + { + try + { + loadProperties( propertiesFile, properties ); + config.setDistribution( prepareDistributionUri() ); + config.setDistributionBase( getProperty( DISTRIBUTION_BASE_PROPERTY, config.getDistributionBase() ) ); + config.setDistributionPath( getProperty( DISTRIBUTION_PATH_PROPERTY, config.getDistributionPath() ) ); + config.setZipBase( getProperty( ZIP_STORE_BASE_PROPERTY, config.getZipBase() ) ); + config.setZipPath( getProperty( ZIP_STORE_PATH_PROPERTY, config.getZipPath() ) ); + } + catch ( Exception e ) + { + throw new RuntimeException( String.format( "Could not load wrapper properties from '%s'.", + propertiesFile ), e ); + } + } + } + + private URI prepareDistributionUri() + throws URISyntaxException + { + URI source = readDistroUrl(); + if ( source.getScheme() == null ) + { + // no scheme means someone passed a relative url. In our context only file relative urls make sense. + return new File( propertiesFile.getParentFile(), source.getSchemeSpecificPart() ).toURI(); + } + else + { + return source; + } + } + + private URI readDistroUrl() + throws URISyntaxException + { + if ( properties.getProperty( DISTRIBUTION_URL_PROPERTY ) != null ) + { + return new URI( getProperty( DISTRIBUTION_URL_PROPERTY ) ); + } + + reportMissingProperty( DISTRIBUTION_URL_PROPERTY ); + return null; // previous line will fail + } + + private static void loadProperties( File propertiesFile, Properties properties ) + throws IOException + { + InputStream inStream = new FileInputStream( propertiesFile ); + try + { + properties.load( inStream ); + } + finally + { + inStream.close(); + } + } + + /** + * Returns the distribution which this wrapper will use. Returns null if no wrapper meta-data was found in the + * specified project directory. + */ + public URI getDistribution() + { + return config.getDistribution(); + } + + /** + * Returns the configuration for this wrapper. + */ + public WrapperConfiguration getConfiguration() + { + return config; + } + + public void execute( String[] args, Installer install, BootstrapMainStarter bootstrapMainStarter ) + throws Exception + { + File mavenHome = install.createDist( config ); + bootstrapMainStarter.start( args, mavenHome ); + } + + private String getProperty( String propertyName ) + { + return getProperty( propertyName, null ); + } + + private String getProperty( String propertyName, String defaultValue ) + { + String value = properties.getProperty( propertyName ); + if ( value != null ) + { + return value; + } + if ( defaultValue != null ) + { + return defaultValue; + } + return reportMissingProperty( propertyName ); + } + + private String reportMissingProperty( String propertyName ) + { + throw new RuntimeException( String.format( "No value with key '%s' specified in wrapper properties file '%s'.", + propertyName, propertiesFile ) ); + } +} diff --git a/maven-wrapper/src/main/java/org/apache/maven/wrapper/cli/AbstractCommandLineConverter.java b/maven-wrapper/src/main/java/org/apache/maven/wrapper/cli/AbstractCommandLineConverter.java new file mode 100644 index 0000000000..31c46fd57c --- /dev/null +++ b/maven-wrapper/src/main/java/org/apache/maven/wrapper/cli/AbstractCommandLineConverter.java @@ -0,0 +1,44 @@ +/* + * Copyright 2009 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.maven.wrapper.cli; + +public abstract class AbstractCommandLineConverter + implements CommandLineConverter +{ + public T convert( Iterable args ) + throws CommandLineArgumentException + { + CommandLineParser parser = new CommandLineParser(); + configure( parser ); + return convert( parser.parse( args ) ); + } + + public T convert( ParsedCommandLine args ) + throws CommandLineArgumentException + { + return convert( args, newInstance() ); + } + + public T convert( Iterable args, T target ) + throws CommandLineArgumentException + { + CommandLineParser parser = new CommandLineParser(); + configure( parser ); + return convert( parser.parse( args ), target ); + } + + protected abstract T newInstance(); +} diff --git a/maven-wrapper/src/main/java/org/apache/maven/wrapper/cli/AbstractPropertiesCommandLineConverter.java b/maven-wrapper/src/main/java/org/apache/maven/wrapper/cli/AbstractPropertiesCommandLineConverter.java new file mode 100644 index 0000000000..e8937c3efe --- /dev/null +++ b/maven-wrapper/src/main/java/org/apache/maven/wrapper/cli/AbstractPropertiesCommandLineConverter.java @@ -0,0 +1,60 @@ +/* + * Copyright 2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.maven.wrapper.cli; + +import java.util.HashMap; +import java.util.Map; + +public abstract class AbstractPropertiesCommandLineConverter + extends AbstractCommandLineConverter> +{ + protected abstract String getPropertyOption(); + + protected abstract String getPropertyOptionDetailed(); + + protected abstract String getPropertyOptionDescription(); + + public void configure( CommandLineParser parser ) + { + CommandLineOption option = parser.option( getPropertyOption(), getPropertyOptionDetailed() ); + option = option.hasArguments(); + option.hasDescription( getPropertyOptionDescription() ); + } + + protected Map newInstance() + { + return new HashMap(); + } + + public Map convert( ParsedCommandLine options, Map properties ) + throws CommandLineArgumentException + { + for ( String keyValueExpression : options.option( getPropertyOption() ).getValues() ) + { + int pos = keyValueExpression.indexOf( "=" ); + if ( pos < 0 ) + { + properties.put( keyValueExpression, "" ); + } + else + { + properties.put( keyValueExpression.substring( 0, pos ), keyValueExpression.substring( pos + 1 ) ); + } + } + return properties; + } +} diff --git a/maven-wrapper/src/main/java/org/apache/maven/wrapper/cli/CommandLineArgumentException.java b/maven-wrapper/src/main/java/org/apache/maven/wrapper/cli/CommandLineArgumentException.java new file mode 100644 index 0000000000..0fed0394bd --- /dev/null +++ b/maven-wrapper/src/main/java/org/apache/maven/wrapper/cli/CommandLineArgumentException.java @@ -0,0 +1,35 @@ +/* + * Copyright 2009 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.maven.wrapper.cli; + +/** + * A {@code CommandLineArgumentException} is thrown when command-line arguments cannot be parsed. + * + * @author Hans Dockter + */ +public class CommandLineArgumentException + extends RuntimeException +{ + public CommandLineArgumentException( String message ) + { + super( message ); + } + + public CommandLineArgumentException( String message, Throwable cause ) + { + super( message, cause ); + } +} diff --git a/maven-wrapper/src/main/java/org/apache/maven/wrapper/cli/CommandLineConverter.java b/maven-wrapper/src/main/java/org/apache/maven/wrapper/cli/CommandLineConverter.java new file mode 100644 index 0000000000..1d27dc293d --- /dev/null +++ b/maven-wrapper/src/main/java/org/apache/maven/wrapper/cli/CommandLineConverter.java @@ -0,0 +1,36 @@ +/* + * Copyright 2009 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.maven.wrapper.cli; + +/** + * @author Hans Dockter + */ +public interface CommandLineConverter +{ + T convert( Iterable args ) + throws CommandLineArgumentException; + + T convert( Iterable args, T target ) + throws CommandLineArgumentException; + + T convert( ParsedCommandLine args ) + throws CommandLineArgumentException; + + T convert( ParsedCommandLine args, T target ) + throws CommandLineArgumentException; + + void configure( CommandLineParser parser ); +} diff --git a/maven-wrapper/src/main/java/org/apache/maven/wrapper/cli/CommandLineOption.java b/maven-wrapper/src/main/java/org/apache/maven/wrapper/cli/CommandLineOption.java new file mode 100644 index 0000000000..e3fde8bdfa --- /dev/null +++ b/maven-wrapper/src/main/java/org/apache/maven/wrapper/cli/CommandLineOption.java @@ -0,0 +1,132 @@ +/* + * Copyright 2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.maven.wrapper.cli; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public class CommandLineOption +{ + private final Set options = new HashSet(); + + private Class argumentType = Void.TYPE; + + private String description; + + private String subcommand; + + private String deprecationWarning; + + private boolean incubating; + + public CommandLineOption( Iterable options ) + { + for ( String option : options ) + { + this.options.add( option ); + } + } + + public Set getOptions() + { + return options; + } + + public CommandLineOption hasArgument() + { + argumentType = String.class; + return this; + } + + public CommandLineOption hasArguments() + { + argumentType = List.class; + return this; + } + + public String getSubcommand() + { + return subcommand; + } + + public CommandLineOption mapsToSubcommand( String command ) + { + this.subcommand = command; + return this; + } + + public String getDescription() + { + StringBuilder result = new StringBuilder(); + if ( description != null ) + { + result.append( description ); + } + if ( deprecationWarning != null ) + { + if ( result.length() > 0 ) + { + result.append( ' ' ); + } + result.append( "[deprecated - " ); + result.append( deprecationWarning ); + result.append( "]" ); + } + if ( incubating ) + { + if ( result.length() > 0 ) + { + result.append( ' ' ); + } + result.append( "[incubating]" ); + } + return result.toString(); + } + + public CommandLineOption hasDescription( String description ) + { + this.description = description; + return this; + } + + public boolean getAllowsArguments() + { + return argumentType != Void.TYPE; + } + + public boolean getAllowsMultipleArguments() + { + return argumentType == List.class; + } + + public CommandLineOption deprecated( String deprecationWarning ) + { + this.deprecationWarning = deprecationWarning; + return this; + } + + public CommandLineOption incubating() + { + incubating = true; + return this; + } + + public String getDeprecationWarning() + { + return deprecationWarning; + } +} diff --git a/maven-wrapper/src/main/java/org/apache/maven/wrapper/cli/CommandLineParser.java b/maven-wrapper/src/main/java/org/apache/maven/wrapper/cli/CommandLineParser.java new file mode 100644 index 0000000000..77460130fb --- /dev/null +++ b/maven-wrapper/src/main/java/org/apache/maven/wrapper/cli/CommandLineParser.java @@ -0,0 +1,675 @@ +/* + * Copyright 2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.maven.wrapper.cli; + +import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.io.Writer; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.Formatter; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; + +/** + *

+ * A command-line parser which supports a command/sub-command style command-line interface. Supports the following + * syntax: + *

+ * + *
+ * <option>* (<sub-command> <sub-command-option>*)*
+ * 
+ *
    + *
  • Short options are a '-' followed by a single character. For example: {@code -a}.
  • + *
  • Long options are '--' followed by multiple characters. For example: {@code --long-option}.
  • + *
  • Options can take arguments. The argument follows the option. For example: {@code -a arg} or {@code --long arg}.
  • + *
  • Arguments can be attached to the option using '='. For example: {@code -a=arg} or {@code --long=arg}.
  • + *
  • Arguments can be attached to short options. For example: {@code -aarg}.
  • + *
  • Short options can be combined. For example {@code -ab} is equivalent to {@code -a -b}.
  • + *
  • Anything else is treated as an extra argument. This includes a single {@code -} character.
  • + *
  • '--' indicates the end of the options. Anything following is not parsed and is treated as extra arguments.
  • + *
  • The parser is forgiving, and allows '--' to be used with short options and '-' to be used with long options.
  • + *
  • The set of options must be known at parse time. Sub-commands and their options do not need to be known at parse + * time. Use {@link ParsedCommandLine#getExtraArguments()} to obtain the non-option command-line arguments.
  • + *
+ */ +public class CommandLineParser +{ + private Map optionsByString = new HashMap(); + + private boolean allowMixedOptions; + + private boolean allowUnknownOptions; + + private final PrintWriter deprecationPrinter; + + public CommandLineParser() + { + this( new OutputStreamWriter( System.out ) ); + } + + public CommandLineParser( Writer deprecationPrinter ) + { + this.deprecationPrinter = new PrintWriter( deprecationPrinter ); + } + + /** + * Parses the given command-line. + * + * @param commandLine The command-line. + * @return The parsed command line. + * @throws org.apache.maven.wrapper.cli.CommandLineArgumentException On parse failure. + */ + public ParsedCommandLine parse( String... commandLine ) + throws CommandLineArgumentException + { + return parse( Arrays.asList( commandLine ) ); + } + + /** + * Parses the given command-line. + * + * @param commandLine The command-line. + * @return The parsed command line. + * @throws org.apache.maven.wrapper.cli.CommandLineArgumentException On parse failure. + */ + public ParsedCommandLine parse( Iterable commandLine ) + throws CommandLineArgumentException + { + ParsedCommandLine parsedCommandLine = + new ParsedCommandLine( new HashSet( optionsByString.values() ) ); + ParserState parseState = new BeforeFirstSubCommand( parsedCommandLine ); + for ( String arg : commandLine ) + { + if ( parseState.maybeStartOption( arg ) ) + { + if ( arg.equals( "--" ) ) + { + parseState = new AfterOptions( parsedCommandLine ); + } + else if ( arg.matches( "--[^=]+" ) ) + { + OptionParserState parsedOption = parseState.onStartOption( arg, arg.substring( 2 ) ); + parseState = parsedOption.onStartNextArg(); + } + else if ( arg.matches( "--[^=]+=.*" ) ) + { + int endArg = arg.indexOf( '=' ); + OptionParserState parsedOption = parseState.onStartOption( arg, arg.substring( 2, endArg ) ); + parseState = parsedOption.onArgument( arg.substring( endArg + 1 ) ); + } + else if ( arg.matches( "-[^=]=.*" ) ) + { + OptionParserState parsedOption = parseState.onStartOption( arg, arg.substring( 1, 2 ) ); + parseState = parsedOption.onArgument( arg.substring( 3 ) ); + } + else + { + assert arg.matches( "-[^-].*" ); + String option = arg.substring( 1 ); + if ( optionsByString.containsKey( option ) ) + { + OptionParserState parsedOption = parseState.onStartOption( arg, option ); + parseState = parsedOption.onStartNextArg(); + } + else + { + String option1 = arg.substring( 1, 2 ); + OptionParserState parsedOption; + if ( optionsByString.containsKey( option1 ) ) + { + parsedOption = parseState.onStartOption( "-" + option1, option1 ); + if ( parsedOption.getHasArgument() ) + { + parseState = parsedOption.onArgument( arg.substring( 2 ) ); + } + else + { + parseState = parsedOption.onComplete(); + for ( int i = 2; i < arg.length(); i++ ) + { + String optionStr = arg.substring( i, i + 1 ); + parsedOption = parseState.onStartOption( "-" + optionStr, optionStr ); + parseState = parsedOption.onComplete(); + } + } + } + else + { + if ( allowUnknownOptions ) + { + // if we are allowing unknowns, just pass through the whole arg + parsedOption = parseState.onStartOption( arg, option ); + parseState = parsedOption.onComplete(); + } + else + { + // We are going to throw a CommandLineArgumentException below, but want the message + // to reflect that we didn't recognise the first char (i.e. the option specifier) + parsedOption = parseState.onStartOption( "-" + option1, option1 ); + parseState = parsedOption.onComplete(); + } + } + } + } + } + else + { + parseState = parseState.onNonOption( arg ); + } + } + + parseState.onCommandLineEnd(); + return parsedCommandLine; + } + + public CommandLineParser allowMixedSubcommandsAndOptions() + { + allowMixedOptions = true; + return this; + } + + public CommandLineParser allowUnknownOptions() + { + allowUnknownOptions = true; + return this; + } + + /** + * Prints a usage message to the given stream. + * + * @param out The output stream to write to. + */ + public void printUsage( Appendable out ) + { + Formatter formatter = new Formatter( out ); + Set orderedOptions = new TreeSet( new OptionComparator() ); + orderedOptions.addAll( optionsByString.values() ); + Map lines = new LinkedHashMap(); + for ( CommandLineOption option : orderedOptions ) + { + Set orderedOptionStrings = new TreeSet( new OptionStringComparator() ); + orderedOptionStrings.addAll( option.getOptions() ); + List prefixedStrings = new ArrayList(); + for ( String optionString : orderedOptionStrings ) + { + if ( optionString.length() == 1 ) + { + prefixedStrings.add( "-" + optionString ); + } + else + { + prefixedStrings.add( "--" + optionString ); + } + } + + String key = join( prefixedStrings, ", " ); + String value = option.getDescription(); + if ( value == null || value.length() == 0 ) + { + value = ""; + } + + lines.put( key, value ); + } + int max = 0; + for ( String optionStr : lines.keySet() ) + { + max = Math.max( max, optionStr.length() ); + } + for ( Map.Entry entry : lines.entrySet() ) + { + if ( entry.getValue().length() == 0 ) + { + formatter.format( "%s%n", entry.getKey() ); + } + else + { + formatter.format( "%-" + max + "s %s%n", entry.getKey(), entry.getValue() ); + } + } + formatter.flush(); + } + + private static String join( Collection things, String separator ) + { + StringBuffer buffer = new StringBuffer(); + boolean first = true; + + if ( separator == null ) + { + separator = ""; + } + + for ( Object thing : things ) + { + if ( !first ) + { + buffer.append( separator ); + } + buffer.append( thing.toString() ); + first = false; + } + return buffer.toString(); + } + + /** + * Defines a new option. By default, the option takes no arguments and has no description. + * + * @param options The options values. + * @return The option, which can be further configured. + */ + public CommandLineOption option( String... options ) + { + for ( String option : options ) + { + if ( optionsByString.containsKey( option ) ) + { + throw new IllegalArgumentException( String.format( "Option '%s' is already defined.", option ) ); + } + if ( option.startsWith( "-" ) ) + { + throw new IllegalArgumentException( + String.format( "Cannot add option '%s' as an option cannot start with '-'.", + option ) ); + } + } + CommandLineOption option = new CommandLineOption( Arrays.asList( options ) ); + for ( String optionStr : option.getOptions() ) + { + this.optionsByString.put( optionStr, option ); + } + return option; + } + + private static class OptionString + { + private final String arg; + + private final String option; + + private OptionString( String arg, String option ) + { + this.arg = arg; + this.option = option; + } + + public String getDisplayName() + { + return arg.startsWith( "--" ) ? "--" + option : "-" + option; + } + + @Override + public String toString() + { + return getDisplayName(); + } + } + + private static abstract class ParserState + { + public abstract boolean maybeStartOption( String arg ); + + boolean isOption( String arg ) + { + return arg.matches( "-.+" ); + } + + public abstract OptionParserState onStartOption( String arg, String option ); + + public abstract ParserState onNonOption( String arg ); + + public void onCommandLineEnd() + { + } + } + + private abstract class OptionAwareParserState + extends ParserState + { + protected final ParsedCommandLine commandLine; + + protected OptionAwareParserState( ParsedCommandLine commandLine ) + { + this.commandLine = commandLine; + } + + @Override + public boolean maybeStartOption( String arg ) + { + return isOption( arg ); + } + + @Override + public ParserState onNonOption( String arg ) + { + commandLine.addExtraValue( arg ); + return allowMixedOptions ? new AfterFirstSubCommand( commandLine ) : new AfterOptions( commandLine ); + } + } + + private class BeforeFirstSubCommand + extends OptionAwareParserState + { + private BeforeFirstSubCommand( ParsedCommandLine commandLine ) + { + super( commandLine ); + } + + @Override + public OptionParserState onStartOption( String arg, String option ) + { + OptionString optionString = new OptionString( arg, option ); + CommandLineOption commandLineOption = optionsByString.get( option ); + if ( commandLineOption == null ) + { + if ( allowUnknownOptions ) + { + return new UnknownOptionParserState( arg, commandLine, this ); + } + else + { + throw new CommandLineArgumentException( String.format( "Unknown command-line option '%s'.", + optionString ) ); + } + } + return new KnownOptionParserState( optionString, commandLineOption, commandLine, this ); + } + } + + private class AfterFirstSubCommand + extends OptionAwareParserState + { + private AfterFirstSubCommand( ParsedCommandLine commandLine ) + { + super( commandLine ); + } + + @Override + public OptionParserState onStartOption( String arg, String option ) + { + CommandLineOption commandLineOption = optionsByString.get( option ); + if ( commandLineOption == null ) + { + return new UnknownOptionParserState( arg, commandLine, this ); + } + return new KnownOptionParserState( new OptionString( arg, option ), commandLineOption, commandLine, this ); + } + } + + private static class AfterOptions + extends ParserState + { + private final ParsedCommandLine commandLine; + + private AfterOptions( ParsedCommandLine commandLine ) + { + this.commandLine = commandLine; + } + + @Override + public boolean maybeStartOption( String arg ) + { + return false; + } + + @Override + public OptionParserState onStartOption( String arg, String option ) + { + return new UnknownOptionParserState( arg, commandLine, this ); + } + + @Override + public ParserState onNonOption( String arg ) + { + commandLine.addExtraValue( arg ); + return this; + } + } + + private static class MissingOptionArgState + extends ParserState + { + private final OptionParserState option; + + private MissingOptionArgState( OptionParserState option ) + { + this.option = option; + } + + @Override + public boolean maybeStartOption( String arg ) + { + return isOption( arg ); + } + + @Override + public OptionParserState onStartOption( String arg, String option ) + { + return this.option.onComplete().onStartOption( arg, option ); + } + + @Override + public ParserState onNonOption( String arg ) + { + return option.onArgument( arg ); + } + + @Override + public void onCommandLineEnd() + { + option.onComplete(); + } + } + + private static abstract class OptionParserState + { + public abstract ParserState onStartNextArg(); + + public abstract ParserState onArgument( String argument ); + + public abstract boolean getHasArgument(); + + public abstract ParserState onComplete(); + } + + private class KnownOptionParserState + extends OptionParserState + { + private final OptionString optionString; + + private final CommandLineOption option; + + private final ParsedCommandLine commandLine; + + private final ParserState state; + + private final List values = new ArrayList(); + + private KnownOptionParserState( OptionString optionString, CommandLineOption option, + ParsedCommandLine commandLine, ParserState state ) + { + this.optionString = optionString; + this.option = option; + this.commandLine = commandLine; + this.state = state; + } + + @Override + public ParserState onArgument( String argument ) + { + if ( !getHasArgument() ) + { + throw new CommandLineArgumentException( + String.format( "Command-line option '%s' does not take an argument.", + optionString ) ); + } + if ( argument.length() == 0 ) + { + throw new CommandLineArgumentException( + String.format( "An empty argument was provided for command-line option '%s'.", + optionString ) ); + } + values.add( argument ); + return onComplete(); + } + + @Override + public ParserState onStartNextArg() + { + if ( option.getAllowsArguments() && values.isEmpty() ) + { + return new MissingOptionArgState( this ); + } + return onComplete(); + } + + @Override + public boolean getHasArgument() + { + return option.getAllowsArguments(); + } + + @Override + public ParserState onComplete() + { + if ( getHasArgument() && values.isEmpty() ) + { + throw new CommandLineArgumentException( + String.format( "No argument was provided for command-line option '%s'.", + optionString ) ); + } + + ParsedCommandLineOption parsedOption = commandLine.addOption( optionString.option, option ); + if ( values.size() + parsedOption.getValues().size() > 1 && !option.getAllowsMultipleArguments() ) + { + throw new CommandLineArgumentException( + String.format( "Multiple arguments were provided for command-line option '%s'.", + optionString ) ); + } + for ( String value : values ) + { + parsedOption.addArgument( value ); + } + if ( option.getDeprecationWarning() != null ) + { + deprecationPrinter.println( "The " + optionString + " option is deprecated - " + + option.getDeprecationWarning() ); + } + if ( option.getSubcommand() != null ) + { + return state.onNonOption( option.getSubcommand() ); + } + + return state; + } + } + + private static class UnknownOptionParserState + extends OptionParserState + { + private final ParserState state; + + private final String arg; + + private final ParsedCommandLine commandLine; + + private UnknownOptionParserState( String arg, ParsedCommandLine commandLine, ParserState state ) + { + this.arg = arg; + this.commandLine = commandLine; + this.state = state; + } + + @Override + public boolean getHasArgument() + { + return true; + } + + @Override + public ParserState onStartNextArg() + { + return onComplete(); + } + + @Override + public ParserState onArgument( String argument ) + { + return onComplete(); + } + + @Override + public ParserState onComplete() + { + commandLine.addExtraValue( arg ); + return state; + } + } + + private static final class OptionComparator + implements Comparator + { + public int compare( CommandLineOption option1, CommandLineOption option2 ) + { + String min1 = Collections.min( option1.getOptions(), new OptionStringComparator() ); + String min2 = Collections.min( option2.getOptions(), new OptionStringComparator() ); + return new CaseInsensitiveStringComparator().compare( min1, min2 ); + } + } + + private static final class CaseInsensitiveStringComparator + implements Comparator + { + public int compare( String option1, String option2 ) + { + int diff = option1.compareToIgnoreCase( option2 ); + if ( diff != 0 ) + { + return diff; + } + return option1.compareTo( option2 ); + } + } + + private static final class OptionStringComparator + implements Comparator + { + public int compare( String option1, String option2 ) + { + boolean short1 = option1.length() == 1; + boolean short2 = option2.length() == 1; + if ( short1 && !short2 ) + { + return -1; + } + if ( !short1 && short2 ) + { + return 1; + } + return new CaseInsensitiveStringComparator().compare( option1, option2 ); + } + } +} diff --git a/maven-wrapper/src/main/java/org/apache/maven/wrapper/cli/ParsedCommandLine.java b/maven-wrapper/src/main/java/org/apache/maven/wrapper/cli/ParsedCommandLine.java new file mode 100644 index 0000000000..e7e34451cc --- /dev/null +++ b/maven-wrapper/src/main/java/org/apache/maven/wrapper/cli/ParsedCommandLine.java @@ -0,0 +1,133 @@ +/* + * Copyright 2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.maven.wrapper.cli; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +public class ParsedCommandLine +{ + private final Map optionsByString = new HashMap(); + + private final Set presentOptions = new HashSet(); + + private final List extraArguments = new ArrayList(); + + ParsedCommandLine( Iterable options ) + { + for ( CommandLineOption option : options ) + { + ParsedCommandLineOption parsedOption = new ParsedCommandLineOption(); + for ( String optionStr : option.getOptions() ) + { + optionsByString.put( optionStr, parsedOption ); + } + } + } + + @Override + public String toString() + { + return String.format( "options: %s, extraArguments: %s", quoteAndJoin( presentOptions ), + quoteAndJoin( extraArguments ) ); + } + + private String quoteAndJoin( Iterable strings ) + { + StringBuilder output = new StringBuilder(); + boolean isFirst = true; + for ( String string : strings ) + { + if ( !isFirst ) + { + output.append( ", " ); + } + output.append( "'" ); + output.append( string ); + output.append( "'" ); + isFirst = false; + } + return output.toString(); + } + + /** + * Returns true if the given option is present in this command-line. + * + * @param option The option, without the '-' or '--' prefix. + * @return true if the option is present. + */ + public boolean hasOption( String option ) + { + option( option ); + return presentOptions.contains( option ); + } + + /** + * See also {@link #hasOption}. + * + * @param logLevelOptions the options to check + * @return true if any of the passed options is present + */ + public boolean hasAnyOption( Collection logLevelOptions ) + { + for ( String option : logLevelOptions ) + { + if ( hasOption( option ) ) + { + return true; + } + } + return false; + } + + /** + * Returns the value of the given option. + * + * @param option The option, without the '-' or '--' prefix. + * @return The option. never returns null. + */ + public ParsedCommandLineOption option( String option ) + { + ParsedCommandLineOption parsedOption = optionsByString.get( option ); + if ( parsedOption == null ) + { + throw new IllegalArgumentException( String.format( "Option '%s' not defined.", option ) ); + } + return parsedOption; + } + + public List getExtraArguments() + { + return extraArguments; + } + + void addExtraValue( String value ) + { + extraArguments.add( value ); + } + + ParsedCommandLineOption addOption( String optionStr, CommandLineOption option ) + { + ParsedCommandLineOption parsedOption = optionsByString.get( optionStr ); + presentOptions.addAll( option.getOptions() ); + return parsedOption; + } +} diff --git a/maven-wrapper/src/main/java/org/apache/maven/wrapper/cli/ParsedCommandLineOption.java b/maven-wrapper/src/main/java/org/apache/maven/wrapper/cli/ParsedCommandLineOption.java new file mode 100644 index 0000000000..7d75a8fec1 --- /dev/null +++ b/maven-wrapper/src/main/java/org/apache/maven/wrapper/cli/ParsedCommandLineOption.java @@ -0,0 +1,52 @@ +/* + * Copyright 2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.maven.wrapper.cli; + +import java.util.ArrayList; +import java.util.List; + +public class ParsedCommandLineOption +{ + private final List values = new ArrayList(); + + public String getValue() + { + if ( !hasValue() ) + { + throw new IllegalStateException( "Option does not have any value." ); + } + if ( values.size() > 1 ) + { + throw new IllegalStateException( "Option has multiple values." ); + } + return values.get( 0 ); + } + + public List getValues() + { + return values; + } + + public void addArgument( String argument ) + { + values.add( argument ); + } + + public boolean hasValue() + { + return !values.isEmpty(); + } +} diff --git a/maven-wrapper/src/main/java/org/apache/maven/wrapper/cli/ProjectPropertiesCommandLineConverter.java b/maven-wrapper/src/main/java/org/apache/maven/wrapper/cli/ProjectPropertiesCommandLineConverter.java new file mode 100644 index 0000000000..8b1a110f87 --- /dev/null +++ b/maven-wrapper/src/main/java/org/apache/maven/wrapper/cli/ProjectPropertiesCommandLineConverter.java @@ -0,0 +1,40 @@ +/* + * Copyright 2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.maven.wrapper.cli; + +public class ProjectPropertiesCommandLineConverter + extends AbstractPropertiesCommandLineConverter +{ + + @Override + protected String getPropertyOption() + { + return "P"; + } + + @Override + protected String getPropertyOptionDetailed() + { + return "project-prop"; + } + + @Override + protected String getPropertyOptionDescription() + { + return "Set project property for the build script (e.g. -Pmyprop=myvalue)."; + } +} diff --git a/maven-wrapper/src/main/java/org/apache/maven/wrapper/cli/SystemPropertiesCommandLineConverter.java b/maven-wrapper/src/main/java/org/apache/maven/wrapper/cli/SystemPropertiesCommandLineConverter.java new file mode 100644 index 0000000000..fb7bc9ac5d --- /dev/null +++ b/maven-wrapper/src/main/java/org/apache/maven/wrapper/cli/SystemPropertiesCommandLineConverter.java @@ -0,0 +1,39 @@ +/* + * Copyright 2011 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.maven.wrapper.cli; + +public class SystemPropertiesCommandLineConverter + extends AbstractPropertiesCommandLineConverter +{ + + @Override + protected String getPropertyOption() + { + return "D"; + } + + @Override + protected String getPropertyOptionDetailed() + { + return "system-prop"; + } + + @Override + protected String getPropertyOptionDescription() + { + return "Set system property of the JVM (e.g. -Dmyprop=myvalue)."; + } +} \ No newline at end of file diff --git a/maven-wrapper/src/test/java/org/apache/maven/wrapper/DownloaderTest.java b/maven-wrapper/src/test/java/org/apache/maven/wrapper/DownloaderTest.java new file mode 100644 index 0000000000..c84096a867 --- /dev/null +++ b/maven-wrapper/src/test/java/org/apache/maven/wrapper/DownloaderTest.java @@ -0,0 +1,49 @@ +package org.apache.maven.wrapper; + +import static org.junit.Assert.assertEquals; + +import java.io.File; +import java.net.URI; + +import org.apache.commons.io.FileUtils; +import org.junit.Before; +import org.junit.Test; + +public class DownloaderTest +{ + + private DefaultDownloader download; + + private File testDir; + + private File downloadFile; + + private File rootDir; + + private URI sourceRoot; + + private File remoteFile; + + @Before + public void setUp() + throws Exception + { + download = new DefaultDownloader( "mvnw", "aVersion" ); + testDir = new File( "target/test-files/DownloadTest" ); + rootDir = new File( testDir, "root" ); + downloadFile = new File( rootDir, "file" ); + remoteFile = new File( testDir, "remoteFile" ); + FileUtils.write( remoteFile, "sometext" ); + sourceRoot = remoteFile.toURI(); + } + + @Test + public void testDownload() + throws Exception + { + assert !downloadFile.exists(); + download.download( sourceRoot, downloadFile ); + assert downloadFile.exists(); + assertEquals( "sometext", FileUtils.readFileToString( downloadFile ) ); + } +} diff --git a/maven-wrapper/src/test/java/org/apache/maven/wrapper/InstallerTest.java b/maven-wrapper/src/test/java/org/apache/maven/wrapper/InstallerTest.java new file mode 100644 index 0000000000..fc02d60940 --- /dev/null +++ b/maven-wrapper/src/test/java/org/apache/maven/wrapper/InstallerTest.java @@ -0,0 +1,196 @@ +package org.apache.maven.wrapper; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.io.File; +import java.net.URI; + +import org.apache.commons.io.FileUtils; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.taskdefs.Zip; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +/** + * @author Hans Dockter + */ +public class InstallerTest +{ + private File testDir = new File( "target/test-files/SystemPropertiesHandlerTest-" + System.currentTimeMillis() ); + + private Installer install; + + private Downloader downloadMock; + + private PathAssembler pathAssemblerMock; + + private boolean downloadCalled; + + private File zip; + + private File distributionDir; + + private File zipStore; + + private File mavenHomeDir; + + private File zipDestination; + + private WrapperConfiguration configuration = new WrapperConfiguration(); + + private Downloader download; + + private PathAssembler pathAssembler; + + private PathAssembler.LocalDistribution localDistribution; + + @Before + public void setup() + throws Exception + { + + testDir.mkdirs(); + + downloadCalled = false; + configuration.setZipBase( PathAssembler.PROJECT_STRING ); + configuration.setZipPath( "someZipPath" ); + configuration.setDistributionBase( PathAssembler.MAVEN_USER_HOME_STRING ); + configuration.setDistributionPath( "someDistPath" ); + configuration.setDistribution( new URI( "http://server/maven-0.9.zip" ) ); + configuration.setAlwaysDownload( false ); + configuration.setAlwaysUnpack( false ); + distributionDir = new File( testDir, "someDistPath" ); + mavenHomeDir = new File( distributionDir, "maven-0.9" ); + zipStore = new File( testDir, "zips" ); + zipDestination = new File( zipStore, "maven-0.9.zip" ); + + download = mock( Downloader.class ); + pathAssembler = mock( PathAssembler.class ); + localDistribution = mock( PathAssembler.LocalDistribution.class ); + + when( localDistribution.getZipFile() ).thenReturn( zipDestination ); + when( localDistribution.getDistributionDir() ).thenReturn( distributionDir ); + when( pathAssembler.getDistribution( configuration ) ).thenReturn( localDistribution ); + + install = new Installer( download, pathAssembler ); + + } + + private void createTestZip( File zipDestination ) + throws Exception + { + File explodedZipDir = new File( testDir, "explodedZip" ); + explodedZipDir.mkdirs(); + zipDestination.getParentFile().mkdirs(); + File mavenScript = new File( explodedZipDir, "maven-0.9/bin/mvn" ); + mavenScript.getParentFile().mkdirs(); + FileUtils.write( mavenScript, "something" ); + + zipTo( explodedZipDir, zipDestination ); + } + + public void testCreateDist() + throws Exception + { + File homeDir = install.createDist( configuration ); + + Assert.assertEquals( mavenHomeDir, homeDir ); + Assert.assertTrue( homeDir.isDirectory() ); + Assert.assertTrue( new File( homeDir, "bin/mvn" ).exists() ); + Assert.assertTrue( zipDestination.exists() ); + + Assert.assertEquals( localDistribution, pathAssembler.getDistribution( configuration ) ); + Assert.assertEquals( distributionDir, localDistribution.getDistributionDir() ); + Assert.assertEquals( zipDestination, localDistribution.getZipFile() ); + + // download.download(new URI("http://some/test"), distributionDir); + // verify(download).download(new URI("http://some/test"), distributionDir); + } + + @Test + public void testCreateDistWithExistingDistribution() + throws Exception + { + + FileUtils.touch( zipDestination ); + mavenHomeDir.mkdirs(); + File someFile = new File( mavenHomeDir, "some-file" ); + FileUtils.touch( someFile ); + + File homeDir = install.createDist( configuration ); + + Assert.assertEquals( mavenHomeDir, homeDir ); + Assert.assertTrue( mavenHomeDir.isDirectory() ); + Assert.assertTrue( new File( homeDir, "some-file" ).exists() ); + Assert.assertTrue( zipDestination.exists() ); + + Assert.assertEquals( localDistribution, pathAssembler.getDistribution( configuration ) ); + Assert.assertEquals( distributionDir, localDistribution.getDistributionDir() ); + Assert.assertEquals( zipDestination, localDistribution.getZipFile() ); + } + + @Test + public void testCreateDistWithExistingDistAndZipAndAlwaysUnpackTrue() + throws Exception + { + + createTestZip( zipDestination ); + mavenHomeDir.mkdirs(); + File garbage = new File( mavenHomeDir, "garbage" ); + FileUtils.touch( garbage ); + configuration.setAlwaysUnpack( true ); + + File homeDir = install.createDist( configuration ); + + Assert.assertEquals( mavenHomeDir, homeDir ); + Assert.assertTrue( mavenHomeDir.isDirectory() ); + Assert.assertFalse( new File( homeDir, "garbage" ).exists() ); + Assert.assertTrue( zipDestination.exists() ); + + Assert.assertEquals( localDistribution, pathAssembler.getDistribution( configuration ) ); + Assert.assertEquals( distributionDir, localDistribution.getDistributionDir() ); + Assert.assertEquals( zipDestination, localDistribution.getZipFile() ); + } + + @Test + public void testCreateDistWithExistingZipAndDistAndAlwaysDownloadTrue() + throws Exception + { + + createTestZip( zipDestination ); + File garbage = new File( mavenHomeDir, "garbage" ); + FileUtils.touch( garbage ); + configuration.setAlwaysUnpack( true ); + + File homeDir = install.createDist( configuration ); + + Assert.assertEquals( mavenHomeDir, homeDir ); + Assert.assertTrue( mavenHomeDir.isDirectory() ); + Assert.assertTrue( new File( homeDir, "bin/mvn" ).exists() ); + Assert.assertFalse( new File( homeDir, "garbage" ).exists() ); + Assert.assertTrue( zipDestination.exists() ); + + Assert.assertEquals( localDistribution, pathAssembler.getDistribution( configuration ) ); + Assert.assertEquals( distributionDir, localDistribution.getDistributionDir() ); + Assert.assertEquals( zipDestination, localDistribution.getZipFile() ); + + // download.download(new URI("http://some/test"), distributionDir); + // verify(download).download(new URI("http://some/test"), distributionDir); + } + + public void zipTo( File directoryToZip, File zipFile ) + { + Zip zip = new Zip(); + zip.setBasedir( directoryToZip ); + zip.setDestFile( zipFile ); + zip.setProject( new Project() ); + + Zip.WhenEmpty whenEmpty = new Zip.WhenEmpty(); + whenEmpty.setValue( "create" ); + zip.setWhenempty( whenEmpty ); + zip.execute(); + } + +} diff --git a/maven-wrapper/src/test/java/org/apache/maven/wrapper/PathAssemblerTest.java b/maven-wrapper/src/test/java/org/apache/maven/wrapper/PathAssemblerTest.java new file mode 100644 index 0000000000..fbd0d648de --- /dev/null +++ b/maven-wrapper/src/test/java/org/apache/maven/wrapper/PathAssemblerTest.java @@ -0,0 +1,145 @@ +/* + * Copyright 2007-2008 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.maven.wrapper; + +import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; + +import java.io.File; +import java.net.URI; +import java.util.regex.Pattern; + +import org.hamcrest.BaseMatcher; +import org.hamcrest.Description; +import org.hamcrest.Matcher; +import org.junit.Before; +import org.junit.Test; + +/** + * @author Hans Dockter + */ +public class PathAssemblerTest +{ + public static final String TEST_MAVEN_USER_HOME = "someUserHome"; + + private PathAssembler pathAssembler = new PathAssembler( new File( TEST_MAVEN_USER_HOME ) ); + + final WrapperConfiguration configuration = new WrapperConfiguration(); + + @Before + public void setup() + { + configuration.setDistributionBase( PathAssembler.MAVEN_USER_HOME_STRING ); + configuration.setDistributionPath( "somePath" ); + configuration.setZipBase( PathAssembler.MAVEN_USER_HOME_STRING ); + configuration.setZipPath( "somePath" ); + } + + @Test + public void distributionDirWithMavenUserHomeBase() + throws Exception + { + configuration.setDistribution( new URI( "http://server/dist/maven-0.9-bin.zip" ) ); + + File distributionDir = pathAssembler.getDistribution( configuration ).getDistributionDir(); + assertThat( distributionDir.getName(), matchesRegexp( "[a-z0-9]+" ) ); + assertThat( distributionDir.getParentFile(), equalTo( file( TEST_MAVEN_USER_HOME + "/somePath/maven-0.9-bin" ) ) ); + } + + @Test + public void distributionDirWithProjectBase() + throws Exception + { + configuration.setDistributionBase( PathAssembler.PROJECT_STRING ); + configuration.setDistribution( new URI( "http://server/dist/maven-0.9-bin.zip" ) ); + + File distributionDir = pathAssembler.getDistribution( configuration ).getDistributionDir(); + assertThat( distributionDir.getName(), matchesRegexp( "[a-z0-9]+" ) ); + assertThat( distributionDir.getParentFile(), equalTo( file( currentDirPath() + "/somePath/maven-0.9-bin" ) ) ); + } + + @Test + public void distributionDirWithUnknownBase() + throws Exception + { + configuration.setDistribution( new URI( "http://server/dist/maven-1.0.zip" ) ); + configuration.setDistributionBase( "unknownBase" ); + + try + { + pathAssembler.getDistribution( configuration ); + fail(); + } + catch ( RuntimeException e ) + { + assertEquals( "Base: unknownBase is unknown", e.getMessage() ); + } + } + + @Test + public void distZipWithMavenUserHomeBase() + throws Exception + { + configuration.setDistribution( new URI( "http://server/dist/maven-1.0.zip" ) ); + + File dist = pathAssembler.getDistribution( configuration ).getZipFile(); + assertThat( dist.getName(), equalTo( "maven-1.0.zip" ) ); + assertThat( dist.getParentFile().getName(), matchesRegexp( "[a-z0-9]+" ) ); + assertThat( dist.getParentFile().getParentFile(), + equalTo( file( TEST_MAVEN_USER_HOME + "/somePath/maven-1.0" ) ) ); + } + + @Test + public void distZipWithProjectBase() + throws Exception + { + configuration.setZipBase( PathAssembler.PROJECT_STRING ); + configuration.setDistribution( new URI( "http://server/dist/maven-1.0.zip" ) ); + + File dist = pathAssembler.getDistribution( configuration ).getZipFile(); + assertThat( dist.getName(), equalTo( "maven-1.0.zip" ) ); + assertThat( dist.getParentFile().getName(), matchesRegexp( "[a-z0-9]+" ) ); + assertThat( dist.getParentFile().getParentFile(), equalTo( file( currentDirPath() + "/somePath/maven-1.0" ) ) ); + } + + private File file( String path ) + { + return new File( path ); + } + + private String currentDirPath() + { + return System.getProperty( "user.dir" ); + } + + public static Matcher matchesRegexp( final String pattern ) + { + return new BaseMatcher() + { + public boolean matches( Object o ) + { + return Pattern.compile( pattern ).matcher( (CharSequence) o ).matches(); + } + + public void describeTo( Description description ) + { + description.appendText( "a CharSequence that matches regexp " ).appendValue( pattern ); + } + }; + } +} diff --git a/maven-wrapper/src/test/java/org/apache/maven/wrapper/SystemPropertiesHandlerTest.java b/maven-wrapper/src/test/java/org/apache/maven/wrapper/SystemPropertiesHandlerTest.java new file mode 100644 index 0000000000..54b5d3c596 --- /dev/null +++ b/maven-wrapper/src/test/java/org/apache/maven/wrapper/SystemPropertiesHandlerTest.java @@ -0,0 +1,60 @@ +package org.apache.maven.wrapper; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; + +import java.io.File; +import java.io.FileOutputStream; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; + +import org.apache.commons.io.IOUtils; +import org.junit.Before; +import org.junit.Test; + +public class SystemPropertiesHandlerTest +{ + + private File tmpDir = new File( "target/test-files/SystemPropertiesHandlerTest" ); + + @Before + public void setupTempDir() + { + tmpDir.mkdirs(); + } + + @Test + public void testParsePropertiesFile() + throws Exception + { + File propFile = new File( tmpDir, "props" ); + Properties props = new Properties(); + props.put( "a", "b" ); + props.put( "systemProp.c", "d" ); + props.put( "systemProp.", "e" ); + + FileOutputStream fos = null; + try + { + fos = new FileOutputStream( propFile ); + props.store( fos, "" ); + } + finally + { + IOUtils.closeQuietly( fos ); + } + + Map expected = new HashMap(); + expected.put( "c", "d" ); + + assertThat( SystemPropertiesHandler.getSystemProperties( propFile ), equalTo( expected ) ); + } + + @Test + public void ifNoPropertyFileExistShouldReturnEmptyMap() + { + Map expected = new HashMap(); + assertThat( SystemPropertiesHandler.getSystemProperties( new File( tmpDir, "unknown" ) ), equalTo( expected ) ); + } +} diff --git a/maven-wrapper/src/test/java/org/apache/maven/wrapper/WrapperExecutorTest.java b/maven-wrapper/src/test/java/org/apache/maven/wrapper/WrapperExecutorTest.java new file mode 100644 index 0000000000..b258f83c0e --- /dev/null +++ b/maven-wrapper/src/test/java/org/apache/maven/wrapper/WrapperExecutorTest.java @@ -0,0 +1,192 @@ +package org.apache.maven.wrapper; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.OutputStream; +import java.net.URI; +import java.util.Properties; + +import org.apache.commons.io.IOUtils; +import org.junit.Assert; +import org.junit.Test; +import org.mockito.Mockito; + +public class WrapperExecutorTest +{ + private final Installer install; + + private final BootstrapMainStarter start; + + private File propertiesFile; + + private Properties properties = new Properties(); + + private File testDir = new File( "target/test-files/SystemPropertiesHandlerTest-" + System.currentTimeMillis() ); + + private File mockInstallDir = new File( testDir, "mock-dir" ); + + public WrapperExecutorTest() + throws Exception + { + install = mock( Installer.class ); + when( install.createDist( Mockito.any( WrapperConfiguration.class ) ) ).thenReturn( mockInstallDir ); + start = mock( BootstrapMainStarter.class ); + + testDir.mkdirs(); + propertiesFile = new File( testDir, "maven/wrapper/maven-wrapper.properties" ); + + properties.put( "distributionUrl", "http://server/test/maven.zip" ); + properties.put( "distributionBase", "testDistBase" ); + properties.put( "distributionPath", "testDistPath" ); + properties.put( "zipStoreBase", "testZipBase" ); + properties.put( "zipStorePath", "testZipPath" ); + + writePropertiesFile( properties, propertiesFile, "header" ); + + } + + @Test + public void loadWrapperMetadataFromFile() + throws Exception + { + WrapperExecutor wrapper = WrapperExecutor.forWrapperPropertiesFile( propertiesFile, System.out ); + + Assert.assertEquals( new URI( "http://server/test/maven.zip" ), wrapper.getDistribution() ); + Assert.assertEquals( new URI( "http://server/test/maven.zip" ), wrapper.getConfiguration().getDistribution() ); + Assert.assertEquals( "testDistBase", wrapper.getConfiguration().getDistributionBase() ); + Assert.assertEquals( "testDistPath", wrapper.getConfiguration().getDistributionPath() ); + Assert.assertEquals( "testZipBase", wrapper.getConfiguration().getZipBase() ); + Assert.assertEquals( "testZipPath", wrapper.getConfiguration().getZipPath() ); + } + + @Test + public void loadWrapperMetadataFromDirectory() + throws Exception + { + WrapperExecutor wrapper = WrapperExecutor.forProjectDirectory( testDir, System.out ); + + Assert.assertEquals( new URI( "http://server/test/maven.zip" ), wrapper.getDistribution() ); + Assert.assertEquals( new URI( "http://server/test/maven.zip" ), wrapper.getConfiguration().getDistribution() ); + Assert.assertEquals( "testDistBase", wrapper.getConfiguration().getDistributionBase() ); + Assert.assertEquals( "testDistPath", wrapper.getConfiguration().getDistributionPath() ); + Assert.assertEquals( "testZipBase", wrapper.getConfiguration().getZipBase() ); + Assert.assertEquals( "testZipPath", wrapper.getConfiguration().getZipPath() ); + } + + @Test + public void useDefaultMetadataNoProeprtiesFile() + throws Exception + { + WrapperExecutor wrapper = WrapperExecutor.forProjectDirectory( new File( testDir, "unknown" ), System.out ); + + Assert.assertNull( wrapper.getDistribution() ); + Assert.assertNull( wrapper.getConfiguration().getDistribution() ); + Assert.assertEquals( PathAssembler.MAVEN_USER_HOME_STRING, wrapper.getConfiguration().getDistributionBase() ); + Assert.assertEquals( Installer.DEFAULT_DISTRIBUTION_PATH, wrapper.getConfiguration().getDistributionPath() ); + Assert.assertEquals( PathAssembler.MAVEN_USER_HOME_STRING, wrapper.getConfiguration().getZipBase() ); + Assert.assertEquals( Installer.DEFAULT_DISTRIBUTION_PATH, wrapper.getConfiguration().getZipPath() ); + } + + @Test + public void propertiesFileOnlyContainsDistURL() + throws Exception + { + + properties = new Properties(); + properties.put( "distributionUrl", "http://server/test/maven.zip" ); + writePropertiesFile( properties, propertiesFile, "header" ); + + WrapperExecutor wrapper = WrapperExecutor.forWrapperPropertiesFile( propertiesFile, System.out ); + + Assert.assertEquals( new URI( "http://server/test/maven.zip" ), wrapper.getDistribution() ); + Assert.assertEquals( new URI( "http://server/test/maven.zip" ), wrapper.getConfiguration().getDistribution() ); + Assert.assertEquals( PathAssembler.MAVEN_USER_HOME_STRING, wrapper.getConfiguration().getDistributionBase() ); + Assert.assertEquals( Installer.DEFAULT_DISTRIBUTION_PATH, wrapper.getConfiguration().getDistributionPath() ); + Assert.assertEquals( PathAssembler.MAVEN_USER_HOME_STRING, wrapper.getConfiguration().getZipBase() ); + Assert.assertEquals( Installer.DEFAULT_DISTRIBUTION_PATH, wrapper.getConfiguration().getZipPath() ); + } + + @Test + public void executeInstallAndLaunch() + throws Exception + { + WrapperExecutor wrapper = WrapperExecutor.forProjectDirectory( propertiesFile, System.out ); + + wrapper.execute( new String[] { "arg" }, install, start ); + verify( install ).createDist( Mockito.any( WrapperConfiguration.class ) ); + verify( start ).start( new String[] { "arg" }, mockInstallDir ); + } + + @Test( ) + public void failWhenDistNotSetInProperties() + throws Exception + { + properties = new Properties(); + writePropertiesFile( properties, propertiesFile, "header" ); + + try + { + WrapperExecutor.forWrapperPropertiesFile( propertiesFile, System.out ); + Assert.fail( "Expected RuntimeException" ); + } + catch ( RuntimeException e ) + { + Assert.assertEquals( "Could not load wrapper properties from '" + propertiesFile + "'.", e.getMessage() ); + Assert.assertEquals( "No value with key 'distributionUrl' specified in wrapper properties file '" + + propertiesFile + "'.", e.getCause().getMessage() ); + } + + } + + @Test + public void failWhenPropertiesFileDoesNotExist() + { + propertiesFile = new File( testDir, "unknown.properties" ); + + try + { + WrapperExecutor.forWrapperPropertiesFile( propertiesFile, System.out ); + Assert.fail( "Expected RuntimeException" ); + } + catch ( RuntimeException e ) + { + Assert.assertEquals( "Wrapper properties file '" + propertiesFile + "' does not exist.", e.getMessage() ); + } + } + + @Test + public void testRelativeDistUrl() + throws Exception + { + + properties = new Properties(); + properties.put( "distributionUrl", "some/relative/url/to/bin.zip" ); + writePropertiesFile( properties, propertiesFile, "header" ); + + WrapperExecutor wrapper = WrapperExecutor.forWrapperPropertiesFile( propertiesFile, System.out ); + Assert.assertNotEquals( "some/relative/url/to/bin.zip", wrapper.getDistribution().getSchemeSpecificPart() ); + Assert.assertTrue( wrapper.getDistribution().getSchemeSpecificPart().endsWith( "some/relative/url/to/bin.zip" ) ); + } + + private void writePropertiesFile( Properties properties, File propertiesFile, String message ) + throws Exception + { + + propertiesFile.getParentFile().mkdirs(); + + OutputStream outStream = null; + try + { + outStream = new FileOutputStream( propertiesFile ); + properties.store( outStream, message ); + } + finally + { + IOUtils.closeQuietly( outStream ); + } + } +} diff --git a/maven-wrapper/src/test/resources/org/apache/maven/wrapper/wrapper.properties b/maven-wrapper/src/test/resources/org/apache/maven/wrapper/wrapper.properties new file mode 100644 index 0000000000..6ff45e7805 --- /dev/null +++ b/maven-wrapper/src/test/resources/org/apache/maven/wrapper/wrapper.properties @@ -0,0 +1,5 @@ +distributionUrl=http://server/test/maven.zip +distributionBase=testDistBase +zipStoreBase=testZipBase +distributionPath=testDistPath +zipStorePath=testZipPath