# This is very basic xcodebuild wrapper for internal Nixenvironment gem needs # It's used instead of popular 'xcodeproj' gem (https://github.com/cocoapods/xcodeproj) # because 'xcodeproj' gem is redundant and also doesn't satisfy all Nixenvironment gem needs require_relative 'cmd_executor' module Nixenvironment class Xcodebuild < CmdExecutor @binary_name = 'xcodebuild' @config_settings = {} @info = {} def self.contains_config?(config) configurations = @info['Build Configurations'] configurations && configurations.include?(config) end def self.contains_scheme?(scheme) schemes = @info['Schemes'] schemes && schemes.include?(scheme) end def self.clean_all_targets execute(nil, %w(-alltargets clean)) end # other => other args to be passed to xcodebuild def self.build(sdk = nil, config = nil, xcconfig = nil, project = nil, target = nil, workspace = nil, scheme = nil, env_var_prefix = nil, xc_args = nil) build_args = [] build_args << "-sdk '#{sdk}'" if sdk.present? build_args << "-configuration '#{config}'" if config.present? build_args << "-xcconfig '#{xcconfig}'" if xcconfig.present? if project.present? build_args << "-project '#{project}'" elsif workspace.present? build_args << "-workspace '#{workspace}'" end if target.present? build_args << "-target '#{target}'" elsif scheme.present? build_args << "-scheme '#{scheme}'" end if env_var_prefix.present? vars_to_define = '' ENV.each do |key, value| if key.start_with?(env_var_prefix) vars_to_define += ' ' + key puts "Environment variable '#{key}' will be defined with value: #{value}" end end build_args << "GCC_PREPROCESSOR_DEFINITIONS='$GCC_PREPROCESSOR_DEFINITIONS#{vars_to_define}'" if vars_to_define.present? end build_args << xc_args if xc_args.present? build_args << '| xcpretty -c; exit ${PIPESTATUS[0]}' execute(nil, build_args) end def self.test(sdk = nil, config = nil, workspace = nil, scheme = nil, code_coverage_config = nil, timeout = nil, destination = nil) build_args = [] build_args << "-sdk '#{sdk}'" if sdk.present? build_args << "-configuration '#{config}'" if config.present? build_args << "-workspace '#{workspace}'" if workspace.present? build_args << "-scheme '#{scheme}'" if scheme.present? build_args << "-xcconfig '#{code_coverage_config}'" if code_coverage_config.present? build_args << "-destination-timeout '#{timeout}'" if timeout.present? build_args << "-destination '#{destination}'" if destination.present? build_args << "| ocunit2junit" execute('test', build_args) end def self.read_config_settings(workspace = nil, project = nil, scheme = nil, target = nil, sdk = nil, config = nil, xcconfig = nil) @config_settings = {} read_info raise "Scheme #{scheme} doesn't exist!" if target.blank? && scheme.present? && !contains_scheme?(scheme) raise "Configuration #{config} doesn't exist!" if config.present? && !contains_config?(config) raise "Either #{PROJECT_TO_BUILD_KEY} or #{WORKSPACE_TO_BUILD_KEY} must be specified!" if project.blank? && workspace.blank? build_args = [] build_args << "-sdk '#{sdk}'" if sdk.present? build_args << "-configuration '#{config}'" if config.present? build_args << "-xcconfig '#{xcconfig}'" if xcconfig.present? if project.present? build_args << "-project '#{project}'" elsif workspace.present? build_args << "-workspace '#{workspace}'" end if target.present? build_args << "-target '#{target}'" elsif scheme.present? build_args << "-scheme '#{scheme}'" end build_args << 'clean' build_args << '-showBuildSettings' puts 'Reading config settings ...' puts cmd_output = execute(nil, build_args, true) env_vars_list = cmd_output.split(/\n/).reject(&:empty?) if env_vars_list.present? build_settings_to_strip = Hash[env_vars_list.map { |it| it.split('=', 2) }] build_settings_to_strip.each do |key, value| @config_settings[key.strip] ||= value.strip if key.present? && value.present? end end if workspace.present? @config_settings[WATCHKIT_EXTENSION_PRODUCT_SETTINGS_PATH_KEY] = product_settings_path(workspace, scheme, WATCHKIT_EXTENSION_SUFFIX) @config_settings[WATCHKIT_APP_PRODUCT_SETTINGS_PATH_KEY] = product_settings_path(workspace, scheme, WATCHKIT_APP_SUFFIX) @config_settings[WIDGET_PRODUCT_SETTINGS_PATH_KEY] = product_settings_path(workspace, scheme, WIDGET_SUFFIX) end build_directory = File.join(Dir.pwd, IOS_BUILDS_FOLDER_NAME) @config_settings[CONFIGURATION_BUILD_DIR_KEY] = build_directory @config_settings[BUILT_PRODUCTS_DIR_KEY] = build_directory @config_settings[DWARF_DSYM_FOLDER_PATH_KEY] = build_directory plist_path = @config_settings[PRODUCT_SETTINGS_PATH_KEY] info_plist = Plist.from_file(plist_path) @config_settings[RESIGNED_BUNDLE_NAME_KEY] ||= info_plist['CFBundleDisplayName'] end def self.product_settings_path(workspace, scheme, scheme_suffix) scheme += scheme_suffix if contains_scheme?(scheme) build_args = [] build_args << "-workspace '#{workspace}'" build_args << "-scheme '#{scheme}'" build_args << 'clean' build_args << '-showBuildSettings' puts "Getting product settings path for scheme '#{scheme}' ..." cmd_output = execute(nil, build_args, true) env_vars_list = cmd_output.split(/\n/).reject(&:empty?) if env_vars_list.present? build_settings_to_strip = Hash.new.compare_by_identity env_vars_list.each do |it| key_value = it.split('=', 2) build_settings_to_strip[key_value.first] = key_value.last end build_settings_to_strip.each do |key, value| if key.present? && value.present? stripped_key = key.strip stripped_value = value.strip return stripped_value if stripped_key == PRODUCT_SETTINGS_PATH_KEY end end end end nil end def self.read_info @info = {} puts cmd_output = execute(nil, %w(-list 2>&1)) puts raise "Getting xcode_project_info error! #{cmd_output}" if cmd_output.include?('error') cmd_output = cmd_output.lines[1..-1].join.split(/\n\n/) cmd_output.each do |pair| key, value = pair.split(/:/) next unless key.present? && value.present? @info[key.strip] = value.lines.map(&:strip).reject(&:empty?) end end def self.config_settings @config_settings end private_class_method :product_settings_path, :read_info, :execute end end