mirror of
https://github.com/zhigang1992/RubyMotion.git
synced 2026-06-13 01:29:00 +08:00
Added support to build iOS extensions through the 'ios-extension' template
This commit is contained in:
@@ -0,0 +1,15 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
Motion::Project::App.setup do |app|
|
||||
# Use `rake config' to see complete project settings.
|
||||
app.extension do |ext|
|
||||
ext.name = '<%= extension_name %>'
|
||||
ext.type = 'ui-services'
|
||||
ext.attributes = {
|
||||
"NSExtensionActivationRule" => "TRUEPREDICATE",
|
||||
"NSExtensionPointName" => "com.apple.ui-services",
|
||||
"NSExtensionPointVersion" => "1.0"
|
||||
}
|
||||
ext.frameworks << "MobileCoreServices"
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,51 @@
|
||||
class ActionViewController < UIViewController
|
||||
|
||||
attr_accessor :imageView
|
||||
|
||||
def initWithNibName(nibNameOrNil, bundle:nibBundleOrNil)
|
||||
super
|
||||
self
|
||||
end
|
||||
|
||||
def viewDidLoad
|
||||
super
|
||||
|
||||
# Get the item[s] we're handling from the extension context.
|
||||
|
||||
# For example, look for an image and place it into an image view.
|
||||
# Replace this with something appropriate for the type[s] your extension supports.
|
||||
imageFound = false
|
||||
self.extensionContext.inputItems.each do |item|
|
||||
item.attachments.each do |itemProvider|
|
||||
if itemProvider.hasItemConformingToTypeIdentifier(KUTTypeImage)
|
||||
# This is an image. We'll load it, then place it in our image view.
|
||||
imageView = WeakRef.new(self.imageView)
|
||||
itemProvider.loadItemForTypeIdentifier(KUTTypeImage, options:nil, completionHandler: proc { |image, error|
|
||||
if image
|
||||
NSOperationQueue.mainQueue.addOperationWithBlock(proc {
|
||||
imageView.setImage(image)
|
||||
}.weak!)
|
||||
end
|
||||
}.weak!)
|
||||
|
||||
imageFound = true
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
break if imageFound
|
||||
end
|
||||
end
|
||||
|
||||
def didReceiveMemoryWarning
|
||||
super
|
||||
# Dispose of any resources that can be recreated.
|
||||
end
|
||||
|
||||
def done
|
||||
# Return any edited content to the host app.
|
||||
# This template doesn't do anything, so we just echo the passed in items.
|
||||
self.extensionContext.completeRequestReturningItems(self.extensionContext.inputItems, completionHandler:nil)
|
||||
end
|
||||
|
||||
end
|
||||
@@ -0,0 +1,15 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
Motion::Project::App.setup do |app|
|
||||
# Use `rake config' to see complete project settings.
|
||||
app.extension do |ext|
|
||||
ext.name = '<%= extension_name %>'
|
||||
ext.type = "keyboard-service"
|
||||
ext.attributes = {
|
||||
"IsASCIICapable" => false,
|
||||
"PrefersRightToLeft" => false,
|
||||
"PrimaryLanguage" => "en-US",
|
||||
"RequestsOpenAccess" => false
|
||||
}
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,55 @@
|
||||
class KeyboardViewController < UIInputViewController
|
||||
|
||||
attr_accessor :nextKeyboardButton
|
||||
|
||||
def initWithNibName(nibNameOrNil, bundle:nibBundleOrNil)
|
||||
super
|
||||
self
|
||||
end
|
||||
|
||||
def updateViewConstraints
|
||||
super
|
||||
# Add custom view sizing constraints here
|
||||
end
|
||||
|
||||
def viewDidLoad
|
||||
super
|
||||
|
||||
# Perform custom UI setup here
|
||||
self.nextKeyboardButton = UIButton.buttonWithType(UIButtonTypeSystem)
|
||||
|
||||
self.nextKeyboardButton.setTitle(NSLocalizedString("Next Keyboard", "Title for 'Next Keyboard' button"), forState:UIControlStateNormal)
|
||||
self.nextKeyboardButton.sizeToFit
|
||||
self.nextKeyboardButton.translatesAutoresizingMaskIntoConstraints = false
|
||||
|
||||
self.nextKeyboardButton.addTarget(self, action:"advanceToNextInputMode", forControlEvents:UIControlEventTouchUpInside)
|
||||
|
||||
self.view.addSubview(self.nextKeyboardButton)
|
||||
|
||||
nextKeyboardButtonLeftSideConstraint = NSLayoutConstraint.constraintWithItem(self.nextKeyboardButton, attribute:NSLayoutAttributeLeft, relatedBy:NSLayoutRelationEqual, toItem:self.view, attribute:NSLayoutAttributeLeft, multiplier:1.0, constant:0.0)
|
||||
nextKeyboardButtonBottomConstraint = NSLayoutConstraint.constraintWithItem(self.nextKeyboardButton, attribute:NSLayoutAttributeBottom, relatedBy:NSLayoutRelationEqual, toItem:self.view, attribute:NSLayoutAttributeBottom, multiplier:1.0, constant:0.0)
|
||||
self.view.addConstraints([nextKeyboardButtonLeftSideConstraint, nextKeyboardButtonBottomConstraint])
|
||||
end
|
||||
|
||||
def didReceiveMemoryWarning
|
||||
super
|
||||
# Dispose of any resources that can be recreated
|
||||
end
|
||||
|
||||
def textWillChange(textInput)
|
||||
# The app is about to change the document's contents. Perform any preparation here.
|
||||
end
|
||||
|
||||
def textDidChange(textInput)
|
||||
# The app has just changed the document's contents, the document context has been updated.
|
||||
|
||||
textColor = nil
|
||||
if self.textDocumentProxy.keyboardAppearance == UIKeyboardAppearanceDark
|
||||
textColor = UIColor.whiteColor
|
||||
else
|
||||
textColor = UIColor.blackColor
|
||||
end
|
||||
self.nextKeyboardButton.setTitleColor(textColor, forState:UIControlStateNormal)
|
||||
end
|
||||
|
||||
end
|
||||
@@ -0,0 +1,20 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
Motion::Project::App.setup do |app|
|
||||
# Use `rake config' to see complete project settings.
|
||||
app.extension do |ext|
|
||||
ext.name = '<%= extension_name %>'
|
||||
ext.type = "fileprovider-ui"
|
||||
ext.attributes = {
|
||||
"UIDocumentPickerModes" => [
|
||||
"UIDocumentPickerModeImport",
|
||||
"UIDocumentPickerModeOpen",
|
||||
"UIDocumentPickerModeExportToService",
|
||||
"UIDocumentPickerModeMoveToService"
|
||||
],
|
||||
"UIDocumentPickerSupportedFileTypes" => [
|
||||
"public.content"
|
||||
]
|
||||
}
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,14 @@
|
||||
class DocumentPickerViewController < UIDocumentPickerExtensionViewController
|
||||
|
||||
def openDocument(sender)
|
||||
documentURL = self.documentStorageURL.URLByAppendingPathComponent("Untitled.txt")
|
||||
|
||||
# TODO: if you do not have a corresponding file provider, you must ensure that the URL returned here is backed by a file
|
||||
self.dismissGrantingAccessToURL(documentURL)
|
||||
end
|
||||
|
||||
def prepareForPresentationInMode(mode)
|
||||
# TODO: present a view controller appropriate for picker mode here
|
||||
end
|
||||
|
||||
end
|
||||
@@ -0,0 +1,10 @@
|
||||
<?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>com.apple.security.application-groups</key>
|
||||
<array>
|
||||
<string>group.sdf.Extension-Test</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
585
lib/motion/project/template/ios-extension-builder.rb
Normal file
585
lib/motion/project/template/ios-extension-builder.rb
Normal file
@@ -0,0 +1,585 @@
|
||||
# encoding: utf-8
|
||||
|
||||
# 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/project/builder'
|
||||
|
||||
module Motion; module Project
|
||||
class Builder
|
||||
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
|
||||
|
||||
# Create manifest file (if needed).
|
||||
manifest_plist = File.join(config.versionized_build_dir('iPhoneOS'), 'manifest.plist')
|
||||
manifest_plist_data = config.manifest_plist_data
|
||||
if manifest_plist_data and (!File.exist?(manifest_plist) or File.mtime(config.project_file) > File.mtime(manifest_plist))
|
||||
App.info 'Create', manifest_plist
|
||||
File.open(manifest_plist, 'w') { |io| io.write(manifest_plist_data) }
|
||||
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")
|
||||
App.info 'Create', bundle_provision
|
||||
FileUtils.cp config.provisioning_profile, bundle_provision
|
||||
|
||||
# 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 build(config, platform, opts)
|
||||
datadir = config.datadir
|
||||
unless File.exist?(File.join(datadir, platform))
|
||||
$stderr.puts "This version of RubyMotion does not support `#{platform}'"
|
||||
exit 1
|
||||
end
|
||||
archs = config.archs[platform]
|
||||
|
||||
static_library = opts.delete(:static)
|
||||
|
||||
ruby = File.join(config.bindir, 'ruby')
|
||||
llc = File.join(config.bindir, 'llc')
|
||||
@nfd = File.join(config.bindir, 'nfd')
|
||||
|
||||
if config.spec_mode and (config.spec_files - config.spec_core_files).empty?
|
||||
App.fail "No spec files in `#{config.specs_dir}'"
|
||||
end
|
||||
|
||||
config.resources_dirs.flatten!
|
||||
config.resources_dirs.uniq!
|
||||
|
||||
# Locate SDK and compilers.
|
||||
sdk = config.sdk(platform)
|
||||
cc = config.locate_compiler(platform, 'clang')
|
||||
cxx = config.locate_compiler(platform, 'clang++')
|
||||
|
||||
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
|
||||
|
||||
# Validate common build directory.
|
||||
if !File.directory?(Builder.common_build_dir) or !File.writable?(Builder.common_build_dir)
|
||||
$stderr.puts "Cannot write into the `#{Builder.common_build_dir}' directory, please remove or check permissions and try again."
|
||||
exit 1
|
||||
end
|
||||
|
||||
# Prepare embedded and external frameworks BridgeSupport files (OSX-only).
|
||||
if config.respond_to?(:embedded_frameworks) && config.respond_to?(:external_frameworks)
|
||||
embedded_frameworks = config.embedded_frameworks.map { |x| File.expand_path(x) }
|
||||
external_frameworks = config.external_frameworks.map { |x| File.expand_path(x) }
|
||||
(embedded_frameworks + external_frameworks).each do |path|
|
||||
headers = Dir.glob(File.join(path, 'Headers/**/*.h'))
|
||||
bs_file = File.join(Builder.common_build_dir, File.expand_path(path) + '.bridgesupport')
|
||||
if !File.exist?(bs_file) or File.mtime(path) > File.mtime(bs_file)
|
||||
FileUtils.mkdir_p(File.dirname(bs_file))
|
||||
config.gen_bridge_metadata(platform, headers, bs_file, '', [])
|
||||
end
|
||||
bs_files << bs_file
|
||||
end
|
||||
else
|
||||
embedded_frameworks = external_frameworks = []
|
||||
end
|
||||
|
||||
# Build object files.
|
||||
objs_build_dir = File.join(build_dir, 'objs')
|
||||
FileUtils.mkdir_p(objs_build_dir)
|
||||
any_obj_file_built = false
|
||||
project_files = Dir.glob("**/*.rb").map{ |x| File.expand_path(x) }
|
||||
is_default_archs = (archs == config.default_archs[platform])
|
||||
rubyc_bs_flags = bs_files.map { |x| "--uses-bs \"" + x + "\" " }.join(' ')
|
||||
|
||||
build_file = Proc.new do |files_build_dir, path|
|
||||
rpath = path
|
||||
path = File.expand_path(path)
|
||||
if is_default_archs && !project_files.include?(path)
|
||||
files_build_dir = File.expand_path(File.join(Builder.common_build_dir, files_build_dir))
|
||||
end
|
||||
obj = File.join(files_build_dir, "#{path}.o")
|
||||
should_rebuild = (!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', rpath
|
||||
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)
|
||||
|
||||
# Assembly.
|
||||
asm = File.join(files_build_dir, "#{path}.#{arch}.s")
|
||||
arm64 = false
|
||||
compiler_exec_arch = case arch
|
||||
when /^arm/
|
||||
(arm64 = (arch == 'arm64')) ? 'x86_64' : 'i386'
|
||||
else
|
||||
arch
|
||||
end
|
||||
sh "/usr/bin/env VM_PLATFORM=\"#{platform}\" VM_KERNEL_PATH=\"#{kernel}\" VM_OPT_LEVEL=\"#{config.opt_level}\" /usr/bin/arch -arch #{compiler_exec_arch} #{ruby} #{rubyc_bs_flags} --emit-llvm \"#{asm}\" #{init_func} \"#{path}\""
|
||||
|
||||
# Object
|
||||
arch_obj = File.join(files_build_dir, "#{path}.#{arch}.o")
|
||||
if arm64
|
||||
# At the time of this writing Apple hasn't yet contributed the source code of the LLVM backend for the "arm64" architecture, so the RubyMotion compiler can't emit proper assembly yet. We work around this limitation by generating bitcode instead and giving it to the linker. Ugly but should be temporary (right?).
|
||||
@dummy_object_file ||= begin
|
||||
src_path = '/tmp/__dummy_object_file__.c'
|
||||
obj_path = '/tmp/__dummy_object_file__.o'
|
||||
File.open(src_path, 'w') { |io| io.puts "static int foo(void) { return 42; }" }
|
||||
sh "#{cc} -c #{src_path} -o #{obj_path} -arch arm64 -miphoneos-version-min=7.0"
|
||||
obj_path
|
||||
end
|
||||
ld_path = File.join(App.config.xcode_dir, 'Toolchains/XcodeDefault.xctoolchain/usr/bin/ld')
|
||||
sh "#{ld_path} \"#{asm}\" \"#{@dummy_object_file}\" -arch arm64 -r -o \"#{arch_obj}\""
|
||||
else
|
||||
sh "#{cc} -fexceptions -c -arch #{arch} \"#{asm}\" -o \"#{arch_obj}\""
|
||||
end
|
||||
|
||||
[asm].each { |x| File.unlink(x) } unless ENV['keep_temps']
|
||||
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
|
||||
|
||||
any_obj_file_built = true
|
||||
[obj, init_func]
|
||||
end
|
||||
|
||||
# Resolve file dependencies.
|
||||
if config.detect_dependencies == true
|
||||
config.dependencies = Dependency.new(config.files - config.exclude_from_detect_dependencies, config.dependencies).run
|
||||
end
|
||||
|
||||
parallel = ParallelBuilder.new(objs_build_dir, build_file)
|
||||
parallel.files = config.ordered_build_files
|
||||
parallel.files += config.spec_files if config.spec_mode
|
||||
parallel.run
|
||||
|
||||
objs = app_objs = parallel.objects
|
||||
spec_objs = []
|
||||
if config.spec_mode
|
||||
app_objs = objs[0...config.ordered_build_files.size]
|
||||
spec_objs = objs[-(config.spec_files.size)..-1]
|
||||
end
|
||||
|
||||
FileUtils.touch(objs_build_dir) if any_obj_file_built
|
||||
|
||||
# Generate init file.
|
||||
init_txt = <<EOS
|
||||
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_define_global_const(const char *, void *);
|
||||
void rb_rb2oc_exc_handler(void);
|
||||
void rb_exit(int);
|
||||
EOS
|
||||
app_objs.each do |_, init_func|
|
||||
init_txt << "void #{init_func}(void *, void *);\n"
|
||||
end
|
||||
init_txt << <<EOS
|
||||
}
|
||||
|
||||
extern "C"
|
||||
void
|
||||
RubyMotionInit(int argc, char **argv)
|
||||
{
|
||||
static bool initialized = false;
|
||||
if (!initialized) {
|
||||
ruby_init();
|
||||
ruby_init_loadpath();
|
||||
if (argc > 0) {
|
||||
const char *progname = argv[0];
|
||||
ruby_script(progname);
|
||||
}
|
||||
#if !__LP64__
|
||||
try {
|
||||
#endif
|
||||
void *self = rb_vm_top_self();
|
||||
EOS
|
||||
init_txt << config.define_global_env_txt
|
||||
app_objs.each do |_, init_func|
|
||||
init_txt << "#{init_func}(self, 0);\n"
|
||||
end
|
||||
init_txt << <<EOS
|
||||
#if !__LP64__
|
||||
}
|
||||
catch (...) {
|
||||
rb_rb2oc_exc_handler();
|
||||
}
|
||||
#endif
|
||||
initialized = true;
|
||||
}
|
||||
}
|
||||
EOS
|
||||
|
||||
# Compile init file.
|
||||
init = File.join(objs_build_dir, 'init.mm')
|
||||
init_o = File.join(objs_build_dir, 'init.o')
|
||||
if !(File.exist?(init) and File.exist?(init_o) and File.read(init) == init_txt)
|
||||
File.open(init, 'w') { |io| io.write(init_txt) }
|
||||
sh "#{cxx} \"#{init}\" #{config.cflags(platform, true)} -c -o \"#{init_o}\""
|
||||
end
|
||||
|
||||
librubymotion = File.join(datadir, platform, 'librubymotion-static.a')
|
||||
if static_library
|
||||
# Create a static archive with all object files + the runtime.
|
||||
lib = File.join(config.versionized_build_dir(platform), config.name + '.a')
|
||||
App.info 'Create', lib
|
||||
objs_list = objs.map { |path, _| path }.unshift(init_o, *config.frameworks_stubs_objects(platform)).map { |x| "\"#{x}\"" }.join(' ')
|
||||
sh "/usr/bin/libtool -static \"#{librubymotion}\" #{objs_list} -o \"#{lib}\""
|
||||
return lib
|
||||
end
|
||||
|
||||
# Generate main file.
|
||||
main_txt = config.main_cpp_file_txt(spec_objs)
|
||||
|
||||
# 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)
|
||||
unless File.exist?(File.dirname(main_exec))
|
||||
App.info 'Create', File.dirname(main_exec)
|
||||
FileUtils.mkdir_p(File.dirname(main_exec))
|
||||
end
|
||||
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 vendor_libs.any? { |lib| File.mtime(lib) > File.mtime(main_exec) } \
|
||||
or File.mtime(librubymotion) > File.mtime(main_exec)
|
||||
App.info 'Link', main_exec
|
||||
objs_list = objs.map { |path, _| path }.unshift(init_o, main_o, *config.frameworks_stubs_objects(platform)).map { |x| "\"#{x}\"" }.join(' ')
|
||||
framework_search_paths = (config.framework_search_paths + (embedded_frameworks + external_frameworks).map { |x| File.dirname(x) }).uniq.map { |x| "-F '#{File.expand_path(x)}'" }.join(' ')
|
||||
frameworks = (config.frameworks_dependencies + (embedded_frameworks + external_frameworks).map { |x| File.basename(x, '.framework') }).map { |x| "-framework #{x}" }.join(' ')
|
||||
weak_frameworks = config.weak_frameworks.map { |x| "-weak_framework #{x}" }.join(' ')
|
||||
vendor_libs = config.vendor_projects.inject([]) do |libs, vendor_project|
|
||||
libs << vendor_project.libs.map { |x|
|
||||
(vendor_project.opts[:force_load] ? '-force_load ' : '-ObjC ') + "\"#{x}\""
|
||||
}
|
||||
end.join(' ')
|
||||
linker_option = begin
|
||||
m = config.deployment_target.match(/(\d+)/)
|
||||
if m[0].to_i < 7
|
||||
"-stdlib=libstdc++"
|
||||
end
|
||||
end || ""
|
||||
sh "#{cxx} -o \"#{main_exec}\" #{objs_list} #{config.ldflags(platform)} -L#{File.join(datadir, platform)} -lrubymotion-static -lobjc -licucore #{linker_option} #{framework_search_paths} #{frameworks} #{weak_frameworks} #{config.libs.join(' ')} #{vendor_libs}"
|
||||
main_exec_created = true
|
||||
|
||||
# Change the install name of embedded frameworks.
|
||||
embedded_frameworks.each do |path|
|
||||
res = `/usr/bin/otool -L \"#{main_exec}\"`.scan(/(.*#{File.basename(path)}.*)\s\(/)
|
||||
if res and res[0] and res[0][0]
|
||||
old_path = res[0][0].strip
|
||||
if platform == "MacOSX"
|
||||
exec_path = "@executable_path/../Frameworks/"
|
||||
else
|
||||
exec_path = "@executable_path/Frameworks/"
|
||||
end
|
||||
new_path = exec_path + old_path.scan(/#{File.basename(path)}.*/)[0]
|
||||
sh "/usr/bin/install_name_tool -change \"#{old_path}\" \"#{new_path}\" \"#{main_exec}\""
|
||||
else
|
||||
App.warn "Cannot locate and fix install name path of embedded framework `#{path}' in executable `#{main_exec}', application might not start"
|
||||
end
|
||||
end
|
||||
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.
|
||||
config.resources_dirs.each do |dir|
|
||||
if File.exist?(dir)
|
||||
ib_resources = []
|
||||
ib_resources.concat((Dir.glob(File.join(dir, '**', '*.xib')) + Dir.glob(File.join(dir, '*.lproj', '*.xib'))).map { |xib| [xib, xib.sub(/\.xib$/, '.nib')] })
|
||||
ib_resources.concat(Dir.glob(File.join(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
|
||||
end
|
||||
|
||||
preserve_resources = []
|
||||
|
||||
# Compile Asset Catalog bundles.
|
||||
assets_bundles = config.assets_bundles
|
||||
unless assets_bundles.empty?
|
||||
app_icons_asset_bundle = config.app_icons_asset_bundle
|
||||
if app_icons_asset_bundle
|
||||
app_icons_info_plist_path = config.app_icons_info_plist_path(platform)
|
||||
app_icons_options = "--output-partial-info-plist \"#{app_icons_info_plist_path}\" " \
|
||||
"--app-icon \"#{config.app_icon_name_from_asset_bundle}\""
|
||||
end
|
||||
|
||||
App.info 'Compile', assets_bundles.join(", ")
|
||||
app_resources_dir = File.expand_path(config.app_resources_dir(platform))
|
||||
FileUtils.mkdir_p(app_resources_dir)
|
||||
cmd = "\"#{config.xcode_dir}/usr/bin/actool\" --output-format human-readable-text " \
|
||||
"--notices --warnings --platform #{config.deploy_platform.downcase} " \
|
||||
"--minimum-deployment-target #{config.deployment_target} " \
|
||||
"#{Array(config.device_family).map { |d| "--target-device #{d}" }.join(' ')} " \
|
||||
"#{app_icons_options} --compress-pngs --compile \"#{app_resources_dir}\" " \
|
||||
"\"#{assets_bundles.map { |f| File.expand_path(f) }.join('" "')}\""
|
||||
$stderr.puts(cmd) if App::VERBOSE
|
||||
actool_output = `#{cmd} 2>&1`
|
||||
$stderr.puts(actool_output) if App::VERBOSE
|
||||
|
||||
# Split output in warnings and compiled files
|
||||
actool_output, actool_compilation_results = actool_output.split('/* com.apple.actool.compilation-results */')
|
||||
actool_compiled_files = actool_compilation_results.strip.split("\n")
|
||||
if actool_document_warnings = actool_output.split('/* com.apple.actool.document.warnings */').last
|
||||
# Propagate warnings to the user.
|
||||
actool_document_warnings.strip.split("\n").each { |w| App.warn(w) }
|
||||
end
|
||||
|
||||
# Remove the partial Info.plist line and preserve all other assets.
|
||||
actool_compiled_files.delete(app_icons_info_plist_path) if app_icons_asset_bundle
|
||||
preserve_resources.concat(actool_compiled_files.map { |f| File.basename(f) })
|
||||
|
||||
config.configure_app_icons_from_asset_bundle(platform) if app_icons_asset_bundle
|
||||
end
|
||||
|
||||
# Compile CoreData Model resources and SpriteKit atlas files.
|
||||
config.resources_dirs.each do |dir|
|
||||
if File.exist?(dir)
|
||||
Dir.glob(File.join(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
|
||||
if cmd = config.spritekit_texture_atlas_compiler
|
||||
Dir.glob(File.join(dir, '*.atlas')).each do |atlas|
|
||||
if File.directory?(atlas)
|
||||
App.info 'Compile', atlas
|
||||
sh "\"#{cmd}\" \"#{atlas}\" \"#{bundle_path}\""
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Copy embedded frameworks.
|
||||
unless embedded_frameworks.empty?
|
||||
app_frameworks = File.join(config.app_bundle(platform), 'Frameworks')
|
||||
FileUtils.mkdir_p(app_frameworks)
|
||||
embedded_frameworks.each do |src_path|
|
||||
dest_path = File.join(app_frameworks, File.basename(src_path))
|
||||
if !File.exist?(dest_path) or File.mtime(src_path) > File.mtime(dest_path)
|
||||
App.info 'Copy', src_path
|
||||
FileUtils.cp_r(src_path, dest_path)
|
||||
end
|
||||
end
|
||||
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(platform)) }
|
||||
sh "/usr/bin/plutil -convert binary1 \"#{bundle_info_plist}\""
|
||||
end
|
||||
|
||||
# Copy resources, handle subdirectories.
|
||||
app_resources_dir = config.app_resources_dir(platform)
|
||||
FileUtils.mkdir_p(app_resources_dir)
|
||||
reserved_app_bundle_files = [
|
||||
'_CodeSignature/CodeResources', 'CodeResources', 'embedded.mobileprovision',
|
||||
'Info.plist', 'PkgInfo', 'ResourceRules.plist',
|
||||
convert_filesystem_encoding(config.name)
|
||||
]
|
||||
resources_exclude_extnames = ['.xib', '.storyboard', '.xcdatamodeld', '.atlas', '.xcassets']
|
||||
resources_paths = []
|
||||
config.resources_dirs.each do |dir|
|
||||
if File.exist?(dir)
|
||||
resources_paths << Dir.chdir(dir) do
|
||||
Dir.glob('**{,/*/**}/*').reject do |x|
|
||||
# Find files with extnames to exclude or files inside bundles to
|
||||
# exclude (e.g. xcassets).
|
||||
File.extname(x) == '.lproj' ||
|
||||
resources_exclude_extnames.include?(File.extname(x)) ||
|
||||
resources_exclude_extnames.include?(File.extname(x.split('/').first))
|
||||
end.map { |file| File.join(dir, file) }
|
||||
end
|
||||
end
|
||||
end
|
||||
resources_paths.flatten!
|
||||
resources_paths.each do |res_path|
|
||||
res = path_on_resources_dirs(config.resources_dirs, res_path)
|
||||
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(app_resources_dir, res)
|
||||
copy_resource(res_path, dest_path)
|
||||
end
|
||||
|
||||
# Compile all .strings files
|
||||
Dir.glob(File.join(app_resources_dir, '{,**/}*.strings')).each do |strings_file|
|
||||
compile_resource_to_binary_plist(strings_file)
|
||||
end
|
||||
|
||||
# Optional support for #eval (OSX-only).
|
||||
if config.respond_to?(:eval_support) and config.eval_support
|
||||
repl_dylib_path = File.join(datadir, '..', 'librubymotion-repl.dylib')
|
||||
dest_path = File.join(app_resources_dir, File.basename(repl_dylib_path))
|
||||
copy_resource(repl_dylib_path, dest_path)
|
||||
preserve_resources << File.basename(repl_dylib_path)
|
||||
end
|
||||
|
||||
# Delete old resource files.
|
||||
resources_files = resources_paths.map { |x| path_on_resources_dirs(config.resources_dirs, x) }
|
||||
Dir.chdir(app_resources_dir) 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)
|
||||
next if preserve_resources.include?(File.basename(bundle_res))
|
||||
App.warn "File `#{bundle_res}' found in app bundle but not in resource directories, 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 distribution mode.
|
||||
if main_exec_created and (config.distribution_mode or ENV['__strip__'])
|
||||
App.info "Strip", main_exec
|
||||
sh "#{config.locate_binary('strip')} #{config.strip_args} \"#{main_exec}\""
|
||||
end
|
||||
end
|
||||
end
|
||||
end; end
|
||||
536
lib/motion/project/template/ios-extension-config.rb
Normal file
536
lib/motion/project/template/ios-extension-config.rb
Normal file
@@ -0,0 +1,536 @@
|
||||
# encoding: utf-8
|
||||
|
||||
# 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/project/xcode_config'
|
||||
|
||||
module Motion; module Project;
|
||||
class IOSExtensionConfig < XcodeConfig
|
||||
register :'ios-extension'
|
||||
# register :'ios-action-extension'
|
||||
# register :'ios-custom-keyboard'
|
||||
# register :'ios-document-picker'
|
||||
# register :'ios-photo-editing'
|
||||
# register :'ios-share-extension'
|
||||
# register :'ios-today-extension'
|
||||
# register :'ios-file-provider'
|
||||
|
||||
# variable :device_family, :interface_orientations, :background_modes,
|
||||
# :status_bar_style, :icons, :prerendered_icon, :fonts, :seed_id,
|
||||
# :provisioning_profile, :manifest_assets
|
||||
|
||||
variable :device_family, :manifest_assets
|
||||
|
||||
def initialize(project_dir, build_mode)
|
||||
super
|
||||
@frameworks = ['UIKit', 'Foundation', 'CoreGraphics']
|
||||
@device_family = :iphone
|
||||
# @interface_orientations = [:portrait, :landscape_left, :landscape_right]
|
||||
# @background_modes = []
|
||||
# @status_bar_style = :default
|
||||
# @icons = []
|
||||
# @prerendered_icon = false
|
||||
@manifest_assets = []
|
||||
end
|
||||
|
||||
def platforms; ['iPhoneSimulator', 'iPhoneOS']; end
|
||||
def local_platform; 'iPhoneSimulator'; end
|
||||
def deploy_platform; 'iPhoneOS'; end
|
||||
|
||||
def archs
|
||||
@archs ||= begin
|
||||
# By default, do not build with 64-bit, as it's still experimental.
|
||||
archs = super
|
||||
archs['iPhoneSimulator'].delete('x86_64')
|
||||
archs['iPhoneOS'].delete('arm64')
|
||||
archs
|
||||
end
|
||||
end
|
||||
|
||||
# def app_icons_info_plist_path(platform)
|
||||
# File.expand_path(File.join(versionized_build_dir(platform), 'AppIcon.plist'))
|
||||
# end
|
||||
|
||||
# def configure_app_icons_from_asset_bundle(platform)
|
||||
# path = app_icons_info_plist_path(platform)
|
||||
# if File.exist?(path)
|
||||
# content = `/usr/libexec/PlistBuddy -c 'Print :CFBundleIcons:CFBundlePrimaryIcon:CFBundleIconFiles' "#{path}"`.strip
|
||||
# self.icons = content.split("\n")[1..-2].map(&:strip)
|
||||
# end
|
||||
# end
|
||||
|
||||
def validate
|
||||
# icons
|
||||
# if !(icons.is_a?(Array) and icons.all? { |x| x.is_a?(String) })
|
||||
# App.fail "app.icons should be an array of strings."
|
||||
# end
|
||||
|
||||
# manifest_assets
|
||||
if !(manifest_assets.is_a?(Array) and manifest_assets.all? { |x| x.is_a?(Hash) and x.keys.include?(:kind) and x.keys.include?(:url) })
|
||||
App.fail "app.manifest_assets should be an array of hashes with values for the :kind and :url keys"
|
||||
end
|
||||
|
||||
super
|
||||
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')
|
||||
|
||||
execs.each do |exec|
|
||||
paths.each do |path|
|
||||
cc = File.join(path, exec)
|
||||
return escape_path(cc) if File.exist?(cc)
|
||||
end
|
||||
end
|
||||
App.fail "Can't locate compilers for platform `#{platform}'"
|
||||
end
|
||||
|
||||
def archive_extension
|
||||
'.ipa'
|
||||
end
|
||||
|
||||
def codesign_certificate
|
||||
super('iPhone')
|
||||
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 distribution_mode
|
||||
dict['application-identifier'] ||= seed_id + '.' + identifier
|
||||
else
|
||||
# Required for gdb.
|
||||
dict['get-task-allow'] = true if dict['get-task-allow'].nil?
|
||||
end
|
||||
Motion::PropertyList.to_s(dict)
|
||||
end
|
||||
|
||||
def common_flags(platform)
|
||||
simulator_version = begin
|
||||
flag = " -miphoneos-version-min=#{deployment_target}"
|
||||
if platform == "iPhoneSimulator"
|
||||
ver = xcode_version[0].match(/(\d+)/)
|
||||
if ver[0].to_i >= 5
|
||||
flag = " -mios-simulator-version-min=#{deployment_target}"
|
||||
end
|
||||
end
|
||||
flag
|
||||
end
|
||||
super + simulator_version
|
||||
end
|
||||
|
||||
def cflags(platform, cplusplus)
|
||||
super + " -g -fobjc-legacy-dispatch -fobjc-abi-version=2"
|
||||
end
|
||||
|
||||
def ldflags(platform)
|
||||
ldflags = super
|
||||
ldflags << " -fobjc-arc" if deployment_target < '5.0'
|
||||
ldflags
|
||||
end
|
||||
|
||||
def bridgesupport_flags
|
||||
extra_flags = (OSX_VERSION >= 10.7 && sdk_version < '7.0') ? '--no-64-bit' : ''
|
||||
"--format complete #{extra_flags}"
|
||||
end
|
||||
|
||||
def bridgesupport_cflags
|
||||
a = sdk_version.scan(/(\d+)\.(\d+)/)[0]
|
||||
sdk_version_headers = ((a[0].to_i * 10000) + (a[1].to_i * 100)).to_s
|
||||
"-miphoneos-version-min=#{sdk_version} -D__ENVIRONMENT_IPHONE_OS_VERSION_MIN_REQUIRED__=#{sdk_version_headers}"
|
||||
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(device_name, family, target, retina)
|
||||
# device = case family
|
||||
# when :iphone, 1
|
||||
# "iPhone"
|
||||
# when :ipad, 2
|
||||
# "iPad"
|
||||
# end
|
||||
|
||||
# ver = xcode_version[0].match(/(\d+)/)
|
||||
# case ver[0].to_i
|
||||
# when 6
|
||||
# (device_name.nil?) ? device + device_retina_xcode6_string(family, target, retina) : device_name
|
||||
# when 5
|
||||
# device + device_retina_xcode5_string(family, target, retina)
|
||||
# else
|
||||
# device + device_retina_xcode4_string(family, target, retina)
|
||||
# end
|
||||
# end
|
||||
|
||||
# def device_retina_xcode4_string(family, target, retina)
|
||||
# case retina
|
||||
# when 'true'
|
||||
# (family == 1 and target >= '6.0') ? ' (Retina 4-inch)' : ' (Retina)'
|
||||
# when '3.5'
|
||||
# ' (Retina 3.5-inch)'
|
||||
# when '4'
|
||||
# ' (Retina 4-inch)'
|
||||
# else
|
||||
# ''
|
||||
# end
|
||||
# end
|
||||
|
||||
# def device_retina_xcode5_string(family, target, retina)
|
||||
# retina4_string = begin
|
||||
# if (target >= '7.0' && App.config.archs['iPhoneSimulator'].include?("x86_64"))
|
||||
# " Retina (4-inch 64-bit)"
|
||||
# else
|
||||
# " Retina (4-inch)"
|
||||
# end
|
||||
# end
|
||||
|
||||
# case retina
|
||||
# when 'true'
|
||||
# (family == 1 and target >= '6.0') ? retina4_string : ' Retina'
|
||||
# when '3.5'
|
||||
# ' Retina (3.5-inch)'
|
||||
# when '4'
|
||||
# ' Retina (4-inch)'
|
||||
# else
|
||||
# if target < '7.0'
|
||||
# ''
|
||||
# else
|
||||
# (family == 1) ? retina4_string : ''
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
|
||||
# def device_retina_xcode6_string(family, target, retina)
|
||||
# case retina
|
||||
# when '3.5'
|
||||
# ' 4s'
|
||||
# when '4'
|
||||
# ' 5s'
|
||||
# else
|
||||
# (family == 1) ? ' 5s' : ' Retina'
|
||||
# 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 background_modes_consts
|
||||
# @background_modes.map do |mode|
|
||||
# case mode
|
||||
# when :audio then 'audio'
|
||||
# when :location then 'location'
|
||||
# when :voip then 'voip'
|
||||
# when :newsstand_content then 'newsstand-content'
|
||||
# when :external_accessory then 'external-accessory'
|
||||
# when :bluetooth_central then 'bluetooth-central'
|
||||
# else
|
||||
# App.fail "Unknown background_modes value: `#{mode}'"
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
|
||||
# def status_bar_style_const
|
||||
# case @status_bar_style
|
||||
# when :default then 'UIStatusBarStyleDefault'
|
||||
# when :black_translucent then 'UIStatusBarStyleBlackTranslucent'
|
||||
# when :black_opaque then 'UIStatusBarStyleBlackOpaque'
|
||||
# when :light_content then 'UIStatusBarStyleLightContent'
|
||||
# else
|
||||
# App.fail "Unknown status_bar_style value: `#{@status_bar_style}'"
|
||||
# end
|
||||
# end
|
||||
|
||||
# def device_id
|
||||
# @device_id ||= begin
|
||||
# deploy = File.join(App.config.bindir, 'ios/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 app_bundle(platform)
|
||||
File.join(versionized_build_dir(platform), bundle_name + '.appex')
|
||||
end
|
||||
|
||||
def app_bundle_executable(platform)
|
||||
File.join(app_bundle(platform), name)
|
||||
end
|
||||
|
||||
def app_resources_dir(platform)
|
||||
app_bundle(platform)
|
||||
end
|
||||
|
||||
# def fonts
|
||||
# @fonts ||= begin
|
||||
# resources_dirs.flatten.inject([]) do |fonts, dir|
|
||||
# if File.exist?(dir)
|
||||
# Dir.chdir(dir) do
|
||||
# fonts.concat(Dir.glob('*.{otf,ttf}'))
|
||||
# end
|
||||
# else
|
||||
# fonts
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
|
||||
def info_plist_data(platform)
|
||||
Motion::PropertyList.to_s({
|
||||
'MinimumOSVersion' => deployment_target,
|
||||
'CFBundleResourceSpecification' => 'ResourceRules.plist',
|
||||
'CFBundleSupportedPlatforms' => [deploy_platform],
|
||||
# 'CFBundleIconFiles' => icons,
|
||||
# 'CFBundleIcons' => {
|
||||
# 'CFBundlePrimaryIcon' => {
|
||||
# 'CFBundleIconFiles' => icons,
|
||||
# 'UIPrerenderedIcon' => prerendered_icon,
|
||||
# }
|
||||
# },
|
||||
# 'UIAppFonts' => fonts,
|
||||
# TODO temp hack to get ints for Instruments, but strings for normal builds.
|
||||
'UIDeviceFamily' => device_family_ints.map { |x| ENV['__USE_DEVICE_INT__'] ? x.to_i : x.to_s },
|
||||
# 'UISupportedInterfaceOrientations' => interface_orientations_consts,
|
||||
# 'UIStatusBarStyle' => status_bar_style_const,
|
||||
# 'UIBackgroundModes' => background_modes_consts,
|
||||
'DTXcode' => begin
|
||||
vers = xcode_version[0].gsub(/\./, '')
|
||||
if vers.length == 2
|
||||
'0' + vers + '0'
|
||||
else
|
||||
'0' + vers
|
||||
end
|
||||
end,
|
||||
'DTXcodeBuild' => xcode_version[1],
|
||||
'DTSDKName' => "#{platform.downcase}#{sdk_version}",
|
||||
'DTSDKBuild' => sdk_build_version(platform),
|
||||
'DTPlatformName' => platform.downcase,
|
||||
'DTCompiler' => 'com.apple.compilers.llvm.clang.1_0',
|
||||
'DTPlatformVersion' => sdk_version,
|
||||
'DTPlatformBuild' => sdk_build_version(platform),
|
||||
}.merge(generic_info_plist).merge(dt_info_plist).merge(info_plist))
|
||||
end
|
||||
|
||||
def manifest_plist_data
|
||||
return nil if manifest_assets.empty?
|
||||
Motion::PropertyList.to_s({
|
||||
'items' => [
|
||||
{ 'assets' => manifest_assets,
|
||||
'metadata' => {
|
||||
'bundle-identifier' => identifier,
|
||||
'bundle-version' => @version,
|
||||
'kind' => 'software',
|
||||
'title' => @name
|
||||
} }
|
||||
]
|
||||
})
|
||||
end
|
||||
|
||||
def supported_sdk_versions(versions)
|
||||
versions.reverse.find { |vers| File.exist?(datadir(vers)) }
|
||||
end
|
||||
|
||||
def sdk_build_version(platform)
|
||||
@sdk_build_version ||= begin
|
||||
sdk_path = sdk(platform)
|
||||
`xcodebuild -version -sdk '#{sdk_path}' ProductBuildVersion`.strip
|
||||
end
|
||||
end
|
||||
|
||||
# TODO datadir should not depend on the template name
|
||||
def datadir(target=deployment_target)
|
||||
File.join(motiondir, 'data', 'ios', target)
|
||||
end
|
||||
|
||||
def main_cpp_file_txt(spec_objs)
|
||||
main_txt = <<EOS
|
||||
#import <UIKit/UIKit.h>
|
||||
#include <objc/message.h>
|
||||
#include <dlfcn.h>
|
||||
|
||||
extern "C" {
|
||||
void rb_define_global_const(const char *, void *);
|
||||
void rb_rb2oc_exc_handler(void);
|
||||
void rb_exit(int);
|
||||
void RubyMotionInit(int argc, char **argv);
|
||||
EOS
|
||||
# if spec_mode
|
||||
# spec_objs.each do |_, init_func|
|
||||
# main_txt << "void #{init_func}(void *, void *);\n"
|
||||
# end
|
||||
# end
|
||||
main_txt << <<EOS
|
||||
}
|
||||
EOS
|
||||
|
||||
# if spec_mode
|
||||
# main_txt << <<EOS
|
||||
# @interface SpecLauncher : NSObject
|
||||
# @end
|
||||
|
||||
|
||||
|
||||
# @implementation SpecLauncher
|
||||
|
||||
# + (id)launcher
|
||||
# {
|
||||
# [UIApplication sharedApplication];
|
||||
|
||||
# // Enable simulator accessibility.
|
||||
# // Thanks http://www.stewgleadow.com/blog/2011/10/14/enabling-accessibility-for-ios-applications/
|
||||
# NSString *simulatorRoot = [[[NSProcessInfo processInfo] environment] objectForKey:@"IPHONE_SIMULATOR_ROOT"];
|
||||
# if (simulatorRoot != nil) {
|
||||
# void *appSupportLibrary = dlopen([[simulatorRoot stringByAppendingPathComponent:@"/System/Library/PrivateFrameworks/AppSupport.framework/AppSupport"] fileSystemRepresentation], RTLD_LAZY);
|
||||
# CFStringRef (*copySharedResourcesPreferencesDomainForDomain)(CFStringRef domain) = (CFStringRef (*)(CFStringRef)) dlsym(appSupportLibrary, "CPCopySharedResourcesPreferencesDomainForDomain");
|
||||
|
||||
# if (copySharedResourcesPreferencesDomainForDomain != NULL) {
|
||||
# CFStringRef accessibilityDomain = copySharedResourcesPreferencesDomainForDomain(CFSTR("com.apple.Accessibility"));
|
||||
|
||||
# if (accessibilityDomain != NULL) {
|
||||
# CFPreferencesSetValue(CFSTR("ApplicationAccessibilityEnabled"), kCFBooleanTrue, accessibilityDomain, kCFPreferencesAnyUser, kCFPreferencesAnyHost);
|
||||
# CFRelease(accessibilityDomain);
|
||||
# }
|
||||
# }
|
||||
# }
|
||||
|
||||
# // Load the UIAutomation framework.
|
||||
# dlopen("/Developer/Library/PrivateFrameworks/UIAutomation.framework/UIAutomation", RTLD_LOCAL);
|
||||
|
||||
# SpecLauncher *launcher = [[self alloc] init];
|
||||
# [[NSNotificationCenter defaultCenter] addObserver:launcher selector:@selector(appLaunched:) name:UIApplicationDidBecomeActiveNotification object:nil];
|
||||
# return launcher;
|
||||
# }
|
||||
|
||||
# - (void)appLaunched:(id)notification
|
||||
# {
|
||||
# // unregister observer to avoid duplicate invocation
|
||||
# [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidBecomeActiveNotification object:nil];
|
||||
# // Give a bit of time for the simulator to attach...
|
||||
# [self performSelector:@selector(runSpecs) withObject:nil afterDelay:0.3];
|
||||
# }
|
||||
|
||||
# - (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];
|
||||
int retval = 0;
|
||||
EOS
|
||||
if ENV['ARR_CYCLES_DISABLE']
|
||||
main_txt << <<EOS
|
||||
setenv("ARR_CYCLES_DISABLE", "1", true);
|
||||
EOS
|
||||
end
|
||||
# main_txt << "[SpecLauncher launcher];\n" if spec_mode
|
||||
main_txt << <<EOS
|
||||
RubyMotionInit(argc, argv);
|
||||
EOS
|
||||
main_txt << <<EOS
|
||||
// retval = (*NSExtensionMain)();
|
||||
dlopen("/System/Library/PrivateFrameworks/PlugInKit.framework/PlugInKit", 0x1);
|
||||
objc_msgSend(NSClassFromString(@"PKService"), @selector(_defaultRun:arguments:), argc, argv);
|
||||
// retval = UIApplicationMain(argc, argv, nil, @"#{delegate_class}");
|
||||
rb_exit(retval);
|
||||
[pool release];
|
||||
return 0;
|
||||
}
|
||||
EOS
|
||||
end
|
||||
end
|
||||
end; end
|
||||
328
lib/motion/project/template/ios-extension.rb
Normal file
328
lib/motion/project/template/ios-extension.rb
Normal file
@@ -0,0 +1,328 @@
|
||||
# encoding: utf-8
|
||||
|
||||
# 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/project/app'
|
||||
require 'motion/util/version'
|
||||
|
||||
App = Motion::Project::App
|
||||
App.template = :'ios-extension'
|
||||
|
||||
require 'motion/project'
|
||||
require 'motion/project/template/ios-extension-config'
|
||||
require 'motion/project/template/ios-extension-builder'
|
||||
|
||||
desc "Build the project, then run the simulator"
|
||||
task :default => :simulator
|
||||
|
||||
desc "Build everything"
|
||||
task :build => ['build:simulator', 'build:device']
|
||||
|
||||
namespace :build do
|
||||
def pre_build_actions(platform)
|
||||
# TODO: Ensure Info.plist gets regenerated on each build so it has ints for
|
||||
# Instruments and strings for normal builds.
|
||||
rm_f File.join(App.config.app_bundle(platform), 'Info.plist')
|
||||
|
||||
# TODO this should go into a iOS specific Builder class which performs this
|
||||
# check before building.
|
||||
App.config.resources_dirs.flatten.each do |dir|
|
||||
next unless File.exist?(dir)
|
||||
Dir.entries(dir).grep(/^Resources$/i).each do |basename|
|
||||
path = File.join(dir, basename)
|
||||
if File.directory?(path)
|
||||
suggestion = basename == 'Resources' ? 'Assets' : 'assets'
|
||||
App.fail "An iOS application cannot be installed if it contains a " \
|
||||
"directory called `resources'. Please rename the " \
|
||||
"directory at path `#{path}' to, for instance, " \
|
||||
"`#{File.join(dir, suggestion)}'."
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
desc "Build the simulator version"
|
||||
task :simulator do
|
||||
pre_build_actions('iPhoneSimulator')
|
||||
App.build('iPhoneSimulator')
|
||||
end
|
||||
|
||||
desc "Build the device version"
|
||||
task :device do
|
||||
pre_build_actions('iPhoneOS')
|
||||
App.build('iPhoneOS')
|
||||
# App.codesign('iPhoneOS')
|
||||
end
|
||||
end
|
||||
|
||||
# desc "Run the simulator"
|
||||
# task :simulator do
|
||||
# deployment_target = Motion::Util::Version.new(App.config.deployment_target)
|
||||
|
||||
# target = ENV['target']
|
||||
# if target && Motion::Util::Version.new(target) < deployment_target
|
||||
# App.fail "It is not possible to simulate an SDK version (#{target}) " \
|
||||
# "lower than the app's deployment target (#{deployment_target})"
|
||||
# end
|
||||
# target ||= App.config.sdk_version
|
||||
|
||||
# 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']
|
||||
# if retina && retina.strip.downcase == 'false' && family_int == 1 # iPhone only
|
||||
# ios_7 = Motion::Util::Version.new('7')
|
||||
# if Motion::Util::Version.new(target) >= ios_7
|
||||
# if deployment_target < ios_7
|
||||
# App.fail "In order to simulate on a non-retina device, please use " \
|
||||
# "the `target' option to specify the simulated SDK version. " \
|
||||
# "E.g.: rake target=#{deployment_target} retina=false"
|
||||
# else
|
||||
# App.fail "It is not possible to simulate on a non-retina device " \
|
||||
# "when not deploying to iOS < 7."
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
|
||||
# if ENV['background_fetch']
|
||||
# modes = App.config.info_plist['UIBackgroundModes']
|
||||
# if modes.nil? || !modes.include?('fetch')
|
||||
# App.fail "In order to launch the application in `background fetch' " \
|
||||
# "mode, you will need to configure your application to enable " \
|
||||
# "it by adding: app.info_plist['UIBackgroundModes'] = ['fetch']"
|
||||
# end
|
||||
# end
|
||||
|
||||
# unless ENV["skip_build"]
|
||||
# Rake::Task["build:simulator"].invoke
|
||||
# end
|
||||
# app = App.config.app_bundle('iPhoneSimulator')
|
||||
|
||||
# if ENV['TMUX']
|
||||
# tmux_default_command = `tmux show-options -g default-command`.strip
|
||||
# unless tmux_default_command.include?("reattach-to-user-namespace")
|
||||
# App.warn(<<END
|
||||
|
||||
# It appears you are using tmux without 'reattach-to-user-namespace', the simulator might not work properly. You can either disable tmux or run the following commands:
|
||||
|
||||
# $ brew install reattach-to-user-namespace
|
||||
# $ echo 'set-option -g default-command "reattach-to-user-namespace -l $SHELL"' >> ~/.tmux.conf
|
||||
|
||||
# END
|
||||
# )
|
||||
# end
|
||||
# end
|
||||
|
||||
# # 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
|
||||
|
||||
# device_name = ENV["device_name"]
|
||||
# simulate_device = App.config.device_family_string(device_name, family_int, target, retina)
|
||||
|
||||
# # Launch the simulator.
|
||||
# xcode = App.config.xcode_dir
|
||||
# env = "DYLD_FRAMEWORK_PATH=\"#{xcode}/../Frameworks\":\"#{xcode}/../OtherFrameworks\""
|
||||
# env << ' SIM_SPEC_MODE=1' if App.config.spec_mode
|
||||
# sim = File.join(App.config.bindir, 'ios/sim')
|
||||
# debug = (ENV['debug'] ? 1 : (App.config.spec_mode ? '0' : '2'))
|
||||
# app_args = (ENV['args'] or '')
|
||||
# App.info 'Simulate', app
|
||||
# at_exit { system("stty echo") } if $stdout.tty? # Just in case the simulator launcher crashes and leaves the terminal without echo.
|
||||
# Signal.trap(:INT) { } if ENV['debug']
|
||||
# system "#{env} #{sim} #{debug} #{family_int} \"#{simulate_device}\" #{target} \"#{xcode}\" \"#{app}\" #{app_args}"
|
||||
# App.config.print_crash_message if $?.exitstatus != 0 && !App.config.spec_mode
|
||||
# exit($?.exitstatus)
|
||||
# end
|
||||
|
||||
# desc "Create an .ipa archive"
|
||||
# task :archive => ['build:device'] do
|
||||
# App.archive
|
||||
# end
|
||||
|
||||
# namespace :archive do
|
||||
# desc "Create an .ipa archive for distribution (AppStore)"
|
||||
# task :distribution do
|
||||
# App.config_without_setup.build_mode = :release
|
||||
# App.config_without_setup.distribution_mode = true
|
||||
# Rake::Task["archive"].invoke
|
||||
# end
|
||||
# end
|
||||
|
||||
# desc "Same as 'spec:simulator'"
|
||||
# task :spec => ['spec:simulator']
|
||||
|
||||
# namespace :spec do
|
||||
# desc "Run the test/spec suite on the simulator"
|
||||
# task :simulator do
|
||||
# App.config_without_setup.spec_mode = true
|
||||
# Rake::Task["simulator"].invoke
|
||||
# end
|
||||
|
||||
# desc "Run the test/spec suite on the device"
|
||||
# task :device do
|
||||
# App.config_without_setup.spec_mode = true
|
||||
# ENV['debug'] ||= '1'
|
||||
# Rake::Task["device"].invoke
|
||||
# end
|
||||
# end
|
||||
|
||||
# $deployed_app_path = nil
|
||||
|
||||
# desc "Deploy on the device"
|
||||
# task :device => :archive do
|
||||
# App.info 'Deploy', App.config.archive
|
||||
# device_id = (ENV['id'] or App.config.device_id)
|
||||
# unless App.config.provisioned_devices.include?(device_id)
|
||||
# App.fail "Device ID `#{device_id}' not provisioned in profile `#{App.config.provisioning_profile}'"
|
||||
# end
|
||||
# env = "XCODE_DIR=\"#{App.config.xcode_dir}\""
|
||||
# deploy = File.join(App.config.bindir, 'ios/deploy')
|
||||
# flags = Rake.application.options.trace ? '-d' : ''
|
||||
# Signal.trap(:INT) { } if ENV['debug']
|
||||
# cmd = "#{env} #{deploy} #{flags} \"#{device_id}\" \"#{App.config.archive}\""
|
||||
# if ENV['install_only']
|
||||
# $deployed_app_path = `#{cmd}`.strip
|
||||
# else
|
||||
# sh(cmd)
|
||||
# end
|
||||
# end
|
||||
|
||||
# desc "Create a .a static library"
|
||||
# task :static do
|
||||
# libs = %w{iPhoneSimulator iPhoneOS}.map do |platform|
|
||||
# '"' + App.build(platform, :static => true) + '"'
|
||||
# end
|
||||
# fat_lib = File.join(App.config.build_dir, App.config.name + '-universal.a')
|
||||
# App.info 'Create', fat_lib
|
||||
# sh "/usr/bin/lipo -create #{libs.join(' ')} -output \"#{fat_lib}\""
|
||||
# end
|
||||
|
||||
# IOS_SIM_INSTRUMENTS_TEMPLATES = [
|
||||
# 'Allocations', 'Leaks', 'Activity Monitor',
|
||||
# 'Zombies', 'Time Profiler', 'System Trace', 'Automation',
|
||||
# 'File Activity', 'Core Data'
|
||||
# ]
|
||||
|
||||
# IOS_DEVICE_INSTRUMENTS_TEMPLATES = [
|
||||
# 'Allocations', 'Leaks', 'Activity Monitor',
|
||||
# 'Zombies', 'Time Profiler', 'System Trace', 'Automation',
|
||||
# 'Energy Diagnostics', 'Network', 'System Usage', 'Core Animation',
|
||||
# 'OpenGL ES Driver', 'OpenGL ES Analysis'
|
||||
# ]
|
||||
|
||||
# desc "Same as profile:simulator"
|
||||
# task :profile => ['profile:simulator']
|
||||
|
||||
# namespace :profile do
|
||||
# desc "Run a build on the simulator through Instruments"
|
||||
# task :simulator do
|
||||
# ENV['__USE_DEVICE_INT__'] = '1'
|
||||
# Rake::Task['build:simulator'].invoke
|
||||
|
||||
# plist = App.config.profiler_config_plist('iPhoneSimulator', ENV['args'], ENV['template'], IOS_SIM_INSTRUMENTS_TEMPLATES)
|
||||
# plist['com.apple.xcode.simulatedDeviceFamily'] = App.config.device_family_ints.first
|
||||
# plist['com.apple.xcode.SDKPath'] = App.config.sdk('iPhoneSimulator')
|
||||
# plist['optionalData']['launchOptions']['architectureType'] = 0
|
||||
# plist['deviceIdentifier'] = App.config.sdk('iPhoneSimulator')
|
||||
# App.profile('iPhoneSimulator', plist)
|
||||
# end
|
||||
|
||||
# namespace :simulator do
|
||||
# desc 'List all built-in Simulator Instruments templates'
|
||||
# task :templates do
|
||||
# puts "Built-in Simulator Instruments templates:"
|
||||
# IOS_SIM_INSTRUMENTS_TEMPLATES.each do |template|
|
||||
# puts "* #{template}"
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
|
||||
# desc "Run a build on the device through Instruments"
|
||||
# task :device do
|
||||
# ENV['__USE_DEVICE_INT__'] = '1'
|
||||
|
||||
# # Create a build that allows debugging but doesn't start a debugger on deploy.
|
||||
# App.config.entitlements['get-task-allow'] = true
|
||||
# ENV['install_only'] = '1'
|
||||
# Rake::Task['device'].invoke
|
||||
|
||||
# if $deployed_app_path.nil? || $deployed_app_path.empty?
|
||||
# App.fail 'Unable to determine remote app path'
|
||||
# end
|
||||
|
||||
# plist = App.config.profiler_config_plist('iPhoneOS', ENV['args'], ENV['template'], IOS_DEVICE_INSTRUMENTS_TEMPLATES, false)
|
||||
# plist['absolutePathOfLaunchable'] = File.join($deployed_app_path, App.config.bundle_name)
|
||||
# plist['deviceIdentifier'] = (ENV['id'] or App.config.device_id)
|
||||
# App.profile('iPhoneOS', plist)
|
||||
# end
|
||||
|
||||
# namespace :device do
|
||||
# desc 'List all built-in device Instruments templates'
|
||||
# task :templates do
|
||||
# puts "Built-in device Instruments templates:"
|
||||
# IOS_DEVICE_INSTRUMENTS_TEMPLATES.each do |template|
|
||||
# puts "* #{template}"
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
|
||||
# desc "Same as crashlog:simulator"
|
||||
# task :crashlog => 'crashlog:simulator'
|
||||
|
||||
# namespace :crashlog do
|
||||
# desc "Open the latest crash report generated by the app in the simulator"
|
||||
# task :simulator => :__local_crashlog
|
||||
|
||||
# desc "Retrieve and symbolicate crash logs generated by the app on the device, and open the latest generated one"
|
||||
# task :device do
|
||||
# device_id = (ENV['id'] or App.config.device_id)
|
||||
# crash_reports_dir = File.expand_path("~/Library/Logs/RubyMotion Device")
|
||||
# mkdir_p crash_reports_dir
|
||||
# deploy = File.join(App.config.bindir, 'ios/deploy')
|
||||
# env = "XCODE_DIR=\"#{App.config.xcode_dir}\" CRASH_REPORTS_DIR=\"#{crash_reports_dir}\""
|
||||
# flags = Rake.application.options.trace ? '-d' : ''
|
||||
# cmd = "#{env} #{deploy} -l #{flags} \"#{device_id}\" \"#{App.config.archive}\""
|
||||
# system(cmd)
|
||||
|
||||
# # Open the latest generated one.
|
||||
# logs = Dir.glob(File.join(crash_reports_dir, "#{App.config.name}_*"))
|
||||
# unless logs.empty?
|
||||
# sh "/usr/bin/open -a Console \"#{logs.last}\""
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
Motion::Project::App.setup do |app|
|
||||
# Use `rake config' to see complete project settings.
|
||||
app.extension do |ext|
|
||||
ext.name = '<%= extension_name %>'
|
||||
ext.type = "fileprovider-nonui"
|
||||
# TODO: its not inside attributes!
|
||||
ext.attributes = {
|
||||
"NSExtensionFileProviderDocumentGroup" => "group.sdf.Extension-Test"
|
||||
}
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,73 @@
|
||||
class FileProvider < NSFileProviderExtension
|
||||
|
||||
def fileCoordinator
|
||||
fileCoordinator = NSFileCoordinator.alloc.init
|
||||
fileCoordinator.setPurposeIdentifier(self.providerIdentifier)
|
||||
fileCoordinator
|
||||
end
|
||||
|
||||
|
||||
def init
|
||||
super
|
||||
self.fileCoordinator.coordinateWritingItemAtURL(self.documentStorageURL, options:0, error:nil, byAccessor: proc { |newURL|
|
||||
error = Pointer.new(:object)
|
||||
NSFileManager.defaultManager.createDirectoryAtURL(newURL,withIntermediateDirectories(true, attributes:nil, error:error))
|
||||
}.weak!)
|
||||
self
|
||||
end
|
||||
|
||||
def providePlaceholderAtURL(url, completionHandler:completionHandler)
|
||||
# Should call + createPlaceholderWithMetadata:atURL: with the placeholder URL, then call the completion handler with that URL.
|
||||
fileName = url.lastPathComponent
|
||||
|
||||
placeholderURL = NSFileProviderExtension.placeholderURLForURL(self.documentStorageURL.URLByAppendingPathComponent(fileName))
|
||||
|
||||
fileSize = 0
|
||||
# TODO: get file size for file at <url> from model
|
||||
|
||||
self.fileCoordinator.coordinateWritingItemAtURL(placeholderURL, options:0, error:nil, byAccessor: proc { |error|
|
||||
|
||||
metadata = { NSURLFileSizeKey => fileSize }
|
||||
NSFileProviderExtension.writePlaceholderAtURL(placeholderURL, withMetadata:metadata, error:nil)
|
||||
}.weak!)
|
||||
|
||||
completionHandler.call(nil) if (completionHandler)
|
||||
end
|
||||
|
||||
def startProvidingItemAtURL(url, completionHandler:completionHandler)
|
||||
# Should ensure that the actual file is in the position returned by URLForItemWithIdentifier:, then call the completion handler
|
||||
error = Pointer.new(:object)
|
||||
fileError = Pointer.new(:object)
|
||||
|
||||
fileData = NSData.data
|
||||
# TODO: get the contents of file at <url> from model
|
||||
|
||||
self.fileCoordinator.coordinateWritingItemAtURL(url, options:0, error:error, byAccessor: proc { |newURL|
|
||||
fileData.writeToURL(newURL,options(0, error:fileError))
|
||||
})
|
||||
if error
|
||||
completionHandler.call(error)
|
||||
else
|
||||
completionHandler.call(fileError)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def itemChangedAtURL(url)
|
||||
# Called at some point after the file has changed; the provider may then trigger an upload
|
||||
|
||||
# TODO: mark file at <url> as needing an update in the model; kick off update process
|
||||
NSLog("Item changed at URL %@", url)
|
||||
end
|
||||
|
||||
def stopProvidingItemAtURL(url)
|
||||
# Called after the last claim to the file has been released. At this point, it is safe for the file provider to remove the content file.
|
||||
# Care should be taken that the corresponding placeholder file stays behind after the content file has been deleted.
|
||||
|
||||
self.fileCoordinator.coordinateWritingItemAtURL(url, options:NSFileCoordinatorWritingForDeleting, error:nil, byAccessor: proc { |newURL|
|
||||
NSFileManager.defaultManager.removeItemAtURL(newURL,error:nil)
|
||||
})
|
||||
self.providePlaceholderAtURL(url, completionHandler:nil)
|
||||
end
|
||||
|
||||
end
|
||||
@@ -0,0 +1,10 @@
|
||||
<?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>com.apple.security.application-groups</key>
|
||||
<array>
|
||||
<string>group.sdf.Extension-Test</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,14 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
Motion::Project::App.setup do |app|
|
||||
# Use `rake config' to see complete project settings.
|
||||
app.extension do |ext|
|
||||
ext.name = '<%= extension_name %>'
|
||||
ext.type = "photo-editing"
|
||||
ext.attributes = {
|
||||
"PHSupportedMediaTypes" => ["Image"]
|
||||
}
|
||||
ext.frameworks << "Photos"
|
||||
ext.frameworks << "PhotosUI"
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,56 @@
|
||||
class PhotoEditingViewController < UIViewController
|
||||
|
||||
attr_accessor :input
|
||||
|
||||
def viewDidLoad
|
||||
super
|
||||
# Do any additional setup after loading the view.
|
||||
end
|
||||
|
||||
def didReceiveMemoryWarning
|
||||
super
|
||||
# Dispose of any resources that can be recreated.
|
||||
end
|
||||
|
||||
# PHContentEditingController
|
||||
|
||||
def canHandleAdjustmentData(adjustmentData)
|
||||
# Inspect the adjustmentData to determine whether your extension can work with past edits.
|
||||
# (Typically, you use its formatIdentifier and formatVersion properties to do this.)
|
||||
false
|
||||
end
|
||||
|
||||
def startContentEditingWithInput(contentEditingInput, placeholderImage:placeholderImage)
|
||||
# Present content for editing, and keep the contentEditingInput for use when closing the edit session.
|
||||
# If you returned YES from canHandleAdjustmentData:, contentEditingInput has the original image and adjustment data.
|
||||
# If you returned NO, the contentEditingInput has past edits "baked in".
|
||||
self.input = contentEditingInput
|
||||
end
|
||||
|
||||
def finishContentEditingWithCompletionHandler(completionHandler)
|
||||
# Update UI to reflect that editing has finished and output is being rendered.
|
||||
|
||||
# Render and provide output on a background queue.
|
||||
|
||||
Dispatch::Queue.concurrent {
|
||||
# Create editing output from the editing input.
|
||||
output = PHContentEditingOutput.alloc.initWithContentEditingInput(self.input)
|
||||
|
||||
# Provide new adjustments and render output to given location.
|
||||
# output.adjustmentData = <#new adjustment data#>;
|
||||
# NSData *renderedJPEGData = <#output JPEG#>;
|
||||
# [renderedJPEGData writeToURL:output.renderedContentURL atomically:YES];
|
||||
|
||||
# Call completion handler to commit edit to Photos.
|
||||
completionHandler.call(output)
|
||||
|
||||
# Clean up temporary files, etc.
|
||||
}
|
||||
end
|
||||
|
||||
def cancelContentEditing
|
||||
# Clean up temporary files, etc.
|
||||
# May be called after finishContentEditingWithCompletionHandler: while you prepare output.
|
||||
end
|
||||
|
||||
end
|
||||
@@ -0,0 +1,15 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
Motion::Project::App.setup do |app|
|
||||
# Use `rake config' to see complete project settings.
|
||||
app.extension do |ext|
|
||||
ext.name = '<%= extension_name %>'
|
||||
ext.type = "share-services"
|
||||
ext.attributes = {
|
||||
"NSExtensionActivationRule" => "TRUEPREDICATE"
|
||||
"NSExtensionPointName" => "1.0"
|
||||
"NSExtensionPointVersion" => "1.0"
|
||||
}
|
||||
ext.frameworks << "MobileCoreServices"
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,20 @@
|
||||
class ShareViewController < SLComposeServiceViewController
|
||||
|
||||
def isContentValid
|
||||
# Do validation of contentText and/or NSExtensionContext attachments here
|
||||
true
|
||||
end
|
||||
|
||||
def didSelectPost
|
||||
# This is called after the user selects Post. Do the upload of contentText and/or NSExtensionContext attachments.
|
||||
|
||||
# Inform the host that we're done, so it un-blocks its UI. Note: Alternatively you could call super's -didSelectPost, which will similarly complete the extension context.
|
||||
self.extensionContext.completeRequestReturningItems(nil, completionHandler:nil)
|
||||
end
|
||||
|
||||
def configurationItems
|
||||
# To add configuration options via table cells at the bottom of the sheet, return an array of SLComposeSheetConfigurationItem here.
|
||||
[]
|
||||
end
|
||||
|
||||
end
|
||||
17
lib/motion/project/template/ios-today-extension/files/.gitignore
vendored
Normal file
17
lib/motion/project/template/ios-today-extension/files/.gitignore
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
.repl_history
|
||||
build
|
||||
tags
|
||||
app/pixate_code.rb
|
||||
resources/*.nib
|
||||
resources/*.momd
|
||||
resources/*.storyboardc
|
||||
.DS_Store
|
||||
nbproject
|
||||
.redcar
|
||||
#*#
|
||||
*~
|
||||
*.sw[po]
|
||||
.eprj
|
||||
.sass-cache
|
||||
.idea
|
||||
.dat*.*
|
||||
@@ -0,0 +1,4 @@
|
||||
source 'https://rubygems.org'
|
||||
|
||||
gem 'rake'
|
||||
# Add your dependencies here:
|
||||
@@ -0,0 +1,19 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
$:.unshift("/Library/RubyMotion/lib")
|
||||
require 'motion/project/template/ios-extension.rb'
|
||||
|
||||
begin
|
||||
require 'bundler'
|
||||
Bundler.require
|
||||
rescue LoadError
|
||||
end
|
||||
|
||||
Motion::Project::App.setup do |app|
|
||||
# Use `rake config' to see complete project settings.
|
||||
app.name = '<%= name %>'
|
||||
app.frameworks << "NotificationCenter"
|
||||
app.info_plist['NSExtension'] = {
|
||||
'NSExtensionPrincipalClass' => 'TodayViewController',
|
||||
'NSExtensionPointIdentifier' => 'com.apple.widget-extension'
|
||||
}
|
||||
end
|
||||
@@ -0,0 +1,34 @@
|
||||
class TodayViewController < UIViewController
|
||||
|
||||
def viewDidLoad
|
||||
super
|
||||
|
||||
label = UILabel.alloc.init
|
||||
label.text = "Hello World"
|
||||
label.textColor = UIColor.whiteColor
|
||||
label.textAlignment = NSTextAlignmentCenter
|
||||
label.setTranslatesAutoresizingMaskIntoConstraints(false)
|
||||
self.view.addSubview(label)
|
||||
|
||||
self.view.setTranslatesAutoresizingMaskIntoConstraints(false)
|
||||
|
||||
self.view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("|-[label]-|", options:0, metrics:nil, views:{"label" => label}))
|
||||
self.view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|-[label]-|", options:0, metrics:nil, views:{"label" => label}))
|
||||
end
|
||||
|
||||
def didReceiveMemoryWarning
|
||||
super
|
||||
# Dispose of any resources that can be recreated.
|
||||
end
|
||||
|
||||
def widgetPerformUpdateWithCompletionHandler(completionHandler)
|
||||
# Perform any setup necessary in order to update the view.
|
||||
|
||||
# If an error is encoutered, use NCUpdateResultFailed
|
||||
# If there's no update required, use NCUpdateResultNoData
|
||||
# If there's an update, use NCUpdateResultNewData
|
||||
|
||||
completionHandler.call(NCUpdateResultNewData)
|
||||
end
|
||||
|
||||
end
|
||||
Reference in New Issue
Block a user