Lane East 的 blog

一百年很短,一秒钟很长

slackware 包管理(pkgtools)分析 makepkg

2010-04-24 17:28

makepkg 是 slackware 中用来制作软件包的工具, 中文注释的代码如下:

 #!/bin/sh

# 检查 tar 的版本
TAR=tar-1.13
umask 022
$TAR --help 1> /dev/null 2> /dev/null
if [ ! $? = 0 ]; then
  TAR=tar
fi
if [ ! "`LC_MESSAGES=C $TAR --version`" = "tar (GNU tar) 1.13

Copyright (C) 1988, 92,93,94,95,96,97,98, 1999 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

Written by John Gilmore and Jay Fenlason." ]; then
  echo "WARNING: pkgtools are unstable with tar > 1.13."
  echo "         You should provide a \"tar-1.13\" in your \$PATH."
  sleep 5
fi

# 这个函数读取一个文件, 将文件中记录的符号链接及对应的目标逐行读取出来,
# 以产生建立符号链接的命令
# 所读取的文件的内容格式为: 链接名 -> 链接目标名
# 详细情况可以参考后面使用此函数的部分.
make_install_script() {
  # 逐行处理
  COUNT=1
  LINE="`sed -n "$COUNT p" $1`"
  while [ ! "$LINE" = "" ]; do
   # 取出链接名, 得到其所在目录(LINKGOESIN)
   # 这个地方并没有考虑到一个比较少见的情况:
   # 如果一个链接名中含有空格,
   # 那么这里的 LINKGOESIN 和下面的 LINKNAMEIS, LINKPOINTSTO 都会不正确
   LINKGOESIN="`echo "$LINE" | cut -f 1 -d " "`" 
   LINKGOESIN="`dirname $LINKGOESIN`" 
   # 取出链接名, 得到其文件名(LINKNAMEIS)
   LINKNAMEIS="`echo "$LINE" | cut -f 1 -d ' '`"
   LINKNAMEIS="`basename "$LINKNAMEIS"`"
   # 获得链接的目标
   LINKPOINTSTO="`echo "$LINE" | cut -f 3 -d ' '`"
   # 生成用于产生符号链接的 shell 命令, 以便存入 install/doinst.sh
   echo "( cd $LINKGOESIN ; rm -rf $LINKNAMEIS )"
   echo "( cd $LINKGOESIN ; ln -sf $LINKPOINTSTO $LINKNAMEIS )"
   # 处理下一行
   COUNT=`expr $COUNT + 1`
   LINE="`sed -n "$COUNT p" $1`"
  done
}

# 使用帮助
# -l, --linkadd    是否把符号链接的处理放到 install/doinst.sh 中
# -p, --prepend    如果已存在 install/doinst.sh, 把处理符号链接的命令放到
#                  已存在内容的前面
# -c, --chown      改变文件属主和访问权限
usage() {
  cat << EOF

Usage: makepkg package_name.tgz

Makes a Slackware compatible "*.tgz" package containing the contents of the 
current and all subdirectories. If symbolic links exist, they will be removed
and an installation script will be made to recreate them later. This script
will be called "install/doinst.sh". You may add any of your own ash-compatible
shell scripts to this file and rebuild the package if you wish.

options:  -l, --linkadd y|n (moves symlinks into doinst.sh: recommended)
          -p, --prepend (prepend rather than append symlinks to an existing
                         doinst.sh.  Useful to link libraries needed by
                         programs in the doinst.sh script)
          -c, --chown y|n (resets all permissions to root:root 755
                           - not generally recommended)

If these options are not set, makepkg will prompt as appropriate.
EOF
}

TMP=/tmp

# 参数处理
while [ 0 ]; do
  # 检查是否用参数指定了 linkadd
  if [ "$1" = "--linkadd" -o "$1" = "-l" ]; then
    if [ "$2" = "y" ]; then
      LINKADD=y
    elif [ "$2" = "n" ]; then
      LINKADD=n
    else
      usage
      exit 2
    fi
    shift 2
  # 检查是否用参数指定了 chown
  elif [ "$1" = "--chown" -o "$1" = "-c" ]; then
    if [ "$2" = "y" ]; then
      CHOWN=y
    elif [ "$2" = "n" ]; then
      CHOWN=n
    else
      usage
      exit 2
    fi
    shift 2
  # 检查是否用参数指定了 prepend
  elif [ "$1" = "-p" -o "$1" = "--prepend" ]; then
    PREPEND=y
    shift 1
  # 检查是否有 -h, -H, --help, 如果有, 则显示帮助信息, 然后退出.
  elif [ "$1" = "-h" -o "$1" = "-H" -o "$1" = "--help" -o $# = 0 ]; then
    usage
    exit 0
  else
    break
  fi
done

echo
echo "Slackware package maker, version 2.1."
# 根据给定的文件名来提取存放位置及包文件名
PACKAGE_NAME=$1
TARGET_NAME="`dirname $PACKAGE_NAME`"
PACKAGE_NAME="`basename $PACKAGE_NAME`"
TAR_NAME="`basename $PACKAGE_NAME .tgz`"
echo
echo "Searching for symbolic links:"
# 创建临时文件, 使用 mktemp 以避免已存在文件的问题
INST=`mktemp $TMP/makepkg.XXXXXX`
# 查指符号链接, 以便产生 "install/doinst.sh"
# 先是用 find . -type l 来查找当前目录下的符号链接
# 然后用 ls 显示出来, 限定 time style 利于后面的 cut 来进行控制
# 这里的 ls 命令需要 coreutils-5.0 或者以上的版本
# 后面的 while read ... done 其实是和 installpkg 中的 crunch 函数是一样的
# 不过此处没有把它放到函数里面去.
# cut -f 8- -d ' ' 是以一个空格为界, 取第 8 段及之后的部分:
# ./链接名 -> 链接目标
# 然后使用 cut -b3- 来去掉前 2 个字节(./)
find . -type l -exec ls -l --time-style=long-iso {} \; | while read foo ; do echo $foo ; done | cut -f 8- -d ' ' | cut -b3- | tee $INST
if [ ! "`cat $INST`" = "" ]; then
  echo
  echo "Making symbolic link creation script:"
  # 产生生成符号链接的命令, 并存入 doinst.sh(不是 install/doinst.sh)
  make_install_script $INST | tee doinst.sh
fi
echo
# 存在符号链接时询问是否建立安装脚本(处理符号链接的命令)
if [ ! "`cat $INST`" = "" ]; then
  # 如果存在 install/doinst.sh, 则询问是否将产生的安装脚本添加到已经存在的 install/doinst.sh 中
  if [ -r install/doinst.sh ]; then
    echo "Unless your existing installation script already contains the code"
    echo "to create these links, you should append these lines to your existing"
    echo "install script. Now's your chance. :^)"
    echo
    echo "Would you like to add this stuff to the existing install script and"
    echo -n "remove the symbolic links ([y]es, [n]o)? "
  # 不存在 install/doinst.sh, 则是询问是否产生安装脚本
  else
    echo "It is recommended that you make these lines your new installation script."
    echo
    echo "Would you like to make this stuff the install script for this package"
    echo -n "and remove the symbolic links ([y]es, [n]o)? "
  fi
  # 如果没有用参数指定 linkadd, 那么则读取用户输入, 否则直接输出之前的选择, 并继续
  if [ ! "$LINKADD" ]; then
    read LINKADD;
    echo
  else
    echo $LINKADD
    echo
  fi
  # linkadd 的情况下, 处理所产生的命令(产生符号链接的命令)
  if [ "$LINKADD" = "y" ]; then
    # 存在 install/doinst.sh
    if [ -r install/doinst.sh ]; then
      UPDATE="t"
      # 如果用户选择了 --prepend 选项,
      # 则将新产生的安装脚本放至已存在的 install/doinst.sh 的开头
      # 否则直接扔到已经 install/doinst.sh 的末尾
      if [ "$PREPEND" = "y" ]; then
        touch install/doinst.sh
        mv install/doinst.sh install/doinst.sh.shipped
        cat doinst.sh > install/doinst.sh
        echo "" >> install/doinst.sh
        cat install/doinst.sh.shipped >> install/doinst.sh
        rm -f install/doinst.sh.shipped
      else
        cat doinst.sh >> install/doinst.sh
      fi
    # 不存在 install/doinst.sh 则直接将 doinst.sh 填入 install/doinst.sh
    else
      mkdir -p install
      cat doinst.sh > install/doinst.sh
    fi
    echo
    # 删除符号链接
    echo "Removing symbolic links:"
    find . -type l -exec rm -v {} \;
    echo
    # 输出提示信息
    if [ "$UPDATE" = "t" ]; then
      if [ "$PREPEND" = "y" ]; then
        echo "Updating your ./install/doinst.sh (prepending symlinks)..."
      else
        echo "Updating your ./install/doinst.sh..."
      fi
    else
      echo "Creating your new ./install/doinst.sh..."
    fi
  fi
# 没有在当前目录找到符号链接, 直接给出提示
else
  echo "No symbolic links were found, so we won't make an installation script."
  echo "You can make your own later in ./install/doinst.sh and rebuild the"
  echo "package if you like."
fi
# 删除临时文件
rm -f doinst.sh $INST

# chown 的相关处理, 非必要的
# 有些软件需要特定的用户组或权限设置, 那样的话, 这一步就不能做
echo
echo "This next step is optional - you can set the directories in your package"
echo "to some sane permissions. If any of the directories in your package have"
echo "special permissions, then DO NOT reset them here!"
echo 
echo "Would you like to reset all directory permissions to 755 (drwxr-xr-x) and"
echo -n "directory ownerships to root.root ([y]es, [n]o)? "
# 根据是否已经选择了 chown 来决定是要求用户输入还是直接输出选择结果
if [ ! "$CHOWN" ]; then
  read CHOWN;
  echo
else
  echo $CHOWN
  echo
fi
# 根据用户选择, 进行权限设置
# 可以看出此处只修改了目录的权限和属主信息
if [ "$CHOWN" = "y" ]; then
  find . -type d -exec chmod -v 755 {} \; 
  find . -type d -exec chown -v root.root {} \;
fi
# 打包
echo
echo "Creating tar file $TAR_NAME.tar..."
echo
# 从上面可以知道,  $TAR_NAME.tar 只是个文件名, 并没有包含目录信息,
# 所以这里是把当前目录打包, 并存入当前目录下的 $TAR_NAME.tar
# 于是就会有这样的情况, tar 会不会把 $TAR_NAME.tar 本身再打包呢?
# 实际上 tar 会给出个提示, 说 $TAR_NAME.tar 就是打包的文件本身, 所以不打包它.
# 这算是个错误提示, 只不过在 slackware 打包的时候用了 v 参数, 所以会显示打包了哪些文件,
# 于是通常用户是注意不到这个信息的
$TAR cvf $TAR_NAME.tar .
# 对空文件进行提示
find . -type f -size 0c | while read file ; do
  echo "WARNING: zero length file $file"
done
find . -type f -name '*.gz' -size 20c | while read file ; do
  echo "WARNING: possible empty gzipped file $file"
done
# 压缩
echo
echo "Gzipping $TAR_NAME.tar..."
gzip -9 $TAR_NAME.tar
echo
echo "Renaming $TAR_NAME.tar.gz to $PACKAGE_NAME..."
# 把 $TAR_NAME.tar.gz 改名为指定的包文件名
mv $TAR_NAME.tar.gz $PACKAGE_NAME
# 如果指定存放包的位置不是当前目录, 则把它移动指定目录
if [ ! "$TARGET_NAME" = "." ]; then
  echo
  echo "Moving $PACKAGE_NAME to $TARGET_NAME..."
  mv $PACKAGE_NAME $TARGET_NAME
fi
# 创建完成, 给个提示
echo
echo "Package creation complete."
echo

分类:

评论

  预览后可提交