initial revision

This commit is contained in:
Laurent Sansonetti
2012-06-01 19:26:29 +02:00
commit c14c432602
11 changed files with 2366 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
*.swp
*.orig
.DS_Store

22
LICENSE Normal file
View File

@@ -0,0 +1,22 @@
Copyright (c) 2012, HipByte SPRL and contributors
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

53
README.rdoc Normal file
View File

@@ -0,0 +1,53 @@
= RubyMotion
RubyMotion is a commercial toolchain for iOS development using the Ruby
programming language.
This repository contains the parts of the RubyMotion product that are
opensource. It does not contain the full product, which can be purchased at
http://www.rubymotion.com.
== Contents
The +lib+ directory contains the project Rakefile library and the build system.
These files are installed as +/Library/RubyMotion/lib+.
In order to use the GitHub version of these files in your RubyMotion project,
you can modify the project Rakefile like this.
$:.unshift(ENV['RUBYMOTION_LIB'] || "/Library/RubyMotion/lib")
Then, simply set the RUBYMOTION_LIB environment variable when using +rake+.
$ cd MyProject
$ RUBYMOTION_LIB=~/src/RubyMotion/lib rake
== Contributions
Please use the GitHub pull-request mechanism to submit contributions.
== License
Copyright (c) 2012, HipByte SPRL and contributors
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

202
lib/motion/project.rb Normal file
View File

@@ -0,0 +1,202 @@
# Copyright (c) 2012, HipByte SPRL and contributors
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
require 'motion/version'
require 'motion/project/app'
require 'motion/project/config'
require 'motion/project/builder'
require 'motion/project/vendor'
require 'motion/project/plist'
App = Motion::Project::App
# Check for software updates.
system('/usr/bin/motion update --check')
if $?.exitstatus == 2
puts '=' * 80
puts " A new version of RubyMotion is available. Run `sudo motion update' to upgrade."
puts '=' * 80
puts ''
end
desc "Build the project, then run the simulator"
task :default => :simulator
desc "Build everything"
task :build => ['build:simulator', 'build:device']
namespace :build do
desc "Build the simulator version"
task :simulator do
App.build('iPhoneSimulator')
end
desc "Build the device version"
task :device do
App.build('iPhoneOS')
App.codesign('iPhoneOS')
end
end
desc "Run the simulator"
task :simulator => ['build:simulator'] do
app = App.config.app_bundle('iPhoneSimulator')
target = App.config.deployment_target
# Cleanup the simulator application sandbox, to avoid having old resource files there.
if ENV['clean']
sim_apps = File.expand_path("~/Library/Application Support/iPhone Simulator/#{target}/Applications")
Dir.glob("#{sim_apps}/**/*.app").each do |app_bundle|
if File.basename(app_bundle) == File.basename(app)
rm_rf File.dirname(app_bundle)
break
end
end
end
# Prepare the device family.
family_int =
if family = ENV['device_family']
App.config.device_family_int(family.downcase.intern)
else
App.config.device_family_ints[0]
end
retina = ENV['retina'] == 'true'
# Configure the SimulateDevice variable (the only way to specify if we want to run in retina mode or not).
sh "/usr/bin/defaults write com.apple.iphonesimulator \"SimulateDevice\" \"'#{App.config.device_family_string(family_int, retina)}'\""
# Launch the simulator.
xcode = App.config.xcode_dir
env = xcode.match(/^\/Applications/) ? "DYLD_FRAMEWORK_PATH=\"#{xcode}/../Frameworks\":\"#{xcode}/../OtherFrameworks\"" : ''
env << ' NO_FOREGROUND_SIM=1' if App.config.spec_mode
sim = File.join(App.config.bindir, 'sim')
debug = (ENV['debug'] || (App.config.spec_mode ? '0' : '2')).to_i
debug = 2 if debug < 0 or debug > 2
App.info 'Simulate', app
at_exit { system("stty echo") } # Just in case the simulator launcher crashes and leaves the terminal without echo.
sh "#{env} #{sim} #{debug} #{family_int} #{target} \"#{xcode}\" \"#{app}\""
end
desc "Create archives for everything"
task :archive => ['archive:development', 'archive:release']
def create_ipa
app_bundle = App.config.app_bundle('iPhoneOS')
archive = App.config.archive
if !File.exist?(archive) or File.mtime(app_bundle) > File.mtime(archive)
App.info 'Create', archive
tmp = "/tmp/ipa_root"
sh "/bin/rm -rf #{tmp}"
sh "/bin/mkdir -p #{tmp}/Payload"
sh "/bin/cp -r \"#{app_bundle}\" #{tmp}/Payload"
Dir.chdir(tmp) do
sh "/bin/chmod -R 755 Payload"
sh "/usr/bin/zip -q -r archive.zip Payload"
end
sh "/bin/cp #{tmp}/archive.zip \"#{archive}\""
end
end
namespace :archive do
desc "Create an .ipa archive for development"
task :development do
App.config_mode = :development
Rake::Task["build:device"].execute
App.archive
end
desc "Create an .ipa for release (AppStore)"
task :release do
App.config_mode = :release
Rake::Task["build:device"].execute
App.archive
end
end
desc "Run specs"
task :spec do
App.config.spec_mode = true
Rake::Task["simulator"].invoke
end
desc "Deploy on the device"
task :device => 'archive:development' do
App.info 'Deploy', App.config.archive
unless App.config.provisioned_devices.include?(App.config.device_id)
App.fail "Connected device ID `#{App.config.device_id}' not provisioned in profile `#{App.config.provisioning_profile}'"
end
deploy = File.join(App.config.bindir, 'deploy')
flags = Rake.application.options.trace ? '-d' : ''
sh "#{deploy} #{flags} \"#{App.config.device_id}\" \"#{App.config.archive}\""
end
desc "Clear build objects"
task :clean do
App.info 'Delete', App.config.build_dir
rm_rf(App.config.build_dir)
App.config.vendor_projects.each { |vendor| vendor.clean }
Dir.glob(App.config.resources_dir + '/**/*.{nib,storyboardc,momd}').each do |p|
App.info 'Delete', p
rm_rf p
end
end
desc "Show project config"
task :config do
map = App.config.variables
map.keys.sort.each do |key|
puts key.ljust(22) + " : #{map[key].inspect}"
end
end
desc "Generate ctags"
task :ctags do
tags_file = 'tags'
config = App.config
if !File.exist?(tags_file) or File.mtime(config.project_file) > File.mtime(tags_file)
bs_files = config.bridgesupport_files + config.vendor_projects.map { |p| Dir.glob(File.join(p.path, '*.bridgesupport')) }.flatten
ctags = File.join(config.bindir, 'ctags')
config = File.join(config.motiondir, 'data', 'bridgesupport-ctags.cfg')
sh "#{ctags} --options=\"#{config}\" #{bs_files.map { |x| '"' + x + '"' }.join(' ')}"
end
end
=begin
# Automatically load project extensions. A project extension is a gem whose
# name starts with `motion-' and which exposes a `lib/motion/project' libdir.
require 'rubygems'
Gem.path.each do |gemdir|
Dir.glob(File.join(gemdir, 'gems', '*')).each do |gempath|
base = File.basename(gempath)
if md = base.match(/^(motion-.*)-((\d+\.)*\d+)/) and File.exist?(File.join(gempath, 'lib', 'motion', 'project'))
ext_name = md[1]
begin
require ext_name
rescue LoadError => e
$stderr.puts "Can't autoload extension `#{ext_name}': #{e.message}"
end
end
end
end
=end

