Files
RubyMotion/lib/motion/project/config.rb

497 lines
15 KiB
Ruby

module Motion; module Project
class Config
VARS = []
def self.variable(*syms)
syms.each do |sym|
attr_accessor sym
VARS << sym.to_s
end
end
class Deps < Hash
def []=(key, val)
key = relpath(key)
val = [val] unless val.is_a?(Array)
val = val.map { |x| relpath(x) }
super
end
def relpath(path)
/^\./.match(path) ? path : File.join('.', path)
end
end
variable :files, :xcode_dir, :sdk_version, :deployment_target, :frameworks,
:libs, :delegate_class, :name, :build_dir, :resources_dir, :specs_dir,
:identifier, :codesign_certificate, :provisioning_profile,
:device_family, :interface_orientations, :version, :icons,
:prerendered_icon, :seed_id, :entitlements, :fonts
attr_accessor :spec_mode
def initialize(project_dir, build_mode)
@project_dir = project_dir
@files = Dir.glob(File.join(project_dir, 'app/**/*.rb'))
@dependencies = {}
@frameworks = ['UIKit', 'Foundation', 'CoreGraphics']
@libs = []
@delegate_class = 'AppDelegate'
@name = 'Untitled'
@resources_dir = File.join(project_dir, 'resources')
@build_dir = File.join(project_dir, 'build')
@specs_dir = File.join(project_dir, 'spec')
@device_family = :iphone
@bundle_signature = '????'
@interface_orientations = [:portrait, :landscape_left, :landscape_right]
@version = '1.0'
@icons = []
@prerendered_icon = false
@vendor_projects = []
@entitlements = {}
@spec_mode = false
@build_mode = build_mode
end
def variables
map = {}
VARS.each do |sym|
map[sym] =
begin
send(sym)
rescue Exception
'Error'
end
end
map
end
def xcode_dir
@xcode_dir ||= begin
# First, honor /usr/bin/xcode-select
xcodeselect = '/usr/bin/xcode-select'
if File.exist?(xcodeselect)
path = `#{xcodeselect} -print-path`.strip
return path if File.exist?(path)
end
# Since xcode-select is borked, we assume the user installed Xcode
# as an app (new in Xcode 4.3).
path = '/Applications/Xcode.app'
if File.exist?(path)
path = File.join(path, 'Contents/Developer')
return path if File.exist?(path)
end
App.fail "Can't locate any version of Xcode on the system."
end
end
def locate_binary(name)
[File.join(xcode_dir, 'usr/bin'), '/usr/bin'].each do |dir|
path = File.join(dir, name)
return path if File.exist?(path)
end
App.fail "Can't locate binary `#{name}' on the system."
end
def validate
# Xcode version
ary = `#{locate_binary('xcodebuild')} -version`.scan(/Xcode\s+([^\n]+)\n/)
if ary and ary[0] and xcode_version = ary[0][0]
App.fail "Xcode 4.x or greater is required" if xcode_version < '4.0'
end
# sdk_version
['iPhoneSimulator', 'iPhoneOS'].each do |platform|
sdk_path = File.join(platforms_dir, platform + '.platform',
"Developer/SDKs/#{platform}#{sdk_version}.sdk")
unless File.exist?(sdk_path)
App.fail "Can't locate #{platform} SDK #{sdk_version} at `#{sdk_path}'"
end
end
# deployment_target
if deployment_target > sdk_version
App.fail "Deployment target `#{deployment_target}' must be equal or lesser than SDK version `#{sdk_version}'"
end
unless File.exist?(datadir)
App.fail "iOS deployment target #{deployment_target} is not supported by this version of RubyMotion"
end
end
def build_dir
unless File.directory?(@build_dir)
tried = false
begin
FileUtils.mkdir_p(@build_dir)
rescue Errno::EACCES
raise if tried
require 'digest/sha1'
hash = Digest::SHA1.hexdigest(File.expand_path(project_dir))
tmp = File.join(ENV['TMPDIR'], hash)
App.warn "Cannot create build_dir `#{@build_dir}'. Check the permissions. Using a temporary build directory instead: `#{tmp}'"
@build_dir = tmp
tried = true
retry
end
end
@build_dir
end
def build_mode_name
@build_mode.to_s.capitalize
end
def development?
@build_mode == :development
end
def release?
@build_mode == :release
end
def development
yield if development?
end
def release
yield if release?
end
def versionized_build_dir(platform)
File.join(build_dir, platform + '-' + deployment_target + '-' + build_mode_name)
end
attr_reader :project_dir
def project_file
File.join(@project_dir, 'Rakefile')
end
def files_dependencies(deps_hash)
res_path = lambda do |x|
path = /^\./.match(x) ? x : File.join('.', x)
unless @files.include?(path)
App.fail "Can't resolve dependency `#{x}'"
end
path
end
deps_hash.each do |path, deps|
deps = [deps] unless deps.is_a?(Array)
@dependencies[res_path.call(path)] = deps.map(&res_path)
end
end
attr_reader :vendor_projects
def vendor_project(path, type, opts={})
@vendor_projects << Motion::Project::Vendor.new(path, type, self, opts)
end
def unvendor_project(path)
@vendor_projects.delete_if { |x| x.path == path }
end
def file_dependencies(file)
deps = @dependencies[file]
if deps
deps = deps.map { |x| file_dependencies(x) }
else
deps = []
end
deps << file
deps
end
def ordered_build_files
@ordered_build_files ||= begin
flat_deps = @files.map { |file| file_dependencies(file) }.flatten
paths = flat_deps.dup
flat_deps.each do |path|
n = paths.count(path)
if n > 1
(n - 1).times { paths.delete_at(paths.rindex(path)) }
end
end
paths
end
end
def bridgesupport_files
@bridgesupport_files ||= begin
# Compute the list of frameworks, including dependencies, that the project uses.
deps = ['RubyMotion']
slf = File.join(sdk('iPhoneSimulator'), 'System', 'Library', 'Frameworks')
frameworks.each do |framework|
framework_path = File.join(slf, framework + '.framework', framework)
if File.exist?(framework_path)
`#{locate_binary('otool')} -L \"#{framework_path}\"`.scan(/\t([^\s]+)\s\(/).each do |dep|
# Only care about public, non-umbrella frameworks (for now).
if md = dep[0].match(/^\/System\/Library\/Frameworks\/(.+)\.framework\/(.+)$/) and md[1] == md[2]
deps << md[1]
end
end
end
deps << framework
end
bs_files = []
deps.uniq.each do |framework|
bs_path = File.join(datadir, 'BridgeSupport', framework + '.bridgesupport')
if File.exist?(bs_path)
bs_files << bs_path
end
end
bs_files
end
end
def spec_files
Dir.glob(File.join(specs_dir, '**', '*.rb'))
end
def motiondir
@motiondir ||= File.expand_path(File.join(File.dirname(__FILE__), '../../..'))
end
def bindir
File.join(motiondir, 'bin')
end
def datadir(target=deployment_target)
File.join(motiondir, 'data', target)
end
def platforms_dir
File.join(xcode_dir, 'Platforms')
end
def platform_dir(platform)
File.join(platforms_dir, platform + '.platform')
end
def sdk_version
@sdk_version ||= begin
versions = Dir.glob(File.join(platforms_dir, 'iPhoneOS.platform/Developer/SDKs/iPhoneOS*.sdk')).map do |path|
File.basename(path).scan(/iPhoneOS(.*)\.sdk/)[0][0]
end
if versions.size == 0
App.fail "Can't find an iOS SDK in `#{platforms_dir}'"
end
supported_vers = versions.reverse.find { |vers| File.exist?(datadir(vers)) }
unless supported_vers
App.fail "RubyMotion doesn't support any of these SDK versions: #{versions.join(', ')}"
end
supported_vers
end
end
def deployment_target
@deployment_target ||= sdk_version
end
def sdk(platform)
File.join(platform_dir(platform), 'Developer/SDKs',
platform + sdk_version + '.sdk')
end
def locate_compiler(platform, *execs)
paths = [File.join(platform_dir(platform), 'Developer/usr/bin')]
paths.unshift File.join(xcode_dir, 'Toolchains/XcodeDefault.xctoolchain/usr/bin') if platform == 'iPhoneSimulator'
execs.each do |exec|
paths.each do |path|
cc = File.join(path, exec)
return cc if File.exist?(cc)
end
end
App.fail "Can't locate compilers for platform `#{platform}'"
end
def archs(platform)
Dir.glob(File.join(datadir, platform, '*.bc')).map do |path|
path.scan(/kernel-(.+).bc$/)[0][0]
end
end
def arch_flags(platform)
archs(platform).map { |x| "-arch #{x}" }.join(' ')
end
def common_flags(platform)
"#{arch_flags(platform)} -isysroot \"#{sdk(platform)}\" -miphoneos-version-min=#{deployment_target} -F#{sdk(platform)}/System/Library/Frameworks"
end
def cflags(platform, cplusplus)
"#{common_flags(platform)} -fexceptions -fblocks -fobjc-legacy-dispatch -fobjc-abi-version=2" + (cplusplus ? '' : ' -std=c99')
end
def ldflags(platform)
common_flags(platform)
end
def bundle_name
@name + (spec_mode ? '_spec' : '')
end
def app_bundle(platform)
File.join(versionized_build_dir(platform), bundle_name + '.app')
end
def app_bundle_dsym(platform)
File.join(versionized_build_dir(platform), bundle_name + '.dSYM')
end
def app_bundle_executable(platform)
File.join(app_bundle(platform), name)
end
def archive
File.join(versionized_build_dir('iPhoneOS'), bundle_name + '.ipa')
end
def identifier
@identifier ||= "com.yourcompany.#{@name.gsub(/\s/, '')}"
end
def device_family_int(family)
case family
when :iphone then 1
when :ipad then 2
else
App.fail "Unknown device_family value: `#{family}'"
end
end
def device_family_ints
ary = @device_family.is_a?(Array) ? @device_family : [@device_family]
ary.map { |family| device_family_int(family) }
end
def interface_orientations_consts
@interface_orientations.map do |ori|
case ori
when :portrait then 'UIInterfaceOrientationPortrait'
when :landscape_left then 'UIInterfaceOrientationLandscapeLeft'
when :landscape_right then 'UIInterfaceOrientationLandscapeRight'
when :portrait_upside_down then 'UIInterfaceOrientationPortraitUpsideDown'
else
App.fail "Unknown interface_orientation value: `#{ori}'"
end
end
end
def info_plist
@info_plist ||= {
'BuildMachineOSBuild' => `sw_vers -buildVersion`.strip,
'MinimumOSVersion' => deployment_target,
'CFBundleDevelopmentRegion' => 'en',
'CFBundleName' => @name,
'CFBundleDisplayName' => @name,
'CFBundleExecutable' => @name,
'CFBundleIdentifier' => identifier,
'CFBundleInfoDictionaryVersion' => '6.0',
'CFBundlePackageType' => 'APPL',
'CFBundleResourceSpecification' => 'ResourceRules.plist',
'CFBundleShortVersionString' => @version,
'CFBundleSignature' => @bundle_signature,
'CFBundleSupportedPlatforms' => ['iPhoneOS'],
'CFBundleVersion' => @version,
'CFBundleIconFiles' => icons,
'CFBundleIcons' => {
'CFBundlePrimaryIcon' => {
'CFBundleIconFiles' => icons,
'UIPrerenderedIcon' => prerendered_icon,
}
},
'UIAppFonts' => fonts,
'UIDeviceFamily' => device_family_ints.map { |x| x.to_s },
'UISupportedInterfaceOrientations' => interface_orientations_consts
}
end
def info_plist_data
Motion::PropertyList.to_s(info_plist)
end
def pkginfo_data
"AAPL#{@bundle_signature}"
end
def codesign_certificate
@codesign_certificate ||= begin
certs = `/usr/bin/security -q find-certificate -a`.scan(/"iPhone Developer: [^"]+"/).uniq
if certs.size == 0
App.fail "Can't find an iPhone Developer certificate in the keychain"
elsif certs.size > 1
App.warn "Found #{certs.size} iPhone Developer certificates in the keychain. Set the `codesign_certificate' project setting. Will use the first certificate: `#{certs[0]}'"
end
certs[0][1..-2] # trim trailing `"` characters
end
end
def device_id
@device_id ||= begin
deploy = File.join(App.config.bindir, 'deploy')
device_id = `#{deploy} -D`.strip
if device_id.empty?
App.fail "Can't find an iOS device connected on USB"
end
device_id
end
end
def provisioning_profile(name = /iOS Team Provisioning Profile/)
@provisioning_profile ||= begin
paths = Dir.glob(File.expand_path("~/Library/MobileDevice/Provisioning\ Profiles/*.mobileprovision")).select do |path|
text = File.read(path)
text.force_encoding('binary') if RUBY_VERSION >= '1.9.0'
text.scan(/<key>\s*Name\s*<\/key>\s*<string>\s*([^<]+)\s*<\/string>/)[0][0].match(name)
end
if paths.size == 0
App.fail "Can't find a provisioning profile named `#{name}'"
elsif paths.size > 1
App.warn "Found #{paths.size} provisioning profiles named `#{name}'. Set the `provisioning_profile' project setting. Will use the first one: `#{paths[0]}'"
end
paths[0]
end
end
def read_provisioned_profile_array(key)
text = File.read(provisioning_profile)
text.force_encoding('binary') if RUBY_VERSION >= '1.9.0'
text.scan(/<key>\s*#{key}\s*<\/key>\s*<array>(.*?)\s*<\/array>/m)[0][0].scan(/<string>(.*?)<\/string>/).map { |str| str[0].strip }
end
private :read_provisioned_profile_array
def provisioned_devices
@provisioned_devices ||= read_provisioned_profile_array('ProvisionedDevices')
end
def seed_id
@seed_id ||= begin
seed_ids = read_provisioned_profile_array('ApplicationIdentifierPrefix')
if seed_ids.size == 0
App.fail "Can't find an application seed ID in the provisioning profile `#{provisioning_profile}'"
elsif seed_ids.size > 1
App.warn "Found #{seed_ids.size} seed IDs in the provisioning profile. Set the `seed_id' project setting. Will use the last one: `#{seed_ids.last}'"
end
seed_ids.last
end
end
def entitlements_data
Motion::PropertyList.to_s(entitlements)
end
def fonts
@fonts ||= begin
Dir.chdir(resources_dir) do
Dir.glob('*.{otf,ttf}')
end
end
end
end
end; end