iOS组件化开发之私有库创建(下)

前言

上篇博客我们叙述了手动创建了静态库的过程,这篇博客我们将讲创建私有库的最后内容,打包静态库并推送到二进制库,然后发布podspec到私有库。

准备

打包成静态库的组件和上篇我们讲的组件需要增加一个仓库,这个仓库保存的是我们打包成静态库的仓库,也就是是所说的二进制库。二进制库只存放静态库还有tag信息,有了二进制库当然还需要私有库,podsepc这时指向的是二进制库不是代码仓库了,所以打包成静态库的组件需要三个仓库,我画个图帮助理解:

image.png

根据上面的描述我们还需要创建一个存放二进制库的仓库,这个仓库跟一般的源码仓库操作一样就不赘述了。

步骤

  1. 编译好组件的代码保证编译没有错误。
  2. 打包静态库
  3. 推送静态库到二进制库,并打tag
  4. 发布spec文件到私有仓库库

静态库的打包

如果用xcode打包的话我们 通常的做法是创建Aggregate的target 的方式去打包framework,对于这个方法我这里就不去演示了,网上真的非常多的教程了。因为我写这一系列最后是用Jenkins自动化打包组件,所以我使用脚本尽心打包framework。脚本如下:

#!/bin/sh
SDK_ROOT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
case $SDK_ROOT_DIR in  
     *\ * )
           echo "您的路径中包含空格,像'make install'是不被支持的."
           exit 1
          ;;
esac
###########################################################################
#
# 模拟器支持架构
SIMULATOR_ARCHS="i386 x86_64"
#
# 真机支持架构
IPHONEOS_ARCHS="armv7 armv7s arm64"
#
# 编译使用的SDK
SDK_NAME="iphoneos8.1"
#
# 编译产品路径,指定指定build目录(默认为脚本所在目录下的build)
BUILT_PRODUCTS_DIR="${SDK_ROOT_DIR}/build"
#
# 当前framework版本,一般使用字母递增来表示
FRAMEWORK_VERSION="A"
#                                                                         
###########################################################################
function show_version() {
        echo "version: 1.1"
        echo "updated date: 2015-04-20"
}
function show_usage() {
        echo "Usage(暂时不支持长选项):\n"
        echo "`printf %-16s "    $ $0"` argument\n" 
        echo "Description:"
        echo "`printf %-16s ` [-h|--help] 显示帮助信息"
        echo "`printf %-16s ` [-v|-V|--version] 显示版本"
        echo "`printf %-16s ` [-c|--configuration ... ] 指定编译配置"
        echo "`printf %-16s ` [-p|--project ... ] 指定编译工程"
        echo "`printf %-16s ` [-P|--frameworkproduct ... ] 指定生成framework产品名"
        echo "`printf %-16s ` [-t|--frameworktarget ... ] 指定需要编译framework的target名"
        echo "`printf %-16s ` [-r|--resourcetarget ... ] 指定资源编译target名"
}
# Call this when there is an error.  This does not return.
function die() {
  echo ""
  echo "FATAL: $*" >&2
  exit 1
}
# 工程名,用以指定需要编译的project
PROJECT_NAME=""
# target名,用以指定需要编译的target,默认与工程名一致
TARGET_NAME=""
# 产品名,用以指定需要编译的target,默认与target名一致
PRODUCT_NAME=""
# 配置,用以指定编译代码的配置
CONFIGURATION=""
# 资源名
RESOURCE_NAME=""
# 编译所有target标识
ALLTARGETS_FLAG=0
# 参数列表
while getopts ":hvVac:p:P:t:r:" OPTNAME
do
  case "$OPTNAME" in
    "h")
      show_usage && exit
      ;;
    "v")
      show_version && exit
      ;;
    "V")
      show_version && exit
      ;;
    "c")
      CONFIGURATION=$OPTARG
      ;;
    "p")
      PROJECT_NAME=$OPTARG
      ;;
    "P")
      PRODUCT_NAME=$OPTARG
      ;;
    "t")
      TARGET_NAME=$OPTARG
      ;;
    "r")
      RESOURCE_NAME=$OPTARG
      ;;
    "?")
      show_usage && exit
      ;;
    ":")
      echo "选项$OPTARG缺少输入参数"
      die
      ;;
    *)
    # Should not occur
      echo "处理选项过程发生未知错误"
      die
      ;;
  esac
