Module: XCSim

Defined in:
lib/xcsim/rbConstants.rb,
lib/xcsim/rbList.rb,
lib/xcsim/rbOSID.rb,
lib/xcsim/rbReports.rb,
lib/xcsim/rbDeviceID.rb,
lib/xcsim/rbDeviceSet.rb,
lib/xcsim/rbOSDevices.rb,
lib/xcsim/rbAppBundles.rb,
lib/xcsim/rbBundleInfo.rb

Overview

XCSim module contains utility for parsing iOS Simulator metadata plist files and providing access to the bundle and data directories of the applications installed on iOS Simulator.

Defined Under Namespace

Classes: BundleInfo, BundleNotFoundError, DeviceID, DeviceListItem, DeviceNotFoundError, GetBundle, GetDeviceList, NonUniqueBundleIDError, OSDevices, OSID, OSNotFoundError

Constant Summary collapse

SIMULATORS_ROOT =

Absolute path of the directory, which stores all of thr iOS Simulator data

File.expand_path("~/Library/Developer/CoreSimulator/Devices")
DEVICE_APP_BUNDLES_RELATIVE_PATH =

Path for the application bundles directory relative to a concrete iOS Simulator device dir

"data/Containers/Bundle/Application"
DEVICE_APP_DATA_RELATIVE_PATH =

Path for the application data directory relative to a concrete iOS Simulator device dir

"data/Containers/Data/Application"
DEVICE_SET_PLIST =

Name of the device_set.plist file

"device_set.plist"
BUNDLE_METADATA_PLIST =

Name of the plist file containing a certain application’s metadata

".com.apple.mobile_container_manager.metadata.plist"
METADATA_ID =

A key in application metadata plist file, which corresponds to the bundle ID of the application

"MCMMetadataIdentifier"
@@deviceSet =
parseDeviceSet("#{XCSim::SIMULATORS_ROOT}/#{XCSim::DEVICE_SET_PLIST}")

Class Method Summary collapse

Class Method Details

.defaultDeviceNameObject

Returns the default device name for use in #xcsim bundle mode when no :device option is provided.



61
62
63
# File 'lib/xcsim/rbDeviceSet.rb', line 61

def self.defaultDeviceName
  "iPhone 5s"
end

.defaultOSNameObject

Returns default OS name for use in #xcsim bundle mode when no :os option is provided. Selects the OS with the highest version number as the default.



55
56
57
# File 'lib/xcsim/rbDeviceSet.rb', line 55

def self.defaultOSName
  @@deviceSet.keys.max.to_s
end

.deviceSetObject

