pod库自动化部署脚本

2,066 阅读7分钟

前言

你是不是对于发布pod库很烦恼,每次都要手动修改版本号,提交到git仓库,打tag,推送到podspec仓库等等。如果能一键发布就好了,省事还省心!那么你就往下看,我用 ruby 实现了一个自动发布到代码仓库(私有/公开都可以)的脚本,只需一步就能完成上面所有操作。

概念

我们平时在做开发的时候,多少会接触到组件化的思想。iOS的组件化一般是通过使用 cocoapods 制成pod库的形式完成的。pod库又分为公开库和私有库两种。比如我们上传到 github 的就属于公开库,提供别人下载使用。而部署在公司内部的则属于私有库,别人访问不了,使用不到。

本文主要是讲如何实现自动部署pod脚本的,因此关于pod库相关的概念只会做个简单的介绍,不会做过多的讲解。如果想要有更深入的了解,可以查阅官方文档

1. repo

一个 repo 就是相当于一个放置相关 pod 库的索引的仓库。怎么理解?比如我们在github上制作的公开库,它的 repo 叫什么?叫 trunk,地址是 https://cdn.cocoapods.org/ 。我们可以直接在本机执行命令: pod repo,将会看到图示。

截屏2021-08-13 下午2.06.44.png

这个仓库就是存放我们部署上去的 podspec 文件的。

2. podspec

而一个 podspec 则是用于描述一个pod库,如库结构,版本,源代码地址等。


了解了这两个,那么我们就可以制作自己的pod库了。

制作pod库

以制作公开库,提交到 github 为例。提交到trunk的官方文档点我

首先你需要现有一个源代码仓库,

1. 创建podspec文件

pod spec create yourSpecName.podspec

2. 编辑podspec文件

例如podspec文件内容如下,编辑里面的source、source_files等。

Pod::Spec.new do |spec|
  spec.name             = 'Reachability'
  spec.version          = '3.1.0'
  spec.license          = { :type => 'BSD' }
  spec.homepage         = 'https://github.com/tonymillion/Reachability'
  spec.authors          = { 'Tony Million' => 'tonymillion@gmail.com' }
  spec.summary          = 'ARC and GCD Compatible Reachability Class for iOS and macOS.'
  spec.source           = { :git => 'https://github.com/tonymillion/Reachability.git', :tag => 'v3.1.0' }
  spec.source_files     = 'Reachability.h,m'
  spec.framework        = 'SystemConfiguration'
  spec.requires_arc     = true
end

3. 提交我们的源代码

git commit -am 'submit'
git push origin

4. 给我们的pod打tag

注意到podspec是有版本号的概念的,它需要和你pod库,也就是源代码库的tag值是一致的。这样,我们的cocoapods才能在解析podspec之后,下载到正确版本对应的源代码。


git tag 0.0.2

#推送指定tag到远端
git push origin 0.0.2

# 或者推送本地所有tag到远端
# git push origin --tags

5. 验证我们的podspec文件格式是否正确

本地验证:

pod lib lint yourSpecName.podspec --allow-warnings

远程验证:

pod spec line yourSpecName.podspec --allow-warnings

6. 发布我们的podspec

pod trunk push yourSpecName.podspec --allow-warnings

更新pod库

此时我们的库中应该已经有一个podspec文件了的,我们需要做的就是更新它的版本号,如果文件结构有变动,就需要改动 source_files,有添加新的依赖库,那么需要修改 dependency

1. 更新podspec的版本号

打开podspec文件,编辑更新version字段就好,一般采用递增的方式。

1. 为我们新版本的代码打tag

# 需要先提交代码
git commit -am 'update'
git push origin
git tag 0.0.2
git push origin 0.0.2
# 或 git push origin --tags

2. 本地验证新修改的podspec文件是否有问题

这一步在这里骑士可以跳过,一般第一次上传没问题,后面也不会出现问题。

pod lib lint *.podspec

3. 部署新版本的podspec

pod trunk push *.podspec

脚本实现

  • 【0821更新】当存在多个podspec时,支持指定podspec上传。
  1. PodPushFile新增 PUSH_PODSPEC_NAME 字段,允许用户指定上传的podspec文件
  2. 当未指定podspec文件时,在搜索路径下发现有多个podspec文件,那么允许用户手动指定

