测量所有目标(包括依赖目标)的 xcodebuild 持续时间

Measuring xcodebuild durations for all targets (including dependent ones)

是否可以测量单个 xcodebuild 命令构建每个不同目标所消耗的时间?

假设我有一个目标,它取决于一些 cocoapodpod1pod2。 我使用 xcodebuild 构建我的目标。我可以测量总时间。 我需要测量分别花费在 pod1pod2 和我的目标

上的时间

我试图在 xcodebuild 的输出中找到答案,但没有找到。

提前致谢!

我最终编写了一个自定义 ruby 脚本来修改我的 xcodeprojPods.xcodeproj 的每个目标。此脚本添加两个 build phases,将目标名称和当前时间戳记录到输出文件中。一个 build phase 首先执行,另一个最后执行。后来我在一个单独的脚本中简单地从另一个时间戳中减去一个时间戳。

这是脚本的结果:


输出文件将如下所示(排序后)

Alamofire end: 1510929112.3409
Alamofire start: 1510929110.2161
AlamofireImage end: 1510929113.6925
AlamofireImage start: 1510929112.5205

输出文件的路径(屏幕截图上的/a/ci_automation/metrics/performance-metrics/a.txt)无论如何都没有硬编码。相反,您将它作为 ruby 脚本的参数传递,如下所示:

$ruby prepare-for-target-build-time-profiling.rb ${PWD}/output.txt

请注意,此脚本需要 cocoapods 1.3.1(可能 1.3)。


这是 ruby 脚本:ruby prepare-for-target-build-time-profiling.rb

#!/usr/bin/env ruby

require 'xcodeproj'
require 'cocoapods'
require 'fileutils'

def inject_build_time_profiling_build_phases(project_path)
    project = Xcodeproj::Project.open(project_path)

    log_time_before_build_phase_name = '[Prefix placeholder] Log time before build'.freeze
    log_time_after_build_phase_name = '[Prefix placeholder] Log time after build'.freeze

    puts "Patching project at path: #{project_path}"
    puts
    project.targets.each do |target|
        puts "Target: #{target.name}"

        first_build_phase = create_leading_build_phase(target, log_time_before_build_phase_name)
        last_build_phase = create_trailing_build_phase(target, log_time_after_build_phase_name)

        puts
    end

    project.save

    puts "Finished patching project at path: #{project_path}"
    puts
end

def create_leading_build_phase(target, build_phase_name)
    remove_existing_build_phase(target, build_phase_name)

    build_phase = create_build_phase(target, build_phase_name)

    shift_build_phase_leftwards(target, build_phase)

    is_build_phase_leading = true

    inject_shell_code_into_build_phase(target, build_phase, is_build_phase_leading)

    return build_phase
end

def create_trailing_build_phase(target, build_phase_name)
    remove_existing_build_phase(target, build_phase_name)

    build_phase = create_build_phase(target, build_phase_name)

    is_build_phase_leading = false

    inject_shell_code_into_build_phase(target, build_phase, is_build_phase_leading)

    return build_phase
end

def remove_existing_build_phase(target, build_phase_name)
    existing_build_phase = target.shell_script_build_phases.find do |build_phase|
        build_phase.name.end_with?(build_phase_name)
        # We use `end_with` instead of `==`, because `cocoapods` adds its `[CP]` prefix to a `build_phase_name`
    end

    if !existing_build_phase.nil?
        puts "deleting build phase #{existing_build_phase.name}"

        target.build_phases.delete(existing_build_phase)
    end
end

def create_build_phase(target, build_phase_name)
    puts "creating build phase: #{build_phase_name}"

    build_phase = Pod::Installer::UserProjectIntegrator::TargetIntegrator
        .create_or_update_shell_script_build_phase(target, build_phase_name)

    return build_phase
end

def shift_build_phase_leftwards(target, build_phase)
    puts "moving build phase leftwards: #{build_phase.name}"

    target.build_phases.unshift(build_phase).uniq! unless target.build_phases.first == build_phase
end

def inject_shell_code_into_build_phase(target, build_phase, is_build_phase_leading)
    start_or_end = is_build_phase_leading ? "start" : "end"

    build_phase.shell_script = <<-SH.strip_heredoc
        timestamp=`echo "scale=4; $(gdate +%s%N/1000000000)" | bc`
        echo "#{target.name} #{start_or_end}: ${timestamp}" >> #{$build_time_logs_output_file}
    SH
end

def parse_arguments
    $build_time_logs_output_file = ARGV[0]

    if $build_time_logs_output_file.to_s.empty? || ! $build_time_logs_output_file.start_with?("/")
        puts "Error: you should pass a full path to a output file as an script's argument. Example:"
        puts "$ruby prepare-for-target-build-time-profiling.rb /path/to/script/output.txt"
        puts
        exit 1
    end
end

def print_arguments
    puts "Arguments:"
    puts "Output path: #{$build_time_logs_output_file}"
    puts
end

def clean_up_before_script
    if File.exist?($build_time_logs_output_file)
        FileUtils.rm($build_time_logs_output_file)
    end

    build_time_logs_output_folder = File.dirname($build_time_logs_output_file)
    unless File.directory?(build_time_logs_output_folder)
        FileUtils.mkdir_p(build_time_logs_output_folder)
    end
end

def main 
    parse_arguments
    print_arguments
    clean_up_before_script
    inject_build_time_profiling_build_phases("path/to/project.xcodeproj")
    inject_build_time_profiling_build_phases("path/to/pods/project.xcodeproj")
end

# arguments:
$build_time_logs_output_file

main