168
lib/motion/project/app.rb Normal file
View File

@@ -0,0 +1,168 @@
# Copyright (c) 2012, HipByte SPRL and contributors
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
module Motion; module Project
class App
VERBOSE =
begin
if Rake.send(:verbose) != true
Rake.send(:verbose, false)
false
else
true
end
rescue
true
end
class << self
def config_mode
@config_mode or :development
end
def config_mode=(mode)
@config_mode = mode
end
def configs
@configs ||= {
:development => Motion::Project::Config.new('.', :development),
:release => Motion::Project::Config.new('.', :release)
}
end
def config
configs[config_mode]
end
def builder
@builder ||= Motion::Project::Builder.new
end
def setup
configs.each_value { |x| yield x }
config.validate
end
def build(platform)
builder.build(config, platform)
end
def archive
builder.archive(config)
end
def codesign(platform)
builder.codesign(config, platform)
end
def create(app_name)
unless app_name.match(/^[a-zA-Z\d\s]+$/)
fail "Invalid app name"
end
if File.exist?(app_name)
fail "Directory `#{app_name}' already exists"
end
App.log 'Create', app_name
Dir.mkdir(app_name)
Dir.chdir(app_name) do
App.log 'Create', File.join(app_name, '.gitignore')
File.open('.gitignore', 'w') do |io|
io.puts ".repl_history"
io.puts "build"
io.puts "resources/*.nib"
io.puts "resources/*.momd"
io.puts "resources/*.storyboardc"
end
App.log 'Create', File.join(app_name, 'Rakefile')
File.open('Rakefile', 'w') do |io|
io.puts <<EOS
$:.unshift(\"#{$motion_libdir}\")
require 'motion/project'
Motion::Project::App.setup do |app|
# Use `rake config' to see complete project settings.
app.name = '#{app_name}'
end
EOS
end
App.log 'Create', File.join(app_name, 'app')
Dir.mkdir('app')
App.log 'Create', File.join(app_name, 'app/app_delegate.rb')
File.open('app/app_delegate.rb', 'w') do |io|
io.puts <<EOS
class AppDelegate
def application(application, didFinishLaunchingWithOptions:launchOptions)
true
end
end
EOS
end
App.log 'Create', File.join(app_name, 'resources')
Dir.mkdir('resources')
App.log 'Create', File.join(app_name, 'spec')
Dir.mkdir('spec')
App.log 'Create', File.join(app_name, 'spec/main_spec.rb')
File.open('spec/main_spec.rb', 'w') do |io|
io.puts <<EOS
describe "Application '#{app_name}'" do
before do
@app = UIApplication.sharedApplication
end
it "has one window" do
@app.windows.size.should == 1
end
end
EOS
end
end
end
def log(what, msg)
require 'thread'
@print_mutex ||= Mutex.new
# Because this method can be called concurrently, we don't want to mess any output.
@print_mutex.synchronize do
what = "\e[1m" + what.rjust(10) + "\e[0m" # bold
$stderr.puts what + ' ' + msg
end
end
def warn(msg)
log 'WARNING!', msg
end
def fail(msg)
log 'ERROR!', msg
exit 1
end
def info(what, msg)
log what, msg unless VERBOSE
end
end
end
end; end

View File

@@ -0,0 +1,487 @@
# Copyright (c) 2012, HipByte SPRL and contributors
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
require 'thread'
module Motion; module Project;
class Builder
include Rake::DSL if Rake.const_defined?(:DSL)
def build(config, platform)
datadir = config.datadir
archs = config.archs(platform)
ruby = File.join(config.bindir, 'ruby')
llc = File.join(config.bindir, 'llc')
if config.spec_mode and config.spec_files.empty?
App.fail "No spec files in `#{config.specs_dir}'"
end
# Locate SDK and compilers.
sdk = config.sdk(platform)
cc = config.locate_compiler(platform, 'gcc')
cxx = config.locate_compiler(platform, 'g++')
build_dir = File.join(config.versionized_build_dir(platform))
App.info 'Build', build_dir
# Prepare the list of BridgeSupport files needed.
bs_files = config.bridgesupport_files
# Build vendor libraries.
vendor_libs = []
config.vendor_projects.each do |vendor_project|
vendor_project.build(platform)
vendor_libs.concat(vendor_project.libs)
bs_files.concat(vendor_project.bs_files)
end
# Build object files.
objs_build_dir = File.join(build_dir, 'objs')
FileUtils.mkdir_p(objs_build_dir)
project_file_changed = File.mtime(config.project_file) > File.mtime(objs_build_dir)
build_file = Proc.new do |path|
obj ||= File.join(objs_build_dir, "#{path}.o")
should_rebuild = (project_file_changed \
or !File.exist?(obj) \
or File.mtime(path) > File.mtime(obj) \
or File.mtime(ruby) > File.mtime(obj))
# Generate or retrieve init function.
init_func = should_rebuild ? "MREP_#{`/usr/bin/uuidgen`.strip.gsub('-', '')}" : `#{config.locate_binary('nm')} #{obj}`.scan(/T\s+_(MREP_.*)/)[0][0]
if should_rebuild
App.info 'Compile', path
FileUtils.mkdir_p(File.dirname(obj))
arch_objs = []
archs.each do |arch|
# Locate arch kernel.
kernel = File.join(datadir, platform, "kernel-#{arch}.bc")
raise "Can't locate kernel file" unless File.exist?(kernel)
# LLVM bitcode.
bc = File.join(objs_build_dir, "#{path}.#{arch}.bc")
bs_flags = bs_files.map { |x| "--uses-bs \"" + x + "\" " }.join(' ')
sh "/usr/bin/env VM_KERNEL_PATH=\"#{kernel}\" #{ruby} #{bs_flags} --emit-llvm \"#{bc}\" #{init_func} \"#{path}\""
# Assembly.
asm = File.join(objs_build_dir, "#{path}.#{arch}.s")
llc_arch = case arch
when 'i386'; 'x86'
when 'x86_64'; 'x86-64'
when /^arm/; 'arm'
else; arch
end
sh "#{llc} \"#{bc}\" -o=\"#{asm}\" -march=#{llc_arch} -relocation-model=pic -disable-fp-elim -jit-enable-eh -disable-cfi"
# Object.
arch_obj = File.join(objs_build_dir, "#{path}.#{arch}.o")
sh "#{cc} -fexceptions -c -arch #{arch} \"#{asm}\" -o \"#{arch_obj}\""
[bc, asm].each { |x| File.unlink(x) }
arch_objs << arch_obj
end
# Assemble fat binary.
arch_objs_list = arch_objs.map { |x| "\"#{x}\"" }.join(' ')
sh "/usr/bin/lipo -create #{arch_objs_list} -output \"#{obj}\""
end
[obj, init_func]
end
# Create builders.
builders_count =
if jobs = ENV['jobs']
jobs.to_i
else
`/usr/sbin/sysctl -n machdep.cpu.thread_count`.strip.to_i
end
builders_count = 1 if builders_count < 1
builders = []
builders_count.times do
queue = []
th = Thread.new do
sleep
objs = []
while path = queue.shift
objs << build_file.call(path)
end
queue.concat(objs)
end
builders << [queue, th]
end
# Feed builders with work.
builder_i = 0
config.ordered_build_files.each do |path|
builders[builder_i][0] << path
builder_i += 1
builder_i = 0 if builder_i == builders_count
end
# Start build.
builders.each do |queue, th|
sleep 0.01 while th.status != 'sleep'
th.wakeup
end
builders.each { |queue, th| th.join }
# Merge the result (based on build order).
objs = []
builder_i = 0
config.ordered_build_files.each do |path|
objs << builders[builder_i][0].shift
builder_i += 1
builder_i = 0 if builder_i == builders_count
end
app_objs = objs
if config.spec_mode
# Build spec files too, but sequentially.
objs << build_file.call(File.expand_path(File.join(File.dirname(__FILE__), '../spec.rb')))
spec_objs = config.spec_files.map { |path| build_file.call(path) }
objs += spec_objs
end
# Generate main file.
main_txt = <<EOS
#import <UIKit/UIKit.h>
extern "C" {
void ruby_sysinit(int *, char ***);
void ruby_init(void);
void ruby_init_loadpath(void);
void ruby_script(const char *);
void ruby_set_argv(int, char **);
void rb_vm_init_compiler(void);
void rb_vm_init_jit(void);
void rb_vm_aot_feature_provide(const char *, void *);
void *rb_vm_top_self(void);
void rb_rb2oc_exc_handler(void);
void rb_exit(int);
EOS
objs.each do |_, init_func|
main_txt << "void #{init_func}(void *, void *);\n"
end
main_txt << <<EOS
}
EOS
if config.spec_mode
main_txt << <<EOS
@interface SpecLauncher : NSObject
@end
@implementation SpecLauncher
+ (id)launcher
{
[UIApplication sharedApplication];
SpecLauncher *launcher = [[self alloc] init];
[[NSNotificationCenter defaultCenter] addObserver:launcher selector:@selector(appLaunched:) name:UIApplicationDidBecomeActiveNotification object:nil];
return launcher;
}
- (void)appLaunched:(id)notification
{
// Give a bit of time for the simulator to attach...
[self performSelector:@selector(runSpecs) withObject:nil afterDelay:0.1];
}
- (void)runSpecs
{
EOS
spec_objs.each do |_, init_func|
main_txt << "#{init_func}(self, 0);\n"
end
main_txt << "[NSClassFromString(@\"Bacon\") performSelector:@selector(run)];\n"
main_txt << <<EOS
}
@end
EOS
end
main_txt << <<EOS
int
main(int argc, char **argv)
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
const char *progname = argv[0];
ruby_init();
ruby_init_loadpath();
ruby_script(progname);
int retval = 0;
try {
void *self = rb_vm_top_self();
EOS
main_txt << "[SpecLauncher launcher];\n" if config.spec_mode
app_objs.each do |_, init_func|
main_txt << "#{init_func}(self, 0);\n"
end
main_txt << <<EOS
retval = UIApplicationMain(argc, argv, nil, @"#{config.delegate_class}");
rb_exit(retval);
}
catch (...) {
rb_rb2oc_exc_handler();
}
[pool release];
return retval;
}
EOS
# Compile main file.
main = File.join(objs_build_dir, 'main.mm')
main_o = File.join(objs_build_dir, 'main.o')
if !(File.exist?(main) and File.exist?(main_o) and File.read(main) == main_txt)
File.open(main, 'w') { |io| io.write(main_txt) }
sh "#{cxx} \"#{main}\" #{config.cflags(platform, true)} -c -o \"#{main_o}\""
end
# Prepare bundle.
bundle_path = config.app_bundle(platform)
unless File.exist?(bundle_path)
App.info 'Create', bundle_path
FileUtils.mkdir_p(bundle_path)
end
# Link executable.
main_exec = config.app_bundle_executable(platform)
main_exec_created = false
if !File.exist?(main_exec) \
or File.mtime(config.project_file) > File.mtime(main_exec) \
or objs.any? { |path, _| File.mtime(path) > File.mtime(main_exec) } \
or File.mtime(main_o) > File.mtime(main_exec) \
or File.mtime(File.join(datadir, platform, 'libmacruby-static.a')) > File.mtime(main_exec)
App.info 'Link', main_exec
objs_list = objs.map { |path, _| path }.unshift(main_o).map { |x| "\"#{x}\"" }.join(' ')
frameworks = config.frameworks_dependencies.map { |x| "-framework #{x}" }.join(' ')
framework_stubs_objs = []
config.frameworks_dependencies.each do |framework|
stubs_obj = File.join(datadir, platform, "#{framework}_stubs.o")
framework_stubs_objs << "\"#{stubs_obj}\"" if File.exist?(stubs_obj)
end
sh "#{cxx} -o \"#{main_exec}\" #{objs_list} #{framework_stubs_objs.join(' ')} #{config.ldflags(platform)} -L#{File.join(datadir, platform)} -lmacruby-static -lobjc -licucore #{frameworks} #{config.libs.join(' ')} #{vendor_libs.map { |x| '-force_load "' + x + '"' }.join(' ')}"
main_exec_created = true
end
# Create bundle/Info.plist.
bundle_info_plist = File.join(bundle_path, 'Info.plist')
if !File.exist?(bundle_info_plist) or File.mtime(config.project_file) > File.mtime(bundle_info_plist)
App.info 'Create', bundle_info_plist
File.open(bundle_info_plist, 'w') { |io| io.write(config.info_plist_data) }
sh "/usr/bin/plutil -convert binary1 \"#{bundle_info_plist}\""
end
# Create bundle/PkgInfo.
bundle_pkginfo = File.join(bundle_path, 'PkgInfo')
if !File.exist?(bundle_pkginfo) or File.mtime(config.project_file) > File.mtime(bundle_pkginfo)
App.info 'Create', bundle_pkginfo
File.open(bundle_pkginfo, 'w') { |io| io.write(config.pkginfo_data) }
end
# Compile IB resources.
if File.exist?(config.resources_dir)
ib_resources = []
ib_resources.concat((Dir.glob(File.join(config.resources_dir, '*.xib')) + Dir.glob(File.join(config.resources_dir, '*.lproj', '*.xib'))).map { |xib| [xib, xib.sub(/\.xib$/, '.nib')] })
ib_resources.concat(Dir.glob(File.join(config.resources_dir, '*.storyboard')).map { |storyboard| [storyboard, storyboard.sub(/\.storyboard$/, '.storyboardc')] })
ib_resources.each do |src, dest|
if !File.exist?(dest) or File.mtime(src) > File.mtime(dest)
App.info 'Compile', src
sh "/usr/bin/ibtool --compile \"#{dest}\" \"#{src}\""
end
end
end
# Compile CoreData Model resources.
if File.exist?(config.resources_dir)
Dir.glob(File.join(config.resources_dir, '*.xcdatamodeld')).each do |model|
momd = model.sub(/\.xcdatamodeld$/, '.momd')
if !File.exist?(momd) or File.mtime(model) > File.mtime(momd)
App.info 'Compile', model
model = File.expand_path(model) # momc wants absolute paths.
momd = File.expand_path(momd)
sh "\"#{App.config.xcode_dir}/usr/bin/momc\" \"#{model}\" \"#{momd}\""
end
end
end
# Copy resources, handle subdirectories.
reserved_app_bundle_files = [
'_CodeSignature/CodeResources', 'CodeResources', 'embedded.mobileprovision',
'Info.plist', 'PkgInfo', 'ResourceRules.plist',
config.name
]
resources_files = []
if File.exist?(config.resources_dir)
resources_files = Dir.chdir(config.resources_dir) do
Dir.glob('**/*').reject { |x| ['.xib', '.storyboard', '.xcdatamodeld', '.lproj'].include?(File.extname(x)) }
end
resources_files.each do |res|
res_path = File.join(config.resources_dir, res)
if reserved_app_bundle_files.include?(res)
App.fail "Cannot use `#{res_path}' as a resource file because it's a reserved application bundle file"
end
dest_path = File.join(bundle_path, res)
if !File.exist?(dest_path) or File.mtime(res_path) > File.mtime(dest_path)
FileUtils.mkdir_p(File.dirname(dest_path))
App.info 'Copy', res_path
FileUtils.cp_r(res_path, File.dirname(dest_path))
end
end
end
# Delete old resource files.
Dir.chdir(bundle_path) do
Dir.glob('**/*').each do |bundle_res|
next if File.directory?(bundle_res)
next if reserved_app_bundle_files.include?(bundle_res)
next if resources_files.include?(bundle_res)
App.warn "File `#{bundle_res}' found in app bundle but not in `#{config.resources_dir}', removing"
FileUtils.rm_rf(bundle_res)
end
end
# Generate dSYM.
dsym_path = config.app_bundle_dsym(platform)
if !File.exist?(dsym_path) or File.mtime(main_exec) > File.mtime(dsym_path)
App.info "Create", dsym_path
sh "/usr/bin/dsymutil \"#{main_exec}\" -o \"#{dsym_path}\""
end
# Strip all symbols. Only in release mode.
if main_exec_created and config.release?
App.info "Strip", main_exec
sh "#{config.locate_binary('strip')} \"#{main_exec}\""
end
end
def codesign(config, platform)
bundle_path = config.app_bundle(platform)
raise unless File.exist?(bundle_path)
# Create bundle/ResourceRules.plist.
resource_rules_plist = File.join(bundle_path, 'ResourceRules.plist')
unless File.exist?(resource_rules_plist)
App.info 'Create', resource_rules_plist
File.open(resource_rules_plist, 'w') do |io|
io.write(<<-PLIST)
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>rules</key>
<dict>
<key>.*</key>
<true/>
<key>Info.plist</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>10</real>
</dict>
<key>ResourceRules.plist</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>100</real>
</dict>
</dict>
</dict>
</plist>
PLIST
end
end
# Copy the provisioning profile.
bundle_provision = File.join(bundle_path, "embedded.mobileprovision")
if !File.exist?(bundle_provision) or File.mtime(config.provisioning_profile) > File.mtime(bundle_provision)
App.info 'Create', bundle_provision
FileUtils.cp config.provisioning_profile, bundle_provision
end
# Codesign.
codesign_cmd = "CODESIGN_ALLOCATE=\"#{File.join(config.platform_dir(platform), 'Developer/usr/bin/codesign_allocate')}\" /usr/bin/codesign"
if File.mtime(config.project_file) > File.mtime(bundle_path) \
or !system("#{codesign_cmd} --verify \"#{bundle_path}\" >& /dev/null")
App.info 'Codesign', bundle_path
entitlements = File.join(config.versionized_build_dir(platform), "Entitlements.plist")
File.open(entitlements, 'w') { |io| io.write(config.entitlements_data) }
sh "#{codesign_cmd} -f -s \"#{config.codesign_certificate}\" --resource-rules=\"#{resource_rules_plist}\" --entitlements #{entitlements} \"#{bundle_path}\""
end
end
def archive(config)
# Create .ipa archive.
app_bundle = config.app_bundle('iPhoneOS')
archive = config.archive
if !File.exist?(archive) or File.mtime(app_bundle) > File.mtime(archive)
App.info 'Create', archive
tmp = "/tmp/ipa_root"
sh "/bin/rm -rf #{tmp}"
sh "/bin/mkdir -p #{tmp}/Payload"
sh "/bin/cp -r \"#{app_bundle}\" #{tmp}/Payload"
Dir.chdir(tmp) do
sh "/bin/chmod -R 755 Payload"
sh "/usr/bin/zip -q -r archive.zip Payload"
end
sh "/bin/cp #{tmp}/archive.zip \"#{archive}\""
end
=begin
# Create .xcarchive. Only in release mode.
if config.release?
xcarchive = File.join(File.dirname(app_bundle), config.name + '.xcarchive')
if !File.exist?(xcarchive) or File.mtime(app_bundle) > File.mtime(xcarchive)
App.info 'Create', xcarchive
apps = File.join(xcarchive, 'Products', 'Applications')
FileUtils.mkdir_p apps
sh "/bin/cp -r \"#{app_bundle}\" \"#{apps}\""
dsyms = File.join(xcarchive, 'dSYMs')
FileUtils.mkdir_p dsyms
sh "/bin/cp -r \"#{config.app_bundle_dsym('iPhoneOS')}\" \"#{dsyms}\""
app_path = "Applications/#{config.name}.app"
info_plist = {
'ApplicationProperties' => {
'ApplicationPath' => app_path,
'CFBundleIdentifier' => config.identifier,
'IconPaths' => config.icons.map { |x| File.join(app_path, x) },
},
'ArchiveVersion' => 1,
'CreationDate' => Time.now,
'Name' => config.name,
'SchemeName' => config.name
}
File.open(File.join(xcarchive, 'Info.plist'), 'w') do |io|
io.write Motion::PropertyList.to_s(info_plist)
end
end
end
=end
end
end
end; end