截屏2021-08-22 下午5.44.00.png

截屏2021-08-22 下午5.44.48.png

脚本使用 ruby 实现的,最新源码点我。为保证使用最新源码,请去 github 上下载。

实现的原理其实也是用了更新podspec的步骤,只不过是加了一些配置而已。

这里直接贴代码【最新】:

#! /usr/bin/ruby

class Color
    def self.natural
        0
    end
    def self.red
        31
    end
    def self.green
        32
    end
    def self.white
        37
    end
end

def color_text(text, color = Color.natural)
    if color == 0
        return text
    end
    return "\033[#{color}m#{text}\033[0m"
end

def die_log(text)
    puts color_text(text, Color.red)
end

# 拉取最新代码
# if system('git pull --rebase origin') == false
#     system('git rebase --abort')
#     puts color_text("There is a conflict, please handle it and retry", Color.red)
#     return
# end


cur_path = Dir.pwd
push_path = cur_path
relate_dir_path = ''
push_podspec_name = ''
user_custom_version = true
verify_podspec_format = true
pod_repo_name = 'trunk'
pod_repo_source =
is_static_lib = false

# 检查是否存在 SpecPushFile 文件,如果不存在,那么创建
if not File::exist?(cur_path + '/PodPushFile')
    system('touch PodPushFile')
    File.open(cur_path + '/PodPushFile', 'w+') do |f|
        f.write("#写入*.podspec所在的相对目录,不写默认会在脚本执行的目录下查找
PUSH_DIR_PATH=
#用户还可以指定要推送的podspec文件的名字,这个存在多个podspec的时候会用到
PUSH_PODSPEC_NAME=
#是否允许用户自定义版本号,不填或填true将允许用户设置自定义的版本号,而不是自增版本号
USER_CUSTOM_VERSION=true
#默认开启验证,可以跳过验证阶段
VERIFY_PODSPEC_FORMAT=true
#pod repo的名字,如果是私有库就填私有库的名字
POD_REPO_NAME=trunk
#pod repo的源地址
POD_REPO_SOURCE=https://github.com/CocoaPods/Specs
#如果这个库是静态库,那么需要设置为true
POD_IS_STATIC_LIBRARY=false")
    end
    puts color_text('Create PodPushFile', Color.green)
    puts color_text("First you should modify 'PodPushFile' file and run the script again", Color.white)
    system('open PodPushFile')
    return
end

puts color_text('Parse PodPushFile...', Color.white)
File.open(cur_path + '/PodPushFile') do |f|
    f.each_line do |line|
        key_value = line.split('=')
        key = key_value.first.to_s.gsub("\n", '').gsub(' ','').gsub("\t",'')
        value =
        if key_value.count > 1
            value = key_value.last.to_s.gsub("\n", '').gsub(' ','').gsub("\t",'')
        end
        # puts "key=#{key},value=#{value}"
        if key.to_s == 'PUSH_DIR_PATH' and not value.nil?
            relate_dir_path = value
            push_path = cur_path + '/' + relate_dir_path
        elsif key.to_s == 'PUSH_PODSPEC_NAME' and not value.nil?
            push_podspec_name = value.to_s
        elsif key.to_s == 'USER_CUSTOM_VERSION' and not value.nil?
            user_custom_version = value == 'true'
        elsif key.to_s == 'VERIFY_PODSPEC_FORMAT' and not value.nil?
            verify_podspec_format = value == 'true'
        elsif key.to_s == 'POD_REPO_NAME' and not value.nil?
            pod_repo_name = value.to_s
        elsif key.to_s == 'POD_REPO_SOURCE' and not value.nil?
            pod_repo_source = value
        elsif key.to_s == 'POD_IS_STATIC_LIBRARY' and not value.nil?
            is_static_lib = value == 'true'
        end
    end
end

# puts "Push path is: #{push_path}, relate dir path is: #{relate_dir_path}"

# 搜索podspec路径
podspec_path = ''
find_podspec_reg = relate_dir_path.length == 0 ? '' : (relate_dir_path + '/')
if push_podspec_name.length > 0
    # 用户指定要推送某个podspec
    if push_podspec_name.include?('.podspec')
        find_podspec_reg += push_podspec_name
    else
        find_podspec_reg += (push_podspec_name + '.podspec')
    end
else
    find_podspec_reg += '*.podspec'
end
#puts "Find podspec reg = #{find_podspec_reg}"
# 有可能存在多个 podspec,当用户没有指定时,需要给用户自主选择
find_podspec_count = 0
podspecs = Array.new
Dir::glob(find_podspec_reg) do |f|
    find_podspec_count += 1
    podspecs << f
end

if podspecs.count > 1
    inputTag = true
    serial = 0
    puts color_text("Find #{podspecs.count} podspec files, please enter the serial number selection:",Color.white)
    while inputTag
        for i in 0...podspecs.count do
            puts "#{i+1}. #{podspecs[i]}"
        end
        serial = gets.chomp
        inputTag = (serial.to_i > podspecs.count || serial.to_i <= 0)
        if inputTag
            puts "Input serial = #{serial}, it's invalid and you need to input 1~#{podspecs.count}:"
        end
    end
    podspec_path = podspecs[serial.to_i-1]
elsif podspecs.count == 1
    podspec_path = podspecs[0]
else
    puts color_text("Can't find any podspec file", Color.red)
    return
end

if not File::exist?(podspec_path)
    die_log("Can't find any podspec file in path: #{podspec_path}, please modify PodPushFile' PUSH_DIR_PATH(key)")
    return
else
    puts "Ready to deal with podspec named " + color_text("#{podspec_path}", Color.white)
end

# 在当前podspec目录下新建一个临时 need_delete_temp.podspec 文件
podspec_dir = File.dirname podspec_path
podspec_absolute_path = cur_path + '/' + podspec_path
temp_podspec_path = podspec_dir + '/need_delete_temp.podspec'
temp_podspec_absolute_path = cur_path + '/' + temp_podspec_path

cur_version = ''
# 读取当前podspec文件的版本
File.open(podspec_absolute_path, 'r+') do |f|
    f.each_line do |line|
        # 查找.version
        version_desc = /.*\.version[\s]*=.*/.match line
        if not version_desc.nil?
            cur_version = version_desc.to_s.split('=').last.to_s.gsub("'", '')
            cur_version = cur_version.gsub(' ', '')
            break
        end
    end
end

puts color_text("Current version = ", Color.white) + color_text("#{cur_version}", Color.green)

# 允许自定义版本号
if user_custom_version == true
    puts color_text "Please input pod lib's new version, if there is no input or less than or equal old version, it will be incremented:", Color.white
    input_version = gets.chomp

    # 判断输入的version是否>当前的版本号
    input_v_s = input_version.to_s.split('.')
    cur_v_s = cur_version.split('.')
    # 比较的位置,从最左边开始
    v_index = 0
    # 输入的version是否有效
    input_valid = false
    while v_index < cur_v_s.count && v_index < input_v_s.count do
        if input_v_s[v_index].to_i > cur_v_s[v_index].to_i
            # 说明用户输入的version比当前的大
            input_valid = true
            break
        elsif input_v_s[v_index].to_i == cur_v_s[v_index].to_i
            v_index += 1
        else
            break
        end
    end

    if input_valid == false
        puts color_text "Input invalid version = #{input_version},will auto +1 in last component", Color.natural
    end
end

if not File.exist? temp_podspec_absolute_path
    # system("cp -f #{podspec_path} #{temp_podspec_path}")
    system("touch #{temp_podspec_path}")
end

new_version = ''
git_source = ''
File.open(temp_podspec_absolute_path, 'r+') do |t|
    File.open(podspec_absolute_path) do |f|
        f.each_line do |line|
            # # 查找.version
            # s.version      = "0.0.2"
            # 需要注意的是,版本号可以是'',也可以是""
            write_line = line
            version_desc = /.*\.version[\s]*=.*/.match line
            if not version_desc.nil?
                version_coms = version_desc.to_s.split('=')
                if input_valid == true and user_custom_version == true
                    new_version = input_version.to_s
                else
                    version_num = version_coms.last.to_s.gsub("'",'').gsub("\"",'').gsub(' ','')
                    v_s = version_num.split('.')
                    # 处理版本号 0.0.1
                    for i in 0...v_s.count do
                        if i == v_s.count - 1
                            new_version += (v_s[i].to_i + 1).to_s
                        else
                            new_version += (v_s[i].to_s + '.')
                        end
                    end
                end
                puts color_text("New version = ",Color.white) + color_text("#{new_version}", Color.green)
                write_line = version_coms.first.to_s + '=' + " '#{new_version}'" + "\n"
            end
            source_desc = /.*\.source[\s]*=.*/.match line
            if not source_desc.nil?
                source_desc = /:git.*,/.match source_desc.to_s
                source_desc = /'.*'/.match source_desc.to_s
                git_source = source_desc.to_s.gsub("'",'')
                puts "git source is #{git_source}"
            end
            t.write write_line
        end
    end
end

puts color_text("Update version from ",Color.white) + color_text("#{cur_version}",Color.green) + color_text(" to ",Color.white) + color_text("#{new_version}", Color.green)

# 将新数据反写回到原始podspec中
system("cp -f #{temp_podspec_path} #{podspec_path}")
system("rm -f #{temp_podspec_path}")


# 如果本地没有这个repo,那么添加
if system("pod repo | grep #{pod_repo_name}") == false
    puts color_text("Add pod repo named '#{pod_repo_name}' with source: #{pod_repo_source}", Color.white)
    system("pod repo add #{pod_repo_name} #{pod_repo_source}")
end

# 提交代码到远程仓库
puts color_text('Start upload code to remote', Color.white)
system("git commit -am 'update version to #{new_version}'")
if system('git push origin') == false
    die_log('[!] git push code error')
end
system("git tag #{new_version}")
if system('git push origin --tags') == false
    die_log('[!] git push tags error')
    return
end

# 验证podspec格式是否正确
if verify_podspec_format == true
    puts color_text("Start verify podspec '#{podspec_path}'...", Color.white)
    if system("pod lib lint #{podspec_path} --allow-warnings") == false
        die_log("[!] pod spec' format invalid")
        return
    end
end

# 提交pod spec到spec仓库
puts color_text("Start push pod '#{podspec_path}' to remote repo '#{pod_repo_name}'", Color.white)
if pod_repo_name == 'trunk'
    if (is_static_lib == true ? system("pod trunk push #{podspec_path} --allow-warnings --use-libraries") : system("pod trunk push #{podspec_path} --allow-warnings")) == false
        puts "If not timeout, you need to check your 'trunk' account like: 'pod trunk me', and register code is 'pod trunk register <your email> <your name>'"
        return
    end
else
    if (is_static_lib == true ? system("pod repo push #{pod_repo_name} #{podspec_path} --allow-warnings --use-libraries") : system("pod repo push #{pod_repo_name} #{podspec_path} --allow-warnings"))  == false
        return
    end
end
puts color_text("Update success ☕️! Current version = #{new_version}", Color.green)

这里提一下 PodPushFile 文件,这个是配置文件,里面有这些配置项:

#写入*.podspec所在的相对目录,不写默认会在脚本执行的目录下查找,如果脚本执行的目录和podspec文件不在同一目录下,那么需要配置下
PUSH_DIR_PATH=


#是否允许用户自定义版本号,不填或填true将允许用户设置自定义的版本号,而不是自增版本号 
USER_CUSTOM_VERSION=true


#默认开启验证,可以跳过验证阶段
VERIFY_PODSPEC_FORMAT=true


#pod repo的名字,如果是私有库就填私有库的名字
POD_REPO_NAME=trunk


#pod repo的源地址,如果是私有仓库,那么填写私有仓库的地址,注意是存放podspec的仓库的地址
POD_REPO_SOURCE=https://github.com/CocoaPods/Specs


#如果这个库是静态库,那么需要设置为true
POD_IS_STATIC_LIBRARY=false

如何使用

使用起来比较简单,将脚本放在在当前的pod根目录下,执行 ruby specpush.rb 即可一键发布。第一次执行会先生成 PodPushFile 文件,配置完后需再次执行 ruby specpush.rb

如果要做到更简便,那么使用 alias 设置个别名吧,这能让你使用起来更方便。以 zsh 为例,到 .zshrc 文件下键入比如 alias podpush="ruby ~/Ruby/Project/rubyRepo/specpush.rb",使用时只要输入 podpush 即可。

如果脚本有任何问题,请评论留言。rubyRepo 这是我的 ruby 仓库地址,里面会不时更新一些有用好玩的 ruby 脚本,喜欢的关注下。