#!/usr/bin/env ruby require 'rubygems' require 'nixenvironment' require 'commander/import' require 'yaml' require 'fileutils' require 'tmpdir' require 'active_support/core_ext/object/blank' require 'terminal-table' require 'colored' require 'nokogiri' require 'xcodeproj' include Nixenvironment CONFIG_SETTINGS_FILE_PATH = File.join(File.dirname(__FILE__), CONFIG_SETTINGS_FILE_NAME) program :name, 'nixenvironment' program :version, VERSION program :description, 'NIX projects build and deploy utility' default_command :help global_option ('--exclude_pattern_for_code_duplication VALUE') { |value| $exclude_pattern_for_code_duplication = value } global_option ('--deploy_host VALUE') { |value| $deploy_host = value } global_option ('--deploy_path VALUE') { |value| $deploy_path = value } global_option ('--deploy_username VALUE') { |value| $deploy_username = value } global_option ('--deploy_password VALUE') { |value| $deploy_password = value } global_option ('--deploy_itunesconnect_username VALUE') { |value| $deploy_itunesconnect_username = value } global_option ('--deploy_team_name VALUE') { |value| $deploy_team_name = value } global_option ('--icons_path VALUE') { |value| $icons_path = value } global_option ('--xctest_destination_device VALUE') { |value| $xctest_destination_device = value } global_option (['-cfp', '--configuration_files_path VALUE']) { |value| $configuration_files_path = value } global_option ('--code_coverage_configuration VALUE') { |value| $code_coverage_configuration = value } global_option ('--code_coverage_ignore_file VALUE') { |value| $code_coverage_ignore_file = value } global_option ('--code_coverage_output_directory VALUE') { |value| $code_coverage_output_directory = value } global_option ('--env_var_prefix VALUE') { |value| $env_var_prefix = value } global_option ('--bundle_id VALUE') { |value| $bundle_id = value } global_option ('--resigned_bundle_id VALUE') { |value| $resigned_bundle_id = value } global_option ('--resigned_watchkit_app_bundle_id VALUE') { |value| $resigned_watchkit_app_bundle_id = value } global_option ('--resigned_watchkit_extension_bundle_id VALUE') { |value| $resigned_watchkit_extension_bundle_id = value } global_option ('--resigned_widget_bundle_id VALUE') { |value| $resigned_widget_bundle_id = value } global_option ('--resigned_bundle_name VALUE') { |value| $resigned_bundle_name = value } global_option ('--resigned_entitlements_path VALUE') { |value| $resigned_entitlements_path = value } global_option ('--resigned_watchkit_extension_entitlements_path VALUE') { |value| $resigned_watchkit_extension_entitlements_path = value } global_option ('--resigned_widget_entitlements_path VALUE') { |value| $resigned_widget_entitlements_path = value } command :init do |c| c.syntax = 'nixenvironment init' c.description = 'Initialize template project of selected type in the destination repository and clone it to current folder' c.action { init } end command :build do |c| c.syntax = 'nixenvironment build [options]' c.description = 'Build project for selected configuration and make signed/resigned ipa' c.option '--project VALUE', String, 'Project name' c.option '--workspace VALUE', String, 'Workspace name' c.option '--scheme VALUE', String, 'Scheme name' c.option '--sdk VALUE', String, 'SDK name' c.option '--config NAME', String, 'Select configuration' c.option '--xcconfig PATH', String, 'Specify the path to .xcconfig file' c.option '--ipa TYPES', String, 'Select sign (device, resigned_device, resigned_adhoc, resigned_appstore)' c.option '--unity_path PATH', String, 'Select unity executable path (UNITY, UNITY_4 or custom path)' c.option '--development_build', 'Enable Development flag in Unity project' c.option '--connect_profiler', 'Enable ConnectWithProfiler flag in Unity project' c.option '--keystore_path PATH', String, 'Specify the path to .keystore file' c.option '--keystore_password PASSWORD', String, 'Specify the password for accessing a .keystore file' c.option '--key_alias_name NAME', String, 'Specify the alias name which should be used from .keystore file to sign a release version of an APK' c.option '--key_alias_password PASSWORD', String, 'Specify the password for accessing an alias name' c.option '--unity_platform TARGET PLATFORM', String, 'Select target platform for unity build (ios | macos | android | winphone | webgl)' c.option '--ndsym', 'Disable .dsym generation for ios project' c.option '--icon_tagger MODE', String, 'Set XcodeIconTagger mode (full, short, off)' c.option '--app_version VERSION', String, 'Specify the version of the app (not implemented for unity-android)' # TODO: implement for unity-android c.option '--build_number BUILD_NUMBER', String, 'Specify the build number of the app (not implemented for unity-android)' # TODO: implement for unity-android c.option '--requires_fullscreen', 'Set UIRequiresFullScreen to YES in Info.plist (ios)' c.option '--skip_working_copy_check', 'Skip working copy cleaning check' c.option '--share_schemes', 'Share schemes for xcode project' c.option '--xc_args ARGS', String, 'Pass arguments to xcodebuild' c.action do |_args, options| $workspace = options.workspace $project = options.project $scheme = options.scheme $sdk = options.sdk options.default :config => 'Debug', :ipa => 'device', :icon_tagger => 'full', :unity_path => 'UNITY' unity_platform = options.unity_platform is_unity_platform = unity_platform.present? need_to_build_ios = true need_to_process_macos_build = false need_to_process_winphone_build = false need_to_process_webgl_build = false $build_number = options.build_number if is_unity_platform ENV['UNITY_BUILD'] = 'UNITY_BUILD' unity_path = ENV[options.unity_path] || options.unity_path unity_build(options.config, unity_platform, unity_path, options.development_build, options.connect_profiler, options.keystore_path, options.keystore_password, options.key_alias_name, options.key_alias_password) need_to_build_ios = unity_platform == 'ios' need_to_process_macos_build = unity_platform == 'macos' need_to_process_winphone_build = unity_platform == 'winphone' need_to_process_webgl_build = unity_platform == 'webgl' end if need_to_process_macos_build plist_path = File.join(UNITY_MACOS_BUILD_PATH, 'Contents/Info.plist') info_plist = Plist.from_file(plist_path) info_plist['RevisionNumber'] = revision info_plist['CFBundleVersion'] = build_number info_plist['CFBundleShortVersionString'] = options.app_version if options.app_version.present? info_plist['CFBundleName'] = $resigned_bundle_name if $resigned_bundle_name.present? info_plist['CFBundleDisplayName'] = $resigned_bundle_name if $resigned_bundle_name.present? info_plist['CFBundleIdentifier'] = $bundle_id if $bundle_id info_plist.save(plist_path, Plist::FORMAT_XML) read_config_settings @config_settings[PRODUCT_SETTINGS_PATH_KEY] = plist_path @config_settings[BUILT_PRODUCTS_DIR_KEY] = UNITY_MACOS_PROJECT_PATH @config_settings[EXECUTABLE_NAME_KEY] = UNITY_MACOS_BUILD_NAME @config_settings[CONFIGURATION_KEY] = options.development_build ? 'Debug' : 'Release' @config_settings[SDK_NAME_KEY] = 'macosx' BuildEnvVarsLoader.save_last_build_vars(@config_settings) Archiver.make_macos_zip elsif need_to_process_winphone_build manifest_path = Dir.glob("#{UNITY_WINPHONE_PROJECT_PATH}/**/*.appxmanifest").first File.open(manifest_path,'w') do |f| manifest = Nokogiri::XML(File.open(f)) manifest.at('Package/Identity')['Version'] = "#{options.app_version}.#{build_number}" end elsif need_to_build_ios Dir.chdir(UNITY_IOS_PROJECT_PATH) if is_unity_platform read_config_settings @config_settings[CONFIGURATION_KEY] = options.config if is_unity_platform @config_settings[WORKSPACE_KEY] = nil @config_settings[PROJECT_KEY] = UNITY_BUILDS_IOS_PROJECT @config_settings[SCHEME_KEY] = UNITY_BUILDS_IOS_SCHEME @config_settings[ICONS_PATH_KEY] = UNITY_BUILDS_ICONS_PATH end begin # Phonegap hotfix (http://mgrebenets.github.io/xcode/2014/05/29/share-xcode-schemes) # TODO: rework this! if options.share_schemes xcproj = Xcodeproj::Project.open(@config_settings[PROJECT_KEY]) xcproj.recreate_user_schemes xcproj.save end Xcodebuild.read_config_settings(@config_settings[WORKSPACE_KEY], @config_settings[PROJECT_KEY], @config_settings[SCHEME_KEY], @config_settings[SDK_KEY], @config_settings[CONFIGURATION_KEY], options.xcconfig) raise 'Failed read_config_settings!' unless Xcodebuild.last_cmd_success? rescue => e error('Build error!', e) end @config_settings.merge!(Xcodebuild.config_settings) { |_key, v1, v2| v1 || v2 } begin prebuild(options.config, options.app_version, options.requires_fullscreen, options.skip_working_copy_check) build(options.config, options.xcconfig, options.ipa, options.ndsym, options.icon_tagger, options.xc_args) ensure restore_info_plists end elsif need_to_process_webgl_build Archiver.make_webgl_zip(UNITY_WEBGL_PROJECT_PATH) end end end command :deploy do |c| c.syntax = 'nixenvironment deploy' c.description = 'Deploy built artifacts to given server' c.option '--unity_platform TARGET PLATFORM', String, 'Select target platform for unity deploy (ios | macos | android | winphone)' c.option '--deployment_names NAMES', String, 'Set names for mds buttons for each ipa type' c.option '--deliver_deploy', 'Not only verify but also submit the build on iTunes Connect. (resigned_appstore builds only)' c.option '--skip_working_copy_check', 'Skip working copy cleaning check' c.action do |_args, options| if ENV[SKIP_DEPLOY_KEY].present? puts("'#{SKIP_DEPLOY_KEY}' is defined. Skipping deploy ...".bold.blue) else unity_platform = options.unity_platform need_to_deploy_ios = false need_to_deploy_macos_build = false need_to_deploy_winphone_build = false if unity_platform.present? case unity_platform when 'ios' Dir.chdir(UNITY_IOS_PROJECT_PATH) need_to_deploy_ios = true when 'macos' #Dir.chdir(UNITY_MACOS_PROJECT_PATH) need_to_deploy_macos_build = true when 'android' system(DEPLOY_APK_SCRIPT_PATH) ? success('Unity android deploy complete!') : error('Unity android deploy error!') when 'winphone' #Dir.chdir(UNITY_WINPHONE_PROJECT_PATH) need_to_deploy_winphone_build = true else error("Error: Unknown unity target platform '#{unity_platform}'!") end else need_to_deploy_ios = true end if need_to_deploy_ios || need_to_deploy_macos_build read_config_settings deploy(options.deliver_deploy, options.deployment_names, options.skip_working_copy_check) end end end end command :clean do |c| c.syntax = 'nixenvironment clean' c.description = 'Remove temp files and clean all targets for xcode project' c.action { clean } end command :test do |c| c.syntax = 'nixenvironment test' c.description = 'Build xctest unit tests and run them in simulator' c.option '--project VALUE', String, 'Project name' c.option '--workspace VALUE', String, 'Workspace name' c.option '--scheme VALUE', String, 'Scheme name' c.option '--sdk VALUE', String, 'SDK name' c.action do |_args, options| $workspace = options.workspace $project = options.project $scheme = options.scheme #own default value $sdk = 'iphonesimulator' $sdk = options.sdk if options.sdk.present? read_config_settings test end end command :code_coverage do |c| c.syntax = 'nixenvironment code_coverage' c.description = 'Generate xctest unit tests code coverage report' c.option '--project VALUE', String, 'Project name' c.option '--workspace VALUE', String, 'Workspace name' c.option '--scheme VALUE', String, 'Scheme name' c.action do |_args, options| $workspace = options.workspace $project = options.project $scheme = options.scheme read_config_settings code_coverage end end command :code_duplication_report do |c| c.syntax = 'nixenvironment code_duplication_report' c.description = 'Generate code duplication report' c.action do read_config_settings code_duplication_report end end command :clean_working_copy do |c| c.syntax = 'nixenvironment clean_working_copy' c.description = 'Make working copy clean' c.option '--all', 'Remove ignored files too' c.action { |_args, options| clean_working_copy(options.all) } end command :slave do |c| c.syntax = 'nixenvironment slave' c.description = 'Jenkins slave' c.option '--init', 'Init Jenkins slave' c.option '--start', 'Start Jenkins slave' c.option '--deinit', 'Deinit Jenkins slave' c.action do |_args, options| if options.init SlaveInitializer.new.start elsif options.deinit SlaveDeinitializer.new.start end if options.start starter = SlaveStarter.new begin starter.start rescue SystemExit, Interrupt starter.stop end end end end command :master do |c| c.syntax = 'nixenvironment master' c.description = 'Jenkins master' c.option '--init', 'Init Jenkins master' c.option '--start', 'Start Jenkins master' c.option '--deinit', 'Deinit Jenkins master' c.option '--start_pu ENV', String, 'Start Provisioning updater. To run develop server enter "develop" argument' c.option '--start_cs', 'Start Unity cache server.' c.option '--stop', 'Stop Jenkins master' c.action do |_args, options| options.default :start_pu => '' begin if options.init MasterInitializer.new.start elsif options.deinit MasterDeinitializer.new.start end rescue AlreadyInitialized puts("Skip by reason: Already initialized") end if options.stop MasterStarter.new.stop end if options.start starter = MasterStarter.new begin starter.start rescue SystemExit, Interrupt starter.stop end elsif options.start_pu != '' MasterStarter.new.start_pu(options.start_pu != 'develop') elsif options.start_cs MasterStarter.new.start_cache_server end end end def init template_project_url = nil template_project_types = [] puts 'Select project type (put index or name):' TEMPLATES_REPO_LIST.each_with_index do |(key, _value), index| selection_index = index + 1 selection = "#{selection_index}. #{key}" template_project_types.push(selection) puts selection end loop do type = ask('> ', template_project_types) type_index = type.to_i - 1 if TEMPLATES_REPO_LIST.key?(type) template_project_url = TEMPLATES_REPO_LIST[type] break elsif (0...TEMPLATES_REPO_LIST.size).include?(type_index) template_project_url = TEMPLATES_REPO_LIST.values[type_index] break end end puts dest_url = ask('Destination repository url: ') Git.ls_remote(dest_url) raise "Repository not found: '#{dest_url}'" unless Git.last_cmd_success? local_directory_to_clone = ask('Directory to clone into: ') raise 'Cloning into an existing directory is not allowed!' if Dir.exist?(local_directory_to_clone) repo_name = File.basename(dest_url, GIT_EXT) template_project_name = File.basename(template_project_url, GIT_EXT) begin adjuster_working_copy_path = File.join(Dir.tmpdir, ADJUSTER_WORKING_COPY_NAME) FileUtils.rm_rf(adjuster_working_copy_path) Dir.mkdir(adjuster_working_copy_path) Dir.chdir(adjuster_working_copy_path) do puts puts 'Cloning template project ...' Git.clone(template_project_url, nil, :r => true) raise "Authentication failed for #{template_project_url}!" unless Git.last_cmd_success? tags = nil Dir.chdir(template_project_name) do puts 'Fetch available tags ...' Git.fetch(:t => true) tags = Git.tag end raise 'Failed to get last tag!' if tags.blank? FileUtils.rm_rf(ADJUSTER_TEMP_PROJECT_NAME) if Dir.exist?(ADJUSTER_TEMP_PROJECT_NAME) FileUtils.cp_r(template_project_name, ADJUSTER_TEMP_PROJECT_NAME) Dir.chdir(ADJUSTER_TEMP_PROJECT_NAME) do puts 'Checkout newest template project tag ...' Git.checkout(tags.last, :orphan => true) puts "Push ... #{dest_url}" Git.remote_add(repo_name, dest_url) Git.commit(:m => 'Initial commit') Git.checkout(Git::DEVELOP, :b => true) Git.push(repo_name, Git::DEFAULT_REFSPEC, :f => true) Git.push(repo_name, Git::DEVELOP, :f => true) raise "Push failed for #{dest_url} repository!" unless Git.last_cmd_success? end end puts "Cloning new created project from #{dest_url} to #{local_directory_to_clone} ..." Git.clone(dest_url, local_directory_to_clone, :r => true) Dir.chdir(local_directory_to_clone) { |_path| Git.checkout(Git::DEVELOP) } raise "Error cloning #{dest_url}!" unless Git.last_cmd_success? rescue => e error('Project initialization failed!', e) end success('Project initialization complete!') end def read_config_settings begin @config_settings = YAML.load(File.read(CONFIG_SETTINGS_FILE_PATH)) rescue => e error("'#{CONFIG_SETTINGS_FILE_PATH}' file processing error!", e) end table = Terminal::Table.new table.title = 'Configuration Settings' table.headings = [{:value => 'Key', :alignment => :center}, {:value => 'Specification', :alignment => :center}, {:value => 'Value', :alignment => :center}] update_project_and_workspace(table) update_config_settings(SCHEME_KEY, $scheme, table, true) update_config_settings(SDK_KEY, $sdk, table, true) update_config_settings(EXCLUDE_PATTERN_FOR_CODE_DUPLICATION_KEY, $exclude_pattern_for_code_duplication, table, true) update_config_settings(DEPLOY_HOST_KEY, $deploy_host, table, true) update_config_settings(DEPLOY_PATH_KEY, $deploy_path, table, true) update_config_settings(DEPLOY_USERNAME_KEY, $deploy_username, table, true) update_config_settings(DEPLOY_PASSWORD_KEY, $deploy_password, table, true) update_config_settings(DEPLOY_ITUNESCONNECT_USERNAME_KEY, $deploy_itunesconnect_username, table, true) update_config_settings(DEPLOY_TEAM_NAME_KEY, $deploy_team_name, table, true) update_config_settings(ICONS_PATH_KEY, $icons_path, table, true) update_config_settings(XCTEST_DESTINATION_DEVICE_KEY, $xctest_destination_device, table, true) update_config_settings(CONFIGURATION_FILES_PATH_KEY, $configuration_files_path, table, true) update_config_settings(CODE_COVERAGE_CONFIGURATION_KEY, $code_coverage_configuration, table, true) update_config_settings(CODE_COVERAGE_IGNORE_FILE_KEY, $code_coverage_ignore_file, table, true) update_config_settings(CODE_COVERAGE_OUTPUT_DIRECTORY_KEY, $code_coverage_output_directory, table, true) update_config_settings(ENV_VAR_PREFIX_KEY, $env_var_prefix, table, true) update_config_settings(BUNDLE_ID_KEY, $bundle_id, table, true) update_config_settings(RESIGNED_BUNDLE_ID_KEY, $resigned_bundle_id, table, true) update_config_settings(RESIGNED_WATCHKIT_APP_BUNDLE_ID_KEY, $resigned_watchkit_app_bundle_id, table, true) update_config_settings(RESIGNED_WATCHKIT_EXTENSION_BUNDLE_ID_KEY, $resigned_watchkit_extension_bundle_id, table, true) update_config_settings(RESIGNED_WIDGET_BUNDLE_ID_KEY, $resigned_widget_bundle_id, table, true) update_config_settings(RESIGNED_BUNDLE_NAME_KEY, $resigned_bundle_name, table, true) update_config_settings(RESIGNED_ENTITLEMENTS_PATH_KEY, $resigned_entitlements_path, table, true) update_config_settings(RESIGNED_WATCHKIT_EXTENSION_ENTITLEMENTS_PATH_KEY, $resigned_watchkit_extension_entitlements_path, table, true) update_config_settings(RESIGNED_WIDGET_ENTITLEMENTS_PATH_KEY, $resigned_widget_entitlements_path, table, false) puts table end def update_project_and_workspace(table) # try to find automatically if not set (will work only in case of single project/workspace in dir) if !$project.present? && !$workspace.present? projects_in_dir = Dir.glob("*.xcodeproj") workspaces_in_dir = Dir.glob("*.xcworkspace") if workspaces_in_dir.count == 1 $workspace = workspaces_in_dir.first elsif workspaces_in_dir.count == 0 && projects_in_dir.count == 1 $project = projects_in_dir.first #reset defaults @config_settings[WORKSPACE_KEY] = nil end end if $project.present? && !$workspace.present? #reset defaults @config_settings[WORKSPACE_KEY] = nil end update_config_settings(PROJECT_KEY, $project, table, true) update_config_settings(WORKSPACE_KEY, $workspace, table, true) end def update_config_settings(key, value, table, need_separator) if value @config_settings[key] = value spec_column = "Specified directly" else spec_column = "Used from Config" end table.add_row [key, spec_column, @config_settings[key]] table.add_separator if need_separator end def revision BuildEnvVarsLoader.load_last_revision['REVISION'] end def build_number $build_number.presence || BuildEnvVarsLoader.load_last_revision['MONOTONIC_REVISION'] end def save_revision(skip_working_copy_check = false) revision, monotonic_revision, working_copy_is_clean = SCM.last_revision build_num = $build_number.presence || monotonic_revision BuildEnvVarsLoader.save_last_revision(revision, build_num, working_copy_is_clean) error('Error! Working copy is not clean!') unless BuildEnvVarsLoader.working_copy_is_clean? unless skip_working_copy_check end def prebuild(config, app_version, requires_fullscreen, skip_working_copy_check) save_revision(skip_working_copy_check) backup_info_plists BuildEnvVarsLoader.save_last_build_vars(@config_settings) update_info_plists(config, app_version, requires_fullscreen) end def build(config, xcconfig, ipa, ndsym, icon_tagger, xc_args) project = @config_settings[PROJECT_KEY] workspace = @config_settings[WORKSPACE_KEY] scheme = @config_settings[SCHEME_KEY] sdk = @config_settings[SDK_KEY] configuration_build_dir = @config_settings[CONFIGURATION_BUILD_DIR_KEY] dwarf_dsym_folder_path = @config_settings[DWARF_DSYM_FOLDER_PATH_KEY] built_products_dir = @config_settings[BUILT_PRODUCTS_DIR_KEY] env_var_prefix = @config_settings[ENV_VAR_PREFIX_KEY] debug_information_format = ndsym ? 'dwarf' : 'dwarf-with-dsym' other_args = { 'DEBUG_INFORMATION_FORMAT' => debug_information_format, 'DWARF_DSYM_FOLDER_PATH' => dwarf_dsym_folder_path, 'CONFIGURATION_BUILD_DIR' => configuration_build_dir, 'BUILT_PRODUCTS_DIR' => built_products_dir, 'GCC_PRECOMPILE_PREFIX_HEADER' => 'NO', # set "Precompile Prefix Header" flag to "NO"; otherwise it can throw error ".pch.pch has been modified since the precompiled header" 'ENABLE_BITCODE' => 'NO'} # need this in order to build unity <= 5.2.0 generated projects. TODO: remove me! other_args_str = other_args.map { |key, value| "#{key}='#{value}'" }.join(' ') if xc_args.present? xc_args.concat(" #{other_args_str}") else xc_args = other_args_str end Xcodebuild.build(sdk, config, xcconfig, project, workspace, scheme, env_var_prefix, xc_args) error('Build error!') unless Xcodebuild.last_cmd_success? if sdk.include?('macos') begin Archiver.make_macos_zip rescue => e error("Make 'macos_zip' error!", e) end success("Make 'macos_zip' complete!") elsif sdk.include?('iphoneos') puts if config == 'Release' puts 'IconTagger: configuration is Release. Skipping ...' else case icon_tagger when 'full' then tag_icon(false) when 'short' then tag_icon(true) when 'off' then puts 'IconTagger is disabled. Skipping ...' else puts "Unknown IconTagger mode: '#{icon_tagger}'. Skipping ..." end end puts ipa.split.each do |current_ipa| begin case current_ipa when 'device' then Archiver.make_signed_ipa when 'resigned_device' then Archiver.make_resigned_ipa_for_device when 'resigned_adhoc' then Archiver.make_resigned_ipa_for_adhoc when 'resigned_appstore' then Archiver.make_resigned_ipa_for_appstore else raise "Unknown ipa '#{current_ipa}'!" end rescue => e error("Make '#{current_ipa}' error!", e) end success("Make '#{current_ipa}' complete!") end else error("Build error! Unknown sdk: #{sdk}!") end success('Build complete!') end def unity_build(configuration, unity_platform, unity_path, development_build, connect_profiler, keystore_path, keystore_password, key_alias_name, key_alias_password) save_revision if File.directory?(UNITY_EDITOR_DIR) FileUtils.cp_r(UNITY_BUILD_SCRIPTS_DIR, UNITY_EDITOR_DIR) else error("Copy #{UNITY_BUILD_SCRIPTS_PATH} error! #{UNITY_EDITOR_DIR} doesn't exist!") end case unity_platform when 'ios' build_path_arg = "buildPath=#{UNITY_IOS_PROJECT_PATH}" development_build_arg = development_build ? ';developmentBuild=' : '' connect_profiler_arg = connect_profiler ? ';connectWithProfiler=' : '' puts 'Generating IOS project from UNITY project ...' unity_success = system("#{unity_path} -projectPath '#{Dir.pwd}' -batchmode -logFile -quit -executeMethod NIXBuilder.MakeiOSBuild -customArgs:'#{build_path_arg}#{development_build_arg}#{connect_profiler_arg}'") error('iOS build unity error!') unless unity_success success("IOS project was generated.\n") when 'macos' build_path_arg = "buildPath=#{UNITY_MACOS_BUILD_PATH}" development_build_arg = development_build ? ';developmentBuild=' : '' unity_success = system("#{unity_path} -projectPath '#{Dir.pwd}' -batchmode -logFile -quit -executeMethod NIXBuilder.MakeMacOSBuild -customArgs:'#{build_path_arg}#{development_build_arg}'") error('MacOS build unity error!') unless unity_success when 'android' development_build_arg = development_build ? '--development-build' : '' keystore_path_arg = keystore_path ? "--keystore-path #{keystore_path}" : '' keystore_password_arg = keystore_password ? "--keystore-password #{keystore_password}" : '' key_alias_name_arg = key_alias_name ? "--key-alias-name #{key_alias_name}" : '' key_alias_password_arg = key_alias_password ? "--key-alias-password #{key_alias_password}" : '' build_success = system("#{UNITY_BUILD_ANDROID_SCRIPT_PATH} --configuration #{configuration} --unity-path #{unity_path}\ #{development_build_arg} #{keystore_path_arg} #{keystore_password_arg} #{key_alias_name_arg} #{key_alias_password_arg}") error('Android build unity error!') unless build_success when 'winphone' build_path_arg = "buildPath=#{UNITY_WINPHONE_PROJECT_PATH}" development_build_arg = development_build ? ';developmentBuild=' : '' puts 'Generating WinPhone project from UNITY project ...' build_success = system("#{unity_path} -projectPath '#{Dir.pwd}' -batchmode -logFile -quit -executeMethod NIXBuilder.MakeWinPhoneBuild -customArgs:'#{build_path_arg}#{development_build_arg}'") error('WinPhone build unity error!') unless build_success when 'webgl' build_path_arg = "buildPath=#{UNITY_WEBGL_PROJECT_PATH}" development_build_arg = development_build ? ';developmentBuild=' : '' unity_success = system("#{unity_path} -projectPath '#{Dir.pwd}' -batchmode -logFile -quit -executeMethod NIXBuilder.MakeWebGLBuild -customArgs:'#{build_path_arg}#{development_build_arg}'") error('WebGL build unity error!') unless unity_success else error("Error: Unknown unity target platform '#{unity_platform}'!") end clean_working_copy(false) success("Unity build complete!\n") end def tag_icon(short_version) plist_path = @config_settings[PRODUCT_SETTINGS_PATH_KEY] info_plist = Plist.from_file(plist_path) version = info_plist['CFBundleShortVersionString'] style = short_version ? 'OneLine' : 'TwoLine' mask_path = File.join(TAGGER_UTILITY_DIRECTORY, "masks/#{style}Mask.png") icons_dir = File.join(Dir.pwd, @config_settings[ICONS_PATH_KEY]) app_product = File.join(@config_settings[BUILT_PRODUCTS_DIR_KEY], @config_settings[EXECUTABLE_NAME_KEY]) + APP_EXT system("#{TAGGER_UTILITY_PATH} --shortVersion='#{version}'\ --buildNumber='#{build_number}'\ --style='#{style}'\ --maskPath='#{mask_path}'\ --plist='#{plist_path}'\ --sourceIconsPath='#{icons_dir}'\ --destinationIconsPath='#{app_product}'") end def backup_info_plists puts @info_plist_backup_name = backup_info_plist(@config_settings[PRODUCT_SETTINGS_PATH_KEY], nil) @watchkit_app_info_plist_backup_name = backup_info_plist(@config_settings[WATCHKIT_APP_PRODUCT_SETTINGS_PATH_KEY], WATCHKIT_APP_PREFIX) @watchkit_extension_info_plist_backup_name = backup_info_plist(@config_settings[WATCHKIT_EXTENSION_PRODUCT_SETTINGS_PATH_KEY], WATCHKIT_EXTENSION_PREFIX) @widget_info_plist_backup_name = backup_info_plist(@config_settings[WIDGET_PRODUCT_SETTINGS_PATH_KEY], WIDGET_PREFIX) puts end def backup_info_plist(product_settings_path, description) return if product_settings_path.blank? puts "Backuping #{description}Info.plist ..." info_plist_backup_name = product_settings_path + '.backup' FileUtils.cp(product_settings_path, info_plist_backup_name) puts "#{description}Info.plist was backuped." info_plist_backup_name end def update_info_plists(config, app_version, requires_fullscreen) build_num = build_number bundle_id = @config_settings[BUNDLE_ID_KEY] update_info_plist(@config_settings[PRODUCT_SETTINGS_PATH_KEY], app_version, requires_fullscreen, build_num, revision, bundle_id, config, nil) update_info_plist(@config_settings[WATCHKIT_APP_PRODUCT_SETTINGS_PATH_KEY], app_version, nil, build_num, nil, nil, nil, WATCHKIT_APP_PREFIX) update_info_plist(@config_settings[WATCHKIT_EXTENSION_PRODUCT_SETTINGS_PATH_KEY], app_version, nil, build_num, nil, nil, nil, WATCHKIT_EXTENSION_PREFIX) update_info_plist(@config_settings[WIDGET_PRODUCT_SETTINGS_PATH_KEY], app_version, nil, build_num, nil, nil, nil, WIDGET_PREFIX) end def update_info_plist(product_settings_path, app_version, requires_fullscreen, build_num, revision, bundle_id, config, description) return if product_settings_path.blank? puts "Updating #{description}Info.plist ..." begin info_plist = Plist.from_file(product_settings_path) info_plist['UIRequiresFullScreen'] = true if requires_fullscreen info_plist['CFBundleShortVersionString'] = app_version if app_version info_plist['CFBundleVersion'] = build_num if build_num info_plist['RevisionNumber'] = revision if revision info_plist['CFBundleIdentifier'] = bundle_id if bundle_id info_plist['Configuration'] = config if config info_plist.save(product_settings_path, Plist::FORMAT_XML) rescue => e error("Update #{description}Info.plist error!", e) end puts "#{description}Info.plist was updated." puts end def restore_info_plists puts restore_info_plist(@config_settings[PRODUCT_SETTINGS_PATH_KEY], @info_plist_backup_name, nil) restore_info_plist(@config_settings[WATCHKIT_APP_PRODUCT_SETTINGS_PATH_KEY], @watchkit_app_info_plist_backup_name, WATCHKIT_APP_PREFIX) restore_info_plist(@config_settings[WATCHKIT_EXTENSION_PRODUCT_SETTINGS_PATH_KEY], @watchkit_extension_info_plist_backup_name, WATCHKIT_EXTENSION_PREFIX) restore_info_plist(@config_settings[WIDGET_PRODUCT_SETTINGS_PATH_KEY], @widget_info_plist_backup_name, WIDGET_PREFIX) puts end def restore_info_plist(product_settings_path, info_plist_backup_name, description) return if product_settings_path.blank? || info_plist_backup_name.blank? puts "Restoring #{description}Info.plist ..." File.delete(product_settings_path) File.rename(info_plist_backup_name, product_settings_path) puts "#{description}Info.plist was restored." end def deploy(deliver_deploy, deployment_names, skip_working_copy_check) deploy_host = @config_settings[DEPLOY_HOST_KEY].blank? ? ENV[DEPLOY_HOST_KEY] : @config_settings[DEPLOY_HOST_KEY] deploy_username = @config_settings[DEPLOY_USERNAME_KEY].blank? ? ENV[DEPLOY_USERNAME_KEY] : @config_settings[DEPLOY_USERNAME_KEY] deploy_password = @config_settings[DEPLOY_PASSWORD_KEY].blank? ? ENV[DEPLOY_PASSWORD_KEY] : @config_settings[DEPLOY_PASSWORD_KEY] deploy_itunesconnect_username = @config_settings[DEPLOY_ITUNESCONNECT_USERNAME_KEY].blank? ? ENV[DEPLOY_ITUNESCONNECT_USERNAME_KEY] : @config_settings[DEPLOY_ITUNESCONNECT_USERNAME_KEY] deploy_team_name = @config_settings[DEPLOY_TEAM_NAME_KEY].blank? ? ENV[DEPLOY_TEAM_NAME_KEY] : @config_settings[DEPLOY_TEAM_NAME_KEY] sdk_name = BuildEnvVarsLoader.load_last_build_vars[SDK_NAME_KEY] deploy_path = sdk_name.include?('macos') ? MACOS_PROJECTS_DEPLOY_PATH : ENV[DEPLOY_PATH_KEY] deploy_path = @config_settings[DEPLOY_PATH_KEY] if @config_settings[DEPLOY_PATH_KEY].present? Deployer.deploy(deploy_host, deploy_path, deploy_username, deploy_password, deploy_itunesconnect_username, deploy_team_name, deliver_deploy, deployment_names, skip_working_copy_check) success('Deploy complete!') end def clean FileUtils.rm_f(Dir.glob('*.pyc')) FileUtils.rm_f(AUTOGENERATED_LAST_REVISION) FileUtils.rm_f(AUTOGENERATED_LAST_BUILD_VARS) FileUtils.rm_f(AUTOGENERATED_COVERAGE) FileUtils.rm_f(AUTOGENERATED_DUPLICATION) FileUtils.rm_rf(AUTOGENERATED_TEST_RESULTS) Xcodebuild.clean_all_targets end def test code_coverage_config = File.join(@config_settings[CONFIGURATION_FILES_PATH_KEY], @config_settings[CODE_COVERAGE_CONFIGURATION_KEY]) Xcodebuild.test(@config_settings[SDK_KEY], 'Debug', @config_settings[WORKSPACE_KEY], @config_settings[PROJECT_KEY], @config_settings[SCHEME_KEY], code_coverage_config, TESTS_AND_COVERAGE_TIMEOUT, @config_settings[XCTEST_DESTINATION_DEVICE_KEY]) error('Run test error!') unless Xcodebuild.last_cmd_success? end def code_coverage code_coverage_ignore_file = File.join(@config_settings[CONFIGURATION_FILES_PATH_KEY], @config_settings[CODE_COVERAGE_IGNORE_FILE_KEY]) code_coverage_ignore_lines = File.readlines(code_coverage_ignore_file) Slather.coverage(@config_settings[WORKSPACE_KEY], @config_settings[SCHEME_KEY], "Debug", @config_settings[CODE_COVERAGE_OUTPUT_DIRECTORY_KEY], code_coverage_ignore_lines, @config_settings[PROJECT_KEY]) error('Code coverage error!') unless Slather.last_cmd_success? end def code_duplication_report duplication_success = system("#{CODE_DUPLICATION_REPORT_SCRIPT_PATH} '#{@config_settings[EXCLUDE_PATTERN_FOR_CODE_DUPLICATION_KEY]}' #{AUTOGENERATED_DUPLICATION}") error('Generate code duplication error!') unless duplication_success end def clean_working_copy(all) SCM.clean_working_copy(all) error('Clean working copy error!') unless $?.success? end def error(msg, e = nil) puts if e.present? puts e.inspect puts e.backtrace end abort "#{msg} #{e.to_s.strip}".red_on_yellow.bold end def success(msg) puts puts msg.green.bold end