View File

@@ -0,0 +1,577 @@
# Copyright (c) 2012, HipByte SPRL and contributors
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
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
xcode_dot_app_path = '/Applications/Xcode.app/Contents/Developer'
# First, honor /usr/bin/xcode-select
xcodeselect = '/usr/bin/xcode-select'
if File.exist?(xcodeselect)
path = `#{xcodeselect} -print-path`.strip
if path != xcode_dot_app_path and File.exist?(xcode_dot_app_path)
@xcode_error_printed ||= false
$stderr.puts(<<EOS) unless @xcode_error_printed
===============================================================================
It appears that you have a version of Xcode installed in /Applications that has
not been set as the default version. It is possible that RubyMotion may be
using old versions of certain tools which could eventually cause issues.
To fix this problem, you can type the following command in the terminal:
$ sudo xcode-select -switch /Applications/Xcode.app/Contents/Developer
===============================================================================
EOS
@xcode_error_printed = true
end
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).
return xcode_dot_app_path if File.exist?(xcode_dot_app_path)
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 frameworks_dependencies
@frameworks_dependencies ||= begin
# Compute the list of frameworks, including dependencies, that the project uses.
deps = []
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
deps.uniq.select { |dep| File.exist?(File.join(datadir, 'BridgeSupport', dep + '.bridgesupport')) }
end
end
def bridgesupport_files
@bridgesupport_files ||= begin
bs_files = []
deps = ['RubyMotion'] + frameworks_dependencies
deps.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_string(family, retina)
device = case family
when :iphone, 1
"iPhone"
when :ipad, 2
"iPad"
end
retina ? device + " (Retina)" : device
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,
'UIStatusBarStyle' => 'UIStatusBarStyleDefault',
'DTXcode' => '0431',
'DTSDKName' => 'iphoneos5.0',
'DTSDKBuild' => '9A334',
'DTPlatformName' => 'iphoneos',
'DTCompiler' => 'com.apple.compilers.llvm.clang.1_0',
'DTPlatformVersion' => '5.1',
'DTXcodeBuild' => '4E1019',
'DTPlatformBuild' => '9B176'
}
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
cert_type = (development? ? 'Developer' : 'Distribution')
certs = `/usr/bin/security -q find-certificate -a`.scan(/"iPhone #{cert_type}: [^"]+"/).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
dict = entitlements
if release?
dict['application-identifier'] ||= seed_id + '.' + identifier
end
Motion::PropertyList.to_s(dict)
end
def fonts
@fonts ||= begin
if File.exist?(resources_dir)
Dir.chdir(resources_dir) do
Dir.glob('*.{otf,ttf}')
end
else
[]
end
end
end
def gen_bridge_metadata(headers, bs_file)
sdk_path = self.sdk('iPhoneSimulator')
includes = headers.map { |header| "-I\"#{File.dirname(header)}\"" }.uniq
a = sdk_version.scan(/(\d+)\.(\d+)/)[0]
sdk_version_headers = ((a[0].to_i * 10000) + (a[1].to_i * 100)).to_s
line = "/usr/bin/gen_bridge_metadata --format complete --no-64-bit --cflags \"-isysroot #{sdk_path} -miphoneos-version-min=#{sdk_version} -D__ENVIRONMENT_IPHONE_OS_VERSION_MIN_REQUIRED__=#{sdk_version_headers} -I. #{includes.join(' ')}\" #{headers.join(' ')} -o \"#{bs_file}\""
unless system(line)
App.fail "Error when generating bridge metadata: #{line}"
end
end
end
end; end

