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 <xcode path>"
  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" => "gz3024@aliyun.com" }

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

  #本篇的需要写二进制仓库
  s.source       = { :git => "git@github.com: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 'git@github.com: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进行自动化发布组件及应用打包,敬请期待!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349

推荐阅读更多精彩内容

  • 1、通过CocoaPods安装项目名称项目信息 AFNetworking网络请求组件 FMDB本地数据库组件 SD...
    阳明先生_X自主阅读 15,969评论 3 119
  • D-2 考完试下午火车到兰州 住兰州(高铁到西安后转兰州,很方便) D-1 兰州:黄河母亲,铁桥,甘肃省博物馆 下...
    笑江西阅读 606评论 1 50
  • #清凉法语# 包容才能宽广 生活中,我们有时难免遇到自己不愿接纳的人,或者心怀怨恨的人,而我们常常会采取避而远之的...
    xcy无名阅读 311评论 0 0
  • 微信号:lfj8406687,欢迎大家加我,购彩中大奖。
    dragron龙9281阅读 636评论 1 0
  • 从零到一,是从无到有的过程,这中间有不为人知的辛酸与血泪,用网红傅园慧的话说,就是“鬼知道这三个月我都经历了些什么...
    星海微语阅读 3,867评论 0 3