maven的仲裁

模块化的问题是无法避免的一个问题。对创业公司来说,在团队和产品在发展到一定程度之前,靠自我约束。

这里有一个案例是项目里依赖了b组件,b组件依赖了a组件1.0.2版本,而用户也直接在pom依赖了a组件并声明的1.0.0版本,结果在仲裁时选择了1.0.0版本的a组件:

 +- com.xxx:a:jar:1.0.0:compile
 |   
 +- ...
 |  +- ...
 |  
 +- com.xxx:b:jar:1.0.0:compile
 |  |  
 |  +- (com.xxx:a:jar:1.0.2:compile - omitted for duplicate)

对于依赖某个组件的多个版本,maven的仲裁过程,并不是简单的使用高版本,而是根据从根节点到各个组件节点之间的路径深度,路径短的组件优先,如果路径深度相同,则是先发现的那个。类似一棵树的广度遍历。

maven wrapper script

这个脚本用于简化mvn的操作,在mac/bash, linux/bash, cygwin/bash下测试过

#!/bin/bash - 

TOP_PID=$$
PROG_NAME=$0
ACTION=$1

function get_children() {
  ps -ef | grep $$ | grep -v grep | awk '$3=='"$$"'{print $2}' | xargs
}

trap "kill $(get_children) >/dev/null 2>&1; exit 1" SIGINT SIGTERM

function usage() {
  echo "Usage: $PROG_NAME {verify|compile|run|debug|package|eclipse|get-src|dep-tree}"
  exit 1;
}

function get_mvn_cmd() {
  if [[ "$OSTYPE" == *cygwin* ]];then
    ppid=$( ps -ef -p $$ | awk 'NR==2{print $3}' )
    user_shell=$( ps -p $ppid | awk 'NR==2{print $8}' )
    #has some trouble with cygwin, while Ctrl-c cannot terminal
    set -m
  else
    ppid=$( ps -oppid= $$ )
    user_shell=$( ps -ocomm= -p $ppid )
  fi

  mvn=$( $user_shell -ic "alias mvn" 2>/dev/null | cut -d'=' -f2 | sed "s/'//g" )

  if [ -z "$mvn" ];then
    $user_shell -ic "which mvn" >/dev/null
    if [ $? -eq 0 ];then
      mvn=$( $user_shell -ic "which mvn" | head -1 )
    fi
  fi

  if [ -z "$mvn" ]; then 
    echo "mvn command not found" >&2
    kill -s TERM $TOP_PID
  else
    echo $mvn
  fi
}

MVN=$( get_mvn_cmd )

function mvn_verify() {
  $MVN verify $@
}

function mvn_compile() {
  $MVN clean compile $@
}

function mvn_run() {
  $MVN scala:run $@
}

function mvn_debug() {
  $MVN scala:run -Dlauncher=debug $@
}

function classpath_check() {
  if [ $# -eq 0 ];then
    echo "please enter classpath dir"
    exit 1
  fi

  if [ ! -d "$1" ]; then 
    echo "not a directory"
    exit 2
  fi

  tmpfile="/tmp/.cp$(date +%s)"
  tmphash="/tmp/.hash$(date +%s)"
  verbose="/tmp/cp-verbose.log"

  declare -a files=(`find "$1" -name "*.jar"`)
  for ((i=0; i < ${#files[@]}; i++)); do
    jarName=`basename ${files[$i]}`
    list=`unzip -l ${files[$i]} | awk -v fn=$jarName '/\.class$/{print $NF,fn}'`
    size=`echo "$list" | wc -l`
    echo $jarName $size >> $tmphash
    echo "$list" 
  done | sort | awk 'NF{
      a[$1]++;m[$1]=m[$1]","$2}END{for(i in a) if(a[i] > 1) print i,substr(m[i],2)
  }' > $tmpfile

  awk '{print $2}' $tmpfile | 
  awk -F',' '{i=1;for(;i<=NF;i++) for(j=i+1;j<=NF;j++) print $i,$j}' | 
  sort | uniq -c | sort -nrk1 | while read line; do
    dup=${line%% *} 
    jars=${line#* }
    jar1=${jars% *}
    jar2=${jars#* }
    len_jar1=`grep -F "$jar1" $tmphash | grep ^"$jar1" | awk '{print $2}'`
    len_jar2=`grep -F "$jar2" $tmphash | grep ^"$jar2" | awk '{print $2}'`
    len=$(($len_jar1 > $len_jar2 ? $len_jar1 : $len_jar2))
    per=$(echo "scale=2; $dup/$len" | bc -l)
    echo ${per/./} $dup $jar1 $jar2 
  done | sort -nr -k1 -k2 | 
  awk 'NR==1{print "Similarity DuplicateClasses File1 File2"}{print "%"$0}'| column -t  

  sort $tmpfile | awk '{print $1,"\n\t\t",$2}' > $verbose
  echo "See $verbose for more details."

  rm -f $tmpfile
  rm -f $tmphash
}

function mvn_package() {
  $MVN clean package -Dmaven.javadoc.skip -Dmaven.test.skip $@
  if [ $? -eq 0 ];then
    echo ""
    classpath_check ./target/lib
  fi
}

function mvn_src() {
  $MVN dependency:sources
}

function mvn_dep_tree() {
  $MVN dependency:tree -Dverbose
}

function mvn_eclipse() {
  $MVN eclipse:eclipse
}

case "$ACTION" in
  verify)
    shift && mvn_verify $@
  ;;
  compile)
    shift && mvn_compile $@
  ;;
  run)
    shift && mvn_run $@
  ;;
  debug)
    shift && mvn_debug $@
  ;;
  package)
    shift && mvn_package $@
  ;;
  get-src)
    mvn_src
  ;;
  dep-tree)
    mvn_dep_tree
  ;;
  eclipse)
    mvn_eclipse
  ;;
  *)
    usage
  ;;
esac

maven调试web应用

mvn tomcat7:run运行web应用的一个小脚本,方便debug:

$ cat mvn-tomcat
#!/bin/bash

suspend="n"
if [ "$1" != "" ]; then
  lower=`echo $1 | tr '[:upper:]' '[:lower:]'`
  if [ "$lower" == "y" ] || [ "$lower" == "n" ]; then
    suspend=$lower
  else
    echo "param error" && exit -1;
  fi
fi

port=8080
if [ "$2" != "" ]; then
  re='^[0-9]+$'
  if ! [[ "$2" =~ $re ]] ; then
     echo "port: error, not a number" && exit -2;
  else
    port=$2
  fi
fi

export MAVEN_OPTS=-agentlib:jdwp=transport=dt_socket,address=8000,server=y,suspend="$suspend"
mvn tomcat7:run -Dmaven.tomcat.port="$port"

把脚本放到放到PATH路径下(比如~/bin目录下)。使用方式:

$ mvn-tomcat # 默认8080端口

$ mvn-tomcat y # 断点suspend

$ mvn-tomcat n 7001 # 指定tomcat用7001端口