View File

@@ -0,0 +1,76 @@
# Copyright (c) 2012, HipByte SPRL and contributors
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
require 'time' # For Time#iso8601
module Motion
class PropertyList
class << self
def to_s(plist)
str = <<EOS
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
EOS
cat_element(plist, str, 1)
str << "</plist>\n"
return str
end
def indent_line(line, indent)
("\t" * indent) + line + "\n"
end
def cat_element(plist, str, indent)
case plist
when Hash
str << indent_line("<dict>", indent)
plist.each do |key, val|
raise "Hash key must be a string" unless key.is_a?(String)
str << indent_line("<key>#{key}</key>", indent + 1)
cat_element(val, str, indent + 1)
end
str << indent_line("</dict>", indent)
when Array
str << indent_line("<array>", indent)
plist.each do |elem|
cat_element(elem, str, indent + 1)
end
str << indent_line("</array>", indent)
when String
str << indent_line("<string>#{plist}</string>", indent)
when TrueClass
str << indent_line("<true/>", indent)
when FalseClass
str << indent_line("<false/>", indent)
when Time
str << indent_line("<date>#{plist.utc.iso8601}</date>", indent)
when Integer
str << indent_line("<real>#{plist}</real>", indent)
else
raise "Invalid plist object of type `#{plist.class}' (must be either a Hash, Array, String, or boolean true/false value)"
end
end
end
end
end

