Merge pull request #9 from lrz/xcassets

Support for Xcode asset catalogs.
This commit is contained in:
Eloy Durán
2013-10-14 07:27:40 -07:00
5 changed files with 116 additions and 16 deletions

4
NEWS
View File

@@ -3,6 +3,10 @@
* Added the `rake clean:all' task which deletes all build object files
(ex. those in ~/Library/RubyMotion/build). We recommend using that task
before building an App Store submission.
* Added support for Xcode asset catalogs. This can be used to manage all your
image assets in a visual way, including your applications icons. You can
create and edit a new catalog like so:
$ mkdir resources/Images.xcassets && open -a Xcode resources/Image.xcassets
* Fixed a long standing limitation in the compiler where overriding in Ruby
an Objective-C method that accepts a C-level block was not possible.
* Fixed a regression where `return' from a block would terminate the app.

View File

@@ -312,14 +312,6 @@ EOS
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
# 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)
@@ -342,6 +334,46 @@ EOS
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)
@@ -378,6 +410,14 @@ EOS
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)
@@ -386,11 +426,17 @@ EOS
'Info.plist', 'PkgInfo', 'ResourceRules.plist',
convert_filesystem_encoding(config.name)
]
resources_exclude_extnames = ['.xib', '.storyboard', '.xcdatamodeld', '.lproj', '.atlas', '.xcassets']
resources_paths = []
config.resources_dirs.each do |dir|
if File.exist?(dir)
resources_paths << Dir.chdir(dir) do
Dir.glob('**{,/*/**}/*').reject { |x| ['.xib', '.storyboard', '.xcdatamodeld', '.lproj', '.atlas'].include?(File.extname(x)) }.map { |file| File.join(dir, file) }
Dir.glob('**{,/*/**}/*').reject do |x|
# Find files with extnames to exclude or files inside bundles to
# exclude (e.g. xcassets).
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
@@ -400,12 +446,7 @@ EOS
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)
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, dest_path)
end
copy_resource(res_path, File.join(app_resources_dir, res))
end
# Delete old resource files.
@@ -415,6 +456,7 @@ EOS
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
@@ -446,6 +488,14 @@ EOS
eval `#{@nfd} "#{string}"`
end
def copy_resource(res_path, dest_path)
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, dest_path)
end
end
class << self
def common_build_dir
dir = File.expand_path("~/Library/RubyMotion/build")

View File

@@ -57,6 +57,18 @@ module Motion; module Project;
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) })

View File

@@ -34,8 +34,8 @@ module Motion; module Project;
def initialize(project_dir, build_mode)
super
@copyright = "Copyright © #{Time.now.year} #{`whoami`.strip}. All rights reserved."
@icon = ''
@copyright = "Copyright © #{Time.now.year} #{`whoami`.strip}. All rights reserved."
@category = 'utilities'
@frameworks = ['AppKit', 'Foundation', 'CoreGraphics']
@embedded_frameworks = []
@@ -45,6 +45,7 @@ module Motion; module Project;
def platforms; ['MacOSX']; end
def local_platform; 'MacOSX'; end
def deploy_platform; 'MacOSX'; end
def device_family; 'mac'; end
def validate
# Embedded frameworks.
@@ -67,6 +68,15 @@ module Motion; module Project;
archs
end
def app_icons_info_plist_path(platform)
'/dev/null'
end
# On OS X only one file is ever created. E.g. NAME.icns
def configure_app_icons_from_asset_bundle(platform)
self.icon = app_icon_name_from_asset_bundle
end
def locate_compiler(platform, *execs)
execs.each do |exec|
cc = File.join('/usr/bin', exec)

View File

@@ -322,5 +322,29 @@ EOS
path = File.join(xcode_dir, 'usr/bin/TextureAtlas')
File.exist?(path) ? path : nil
end
def assets_bundles
xcassets_bundles = []
resources_dirs.each do |dir|
if File.exist?(dir)
xcassets_bundles.concat(Dir.glob(File.join(dir, '*.xcassets')))
end
end
xcassets_bundles
end
def app_icons_asset_bundle
app_icons_asset_bundles = assets_bundles.map { |b| Dir.glob(File.join(b, '*.appiconset')) }.flatten
if app_icons_asset_bundles.size > 1
App.warn "Found #{app_icons_asset_bundles.size} app icon sets across all " \
"xcasset bundles. Only the first one (alphabetically) " \
"will be used."
end
app_icons_asset_bundles.sort.first
end
def app_icon_name_from_asset_bundle
File.basename(app_icons_asset_bundle, '.appiconset')
end
end
end; end