done
XCODEPROJ_SEARCH_RESULT=`find . -name "*.xcodeproj" -d 1`
if [ -n "${XCODEPROJ_SEARCH_RESULT}" ]; then
  FILENAME="`basename ${XCODEPROJ_SEARCH_RESULT}`"
  XCODEPROJ_NAME="${FILENAME%.*}"
fi

test -n "${CONFIGURATION}"    || CONFIGURATION="Release"
test -n "${PROJECT_NAME}"     || PROJECT_NAME="${XCODEPROJ_NAME}"
test -n "${TARGET_NAME}"      || TARGET_NAME="${PROJECT_NAME}"
test -n "${PRODUCT_NAME}"     || PRODUCT_NAME="${TARGET_NAME}"

if [[ -z "${RESOURCE_NAME}" ]]; then
  TARGET_EXIST_STRING=`xcodebuild -project ./$PROJECT_NAME.xcodeproj -list | grep "${TARGET_NAME}Bundle"`
  test -z "${TARGET_EXIST_STRING}" || RESOURCE_NAME="${TARGET_NAME}Bundle"
fi

set -e
set +u
# 避免递归调用
if [[ $SF_MASTER_SCRIPT_RUNNING ]]
then
exit 0
fi
set -u
export SF_MASTER_SCRIPT_RUNNING=1
DEVELOPER=`xcode-select -print-path`
if [ ! -d "$DEVELOPER" ]; then
  echo "xcode路径没有被设置正确,$DEVELOPER不存在"
  echo "运行"
  echo "sudo xcode-select -switch "
  echo "来进行默认安装:"
  echo "sudo xcode-select -switch /Applications/Xcode.app/Contents/Developer"
  exit 1
