diff --git a/lib/sim_launcher.rb b/lib/sim_launcher.rb index faa123c..75a809a 100644 --- a/lib/sim_launcher.rb +++ b/lib/sim_launcher.rb @@ -3,18 +3,24 @@ require 'sim_launcher/simulator' require 'sim_launcher/sdk_detector' +# Simulator Launcher module SimLauncher + # Default derived data path DERIVED_DATA = File.expand_path("~/Library/Developer/Xcode/DerivedData") + # Default derived data info plist DEFAULT_DERIVED_DATA_INFO = File.expand_path("#{DERIVED_DATA}/*/info.plist") - def self.check_app_path( app_path ) - unless File.exists?( app_path ) + # Check app path + # @param [String] app_path app path to check + # @return [String] Error message or _nil_ on success + def self.check_app_path(app_path) + unless File.exists?(app_path) return "The specified app path doesn't seem to exist: #{app_path}" end unless File.directory? app_path - file_appears_to_be_a_binary = !!( `file "#{app_path}"` =~ /Mach-O executable/ ) + file_appears_to_be_a_binary = !!(`file "#{app_path}"` =~ /Mach-O executable/) if file_appears_to_be_a_binary return <<-EOS The specified app path is a binary executable, rather than a directory. You need to provide the path to the app *bundle*, not the app executable itself. @@ -29,6 +35,9 @@ def self.check_app_path( app_path ) nil end + # Get derived data directory for the project + # @param [String] project_name Name of the project + # @return [String] Derived data directory path def self.derived_data_dir_for_project_name(project_name) build_dirs = Dir.glob("#{DERIVED_DATA}/*").find_all do |xc_proj| @@ -65,6 +74,9 @@ def self.derived_data_dir_for_project_name(project_name) end end + # Get app bundle for path or raise an exception + # @param [String] path Path + # @return [String] App bundle path def self.app_bundle_or_raise(path) bundle_path = nil diff --git a/lib/sim_launcher/client.rb b/lib/sim_launcher/client.rb index d2c4833..404e4c1 100644 --- a/lib/sim_launcher/client.rb +++ b/lib/sim_launcher/client.rb @@ -2,30 +2,49 @@ require 'cgi' require 'net/http' +# Simulator Launcher module SimLauncher + # Client class Client + # Default server URI DEFAULT_SERVER_URI = "http://localhost:8881" - def initialize( app_path, sdk, family ) - @app_path = File.expand_path( app_path ) + # Initialized + # @param [String] app_path App path + # @param [String] sdk SDK + # @param [String] family Device family + def initialize(app_path, sdk, family) + @app_path = File.expand_path(app_path) @sdk = sdk @family = family self.server_uri = DEFAULT_SERVER_URI end - def self.for_ipad_app( app_path, sdk = nil ) - self.new( app_path, sdk, 'ipad' ) + # Client for iPad app + # @param [String] app_path App path + # @param [String] sdk SDK + # @return [Client] Client object for iPad app + def self.for_ipad_app(app_path, sdk = nil) + self.new(app_path, sdk, 'ipad') end - def self.for_iphone_app( app_path, sdk = nil ) - self.new( app_path, sdk, 'iphone' ) + # Client for iPhone app + # @param [String] app_path App path + # @param [String] sdk SDK + # @return [Client] Client object for iPhone app + def self.for_iphone_app(app_path, sdk = nil) + self.new(app_path, sdk, 'iphone') end + # Set server URI + # @param [URI] uri Server URI def server_uri=(uri) @server_uri = URI.parse( uri.to_s ) end - def launch(restart=false) + # Launch + # @param [Boolean] restart Restart if this flag is _true_ + def launch(restart = false) begin full_request_uri = launch_uri(restart) puts "requesting #{full_request_uri}" if $DEBUG @@ -38,14 +57,15 @@ def launch(restart=false) end end + # Relaunch def relaunch launch(true) end - # check that there appears to be a server ready for us to send commands to + # Check that there appears to be a server ready for us to send commands to def ping # our good-enough solution is just request the list of available iOS sdks and - # check that we get a 200 response + # check that we get a 200 response begin uri = list_sdks_uri Net::HTTP.start( uri.host, uri.port) do |http| @@ -59,6 +79,9 @@ def ping private + # Lauch with URI + # @param [Boolean] requesting_restart Restart request flag + # @return [URI] Full request URI def launch_uri(requesting_restart) full_request_uri = @server_uri.dup full_request_uri.path = "/launch_#{@family}_app" @@ -68,6 +91,8 @@ def launch_uri(requesting_restart) full_request_uri end + # List SDKs URI + # @param [URI] SDKs URI def list_sdks_uri full_request_uri = @server_uri.dup full_request_uri.path = "/showsdks" diff --git a/lib/sim_launcher/direct_client.rb b/lib/sim_launcher/direct_client.rb index ba1397f..645f0a9 100644 --- a/lib/sim_launcher/direct_client.rb +++ b/lib/sim_launcher/direct_client.rb @@ -1,35 +1,52 @@ +# Simulator Launcher module SimLauncher + # Direct Client class DirectClient - def initialize( app_path, sdk, family ) - @app_path = File.expand_path( app_path ) + + # Initialize + # @param [String] app_path App path + # @param [String] sdk SDK + # @param [String] family Device family + def initialize(app_path, sdk, family) + @app_path = File.expand_path(app_path) @sdk = sdk @family = family end - def self.for_ipad_app( app_path, sdk = nil ) - self.new( app_path, sdk, 'ipad' ) + # Direct client for iPad app + # @param [String] app_path App path + # @param [String] sdk SDK + # @return [DirectClient] Direct client object for iPad app + def self.for_ipad_app(app_path, sdk = nil) + self.new(app_path, sdk, 'ipad') end - def self.for_iphone_app( app_path, sdk = nil ) - self.new( app_path, sdk, 'iphone' ) + # Direct client for iPhone app + # @param [String] app_path App path + # @param [String] sdk SDK + # @return [DirectClient] Direct client object for iPhone app + def self.for_iphone_app(app_path, sdk = nil) + self.new(app_path, sdk, 'iphone') end + # Launch def launch - SimLauncher::Simulator.new.launch_ios_app( @app_path, @sdk, @family ) + SimLauncher::Simulator.new.launch_ios_app(@app_path, @sdk, @family) end + # Rotate left def rotate_left simulator = SimLauncher::Simulator.new simulator.rotate_left end - + + # Rotate right def rotate_right simulator = SimLauncher::Simulator.new simulator.rotate_right end - - + # Relaunch def relaunch simulator = SimLauncher::Simulator.new simulator.quit_simulator diff --git a/lib/sim_launcher/sdk_detector.rb b/lib/sim_launcher/sdk_detector.rb index 684c61e..c0c0c00 100644 --- a/lib/sim_launcher/sdk_detector.rb +++ b/lib/sim_launcher/sdk_detector.rb @@ -1,16 +1,24 @@ +# Simulator Launcher module SimLauncher + # SDK Detector class SdkDetector + # Initialize + # @param [Simulator] simulator Simulator to initialize with def initialize(simulator = Simulator.new) @simulator = simulator end + # Get available SDK versions + # @return [String] SDK versions def available_sdk_versions @simulator.showsdks.split("\n").map { |sdk_line| sdk_line[/\(([\d.]+)\)$/,1] # grab any "(x.x)" part at the end of the line }.compact end + # Get latest SDK version + # @return [String] Latest SDK version def latest_sdk_version available_sdk_versions.sort.last end diff --git a/lib/sim_launcher/simulator.rb b/lib/sim_launcher/simulator.rb index f157861..896513e 100644 --- a/lib/sim_launcher/simulator.rb +++ b/lib/sim_launcher/simulator.rb @@ -1,108 +1,205 @@ +# Simulator Launcher module SimLauncher -class Simulator + # Simulator + class Simulator - def initialize( iphonesim_path_external = nil ) - @iphonesim_path = iphonesim_path_external || iphonesim_path(xcode_version) - end + # Initialize + # @param [Stirng] iphonesim_path_external External iphone simulator path + def initialize(iphonesim_path_external = nil) + @iphonesim_path = iphonesim_path_external || iphonesim_path(xcode_version) + end - def showsdks - run_synchronous_command( 'showsdks' ) - end + # Display available SKDs + def showsdks + run_synchronous_command('showsdks') + end - def start_simulator(sdk_version=nil, device_family="iphone") - sdk_version ||= SdkDetector.new(self).latest_sdk_version - run_synchronous_command( :start, '--sdk', sdk_version, '--family', device_family, '--exit' ) - end + # Start simulator + # @param [String] sdk_version SKD version (e.g. "6.1", "7.0") + # @param [String] device_family Device family ("iphone", "ipad") + # @param [Hash] options Addtional options to pass to ios-sim executable + # @option options [nil] :retina Retina device, use _nil_ value (:retina => nil) + # @option options [nil] :tall Tall retina device (4-inch), use _nil_ value (:tall => nil) + # @option options [String] :env Environment variables plist file + # @option options [String] :setenv Evnironment varible key-valur pair (:setenv => "DEBUG=1") + # @example + # s = SimLauncher::Simulator.new + # # iPad retina, SDK 6.1, load environment variables from env.plist + # options = { :retina => nil, :env => "env.plist" } + # s.start_simulator("6.1", "ipad", options) + # # iPhone tall retina (4-inch), SDK 7.0, set environment variables 'dev' and 'reset' to true + # options = { :retina => nil, :tall => nil, :setenv => "dev=true", :setenv => "reset=true" } + # s.start_simulator("7.0", "iphone", options) + # # Start with no options (backwards compatibility) + # s.start_simulator("7.0", "iphone") + def start_simulator(sdk_version = nil, device_family = "iphone", options = {}) + sdk_version ||= SdkDetector.new(self).latest_sdk_version + options = options.map { |k, v| ["--#{k.to_s}"] + (v.nil? ? [] : ["#{v}"]) }.flatten + run_synchronous_command( :start, '--sdk', sdk_version, '--family', device_family, '--exit', *options) + end + # Rotate simulator left + def rotate_left + script_dir = File.join(File.dirname(__FILE__), "..", "..", "scripts") + rotate_script = File.expand_path("#{script_dir}/rotate_simulator_left.applescript") + system("osascript #{rotate_script}") + end - def rotate_left - script_dir = File.join(File.dirname(__FILE__),"..","..","scripts") - rotate_script = File.expand_path("#{script_dir}/rotate_simulator_left.applescript") - system("osascript #{rotate_script}") - end + # Rotate simulator right + def rotate_right + script_dir = File.join(File.dirname(__FILE__), "..", "..", "scripts") + rotate_script = File.expand_path("#{script_dir}/rotate_simulator_right.applescript") + system("osascript #{rotate_script}") + end - def rotate_right - script_dir = File.join(File.dirname(__FILE__),"..","..","scripts") - rotate_script = File.expand_path("#{script_dir}/rotate_simulator_right.applescript") - system("osascript #{rotate_script}") - end + # Reset simulator + # @param [Array] sdks Array of SDKs to reset simulator for + def reset(sdks = nil) + script_dir = File.join(File.dirname(__FILE__), "..", "..", "scripts") + reset_script = File.expand_path("#{script_dir}/reset_simulator.applescript") + + sdks ||= SimLauncher::SdkDetector.new(self).available_sdk_versions - def reset(sdks=nil) - script_dir = File.join(File.dirname(__FILE__),"..","..","scripts") - reset_script = File.expand_path("#{script_dir}/reset_simulator.applescript") + sdks.each do |sdk_path_str| + start_simulator(sdk_path_str, "iphone") + system("osascript #{reset_script}") + start_simulator(sdk_path_str, "ipad") + system("osascript #{reset_script}") + end - sdks ||= SimLauncher::SdkDetector.new(self).available_sdk_versions + quit_simulator - sdks.each do |sdk_path_str| - start_simulator(sdk_path_str,"iphone") - system("osascript #{reset_script}") - start_simulator(sdk_path_str,"ipad") - system("osascript #{reset_script}") end - quit_simulator + # Launch iOS app with options and arguments + # @param [String] app_path Application bundle path + # @param [String] sdk_version SDK version (e.g. "6.1", "7.0") + # @param [String] device_family Device family ("iphone", "ipad") + # @param [Hash] options Addtional options to pass to ios-sim executable (@see start_simulator) + # @param [Array] app_args Application arguments + def launch_ios_app_with_options(app_path, sdk_version, device_family, options = {}, app_args = nil) + if problem = SimLauncher.check_app_path(app_path) + bangs = '!'*80 + raise "\n#{bangs}\nENCOUNTERED A PROBLEM WITH THE SPECIFIED APP PATH:\n\n#{problem}\n#{bangs}" + end + sdk_version ||= SdkDetector.new(self).latest_sdk_version + options = options.map { |k, v| ["--#{k.to_s}"] + (v.nil? ? [] : ["#{v}"]) }.flatten + args = ["--args"] + app_args.flatten if app_args + run_synchronous_command(:launch, app_path, '--sdk', sdk_version, '--family', device_family, '--exit', *options, *args) + end - end + # Launch iOS app with arguments + # @param [String] app_path Application bundle path + # @param [String] sdk_version SDK version (e.g. "6.1", "7.0") + # @param [String] device_family Device family ("iphone", "ipad") + # @param [Array] app_args Application arguments + def launch_ios_app(app_path, sdk_version, device_family, app_args = nil) + launch_ios_app_with_options(app_path, sdk_version, device_family, {}, app_args) + end - def launch_ios_app(app_path, sdk_version, device_family, app_args = nil) - if problem = SimLauncher.check_app_path( app_path ) - bangs = '!'*80 - raise "\n#{bangs}\nENCOUNTERED A PROBLEM WITH THE SPECIFIED APP PATH:\n\n#{problem}\n#{bangs}" + # Launch iPad app using app bundle + # @param [String] app_path Application bundle path + # @param [String] sdk SDK version (e.g. "6.1", "7.0") + # @param [Hash] options Addtional options to pass to ios-sim executable (@see start_simulator) + # @param [Array] app_args Application arguments + def launch_ipad_app(app_path, sdk, options = {}, app_args = nil) + launch_ios_app_with_options(app_path, sdk, 'ipad', options, app_args) end - sdk_version ||= SdkDetector.new(self).latest_sdk_version - args = ["--args"] + app_args.flatten if app_args - run_synchronous_command( :launch, app_path, '--sdk', sdk_version, '--family', device_family, '--exit', *args ) - end - def launch_ipad_app( app_path, sdk ) - launch_ios_app( app_path, sdk, 'ipad' ) - end + # Launch iPad app using app name + # @param [String] app_name Application name + # @param [String] sdk SDK version (e.g. "6.1", "7.0") + # @param [Hash] options Addtional options to pass to ios-sim executable (@see start_simulator) + # @param [Array] app_args Application arguments + def launch_ipad_app_with_name(app_name, sdk, options = {}, app_args = nil) + app_path = SimLauncher.app_bundle_or_raise(app_name) + launch_ios_app_with_options(app_path, sdk, 'iphone', options, app_args) + end - def launch_ipad_app_with_name( app_name, sdk ) - app_path = SimLauncher.app_bundle_or_raise(app_name) - launch_ios_app( app_path, sdk, 'iphone' ) - end + # Launch iPhone app using app bundle + # @param [String] app_path Application bundle path + # @param [String] sdk SDK version (e.g. "6.1", "7.0") + # @param [Hash] options Addtional options to pass to ios-sim executable (@see start_simulator) + # @param [Array] app_args Application arguments + def launch_iphone_app(app_path, sdk, options = {}, app_args = nil) + launch_ios_app_with_options(app_path, sdk, 'iphone', options, app_args) + end - def launch_iphone_app( app_path, sdk ) - launch_ios_app( app_path, sdk, 'iphone' ) - end + # Launch iPhone app using app name + # @param [String] app_name Application name + # @param [String] sdk SDK version (e.g. "6.1", "7.0") + # @param [Hash] options Addtional options to pass to ios-sim executable (@see start_simulator) + # @param [Array] app_args Application arguments + def launch_iphone_app_with_name(app_name, sdk, options = {}, app_args = nil) + app_path = SimLauncher.app_bundle_or_raise(app_name) + launch_ios_app_with_options(app_path, sdk, 'iphone', options, app_args) + end - def launch_iphone_app_with_name( app_name, sdk ) - app_path = SimLauncher.app_bundle_or_raise(app_name) - launch_ios_app( app_path, sdk, 'iphone' ) - end + # Quit simulator + def quit_simulator + `echo 'application "iPhone Simulator" quit' | osascript` + end - def quit_simulator - `echo 'application "iPhone Simulator" quit' | osascript` - end + # Run synchronous shell command + # @param [Array] args Command line arguments + def run_synchronous_command(*args) + args.compact! + cmd = cmd_line_with_args(args) + puts "executing #{cmd}" if $DEBUG + `#{cmd}` + end - def run_synchronous_command( *args ) - args.compact! - cmd = cmd_line_with_args( args ) - puts "executing #{cmd}" if $DEBUG - `#{cmd}` - end + # Return shell command string with given arguments + # @param [Array] args Command line arguments + # @return [String] Shell command string + def cmd_line_with_args(args) + cmd_sections = [@iphonesim_path] + args.map { |x| "\"#{x.to_s}\"" } << '2>&1' + cmd_sections.join(' ') + end - def cmd_line_with_args( args ) - cmd_sections = [@iphonesim_path] + args.map{ |x| "\"#{x.to_s}\"" } << '2>&1' - cmd_sections.join(' ') - end - - def xcode_version - version = `xcodebuild -version` - raise "xcodebuild not found" unless $? == 0 - version[/([0-9]\.[0-9])/, 1].to_f - end - - def iphonesim_path(version) - installed = `which ios-sim` - if installed =~ /(.*ios-sim)/ - puts "Using installed ios-sim at #{$1}" - return $1 + # Get current Xcode version + # @return [String] Xcode version string + def xcode_version + version = `xcodebuild -version` + raise "xcodebuild not found" unless $? == 0 + version[/([0-9]\.[0-9])/, 1].to_f + end + + # Get ios-sim version + # @return [String] ios-sim version string + def iphonesim_version + `ios-sim --version` + end + + # Get ios-sim executable path + # @param [String] version Xcode version + # @return [String] ios-sim executable path + def iphonesim_path(version) + installed = `which ios-sim` + if installed =~ /(.*ios-sim)/ + puts "Using installed ios-sim at #{$1}" + return $1 + end + + File.join(File.dirname(__FILE__), '..', '..', 'native', 'ios-sim') end - File.join( File.dirname(__FILE__), '..', '..', 'native', 'ios-sim' ) end end + +# Test +def test(mode=1) + $DEBUG = 1 + s = SimLauncher::Simulator.new + puts s.iphonesim_version + + opts0 = { :retina => nil } + opts1 = { :retina => nil, :env => "env.plist" } + opts2 = { :retina => nil, :setenv => "dev=true" } + opts3 = {} + + opts_all = [opts0, opts1, opts2, opts3] + s.start_simulator("6.1", "ipad", opts_all[mode]) + s.launch_ios_app_with_options("build/TheAustralian.app", "6.1", "ipad", opts_all[mode]) end diff --git a/lib/sim_launcher/version.rb b/lib/sim_launcher/version.rb index 85333ee..6da835c 100644 --- a/lib/sim_launcher/version.rb +++ b/lib/sim_launcher/version.rb @@ -1,3 +1,5 @@ +# Simulator Launcher version module SimLauncher + # Simulator Launcher version VERSION = "0.4.9" end diff --git a/scripts/reset_simulator.applescript b/scripts/reset_simulator.applescript index 11b0632..e67a009 100644 --- a/scripts/reset_simulator.applescript +++ b/scripts/reset_simulator.applescript @@ -1,7 +1,7 @@ tell application "System Events" - + tell process "iPhone Simulator" - + tell menu bar 1 tell menu bar item "iOs Simulator" tell menu "iOs Simulator" @@ -15,4 +15,4 @@ tell application "System Events" click button "Reset" end tell end tell -end tell \ No newline at end of file +end tell