From 8d0525f49476ffed761eebcd86f70393f554c42a Mon Sep 17 00:00:00 2001 From: Laurent Sansonetti Date: Mon, 29 Sep 2014 17:13:55 +0100 Subject: [PATCH] add support for the R class (thanks to Mark for the patch) --- bin/android/gen_bridge_metadata | 100 +++++++---- lib/motion/project/template/android.rb | 168 ++++++++++-------- lib/motion/project/template/android/config.rb | 2 +- 3 files changed, 160 insertions(+), 110 deletions(-) diff --git a/bin/android/gen_bridge_metadata b/bin/android/gen_bridge_metadata index df6445e0..0d8ace3f 100755 --- a/bin/android/gen_bridge_metadata +++ b/bin/android/gen_bridge_metadata @@ -2,52 +2,49 @@ # encoding: utf-8 require 'fileutils' +require 'pathname' +require 'optparse' -if ARGV.size != 2 - $stderr.puts "Usage: #{__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 - 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 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 = '<init>' end class_method = elem_line.include?('static') - bs_data << < EOS elsif md = elem_line.match(/\s([^;\s]+);$/) =begin # A constant. - constant = md[1] - bs_data << < EOS =end end end - bs_data << < EOS end - bs_data << <"]/, ">" => ">", "<" => "<", "&" => "&", '"' => """) +end + +def die(*msg) + $stderr.puts msg + exit 1 +end + +if __FILE__ == $0 + OptionParser.new do |opts| + opts.banner = "Usage: #{File.basename(__FILE__)} [options] [|...]" + 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 + ARGV.each { |file| gen_bridge_metadata(file) } + + @bs_data << < 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 diff --git a/lib/motion/project/template/android.rb b/lib/motion/project/template/android.rb index 7fa18315..c7f86518 100644 --- a/lib/motion/project/template/android.rb +++ b/lib/motion/project/template/android.rb @@ -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 + # 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 + end + # Custom manifest entries. + App.config.manifest_xml_lines(nil).each { |line| android_manifest_txt << "\t" + line + "\n" } + android_manifest_txt << < +EOS + App.config.manifest_xml_lines('application').each { |line| android_manifest_txt << "\t\t" + line + "\n" } + # Main activity. + android_manifest_txt << < + + + + + +EOS + # Sub-activities. + (App.config.sub_activities.uniq - [App.config.main_activity]).each do |activity| + android_manifest_txt << < + + +EOS + end + android_manifest_txt << < + +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 - # 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 - end - # Custom manifest entries. - App.config.manifest_xml_lines(nil).each { |line| android_manifest_txt << "\t" + line + "\n" } - android_manifest_txt << < -EOS - App.config.manifest_xml_lines('application').each { |line| android_manifest_txt << "\t\t" + line + "\n" } - # Main activity. - android_manifest_txt << < - - - - - -EOS - # Sub-activities. - (App.config.sub_activities.uniq - [App.config.main_activity]).each do |activity| - android_manifest_txt << < - - -EOS - end - android_manifest_txt << < - -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' diff --git a/lib/motion/project/template/android/config.rb b/lib/motion/project/template/android/config.rb index 148357df..206e4757 100644 --- a/lib/motion/project/template/android/config.rb +++ b/lib/motion/project/template/android/config.rb @@ -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