fi
SDK_IPHONEOS="iphoneos"
SDK_IPHONESIMULATOR="iphonesimulator"
# The following conditionals come from
# https://github.com/kstenerud/iOS-Universal-Framework
if [[ "$SDK_NAME" =~ ([A-Za-z]+) ]]
then
SF_SDK_PLATFORM=${BASH_REMATCH[1]}
else
echo "Could not find platform name from SDK_NAME: $SDK_NAME"
exit 1
fi
if [[ "$SDK_NAME" =~ ([0-9]+.*$) ]]
then
SF_SDK_VERSION=${BASH_REMATCH[1]}
else
echo "Could not find sdk version from SDK_NAME: $SDK_NAME"
exit 1
fi
if [[ "$SF_SDK_PLATFORM" = "iphoneos" ]]
then
SF_OTHER_PLATFORM=iphonesimulator
else
SF_OTHER_PLATFORM=iphoneos
fi
function buildFramework() {
  xcodebuild -project ./$PROJECT_NAME.xcodeproj -target $TARGET_NAME -sdk $SDK_IPHONEOS -configuration $CONFIGURATION ARCHS="${IPHONEOS_ARCHS}" build
  xcodebuild -project ./$PROJECT_NAME.xcodeproj -target $TARGET_NAME -sdk $SDK_IPHONESIMULATOR -configuration $CONFIGURATION ARCHS="${SIMULATOR_ARCHS}" build  
}
function buildBundle() {
  xcodebuild -project ./$PROJECT_NAME.xcodeproj -target $RESOURCE_NAME -sdk $SDK_IPHONEOS -configuration $CONFIGURATION ARCHS="${IPHONEOS_ARCHS}" build
}
function copyFramework() {
  # prepare_framework
  mkdir -p "${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.framework/Versions/${FRAMEWORK_VERSION}/Headers"
  # Link the "Current" version to "${FRAMEWORK_VERSION}"
  ln -sfh ${FRAMEWORK_VERSION} "${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.framework/Versions/Current"
  ln -sfh Versions/Current/Headers "${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.framework/Headers"
  ln -sfh "Versions/Current/${PRODUCT_NAME}" "${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.framework/${PRODUCT_NAME}"
  # The -a ensures that the headers maintain the source modification date so that we don't constantly
  # cause propagating rebuilds of files that import these headers.
  TARGET_BUILD_DIR="${BUILT_PRODUCTS_DIR}/${CONFIGURATION}-${SDK_IPHONEOS}"
  cp -a "${TARGET_BUILD_DIR}/include/${PRODUCT_NAME}/" "${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.framework/Versions/${FRAMEWORK_VERSION}/Headers"
  # compile_framework
  SF_TARGET_NAME=${PROJECT_NAME}
  SF_EXECUTABLE_Name="lib${SF_TARGET_NAME}.a"
  SF_WRAPPER_NAME="${SF_TARGET_NAME}.framework"  
  PLATFORM_EXECUTABLE_PATH="${BUILT_PRODUCTS_DIR}/${CONFIGURATION}-${SF_SDK_PLATFORM}/${SF_EXECUTABLE_Name}"
  OTHER_PLATFORM_EXECUTABLE_PATH="${BUILT_PRODUCTS_DIR}/${CONFIGURATION}-${SF_OTHER_PLATFORM}/${SF_EXECUTABLE_Name}"
  OUTPUT_PATH="${BUILT_PRODUCTS_DIR}/${SF_WRAPPER_NAME}/Versions/${FRAMEWORK_VERSION}/${SF_TARGET_NAME}"
  # Smash the two static libraries into one fat binary and store it in the .framework
  lipo -create "${PLATFORM_EXECUTABLE_PATH}" "${OTHER_PLATFORM_EXECUTABLE_PATH}" -output "${OUTPUT_PATH}"
  # Delete temporary folder if exists
  FINAL_OUTPUT_PATH="output/framework/${SF_WRAPPER_NAME}"
  if [ -d "${FINAL_OUTPUT_PATH}" ]
  mkdir -p "${FINAL_OUTPUT_PATH}"
  then
  rm -dR "${FINAL_OUTPUT_PATH}"
  fi
  # Copy the binary to the other architecture folder to have a complete framework in both.
  cp -a "${BUILT_PRODUCTS_DIR}/${SF_WRAPPER_NAME}" "${FINAL_OUTPUT_PATH}"
}
function copyBundle() {
  # Resources path
  RESOURCE_BUILD_PATH="${BUILT_PRODUCTS_DIR}/${CONFIGURATION}-${SF_SDK_PLATFORM}"
  # Resources name
  RESOURCE_PRODUCT_NAME="${RESOURCE_NAME}.bundle"
  # Delete temporary folder if exists
  FINAL_RESOURCE_OUTPUT_PATH="output/resources/${RESOURCE_PRODUCT_NAME}"
  if [ -d "${FINAL_RESOURCE_OUTPUT_PATH}" ]
  mkdir -p "${FINAL_RESOURCE_OUTPUT_PATH}"
  then
  rm -dR "${FINAL_RESOURCE_OUTPUT_PATH}"
  fi
  cp -a "${RESOURCE_BUILD_PATH}/${RESOURCE_PRODUCT_NAME}" "${FINAL_RESOURCE_OUTPUT_PATH}"
}
test -z "${PROJECT_NAME}" || (buildFramework && copyFramework)
test -z "${RESOURCE_NAME}" || (buildBundle && copyBundle)

工程我们根据上篇讲的已经创建了好了,但是需要修改下编译的sdk版本为iOS 8 不然运行脚本会出错,当然你修改脚本的SDK_NAME的版本也是可以的,这里修改源码工程:

