OLD | NEW |
1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | 2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. |
3 # Use of this source code is governed by a BSD-style license that can be | 3 # Use of this source code is governed by a BSD-style license that can be |
4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
5 | 5 |
6 """Extracts native methods from a Java file and generates the JNI bindings. | 6 """Extracts native methods from a Java file and generates the JNI bindings. |
7 If you change this, please run and update the tests.""" | 7 If you change this, please run and update the tests.""" |
8 | 8 |
9 import collections | 9 import collections |
10 import errno | 10 import errno |
(...skipping 431 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
442 for line1, line2 in zip(unmatched_lines, unmatched_lines[1:]): | 442 for line1, line2 in zip(unmatched_lines, unmatched_lines[1:]): |
443 if '@CalledByNative' in line1: | 443 if '@CalledByNative' in line1: |
444 raise ParseError('could not parse @CalledByNative method signature', | 444 raise ParseError('could not parse @CalledByNative method signature', |
445 line1, line2) | 445 line1, line2) |
446 return MangleCalledByNatives(called_by_natives) | 446 return MangleCalledByNatives(called_by_natives) |
447 | 447 |
448 | 448 |
449 class JNIFromJavaP(object): | 449 class JNIFromJavaP(object): |
450 """Uses 'javap' to parse a .class file and generate the JNI header file.""" | 450 """Uses 'javap' to parse a .class file and generate the JNI header file.""" |
451 | 451 |
452 def __init__(self, contents, namespace): | 452 def __init__(self, contents, options): |
453 self.contents = contents | 453 self.contents = contents |
454 self.namespace = namespace | 454 self.namespace = options.namespace |
455 self.fully_qualified_class = re.match( | 455 self.fully_qualified_class = re.match( |
456 '.*?(class|interface) (?P<class_name>.*?)( |{)', | 456 '.*?(class|interface) (?P<class_name>.*?)( |{)', |
457 contents[1]).group('class_name') | 457 contents[1]).group('class_name') |
458 self.fully_qualified_class = self.fully_qualified_class.replace('.', '/') | 458 self.fully_qualified_class = self.fully_qualified_class.replace('.', '/') |
459 # Java 7's javap includes type parameters in output, like HashSet<T>. Strip | 459 # Java 7's javap includes type parameters in output, like HashSet<T>. Strip |
460 # away the <...> and use the raw class name that Java 6 would've given us. | 460 # away the <...> and use the raw class name that Java 6 would've given us. |
461 self.fully_qualified_class = self.fully_qualified_class.split('<', 1)[0] | 461 self.fully_qualified_class = self.fully_qualified_class.split('<', 1)[0] |
462 JniParams.SetFullyQualifiedClass(self.fully_qualified_class) | 462 JniParams.SetFullyQualifiedClass(self.fully_qualified_class) |
463 self.java_class_name = self.fully_qualified_class.split('/')[-1] | 463 self.java_class_name = self.fully_qualified_class.split('/')[-1] |
464 if not self.namespace: | 464 if not self.namespace: |
(...skipping 24 matching lines...) Expand all Loading... |
489 system_class=True, | 489 system_class=True, |
490 unchecked=False, | 490 unchecked=False, |
491 static=False, | 491 static=False, |
492 java_class_name='', | 492 java_class_name='', |
493 return_type=self.fully_qualified_class, | 493 return_type=self.fully_qualified_class, |
494 name='Constructor', | 494 name='Constructor', |
495 params=JniParams.Parse(match.group('params').replace('.', '/')), | 495 params=JniParams.Parse(match.group('params').replace('.', '/')), |
496 is_constructor=True)] | 496 is_constructor=True)] |
497 self.called_by_natives = MangleCalledByNatives(self.called_by_natives) | 497 self.called_by_natives = MangleCalledByNatives(self.called_by_natives) |
498 self.inl_header_file_generator = InlHeaderFileGenerator( | 498 self.inl_header_file_generator = InlHeaderFileGenerator( |
499 self.namespace, self.fully_qualified_class, [], self.called_by_natives) | 499 self.namespace, self.fully_qualified_class, [], |
| 500 self.called_by_natives, options) |
500 | 501 |
501 def GetContent(self): | 502 def GetContent(self): |
502 return self.inl_header_file_generator.GetContent() | 503 return self.inl_header_file_generator.GetContent() |
503 | 504 |
504 @staticmethod | 505 @staticmethod |
505 def CreateFromClass(class_file, namespace): | 506 def CreateFromClass(class_file, options): |
506 class_name = os.path.splitext(os.path.basename(class_file))[0] | 507 class_name = os.path.splitext(os.path.basename(class_file))[0] |
507 p = subprocess.Popen(args=['javap', class_name], | 508 p = subprocess.Popen(args=['javap', class_name], |
508 cwd=os.path.dirname(class_file), | 509 cwd=os.path.dirname(class_file), |
509 stdout=subprocess.PIPE, | 510 stdout=subprocess.PIPE, |
510 stderr=subprocess.PIPE) | 511 stderr=subprocess.PIPE) |
511 stdout, _ = p.communicate() | 512 stdout, _ = p.communicate() |
512 jni_from_javap = JNIFromJavaP(stdout.split('\n'), namespace) | 513 jni_from_javap = JNIFromJavaP(stdout.split('\n'), options) |
513 return jni_from_javap | 514 return jni_from_javap |
514 | 515 |
515 | 516 |
516 class JNIFromJavaSource(object): | 517 class JNIFromJavaSource(object): |
517 """Uses the given java source file to generate the JNI header file.""" | 518 """Uses the given java source file to generate the JNI header file.""" |
518 | 519 |
519 def __init__(self, contents, fully_qualified_class): | 520 def __init__(self, contents, fully_qualified_class, options): |
520 contents = self._RemoveComments(contents) | 521 contents = self._RemoveComments(contents) |
521 JniParams.SetFullyQualifiedClass(fully_qualified_class) | 522 JniParams.SetFullyQualifiedClass(fully_qualified_class) |
522 JniParams.ExtractImportsAndInnerClasses(contents) | 523 JniParams.ExtractImportsAndInnerClasses(contents) |
523 jni_namespace = ExtractJNINamespace(contents) | 524 jni_namespace = ExtractJNINamespace(contents) |
524 natives = ExtractNatives(contents) | 525 natives = ExtractNatives(contents) |
525 called_by_natives = ExtractCalledByNatives(contents) | 526 called_by_natives = ExtractCalledByNatives(contents) |
526 if len(natives) == 0 and len(called_by_natives) == 0: | 527 if len(natives) == 0 and len(called_by_natives) == 0: |
527 raise SyntaxError('Unable to find any JNI methods for %s.' % | 528 raise SyntaxError('Unable to find any JNI methods for %s.' % |
528 fully_qualified_class) | 529 fully_qualified_class) |
529 inl_header_file_generator = InlHeaderFileGenerator( | 530 inl_header_file_generator = InlHeaderFileGenerator( |
530 jni_namespace, fully_qualified_class, natives, called_by_natives) | 531 jni_namespace, fully_qualified_class, natives, called_by_natives, |
| 532 options) |
531 self.content = inl_header_file_generator.GetContent() | 533 self.content = inl_header_file_generator.GetContent() |
532 | 534 |
533 def _RemoveComments(self, contents): | 535 def _RemoveComments(self, contents): |
534 # We need to support both inline and block comments, and we need to handle | 536 # We need to support both inline and block comments, and we need to handle |
535 # strings that contain '//' or '/*'. Rather than trying to do all that with | 537 # strings that contain '//' or '/*'. Rather than trying to do all that with |
536 # regexps, we just pipe the contents through the C preprocessor. We tell cpp | 538 # regexps, we just pipe the contents through the C preprocessor. We tell cpp |
537 # the file has already been preprocessed, so it just removes comments and | 539 # the file has already been preprocessed, so it just removes comments and |
538 # doesn't try to parse #include, #pragma etc. | 540 # doesn't try to parse #include, #pragma etc. |
539 # | 541 # |
540 # TODO(husky): This is a bit hacky. It would be cleaner to use a real Java | 542 # TODO(husky): This is a bit hacky. It would be cleaner to use a real Java |
541 # parser. Maybe we could ditch JNIFromJavaSource and just always use | 543 # parser. Maybe we could ditch JNIFromJavaSource and just always use |
542 # JNIFromJavaP; or maybe we could rewrite this script in Java and use APT. | 544 # JNIFromJavaP; or maybe we could rewrite this script in Java and use APT. |
543 # http://code.google.com/p/chromium/issues/detail?id=138941 | 545 # http://code.google.com/p/chromium/issues/detail?id=138941 |
544 p = subprocess.Popen(args=['cpp', '-fpreprocessed'], | 546 p = subprocess.Popen(args=['cpp', '-fpreprocessed'], |
545 stdin=subprocess.PIPE, | 547 stdin=subprocess.PIPE, |
546 stdout=subprocess.PIPE, | 548 stdout=subprocess.PIPE, |
547 stderr=subprocess.PIPE) | 549 stderr=subprocess.PIPE) |
548 stdout, _ = p.communicate(contents) | 550 stdout, _ = p.communicate(contents) |
549 return stdout | 551 return stdout |
550 | 552 |
551 def GetContent(self): | 553 def GetContent(self): |
552 return self.content | 554 return self.content |
553 | 555 |
554 @staticmethod | 556 @staticmethod |
555 def CreateFromFile(java_file_name): | 557 def CreateFromFile(java_file_name, options): |
556 contents = file(java_file_name).read() | 558 contents = file(java_file_name).read() |
557 fully_qualified_class = ExtractFullyQualifiedJavaClassName(java_file_name, | 559 fully_qualified_class = ExtractFullyQualifiedJavaClassName(java_file_name, |
558 contents) | 560 contents) |
559 return JNIFromJavaSource(contents, fully_qualified_class) | 561 return JNIFromJavaSource(contents, fully_qualified_class, options) |
560 | 562 |
561 | 563 |
562 class InlHeaderFileGenerator(object): | 564 class InlHeaderFileGenerator(object): |
563 """Generates an inline header file for JNI integration.""" | 565 """Generates an inline header file for JNI integration.""" |
564 | 566 |
565 def __init__(self, namespace, fully_qualified_class, natives, | 567 def __init__(self, namespace, fully_qualified_class, natives, |
566 called_by_natives): | 568 called_by_natives, options): |
567 self.namespace = namespace | 569 self.namespace = namespace |
568 self.fully_qualified_class = fully_qualified_class | 570 self.fully_qualified_class = fully_qualified_class |
569 self.class_name = self.fully_qualified_class.split('/')[-1] | 571 self.class_name = self.fully_qualified_class.split('/')[-1] |
570 self.natives = natives | 572 self.natives = natives |
571 self.called_by_natives = called_by_natives | 573 self.called_by_natives = called_by_natives |
572 self.header_guard = fully_qualified_class.replace('/', '_') + '_JNI' | 574 self.header_guard = fully_qualified_class.replace('/', '_') + '_JNI' |
| 575 self.script_name = options.script_name |
573 | 576 |
574 def GetContent(self): | 577 def GetContent(self): |
575 """Returns the content of the JNI binding file.""" | 578 """Returns the content of the JNI binding file.""" |
576 template = Template("""\ | 579 template = Template("""\ |
577 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 580 // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
578 // Use of this source code is governed by a BSD-style license that can be | 581 // Use of this source code is governed by a BSD-style license that can be |
579 // found in the LICENSE file. | 582 // found in the LICENSE file. |
580 | 583 |
581 | 584 |
582 // This file is autogenerated by | 585 // This file is autogenerated by |
(...skipping 26 matching lines...) Expand all Loading... |
609 | 612 |
610 // Step 3: RegisterNatives. | 613 // Step 3: RegisterNatives. |
611 | 614 |
612 static bool RegisterNativesImpl(JNIEnv* env) { | 615 static bool RegisterNativesImpl(JNIEnv* env) { |
613 $REGISTER_NATIVES_IMPL | 616 $REGISTER_NATIVES_IMPL |
614 return true; | 617 return true; |
615 } | 618 } |
616 $CLOSE_NAMESPACE | 619 $CLOSE_NAMESPACE |
617 #endif // ${HEADER_GUARD} | 620 #endif // ${HEADER_GUARD} |
618 """) | 621 """) |
619 script_components = os.path.abspath(sys.argv[0]).split(os.path.sep) | |
620 base_index = script_components.index('base') | |
621 script_name = os.sep.join(script_components[base_index:]) | |
622 values = { | 622 values = { |
623 'SCRIPT_NAME': script_name, | 623 'SCRIPT_NAME': self.script_name, |
624 'FULLY_QUALIFIED_CLASS': self.fully_qualified_class, | 624 'FULLY_QUALIFIED_CLASS': self.fully_qualified_class, |
625 'CLASS_PATH_DEFINITIONS': self.GetClassPathDefinitionsString(), | 625 'CLASS_PATH_DEFINITIONS': self.GetClassPathDefinitionsString(), |
626 'FORWARD_DECLARATIONS': self.GetForwardDeclarationsString(), | 626 'FORWARD_DECLARATIONS': self.GetForwardDeclarationsString(), |
627 'METHOD_STUBS': self.GetMethodStubsString(), | 627 'METHOD_STUBS': self.GetMethodStubsString(), |
628 'OPEN_NAMESPACE': self.GetOpenNamespaceString(), | 628 'OPEN_NAMESPACE': self.GetOpenNamespaceString(), |
629 'REGISTER_NATIVES_IMPL': self.GetRegisterNativesImplString(), | 629 'REGISTER_NATIVES_IMPL': self.GetRegisterNativesImplString(), |
630 'CLOSE_NAMESPACE': self.GetCloseNamespaceString(), | 630 'CLOSE_NAMESPACE': self.GetCloseNamespaceString(), |
631 'HEADER_GUARD': self.header_guard, | 631 'HEADER_GUARD': self.header_guard, |
632 } | 632 } |
633 return WrapOutput(template.substitute(values)) | 633 return WrapOutput(template.substitute(values)) |
(...skipping 352 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
986 except OSError as e: | 986 except OSError as e: |
987 if e.errno != errno.EEXIST: | 987 if e.errno != errno.EEXIST: |
988 raise | 988 raise |
989 extracted_file_name = os.path.join(out_dir, os.path.basename(input_file)) | 989 extracted_file_name = os.path.join(out_dir, os.path.basename(input_file)) |
990 with open(extracted_file_name, 'w') as outfile: | 990 with open(extracted_file_name, 'w') as outfile: |
991 outfile.write(jar_file.read(input_file)) | 991 outfile.write(jar_file.read(input_file)) |
992 | 992 |
993 return extracted_file_name | 993 return extracted_file_name |
994 | 994 |
995 | 995 |
996 def GenerateJNIHeader(input_file, output_file, namespace, skip_if_same): | 996 def GenerateJNIHeader(input_file, output_file, options): |
997 try: | 997 try: |
998 if os.path.splitext(input_file)[1] == '.class': | 998 if os.path.splitext(input_file)[1] == '.class': |
999 jni_from_javap = JNIFromJavaP.CreateFromClass(input_file, namespace) | 999 jni_from_javap = JNIFromJavaP.CreateFromClass(input_file, options) |
1000 content = jni_from_javap.GetContent() | 1000 content = jni_from_javap.GetContent() |
1001 else: | 1001 else: |
1002 jni_from_java_source = JNIFromJavaSource.CreateFromFile(input_file) | 1002 jni_from_java_source = JNIFromJavaSource.CreateFromFile( |
| 1003 input_file, options) |
1003 content = jni_from_java_source.GetContent() | 1004 content = jni_from_java_source.GetContent() |
1004 except ParseError, e: | 1005 except ParseError, e: |
1005 print e | 1006 print e |
1006 sys.exit(1) | 1007 sys.exit(1) |
1007 if output_file: | 1008 if output_file: |
1008 if not os.path.exists(os.path.dirname(os.path.abspath(output_file))): | 1009 if not os.path.exists(os.path.dirname(os.path.abspath(output_file))): |
1009 os.makedirs(os.path.dirname(os.path.abspath(output_file))) | 1010 os.makedirs(os.path.dirname(os.path.abspath(output_file))) |
1010 if skip_if_same and os.path.exists(output_file): | 1011 if options.optimize_generation and os.path.exists(output_file): |
1011 with file(output_file, 'r') as f: | 1012 with file(output_file, 'r') as f: |
1012 existing_content = f.read() | 1013 existing_content = f.read() |
1013 if existing_content == content: | 1014 if existing_content == content: |
1014 return | 1015 return |
1015 with file(output_file, 'w') as f: | 1016 with file(output_file, 'w') as f: |
1016 f.write(content) | 1017 f.write(content) |
1017 else: | 1018 else: |
1018 print output | 1019 print output |
1019 | 1020 |
1020 | 1021 |
| 1022 def GetScriptName(): |
| 1023 script_components = os.path.abspath(sys.argv[0]).split(os.path.sep) |
| 1024 base_index = 0 |
| 1025 for idx, value in enumerate(script_components): |
| 1026 if value == 'base' or value == 'third_party': |
| 1027 base_index = idx |
| 1028 break |
| 1029 return os.sep.join(script_components[base_index:]) |
| 1030 |
| 1031 |
1021 def main(argv): | 1032 def main(argv): |
1022 usage = """usage: %prog [OPTIONS] | 1033 usage = """usage: %prog [OPTIONS] |
1023 This script will parse the given java source code extracting the native | 1034 This script will parse the given java source code extracting the native |
1024 declarations and print the header file to stdout (or a file). | 1035 declarations and print the header file to stdout (or a file). |
1025 See SampleForTests.java for more details. | 1036 See SampleForTests.java for more details. |
1026 """ | 1037 """ |
1027 option_parser = optparse.OptionParser(usage=usage) | 1038 option_parser = optparse.OptionParser(usage=usage) |
1028 option_parser.add_option('-j', dest='jar_file', | 1039 option_parser.add_option('-j', dest='jar_file', |
1029 help='Extract the list of input files from' | 1040 help='Extract the list of input files from' |
1030 ' a specified jar file.' | 1041 ' a specified jar file.' |
1031 ' Uses javap to extract the methods from a' | 1042 ' Uses javap to extract the methods from a' |
1032 ' pre-compiled class. --input should point' | 1043 ' pre-compiled class. --input should point' |
1033 ' to pre-compiled Java .class files.') | 1044 ' to pre-compiled Java .class files.') |
1034 option_parser.add_option('-n', dest='namespace', | 1045 option_parser.add_option('-n', dest='namespace', |
1035 help='Uses as a namespace in the generated header,' | 1046 help='Uses as a namespace in the generated header,' |
1036 ' instead of the javap class name.') | 1047 ' instead of the javap class name.') |
1037 option_parser.add_option('--input_file', | 1048 option_parser.add_option('--input_file', |
1038 help='Single input file name. The output file name ' | 1049 help='Single input file name. The output file name ' |
1039 'will be derived from it. Must be used with ' | 1050 'will be derived from it. Must be used with ' |
1040 '--output_dir.') | 1051 '--output_dir.') |
1041 option_parser.add_option('--output_dir', | 1052 option_parser.add_option('--output_dir', |
1042 help='The output directory. Must be used with ' | 1053 help='The output directory. Must be used with ' |
1043 '--input') | 1054 '--input') |
1044 option_parser.add_option('--optimize_generation', type="int", | 1055 option_parser.add_option('--optimize_generation', type="int", |
1045 default=0, help='Whether we should optimize JNI ' | 1056 default=0, help='Whether we should optimize JNI ' |
1046 'generation by not regenerating files if they have ' | 1057 'generation by not regenerating files if they have ' |
1047 'not changed.') | 1058 'not changed.') |
1048 option_parser.add_option('--jarjar', | 1059 option_parser.add_option('--jarjar', |
1049 help='Path to optional jarjar rules file.') | 1060 help='Path to optional jarjar rules file.') |
| 1061 option_parser.add_option('--script_name', default=GetScriptName(), |
| 1062 help='The name of this script in the generated ' |
| 1063 'header.') |
1050 options, args = option_parser.parse_args(argv) | 1064 options, args = option_parser.parse_args(argv) |
1051 if options.jar_file: | 1065 if options.jar_file: |
1052 input_file = ExtractJarInputFile(options.jar_file, options.input_file, | 1066 input_file = ExtractJarInputFile(options.jar_file, options.input_file, |
1053 options.output_dir) | 1067 options.output_dir) |
| 1068 elif options.input_file: |
| 1069 input_file = options.input_file |
1054 else: | 1070 else: |
1055 input_file = options.input_file | 1071 option_parser.print_help() |
| 1072 print '\nError: Must specify --jar_file or --input_file.' |
| 1073 return 1 |
1056 output_file = None | 1074 output_file = None |
1057 if options.output_dir: | 1075 if options.output_dir: |
1058 root_name = os.path.splitext(os.path.basename(input_file))[0] | 1076 root_name = os.path.splitext(os.path.basename(input_file))[0] |
1059 output_file = os.path.join(options.output_dir, root_name) + '_jni.h' | 1077 output_file = os.path.join(options.output_dir, root_name) + '_jni.h' |
1060 if options.jarjar: | 1078 if options.jarjar: |
1061 with open(options.jarjar) as f: | 1079 with open(options.jarjar) as f: |
1062 JniParams.SetJarJarMappings(f.read()) | 1080 JniParams.SetJarJarMappings(f.read()) |
1063 GenerateJNIHeader(input_file, output_file, options.namespace, | 1081 GenerateJNIHeader(input_file, output_file, options) |
1064 options.optimize_generation) | |
1065 | 1082 |
1066 | 1083 |
1067 if __name__ == '__main__': | 1084 if __name__ == '__main__': |
1068 sys.exit(main(sys.argv)) | 1085 sys.exit(main(sys.argv)) |
OLD | NEW |