add support for the R class (thanks to Mark for the patch)

This commit is contained in:
Laurent Sansonetti
2014-09-29 17:13:55 +01:00
parent 0429f305d5
commit 8d0525f494
3 changed files with 160 additions and 110 deletions

View File

@@ -2,52 +2,49 @@
# encoding: utf-8
require 'fileutils'
require 'pathname'
require 'optparse'
if ARGV.size != 2
$stderr.puts "Usage: #{__FILE__} <jar-file> <output-file>"
exit 1
end
def gen_bridge_metadata(file)
jar_path = File.expand_path(ARGV[0])
bs_path = File.expand_path(ARGV[1])
file = File.expand_path(file)
tmp_path = File.join(ENV['TMPDIR'], '__android_bridgesupport__', File.basename(jar_path))
FileUtils.rm_rf tmp_path
FileUtils.mkdir_p tmp_path
case ext = File.extname(file)
when '.jar'
class_path = File.join(ENV['TMPDIR'], '__android_bridgesupport__', File.basename(file))
FileUtils.rm_rf class_path
FileUtils.mkdir_p class_path
puts "Extracting #{file} to #{class_path}"
exit 1 unless system "/usr/bin/unzip -q \"#{file}\" -d \"#{class_path}\""
puts "Extracting #{jar_path} to #{tmp_path}"
Dir.chdir(tmp_path) do
# Unzip the .jar archive.
exit 1 unless system "/usr/bin/unzip -q \"#{jar_path}\""
classes = Dir.glob(File.join(class_path ,"**/*.class")).map { |c| c.gsub(class_path, '') }
when '.class'
class_path = File.dirname(file)
classes = [File.basename(file)]
else
die "Invalid file extension '#{ext}'. Supported extensions are 'jar' and 'class'"
end
# Decompile classes.
classes = Dir.glob("**/*.class").map { |x| x.gsub('/', '.').gsub('$', '.').sub(/\.class$/, '') }
javap_tmp_path = File.join(File.dirname(tmp_path), 'classes.txt')
system "/usr/bin/javap -s #{classes.join(' ')} > \"#{javap_tmp_path}\""
classes = classes.map { |x| x.gsub('/', '.').gsub('$', '.').sub(/\.class$/, '') }
txt = `/usr/bin/javap -classpath "#{class_path}" -s #{classes.join(' ')}`
# Create the BridgeSupport text.
bs_data = ''
bs_data << <<EOS
<?xml version='1.0'?>
<signatures version='1.0'>
EOS
txt = File.read(javap_tmp_path)
res = txt.scan(/(class)\s+([^\s]+)\s+extends\s+[^{]+\s*\{([^}]+)\}/)
res += txt.scan(/(class)\s([^\s{]+)\s*\{([^}]+)\}/) # Also grab classes without superclasses (ex. java.lang.Object)
res += txt.scan(/(interface)\s([^\s{]+)\s*\{([^}]+)\}/) # Also grab interfaces
res.each do |type, elem, body_txt|
elem_path = elem.gsub(/\./, '/')
bs_data << <<EOS
elem_path = encode_xml(elem.gsub(/\./, '/'))
@bs_data << <<EOS
<#{type} name=\"#{elem_path}\">
EOS
body_txt.strip.split(/\n/).each_cons(2) do |elem_line, signature_line|
signature_line = signature_line.strip
signature_line = encode_xml(signature_line.strip)
md = signature_line.match(/^Signature:\s+(.+)$/)
next unless md
signature = md[1]
elem_line = elem_line.strip
elem_line = encode_xml(elem_line.strip)
if md = elem_line.match(/\s([^(\s]+)\(/)
# A method.
method = md[1]
@@ -56,27 +53,60 @@ EOS
method = '&lt;init&gt;'
end
class_method = elem_line.include?('static')
bs_data << <<EOS
@bs_data << <<EOS
<method name=\"#{method}\" type=\"#{signature}\"#{class_method ? ' class_method="true"' : ''}/>
EOS
elsif md = elem_line.match(/\s([^;\s]+);$/)
=begin
# A constant.
constant = md[1]
bs_data << <<EOS
constant = md[1]
@bs_data << <<EOS
<const name=\"#{constant}\" type=\"#{signature}\"/>
EOS
=end
end
end
bs_data << <<EOS
@bs_data << <<EOS
</#{type}>
EOS
end
bs_data << <<EOS
end
def encode_xml(string)
string.gsub(/[&<>"]/, ">" => "&gt;", "<" => "&lt;", "&" => "&amp;", '"' => "&quot;")
end
def die(*msg)
$stderr.puts msg
exit 1
end
if __FILE__ == $0
OptionParser.new do |opts|
opts.banner = "Usage: #{File.basename(__FILE__)} [options] [<jar-file>|<class-file>...]"
opts.separator ''
opts.separator 'Options:'
opts.on('-o', '--output FILE', 'Write output to the given file.') do |opt|
die 'Output file can\'t be specified more than once' if @out_file
@out_file = opt
end
if ARGV.empty?
die opts.banner
else
opts.parse!(ARGV)
@bs_data = ''
@bs_data << <<EOS
<?xml version='1.0'?>
<signatures version='1.0'>
EOS
ARGV.each { |file| gen_bridge_metadata(file) }
@bs_data << <<EOS
</signatures>
EOS
# Write the BridgeSupport data down.
File.open(bs_path, 'w') { |io| io.write(bs_data) }
File.open(@out_file, 'w') { |io| io.write(@bs_data) }
end
end
end

View File

@@ -37,11 +37,104 @@ task :build do
app_build_dir = App.config.versionized_build_dir
mkdir_p app_build_dir
# Generate the Android manifest file.
android_manifest_txt = ''
android_manifest_txt << <<EOS
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="#{App.config.package}" android:versionCode="#{App.config.version_code}" android:versionName="#{App.config.version_name}">
<uses-sdk android:minSdkVersion="#{App.config.api_version}"/>
EOS
# Application permissions
permissions = Array(App.config.permissions)
if App.config.development?
# In development mode, we need the INTERNET permission in order to create
# the REPL socket.
permissions |= ['android.permission.INTERNET']
end
permissions.each do |permission|
permission = "android.permission.#{permission.to_s.upcase}" if permission.is_a?(Symbol)
android_manifest_txt << <<EOS
<uses-permission android:name="#{permission}"></uses-permission>
EOS
end
# Custom manifest entries.
App.config.manifest_xml_lines(nil).each { |line| android_manifest_txt << "\t" + line + "\n" }
android_manifest_txt << <<EOS
<application android:label="#{App.config.name}" android:debuggable="#{App.config.development? ? 'true' : 'false'}" #{App.config.icon ? ('android:icon="@drawable/' + App.config.icon + '"') : ''}>
EOS
App.config.manifest_xml_lines('application').each { |line| android_manifest_txt << "\t\t" + line + "\n" }
# Main activity.
android_manifest_txt << <<EOS
<activity android:name="#{App.config.main_activity}" android:label="#{App.config.name}">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
EOS
# Sub-activities.
(App.config.sub_activities.uniq - [App.config.main_activity]).each do |activity|
android_manifest_txt << <<EOS
<activity android:name="#{activity}" android:label="#{activity}" android:parentActivityName="#{App.config.main_activity}">
<meta-data android:name="android.support.PARENT_ACTIVITY" android:value="#{App.config.main_activity}"/>
</activity>
EOS
end
android_manifest_txt << <<EOS
</application>
</manifest>
EOS
android_manifest = File.join(app_build_dir, 'AndroidManifest.xml')
if !File.exist?(android_manifest) or File.read(android_manifest) != android_manifest_txt
App.info 'Create', android_manifest
File.open(android_manifest, 'w') { |io| io.write(android_manifest_txt) }
end
# Create R.java files.
java_dir = File.join(app_build_dir, 'java')
java_app_package_dir = File.join(java_dir, *App.config.package.split(/\./))
mkdir_p java_app_package_dir
r_bs = File.join(app_build_dir, 'R.bridgesupport')
android_jar = "#{App.config.sdk_path}/platforms/android-#{App.config.api_version}/android.jar"
resources_dirs = []
App.config.resources_dirs.flatten.each do |dir|
next unless File.exist?(dir)
next unless File.directory?(dir)
resources_dirs << dir
end
all_resources = (resources_dirs + App.config.vendored_projects.map { |x| x[:resources] }.compact)
aapt_resources_flags = all_resources.map { |x| '-S "' + x + '"' }.join(' ')
r_java_mtime = Dir.glob(java_dir + '/**/R.java').map { |x| File.mtime(x) }.max
bs_files = []
classes_changed = false
if !r_java_mtime or all_resources.any? { |x| Dir.glob(x + '/**/*').any? { |y| File.mtime(y) > r_java_mtime } }
extra_packages = App.config.vendored_projects.map { |x| x[:package] }.compact.map { |x| "--extra-packages #{x}" }.join(' ')
sh "\"#{App.config.build_tools_dir}/aapt\" package -f -M \"#{android_manifest}\" #{aapt_resources_flags} -I \"#{android_jar}\" -m -J \"#{java_dir}\" #{extra_packages} --auto-add-overlay"
r_java = Dir.glob(java_dir + '/**/R.java')
classes_dir = File.join(app_build_dir, 'classes')
mkdir_p classes_dir
r_java.each do |java_path|
sh "/usr/bin/javac -d \"#{classes_dir}\" -classpath #{classes_dir} -sourcepath \"#{java_dir}\" -target 1.5 -bootclasspath \"#{android_jar}\" -encoding UTF-8 -g -source 1.5 \"#{java_path}\""
end
r_classes = Dir.glob(classes_dir + '/**/R\$*[a-z]*.class').map { |c| "'#{c}'" }
sh "#{App.config.bin_exec('android/gen_bridge_metadata')} #{r_classes.join(' ')} -o \"#{r_bs}\" "
classes_changed = true
end
bs_files << r_bs if File.exist?(r_bs)
# Compile Ruby files.
ruby = App.config.bin_exec('ruby')
init_func_n = 0
ruby_objs = []
bs_files = Dir.glob(File.join(App.config.versioned_datadir, 'BridgeSupport/*.bridgesupport'))
bs_files += Dir.glob(File.join(App.config.versioned_datadir, 'BridgeSupport/*.bridgesupport'))
bs_files += App.config.vendored_bs_files
ruby_bs_flags = bs_files.map { |x| "--uses-bs \"#{x}\"" }.join(' ')
objs_build_dir = File.join(app_build_dir, 'obj', 'local', App.config.armeabi_directory_name)
@@ -147,59 +240,6 @@ EOS
end
end
# Generate the Android manifest file.
android_manifest_txt = ''
android_manifest_txt << <<EOS
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="#{App.config.package}" android:versionCode="#{App.config.version_code}" android:versionName="#{App.config.version_name}">
<uses-sdk android:minSdkVersion="#{App.config.api_version}"/>
EOS
# Application permissions
permissions = Array(App.config.permissions)
if App.config.development?
# In development mode, we need the INTERNET permission in order to create
# the REPL socket.
permissions |= ['android.permission.INTERNET']
end
permissions.each do |permission|
permission = "android.permission.#{permission.to_s.upcase}" if permission.is_a?(Symbol)
android_manifest_txt << <<EOS
<uses-permission android:name="#{permission}"></uses-permission>
EOS
end
# Custom manifest entries.
App.config.manifest_xml_lines(nil).each { |line| android_manifest_txt << "\t" + line + "\n" }
android_manifest_txt << <<EOS
<application android:label="#{App.config.name}" android:debuggable="#{App.config.development? ? 'true' : 'false'}" #{App.config.icon ? ('android:icon="@drawable/' + App.config.icon + '"') : ''}>
EOS
App.config.manifest_xml_lines('application').each { |line| android_manifest_txt << "\t\t" + line + "\n" }
# Main activity.
android_manifest_txt << <<EOS
<activity android:name="#{App.config.main_activity}" android:label="#{App.config.name}">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
EOS
# Sub-activities.
(App.config.sub_activities.uniq - [App.config.main_activity]).each do |activity|
android_manifest_txt << <<EOS
<activity android:name="#{activity}" android:label="#{activity}" android:parentActivityName="#{App.config.main_activity}">
<meta-data android:name="android.support.PARENT_ACTIVITY" android:value="#{App.config.main_activity}"/>
</activity>
EOS
end
android_manifest_txt << <<EOS
</application>
</manifest>
EOS
android_manifest = File.join(app_build_dir, 'AndroidManifest.xml')
if !File.exist?(android_manifest) or File.read(android_manifest) != android_manifest_txt
App.info 'Create', android_manifest
File.open(android_manifest, 'w') { |io| io.write(android_manifest_txt) }
end
# Create java files based on the classes map files.
java_classes = {}
Dir.glob(objs_build_dir + '/**/*.map') do |map|
@@ -250,9 +290,6 @@ EOS
end
end
end
java_dir = File.join(app_build_dir, 'java')
java_app_package_dir = File.join(java_dir, *App.config.package.split(/\./))
mkdir_p java_app_package_dir
java_classes.each do |name, klass|
klass_super = klass[:super]
klass_super = 'java.lang.Object' if klass_super == '$blank$'
@@ -279,29 +316,12 @@ EOS
end
end
# Create R.java files.
android_jar = "#{App.config.sdk_path}/platforms/android-#{App.config.api_version}/android.jar"
resources_dirs = []
App.config.resources_dirs.flatten.each do |dir|
next unless File.exist?(dir)
next unless File.directory?(dir)
resources_dirs << dir
end
all_resources = (resources_dirs + App.config.vendored_projects.map { |x| x[:resources] }.compact)
aapt_resources_flags = all_resources.map { |x| '-S "' + x + '"' }.join(' ')
r_java_mtime = Dir.glob(java_dir + '/**/R.java').map { |x| File.mtime(x) }.max
if !r_java_mtime or all_resources.any? { |x| Dir.glob(x + '/**/*').any? { |y| File.mtime(y) > r_java_mtime } }
extra_packages = App.config.vendored_projects.map { |x| x[:package] }.compact.map { |x| "--extra-packages #{x}" }.join(' ')
sh "\"#{App.config.build_tools_dir}/aapt\" package -f -M \"#{android_manifest}\" #{aapt_resources_flags} -I \"#{android_jar}\" -m -J \"#{java_dir}\" #{extra_packages} --auto-add-overlay"
end
# Compile java files.
vendored_jars = App.config.vendored_projects.map { |x| x[:jar] }
vendored_jars += [File.join(App.config.versioned_datadir, 'rubymotion.jar')]
classes_dir = File.join(app_build_dir, 'classes')
mkdir_p classes_dir
class_path = [classes_dir, "#{App.config.sdk_path}/tools/support/annotations.jar", *vendored_jars].map { |x| "\"#{x}\"" }.join(':')
classes_changed = false
Dir.glob(File.join(app_build_dir, 'java', '**', '*.java')).each do |java_path|
paths = java_path.split('/')
paths[paths.index('java')] = 'classes'

View File

@@ -240,7 +240,7 @@ module Motion; module Project;
bs_file = File.join(File.dirname(jar_file), File.basename(jar_file) + '.bridgesupport')
if !File.exist?(bs_file) or File.mtime(jar_file) > File.mtime(bs_file)
App.info 'Create', bs_file
sh "#{bin_exec('android/gen_bridge_metadata')} \"#{jar_file}\" \"#{bs_file}\""
sh "#{bin_exec('android/gen_bridge_metadata')} -o \"#{bs_file}\" \"#{jar_file}\""
end
bs_file
end