View File

@@ -0,0 +1,201 @@
# Copyright (c) 2012, HipByte SPRL and contributors
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
module Motion; module Project;
class Vendor
include Rake::DSL if Rake.const_defined?(:DSL)
def initialize(path, type, config, opts)
@path = path.to_s
@type = type
@config = config
@opts = opts
@libs = []
@bs_files = []
end
attr_reader :path, :libs, :bs_files
def build(platform)
App.info 'Build', @path
send gen_method('build'), platform, @opts
if @libs.empty?
App.fail "Building vendor project `#{@path}' failed to create at least one `.a' library."
end
end
def clean
send gen_method('clean')
end
def build_static(platform, opts)
Dir.chdir(@path) do
build_dir = "build-#{platform}"
libs = (opts.delete(:products) or Dir.glob('*.a'))
source_files = (opts.delete(:source_files) or Dir.glob('**/*.{c,m,cpp,cxx,mm,h}'))
cflags = (opts.delete(:cflags) or '')
source_files.each do |srcfile|
objfile = File.join(build_dir, srcfile + '.o')
next if File.exist?(objfile) and File.mtime(objfile) > File.mtime(srcfile)
cplusplus = false
compiler =
case File.extname(srcfile)
when '.c', '.m'
@config.locate_compiler(platform, 'clang', 'gcc')
when '.cpp', '.cxx', '.mm'
cplusplus = true
@config.locate_compiler(platform, 'clang++', 'g++')
else
# Not a valid source file, skip.
next
end
pch = File.join(build_dir, File.basename(@path) + '.pch')
unless File.exist?(pch)
FileUtils.mkdir_p File.dirname(pch)
File.open(pch, 'w') do |io|
io.puts <<EOS
#ifdef __OBJC__
# import <UIKit/UIKit.h>
#endif
EOS
end
end
App.info 'Compile', File.join(@path, srcfile)
FileUtils.mkdir_p File.dirname(objfile)
sh "#{compiler} #{cflags} #{@config.cflags(platform, cplusplus)} -I. -include \"#{pch}\" -c \"#{srcfile}\" -o \"#{objfile}\""
end
if File.exist?(build_dir)
libname = 'lib' + File.basename(@path) + '.a'
Dir.chdir(build_dir) do
objs = Dir.glob('**/*.o')
FileUtils.rm_rf libname
unless objs.empty?
sh "#{@config.locate_binary('ar')} cq #{libname} #{objs.join(' ')}"
end
end
libpath = File.join(build_dir, libname)
libs << libpath if File.exist?(libpath)
end
headers = source_files.select { |p| File.extname(p) == '.h' }
bs_files = []
unless headers.empty?
bs_file = File.basename(@path) + '.bridgesupport'
if !File.exist?(bs_file) or headers.any? { |h| File.mtime(h) > File.mtime(bs_file) }
@config.gen_bridge_metadata(headers, bs_file)
end
bs_files << bs_file
end
@libs = libs.map { |x| File.expand_path(x) }
@bs_files = bs_files.map { |x| File.expand_path(x) }
end
end
def clean_static
['iPhoneSimulator', 'iPhoneOS'].each do |platform|
build_dir = File.join(@path, "build-#{platform}")
if File.exist?(build_dir)
App.info 'Delete', build_dir
FileUtils.rm_rf build_dir
end
end
end
def build_xcode(platform, opts)
Dir.chdir(@path) do
build_dir = "build-#{platform}"
if !File.exist?(build_dir)
FileUtils.mkdir build_dir
# Prepare Xcode project settings.
xcodeproj = opts.delete(:xcodeproj) || begin
projs = Dir.glob('*.xcodeproj')
if projs.size != 1
App.fail "Can't locate Xcode project file for vendor project #{@path}"
end
projs[0]
end
target = opts.delete(:target)
scheme = opts.delete(:scheme)
if target and scheme
App.fail "Both :target and :scheme are provided"
end
configuration = opts.delete(:configuration) || 'Release'
# Unset environment variables that could potentially make the build
# to fail.
%w{CC CXX CFLAGS CXXFLAGS LDFLAGS}.each { |f| ENV[f] &&= nil }
# Build project into `build' directory. We delete the build directory
# each time because Xcode is too stupid to be trusted to use the
# same build directory for different platform builds.
rm_rf 'build'
xcopts = ''
xcopts << "-target \"#{target}\" " if target
xcopts << "-scheme \"#{scheme}\" " if scheme
sh "/usr/bin/xcodebuild -project \"#{xcodeproj}\" #{xcopts} -configuration \"#{configuration}\" -sdk #{platform.downcase}#{@config.sdk_version} #{@config.arch_flags(platform)} CONFIGURATION_BUILD_DIR=build build"
# Copy .a files into the platform build directory.
prods = opts.delete(:products)
Dir.glob('build/*.a').each do |lib|
next if prods and !prods.include?(File.basename(lib))
lib = File.readlink(lib) if File.symlink?(lib)
sh "/bin/cp \"#{lib}\" \"#{build_dir}\""
end
end
# Generate the bridgesupport file if we need to.
bs_file = File.expand_path(File.basename(@path) + '.bridgesupport')
headers_dir = opts.delete(:headers_dir)
if !File.exist?(bs_file) and headers_dir
project_dir = File.expand_path(@config.project_dir)
headers = Dir.glob(File.join(project_dir, headers_dir, '**/*.h'))
@config.gen_bridge_metadata(headers, bs_file)
end
@bs_files = Dir.glob('*.bridgesupport').map { |x| File.expand_path(x) }
@libs = Dir.glob("#{build_dir}/*.a").map { |x| File.expand_path(x) }
end
end
def clean_xcode
Dir.chdir(@path) do
['build', 'build-iPhoneOS', 'build-iPhoneSimulator'].each { |x| rm_rf x }
end
end
private
def gen_method(prefix)
method = "#{prefix}_#{@type.to_s}".intern
raise "Invalid vendor project type: #{@type}" unless respond_to?(method)
method
end
end
end; end

