class RunLoop::App

A class for interacting with .app bundles.

Attributes

path[R]

@!attribute [r] path @return [String] The path to the app bundle .app

Public Class Methods

cached_app_on_simulator?(app_bundle_path) click to toggle source

@!visibility private

Starting in Xcode 10 betas, this can happen if there was an incomplete install or uninstall.

# File lib/run_loop/app.rb, line 93
def self.cached_app_on_simulator?(app_bundle_path)
  return false if Dir[File.join(app_bundle_path, "**/*")].length != 2
  return false if !app_bundle_path[RunLoop::Regex::CORE_SIMULATOR_UDID_REGEX]
  [File.join(app_bundle_path, "Info.plist"),
   File.join(app_bundle_path, "Icon.png")].all? do |file|
    File.exist?(file)
  end
end
new(app_bundle_path) click to toggle source

Creates a new App instance.

@note The ‘app_bundle_path` is expanded during initialization.

@param [String] app_bundle_path A path to a .app @return [RunLoop::App] A instance of App with a path.

# File lib/run_loop/app.rb, line 15
    def initialize(app_bundle_path)
      @path = File.expand_path(app_bundle_path)

      if !App.valid?(app_bundle_path)
        if App.cached_app_on_simulator?(app_bundle_path)
          raise RuntimeError, %Q{
App is "cached" on the simulator.

#{app_bundle_path}

This can happen if there was an incomplete install or uninstall.

Try manually deleting the application data container and relaunching the simulator.

$ rm -r #{File.dirname(app_bundle_path)}
$ run-loop simctl manage-processes
}
        else
          raise ArgumentError,
%Q{App does not exist at path or is not an app bundle.

#{app_bundle_path}

Bundle must:

1. be a directory that exists,
2. have a .app extension,
3. contain an Info.plist,
4. and the app binary (CFBundleExecutable) must exist
}
        end
      end
    end
valid?(app_bundle_path) click to toggle source

@!visibility private

# File lib/run_loop/app.rb, line 78
def self.valid?(app_bundle_path)
  return false if app_bundle_path.nil?

  return false if !File.directory?(app_bundle_path)
  return false if !File.extname(app_bundle_path) == ".app"

  return false if !self.info_plist_exist?(app_bundle_path)
  return false if !self.executable_file_exist?(app_bundle_path)
  true
end

Private Class Methods

executable_file_exist?(app_bundle_path) click to toggle source

@!visibility private

# File lib/run_loop/app.rb, line 278
def self.executable_file_exist?(app_bundle_path)
  return false if !self.info_plist_exist?(app_bundle_path)
  info_plist = File.join(app_bundle_path, "Info.plist")
  pbuddy = RunLoop::PlistBuddy.new
  name = pbuddy.plist_read("CFBundleExecutable", info_plist)
  if name
    File.exist?(File.join(app_bundle_path, name))
  else
    false
  end
end
info_plist_exist?(app_bundle_path) click to toggle source

@!visibility private

# File lib/run_loop/app.rb, line 272
def self.info_plist_exist?(app_bundle_path)
  info_plist = File.join(app_bundle_path, "Info.plist")
  File.exist?(info_plist)
end

Public Instance Methods

arches() click to toggle source

Returns the arches for the binary.

# File lib/run_loop/app.rb, line 133
def arches
  @arches ||= lipo.info
end
build_version() click to toggle source

Returns the CFBundleVersion of the app as Version instance.

Apple docs:

CFBundleVersion specifies the build version number of the bundle, which identifies an iteration (released or unreleased) of the bundle. The build version number should be a string comprised of three non-negative, period-separated integers with the first integer being greater than zero. The string should only contain numeric (0-9) and period (.) characters. Leading zeros are truncated from each integer and will be ignored (that is, 1.02.3 is equivalent to 1.2.3).

@return [RunLoop::Version, nil] Returns a Version instance if the

CFBundleVersion string is well formed and nil if not.
# File lib/run_loop/app.rb, line 233
def build_version
  string = plist_buddy.plist_read("CFBundleVersion", info_plist_path)
  begin
    version = RunLoop::Version.new(string)
  rescue
    if string && string != ""
      RunLoop.log_debug("CFBundleVersion: '#{string}' is not a well formed version string")
    else
      RunLoop.log_debug("CFBundleVersion is not defined in Info.plist")
    end
    version = nil
  end
  version
end
Also aliased as: bundle_version
bundle_identifier() click to toggle source

Inspects the app’s Info.plist for the bundle identifier. @return [String] The value of CFBundleIdentifier. @raise [RuntimeError] If the plist cannot be read or the

CFBundleIdentifier is empty or does not exist.
# File lib/run_loop/app.rb, line 112
def bundle_identifier
  identifier = plist_buddy.plist_read("CFBundleIdentifier", info_plist_path)
  unless identifier
    raise "Expected key 'CFBundleIdentifier' in '#{info_plist_path}'"
  end
  identifier
end
bundle_version()
Alias for: build_version
calabash_server_id() click to toggle source

@!visibility private Return the fingerprint of the linked server

# File lib/run_loop/app.rb, line 161
def calabash_server_id
  name = plist_buddy.plist_read("CFBundleExecutable", info_plist_path)
  app_executable = File.join(self.path, name)
  strings(app_executable).server_id
end
calabash_server_version() click to toggle source

Inspects the app’s file for the server version

# File lib/run_loop/app.rb, line 150
def calabash_server_version
  version = nil
  executables.each do |executable|
    version = strings(executable).server_version
    break if version
  end
  version
end
codesign_info() click to toggle source

@!visibility private

# File lib/run_loop/app.rb, line 168
def codesign_info
  RunLoop::Codesign.info(path)
end
developer_signed?() click to toggle source

@!visibility private

# File lib/run_loop/app.rb, line 173
def developer_signed?
  RunLoop::Codesign.developer?(path)
end
distribution_signed?() click to toggle source

@!visibility private

# File lib/run_loop/app.rb, line 178
def distribution_signed?
  RunLoop::Codesign.distribution?(path)
end
executable_name() click to toggle source

Inspects the app’s Info.plist for the executable name. @return [String] The value of CFBundleExecutable. @raise [RuntimeError] If the plist cannot be read or the

CFBundleExecutable is empty or does not exist.
# File lib/run_loop/app.rb, line 124
def executable_name
  name = plist_buddy.plist_read("CFBundleExecutable", info_plist_path)
  unless name
    raise "Expected key 'CFBundleExecutable' in '#{info_plist_path}'"
  end
  name
end
executables() click to toggle source

@!visibility private Collects the paths to executables in the bundle.

# File lib/run_loop/app.rb, line 253
def executables
  executables = []
  Dir.glob("#{path}/**/*") do |file|
    next if skip_executable_check?(file)
    if otool.executable?(file)
      executables << file
    end
  end
  executables
end
info_plist_path() click to toggle source

Returns the Info.plist path. @raise [RuntimeError] If there is no Info.plist.

# File lib/run_loop/app.rb, line 104
def info_plist_path
  @info_plist_path ||= File.join(path, 'Info.plist')
end
inspect() click to toggle source

@!visibility private

# File lib/run_loop/app.rb, line 68
def inspect
  to_s
end
marketing_version() click to toggle source

Returns the CFBundleShortVersionString of the app as Version instance.

Apple docs:

CFBundleShortVersionString specifies the release version number of the bundle, which identifies a released iteration of the app. The release version number is a string comprised of three period-separated integers.

The first integer represents major revisions to the app, such as revisions that implement new features or major changes. The second integer denotes revisions that implement less prominent features. The third integer represents maintenance releases.

The value for this key differs from the value for CFBundleVersion, which identifies an iteration (released or unreleased) of the app. This key can be localized by including it in your InfoPlist.strings files.

@return [RunLoop::Version, nil] Returns a Version instance if the

CFBundleShortVersion string is well formed and nil if not.
# File lib/run_loop/app.rb, line 201
def marketing_version
  string = plist_buddy.plist_read("CFBundleShortVersionString", info_plist_path)
  begin
    version = RunLoop::Version.new(string)
  rescue
    if string && string != ""
      RunLoop.log_debug("CFBundleShortVersionString: '#{string}' is not a well formed version string")
    else
      RunLoop.log_debug("CFBundleShortVersionString is not defined in Info.plist")
    end
    version = nil
  end
  version
end
Also aliased as: short_bundle_version
physical_device?() click to toggle source

True if the app has been built for physical devices

# File lib/run_loop/app.rb, line 143
def physical_device?
  arches.any? do |arch|
    arch[/arm/, 0]
  end
end
sha1() click to toggle source

Returns the sha1 of the application.

# File lib/run_loop/app.rb, line 265
def sha1
  RunLoop::Directory.directory_digest(path)
end
short_bundle_version()
Alias for: marketing_version
simulator?() click to toggle source

True if the app has been built for the simulator

# File lib/run_loop/app.rb, line 138
def simulator?
  arches.include?("i386") || arches.include?("x86_64")
end
to_s() click to toggle source

@!visibility private

# File lib/run_loop/app.rb, line 50
def to_s
  cf_bundle_version = bundle_version
  cf_bundle_short_version = short_bundle_version

  if cf_bundle_version && cf_bundle_short_version
    version = "#{cf_bundle_version.to_s} / #{cf_bundle_short_version}"
  elsif cf_bundle_version
    version = cf_bundle_version.to_s
  elsif cf_bundle_short_version
    version = cf_bundle_short_version
  else
    version = ""
  end

  "#<APP #{bundle_identifier} #{version} #{path}>"
end
valid?() click to toggle source

Is this a valid app?

# File lib/run_loop/app.rb, line 73
def valid?
  App.valid?(path)
end

Private Instance Methods

build_artifact?(file) click to toggle source

@!visibility private

# File lib/run_loop/app.rb, line 413
def build_artifact?(file)
  File.extname(file) == ".xcconfig"
end
code_signing_asset?(file) click to toggle source

@!visibility private

# File lib/run_loop/app.rb, line 382
def code_signing_asset?(file)
  name = File.basename(file)
  extension = File.extname(file)
  dirname = File.basename(File.dirname(file))

  name == "PkgInfo" ||
    name == "embedded" ||
    extension == ".mobileprovision" ||
    extension == ".xcent" ||
    dirname == "_CodeSignature"
end
core_data_asset?(file) click to toggle source

@!visibility private

# File lib/run_loop/app.rb, line 395
def core_data_asset?(file)
  extension = File.extname(file)
  dir_extension = File.extname(File.dirname(file))

  dir_extension == ".momd" ||
    extension == ".mom" ||
    extension == ".db" ||
    extension == ".omo"
end
font?(file) click to toggle source

@!visibility private

# File lib/run_loop/app.rb, line 406
def font?(file)
  extension = File.extname(file)

  extension == ".ttf" || extension == ".otf"
end
image?(file) click to toggle source

@!visibility private

# File lib/run_loop/app.rb, line 349
def image?(file)
  extension = File.extname(file)

  extension == ".jpeg" ||
  extension == ".jpg" ||
  extension == ".gif" ||
  extension == ".png" ||
  extension == ".tiff" ||
  extension == ".svg" ||
  extension == ".pdf" ||
  extension == ".car" ||
  file[/iTunesArtwork/, 0]
end
lipo() click to toggle source

@!visibility private

# File lib/run_loop/app.rb, line 291
def lipo
  @lipo ||= RunLoop::Lipo.new(path)
end
lproj_asset?(file) click to toggle source

@!visibility private

# File lib/run_loop/app.rb, line 369
def lproj_asset?(file)
  extension = File.extname(file)
  dir_extension = File.extname(File.dirname(file))

  dir_extension == ".lproj" ||
    dir_extension == ".storyboard" ||
    dir_extension == ".storyboardc" ||
    extension == ".strings" ||
    extension == ".xib" ||
    extension == ".nib"
end
otool() click to toggle source

@!visibility private

# File lib/run_loop/app.rb, line 301
def otool
  @otool ||= RunLoop::Otool.new(xcode)
end
plist?(file) click to toggle source

@!visibility private

# File lib/run_loop/app.rb, line 364
def plist?(file)
  File.extname(file) == ".plist"
end
plist_buddy() click to toggle source

@!visibility private

# File lib/run_loop/app.rb, line 296
def plist_buddy
  @plist_buddy ||= RunLoop::PlistBuddy.new
end
skip_executable_check?(file) click to toggle source

@!visibility private

# File lib/run_loop/app.rb, line 317
def skip_executable_check?(file)
  File.directory?(file) ||
    image?(file) ||
    text?(file) ||
    plist?(file) ||
    lproj_asset?(file) ||
    code_signing_asset?(file) ||
    core_data_asset?(file) ||
    font?(file) ||
    build_artifact?(file)
end
strings(file) click to toggle source

@!visibility private A strings factory

# File lib/run_loop/app.rb, line 312
def strings(file)
  RunLoop::Strings.new(file)
end
text?(file) click to toggle source

@!visibility private

# File lib/run_loop/app.rb, line 330
def text?(file)
   extension = File.extname(file)
   filename = File.basename(file)

   extension == ".txt" ||
     extension == ".md" ||
     extension == ".html" ||
     extension == ".xml" ||
     extension == ".json" ||
     extension == ".yaml" ||
     extension == ".yml" ||
     extension == ".rtf" ||

     ["NOTICE", "LICENSE", "README", "ABOUT"].any? do |elm|
       filename[/#{elm}/]
     end
end
xcode() click to toggle source

@!visibility private

# File lib/run_loop/app.rb, line 306
def xcode
  @xcode ||= RunLoop::Xcode.new
end