Returns default device set of OSDevices class (as parsed by #parseDeviceSet with default iOS Simulators path)



49
50
51
# File 'lib/xcsim/rbDeviceSet.rb', line 49

def self.deviceSet
  @@deviceSet
end

.findBundleDataPath(deviceID, bundleID) ⇒ Object

call-seq:

findBundleDataPath(deviceID, bundleID) => String
deviceID

A DeviceID object representing the device simulator on which the application is installed

bundleID

Bundle ID of the application to search for

Finds an absolute path for application data directory of an application with a given bundle ID installed on a given device.

Searches for directories matching bundleID inside the application data directory of the device. Raises a NonUniqueBundleIDError if multiple matching directories are found (bundle IDs are expected to be unique). Returns the found directory or nil otherwise.



45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/xcsim/rbAppBundles.rb', line 45

def self.findBundleDataPath(deviceID, bundleID)
  path = deviceID.appDataPath

  return nil unless File.directory? path

  subdirs = Dir.entries(path).select do |entry|
    File.directory? File.join(path, entry) and !(entry =='.' || entry == '..')
  end

  metadataPairs = subdirs.map do |dir|
    metadataPath = "#{path}/#{dir}/#{BUNDLE_METADATA_PLIST}"

    if File.exists? metadataPath
      plist = CFPropertyList::List.new(:file => metadataPath)
      plist = CFPropertyList.native_types(plist.value)

      { :plist => plist, :dir => "#{path}/#{dir}" }
    else
      nil
    end
  end
  .compact

  result = metadataPairs.select{ |pair| pair[:plist][METADATA_ID] == bundleID }

  if result.count > 1
    raise NonUniqueBundleIDError.new(deviceID, bundleID, result.map{|pair| pair[:dir]})
  elsif result.empty?
    return nil
  else
    result.first[:dir]
  end
end

.parseDeviceSet(path) ⇒ Object

call-seq:

parseDeviceSet(absolutePath) => Hash

Parses a device_set.plist file located at the given path

Returns a Hash of OSID => OSDevices for iOS Simulator OSes, which have at least one device simulator installed.



18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# File 'lib/xcsim/rbDeviceSet.rb', line 18

def self.parseDeviceSet(path)
  plist = CFPropertyList::List.new(:file => path)
  plist = CFPropertyList.native_types(plist.value)
  defaultDevices = plist["DefaultDevices"]

  osIDs = defaultDevices
    .keys
    .map{|s| OSID.fromPrefixedString(s) }
    .compact

  oses = osIDs.map do |id|
    osDevices = defaultDevices[id.key]
    devices = osDevices
      .keys
      .map{ |s| DeviceID.fromPrefixedString(s, osDevices[s])}
      .compact
      .select{ |device| File.directory? device.appBundlesPath }
      .select{ |device| File.directory? device.appDataPath }

      (devices.count > 0) ? OSDevices.new(id, devices) : nil
  end
  .compact

  osHash = {}
  oses.each{ |os| osHash[os.id] = os }

  osHash
end

.parseInstalledBundles(deviceID) ⇒ Object

call-seq:

parseInstalledBundles(deviceID) => Hash
deviceID

A DeviceID object to search installed app bundles for

Searches the given iOS Simulator device for installed application bundles (excluding the system applications such as Safari) and returns a Hash of bundle ID string => BundleInfo containing BundleInfo instances corresponding the found bundles.

May raise a NonUniqueBundleIDError if multiple data directories are found for one of the bundles.



92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
# File 'lib/xcsim/rbAppBundles.rb', line 92

def self.parseInstalledBundles(deviceID)
  path = deviceID.appBundlesPath

  return {} unless File.directory? path

  subdirs = Dir.entries(path).select do |entry|
    File.directory? File.join(path, entry) and !(entry =='.' || entry == '..')
  end

  bundlePlists = subdirs.map do |dir|
    plistPath = "#{path}/#{dir}/#{BUNDLE_METADATA_PLIST}"

    if File.exists? plistPath
      plist = CFPropertyList::List.new(:file => plistPath)
      plist = CFPropertyList.native_types(plist.value)

      { :plist => plist, :dir => "#{path}/#{dir}" }
    else
      nil
    end
  end
  .compact

  bundleInfos = bundlePlists.map do |pair|
    bundleID = pair[:plist][METADATA_ID]
    bundlePath = pair[:dir]
    dataPath = findBundleDataPath(deviceID, bundleID)
    BundleInfo.new(bundleID, bundlePath, dataPath)
  end

  bundleInfosHash = {}
  bundleInfos.each{ |info| bundleInfosHash[info.bundleID] = info }

  bundleInfosHash
end

.reportFromDeviceList(list) ⇒ Object

Prepares a more human-readable report from an array of DeviceListItem objects.

Depending on the unique OS versions and device models referenced by the list items, may return one of the following results:

List of application bundles

When list contains a single item

List of device names

When list contains multiple devices from a single OS version

List of OS version strings

When list contains a multiple devices from multiple OS version



13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# File 'lib/xcsim/rbReports.rb', line 13

def self.reportFromDeviceList(list)
  uniqueOSes = list.map{ |item| item.os.id }.uniq
  uniqueDevices = list.map { |item| item.device.name }.uniq

  countByOS = {}
  list.each do |item|
    count = countByOS[item.os.id] || 0
    countByOS[item.os.id] = count+1
  end

  if uniqueOSes.empty? || uniqueDevices.empty?
    raise ArgumentError

  elsif uniqueOSes.count == 1 && uniqueDevices.count == 1
    list.first.bundles

  elsif uniqueOSes.count == 1
    list.map{ |item| item.device.name }

  elsif false == (countByOS.values.include? 1)
    countByOS.map { |id, count| "#{id} (#{count} devices)"}

  else
    list
  end
end