551
lib/motion/spec.rb Normal file
View File

@@ -0,0 +1,551 @@
# Bacon -- small RSpec clone.
#
# "Truth will sooner come out from error than from confusion." ---Francis Bacon
#
# Copyright (C) 2011 Eloy Durán eloy.de.enige@gmail.com
# Copyright (C) 2007 - 2011 Christian Neukirchen <purl.org/net/chneukirchen>
#
# Bacon is freely distributable under the terms of an MIT-style license.
# See COPYING or http://www.opensource.org/licenses/mit-license.php.
module Bacon
VERSION = "1.3"
Counter = Hash.new(0)
ErrorLog = ""
Shared = Hash.new { |_, name|
raise NameError, "no such context: #{name.inspect}"
}
RestrictName = // unless defined? RestrictName
RestrictContext = // unless defined? RestrictContext
Backtraces = true unless defined? Backtraces
module SpecDoxOutput
def handle_specification_begin(name)
puts spaces + name
end
def handle_specification_end
puts if Counter[:context_depth] == 1
end
def handle_requirement_begin(description)
print "#{spaces} - #{description}"
end
def handle_requirement_end(error)
puts error.empty? ? "" : " [#{error}]"
end
def handle_summary
print ErrorLog if Backtraces
puts "%d specifications (%d requirements), %d failures, %d errors" %
Counter.values_at(:specifications, :requirements, :failed, :errors)
end
def spaces
" " * (Counter[:context_depth] - 1)
end
end
module TestUnitOutput
def handle_specification_begin(name); end
def handle_specification_end ; end
def handle_requirement_begin(description) end
def handle_requirement_end(error)
if error.empty?
print "."
else
print error[0..0]
end
end
def handle_summary
puts "", "Finished in #{Time.now - @timer} seconds."
puts ErrorLog if Backtraces
puts "%d tests, %d assertions, %d failures, %d errors" %
Counter.values_at(:specifications, :requirements, :failed, :errors)
end
end
module TapOutput
def handle_specification_begin(name); end
def handle_specification_end ; end
def handle_requirement_begin(description)
ErrorLog.replace ""
end
def handle_requirement_end(error)
if error.empty?
puts "ok %-3d - %s" % [Counter[:specifications], description]
else
puts "not ok %d - %s: %s" %
[Counter[:specifications], description, error]
puts ErrorLog.strip.gsub(/^/, '# ') if Backtraces
end
end
def handle_summary
puts "1..#{Counter[:specifications]}"
puts "# %d tests, %d assertions, %d failures, %d errors" %
Counter.values_at(:specifications, :requirements, :failed, :errors)
end
end
module KnockOutput
def handle_specification_begin(name); end
def handle_specification_end ; end
def handle_requirement_begin(description)
ErrorLog.replace ""
end
def handle_requirement_end(error)
if error.empty?
puts "ok - %s" % [description]
else
puts "not ok - %s: %s" % [description, error]
puts ErrorLog.strip.gsub(/^/, '# ') if Backtraces
end
end
def handle_summary; end
end
extend SpecDoxOutput # default
class Error < RuntimeError
attr_accessor :count_as
def initialize(count_as, message)
@count_as = count_as
super message
end
end
class Specification
attr_reader :description
def initialize(context, description, block, before_filters, after_filters)
@context, @description, @block = context, description, block
@before_filters, @after_filters = before_filters.dup, after_filters.dup
@postponed_blocks_count = 0
@ran_spec_block = false
@ran_after_filters = false
@exception_occurred = false
@error = ""
end
def postponed?
@postponed_blocks_count != 0
end
def run_before_filters
execute_block { @before_filters.each { |f| @context.instance_eval(&f) } }
end
def run_spec_block
@ran_spec_block = true
# If an exception occurred, we definitely don't need to perform the actual spec anymore
unless @exception_occurred
execute_block { @context.instance_eval(&@block) }
end
finish_spec unless postponed?
end
def run_after_filters
@ran_after_filters = true
execute_block { @after_filters.each { |f| @context.instance_eval(&f) } }
end
def run
Bacon.handle_requirement_begin(@description)
Counter[:depth] += 1
run_before_filters
@number_of_requirements_before = Counter[:requirements]
run_spec_block unless postponed?
end
def schedule_block(seconds, &block)
# If an exception occurred, we definitely don't need to schedule any more blocks
unless @exception_occurred
@postponed_blocks_count += 1
performSelector("run_postponed_block:", withObject:block, afterDelay:seconds)
end
end
def postpone_block(timeout = 1, &block)
# If an exception occurred, we definitely don't need to schedule any more blocks
unless @exception_occurred
if @postponed_block
raise "Only one indefinite `wait' block at the same time is allowed!"
else
@postponed_blocks_count += 1
@postponed_block = block
performSelector("postponed_block_timeout_exceeded", withObject:nil, afterDelay:timeout)
end
end
end
def postpone_block_until_change(object_to_observe, key_path, timeout = 1, &block)
# If an exception occurred, we definitely don't need to schedule any more blocks
unless @exception_occurred
if @postponed_block
raise "Only one indefinite `wait' block at the same time is allowed!"
else
@postponed_blocks_count += 1
@postponed_block = block
@observed_object_and_key_path = [object_to_observe, key_path]
object_to_observe.addObserver(self, forKeyPath:key_path, options:0, context:nil)
performSelector("postponed_change_block_timeout_exceeded", withObject:nil, afterDelay:timeout)
end
end
end
def observeValueForKeyPath(key_path, ofObject:object, change:_, context:__)
resume
end
def postponed_change_block_timeout_exceeded
remove_observer!
postponed_block_timeout_exceeded
end
def remove_observer!
if @observed_object_and_key_path
object, key_path = @observed_object_and_key_path
object.removeObserver(self, forKeyPath:key_path)
@observed_object_and_key_path = nil
end
end
def postponed_block_timeout_exceeded
cancel_scheduled_requests!
execute_block { raise Error.new(:failed, "timeout exceeded: #{@context.name} - #{@description}") }
@postponed_blocks_count = 0
finish_spec
end
def resume
NSObject.cancelPreviousPerformRequestsWithTarget(self, selector:'postponed_block_timeout_exceeded', object:nil)
NSObject.cancelPreviousPerformRequestsWithTarget(self, selector:'postponed_change_block_timeout_exceeded', object:nil)
remove_observer!
block, @postponed_block = @postponed_block, nil
run_postponed_block(block)
end
def run_postponed_block(block)
# If an exception occurred, we definitely don't need execute any more blocks
execute_block(&block) unless @exception_occurred
@postponed_blocks_count -= 1
unless postponed?
if @ran_after_filters
exit_spec
elsif @ran_spec_block
finish_spec
else
run_spec_block
end
end
end
def finish_spec
if !@exception_occurred && Counter[:requirements] == @number_of_requirements_before
# the specification did not contain any requirements, so it flunked
execute_block { raise Error.new(:missing, "empty specification: #{@context.name} #{@description}") }
end
run_after_filters
exit_spec unless postponed?
end
def cancel_scheduled_requests!
NSObject.cancelPreviousPerformRequestsWithTarget(@context)
NSObject.cancelPreviousPerformRequestsWithTarget(self)
end
def exit_spec
cancel_scheduled_requests!
Counter[:depth] -= 1
Bacon.handle_requirement_end(@error)
@context.specification_did_finish(self)
end
def execute_block
begin
yield
rescue Object => e
@exception_occurred = true
ErrorLog << "#{e.class}: #{e.message}\n"
lines = $DEBUG ? e.backtrace : e.backtrace.find_all { |line| line !~ /bin\/macbacon|\/mac_bacon\.rb:\d+/ }
lines.each_with_index { |line, i|
ErrorLog << "\t#{line}#{i==0 ? ": #{@context.name} - #{@description}" : ""}\n"
}
ErrorLog << "\n"
@error = if e.kind_of? Error
Counter[e.count_as] += 1
e.count_as.to_s.upcase
else
Counter[:errors] += 1
"ERROR: #{e.class}"
end
end
end
end
def self.add_context(context)
(@contexts ||= []) << context
end
def self.current_context_index
@current_context_index ||= 0
end
def self.current_context
@contexts[current_context_index]
end
def self.run
@timer ||= Time.now
Counter[:context_depth] += 1
handle_specification_begin(current_context.name)
current_context.performSelector("run", withObject:nil, afterDelay:0)
end
def self.context_did_finish(context)
handle_specification_end
Counter[:context_depth] -= 1
if (@current_context_index + 1) < @contexts.size
@current_context_index += 1
run
else
# DONE
handle_summary
exit(Counter.values_at(:failed, :errors).inject(:+))
end
end
class Context
attr_reader :name, :block
def initialize(name, before = nil, after = nil, &block)
@name = name
@before, @after = (before ? before.dup : []), (after ? after.dup : [])
@block = block
@specifications = []
@current_specification_index = 0
Bacon.add_context(self)
instance_eval(&block)
end
def run
# TODO
#return unless name =~ RestrictContext
if spec = current_specification
spec.performSelector("run", withObject:nil, afterDelay:0)
else
Bacon.context_did_finish(self)
end
end
def current_specification
@specifications[@current_specification_index]
end
def specification_did_finish(spec)
if (@current_specification_index + 1) < @specifications.size
@current_specification_index += 1
run
else
Bacon.context_did_finish(self)
end
end
def before(&block); @before << block; end
def after(&block); @after << block; end
def behaves_like(*names)
names.each { |name| instance_eval(&Shared[name]) }
end
def it(description, &block)
return unless description =~ RestrictName
block ||= lambda { should.flunk "not implemented" }
Counter[:specifications] += 1
@specifications << Specification.new(self, description, block, @before, @after)
end
def should(*args, &block)
if Counter[:depth]==0
it('should '+args.first,&block)
else
super(*args,&block)
end
end
def describe(*args, &block)
context = Bacon::Context.new(args.join(' '), @before, @after, &block)
(parent_context = self).methods(false).each {|e|
class<<context; self end.send(:define_method, e) {|*args| parent_context.send(e, *args)}
}
context
end
def wait(seconds = nil, &block)
if seconds
current_specification.schedule_block(seconds, &block)
else
current_specification.postpone_block(&block)
end
end
def wait_max(timeout, &block)
current_specification.postpone_block(timeout, &block)
end
def wait_for_change(object_to_observe, key_path, timeout = 1, &block)
current_specification.postpone_block_until_change(object_to_observe, key_path, timeout, &block)
end
def resume
current_specification.resume
end
def raise?(*args, &block); block.raise?(*args); end
def throw?(*args, &block); block.throw?(*args); end
def change?(*args, &block); block.change?(*args); end
end
end
class Object
def true?; false; end
def false?; false; end
end
class TrueClass
def true?; true; end
end
class FalseClass
def false?; true; end
end
class Proc
def raise?(*exceptions)
call
rescue *(exceptions.empty? ? RuntimeError : exceptions) => e
e
else
false
end
def throw?(sym)
catch(sym) {
call
return false
}
return true
end
def change?
pre_result = yield
called = call
post_result = yield
pre_result != post_result
end
end
class Numeric
def close?(to, delta)
(to.to_f - self).abs <= delta.to_f rescue false
end
end
class Object
def should(*args, &block) Should.new(self).be(*args, &block) end
end
module Kernel
private
def describe(*args, &block) Bacon::Context.new(args.join(' '), &block) end
def shared(name, &block) Bacon::Shared[name] = block end
end
class Should
# Kills ==, ===, =~, eql?, equal?, frozen?, instance_of?, is_a?,
# kind_of?, nil?, respond_to?, tainted?
instance_methods.each { |name| undef_method name if name =~ /\?|^\W+$/ }
def initialize(object)
@object = object
@negated = false
end
def not(*args, &block)
@negated = !@negated
if args.empty?
self
else
be(*args, &block)
end
end
def be(*args, &block)
if args.empty?
self
else
block = args.shift unless block_given?
satisfy(*args, &block)
end
end
alias a be
alias an be
def satisfy(*args, &block)
if args.size == 1 && String === args.first
description = args.shift
else
description = ""
end
r = yield(@object, *args)
if Bacon::Counter[:depth] > 0
Bacon::Counter[:requirements] += 1
raise Bacon::Error.new(:failed, description) unless @negated ^ r
r
else
@negated ? !r : !!r
end
end
def method_missing(name, *args, &block)
name = "#{name}?" if name.to_s =~ /\w[^?]\z/
desc = @negated ? "not " : ""
desc << @object.inspect << "." << name.to_s
desc << "(" << args.map{|x|x.inspect}.join(", ") << ") failed"
satisfy(desc) { |x| x.__send__(name, *args, &block) }
end
def equal(value) self == value end
def match(value) self =~ value end
def identical_to(value) self.equal? value end
alias same_as identical_to
def flunk(reason="Flunked")
raise Bacon::Error.new(:failed, reason)
end
end

26
lib/motion/version.rb Normal file
View File

@@ -0,0 +1,26 @@
# Copyright (c) 2012, HipByte SPRL and contributors
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
module Motion
Version = "1.9"
end