image.png

脚本使用

你可以复制上面的脚本内容,在工程的根目录下创建一个以.sh为后缀的文件,如图:

image.png

使用很简单,只要命令行cd 到工程目录下,输入以下命令:

./build.sh

如果提示文件没有使用权限,你需要执行以下命令

$ sudo chmod -R 777 ./build.sh

输入之后会很很多log输出,有BUILD SUCCEEDED代表打包成功了,如果打包错误会有具体的编译错误信息,可以根据错误信息来修改再重新运行:

image.png

执行完之后会在根目录下生成build和output的两个目录,我们只需要关心output文件夹,因为它输出我们需要的静态库及图片bundle:

image.png

静态库推送

静态库打包好之后就是推送到二进制仓库了,这推送的过程和我们推送代码的流程是一样的,podsepc文件我们也需要一起推送到二进制库,这个不是必须的但是为了方便我们以后查看版本所以和静态库一起推送。

podsepc文件我们需要修改下内容,主要是source变成二进制库了,还有其他头文件位置及图片bundle等示例如下:


Pod::Spec.new do |s|

  s.name         = "StaticKit"
  s.version      = "1.0.1"
  s.summary      = "测试."


  s.description  = <<-DESC
                      长描述
                   DESC
  #项目首页显示用不重要
  s.homepage     = "https://github.com/GuoMs/StaticKit"
 
  s.license      = "MIT"
  
  s.author             = { "dd" => "[email protected]" }

  s.platform     = :ios, "8.0"
  #上篇写的内容,source是代码仓库
  #s.source       = { :git => "[email protected]:GuoMs/testDemo.git", :tag => "#{s.version}" }
  #s.source_files  = "testDemo", "testDemo/**/*.{h,m}"

  #本篇的需要写二进制仓库
  s.source       = { :git => "[email protected]:GuoMs/StaticFramework.git", :tag => "#{s.version}" }
  s.public_header_files = "#{s.name}.framework/Versions/A/**/*.h"
  s.source_files = "#{s.name}.framework/Versions/A/**/*.h"
  s.resources = "#{s.name}_res.bundle"
  s.preserve_paths = "*.framework"
  s.vendored_frameworks = "#{s.name}.framework"


end

推送的时候我们可以重新创建一个文件夹,把二进制库clone 下来,把StaticKit.frame、StaticKit.bundle、StaticKit.podspec三个文件夹复制进去,如果你之前发过就直接覆盖,然后我们提交文件,最后需要推送tag,tag的版本号应该和podspec是一致的的。

对于上面推送的步骤不懂可以翻看前面的文章,做完上面的步骤之后我们可以在二进制仓库的网页中看到推送的静态库及bundle:


image.png

最后一步发布我们的podsepc文件到私有库,这个操作在上篇中有说明,不懂可以往前翻翻,推送完之后我们的私有仓库多了一个文件:

image.png

项目引用

推送到私有库之后,我们就可以在工程中写对应的版本进行pod静态库进行开发了,之前我们的版本是1.0.1,在podfile中引入:

source 'https://github.com/CocoaPods/Specs.git'
#私有库地址
source '[email protected]:GuoMs/mySpecs.git'

platform :ios, '8.0'

project 'FrameWorkDemo'

target 'FrameWorkDemo' do
  #静态库的版本号
  pod 'StaticKit','1.0.1'
  pod 'AFNetworking'
  pod 'Masonry'

end

更新之后在pod下就可以看到静态库和图片的bundle了,运行正常一个组件算是完成了:

image.png

最后

到这里关于组件的创建就告一段落了,可能还有一些特殊项目需求需要自己去改动一些东西,如果有遇到什么问题或者我有什么错误的地方欢迎留言讨论。后面的文章开始我将开始教大家使用Jenkins进行自动化发布组件及应用打包,敬请期待!

你可能感兴趣的:(iOS组件化开发之私有库创建(下))