Eloy Duran
e.dur****@super*****
Sat Apr 26 04:38:42 JST 2008
Hi Scott, Laurent asked me if I could send your patch to the list because he has some thoughts and maybe others as well. So here it is (http://pasternak.superalloy.nl/pastes/335 ). Cheers, Eloy diff --git a/framework/src/objc/BridgeSupport.m b/framework/src/objc/ BridgeSupport.m index 13863b1..5b60665 100644 --- a/framework/src/objc/BridgeSupport.m +++ b/framework/src/objc/BridgeSupport.m @@ -26,6 +26,8 @@ #import "ocexception.h" #import "objc_compat.h" +#define BRIDGE_SUPPORT_NAME @"BridgeSupport" + static VALUE cOSXBoxed; static ID ivarEncodingID; @@ -2016,6 +2018,321 @@ osx_lookup_informal_protocol_method_type (VALUE rcv, VALUE sel, return method == NULL ? Qnil : rb_str_new2(method->encoding); } +NSString *_find_framework_in_directory(NSString *base_path, NSString *framework_name) +{ + // Given a base directory, search for a Frameworks folder and a PrivateFrameworks + // folder, If a framework with a given name can be found in that folder, return + // the path to the framework. If no such framework exists, then return nil. + + // Make sure that the framework_name has an extension, add .framework if not + if([[framework_name pathExtension] length] == 0) { + framework_name = [framework_name stringByAppendingPathExtension: @"framework"]; + } + + NSString *frameworks_test_path = [[base_path stringByAppendingPathComponent: @"Frameworks"] stringByAppendingPathComponent: framework_name]; + NSString *shared_frameworks_test_path = [[base_path stringByAppendingPathComponent: @"SharedFrameworks"] stringByAppendingPathComponent: framework_name]; + NSString *private_frameworks_test_path = [[base_path stringByAppendingPathComponent: @"PrivateFrameworks"] stringByAppendingPathComponent: framework_name]; + + NSString *retVal = nil; + BOOL isDirectory = false; + NSFileManager *fileManager = [NSFileManager defaultManager]; + + if(!([fileManager fileExistsAtPath: private_frameworks_test_path isDirectory: &isDirectory] && isDirectory)) { + + // The framework is not in private frameworks... try the public ones. + if(!([fileManager fileExistsAtPath: frameworks_test_path isDirectory: &isDirectory] && isDirectory)) { + // In documentation and on the lists, it looks like SharedFrameworks + // may be unused functionality in modern Mac OS X but we'll check it anyway + // for completeness + if(([fileManager fileExistsAtPath: shared_frameworks_test_path isDirectory: &isDirectory] && isDirectory)) { + retVal = shared_frameworks_test_path; + } + } else { + retVal = frameworks_test_path; + } + } else { + retVal = private_frameworks_test_path; + } + + return retVal; +} + +NSString * +_find_shortcut_load_path(NSString *path_hint) +{ + // The system recognizes some shortcuts to frameworks that are buried in + // other umbrella frameworks. This dictionary gives those shortcuts. + // + // Ideally external code would just load the umbrella frameworks, but this + // is retained for backward compatibility with existing code + NSDictionary *shortcuts = [NSDictionary dictionaryWithObjectsAndKeys: + @"/System/Library/Frameworks/ApplicationServices.framework/ Frameworks/CoreGraphics.framework", @"CoreGraphics", + @"/System/Library/Frameworks/Quartz.framework/Frameworks/ PDFKit.framework", @"PDFKit", + @"/System/Library/Frameworks/Quartz.framework/Frameworks/ QuartzComposer.framework", @"QuartzComposer", + @"/System/Library/Frameworks/Quartz.framework/Frameworks/ ImageKit.framework", @"ImageKit", + nil, nil]; + + return [shortcuts objectForKey: path_hint]; +} + +static NSString *nsstring_for_ruby_path(VALUE ruby_path) +{ + // Convert a ruby string into a NSString with a file system path. + Check_Type(ruby_path, T_STRING); + const char *path_cstr = RSTRING_PTR(ruby_path); + return [[NSFileManager defaultManager] stringWithFileSystemRepresentation: path_cstr + length: strlen(path_cstr)]; +} + +NSString * +_find_application_load_path(VALUE mOSX, NSString *path_hint) +{ + // When the init routines in RBRuntime.m are loading an appilcation, plugin + // or other bundle, they stuff the location of the frameworks and/or + // shared frameworks directory of that bundle in the array-valued constant + // RUBYCOCOA_FRAMEWORK_PATHS in the Ruby environment. This routine searches + // those paths for a framework matching the given path hint. + NSString *framework_load_path = nil; + + // Make sure that the path_hint has an extension, add .framework if not + if([[path_hint pathExtension] length] == 0) { + path_hint = [path_hint stringByAppendingPathExtension: @"framework"]; + } + + RB_ID constant_name = rb_intern("RUBYCOCOA_FRAMEWORK_PATHS"); + if(rb_const_defined(mOSX, constant_name)) { + long index; + VALUE ruby_array = rb_const_get(mOSX, constant_name); + NSFileManager *fileManager = [NSFileManager defaultManager]; + + Check_Type(ruby_array, T_ARRAY); + for(index = 0; (nil == framework_load_path) && index < RARRAY_LEN(ruby_array); index++) + { + VALUE ruby_search_path = rb_ary_entry(ruby_array, index); + Check_Type(ruby_search_path, T_STRING); + + BOOL isDirectory = false; + NSString *search_path = nsstring_for_ruby_path(ruby_search_path); + NSString *framework_test_path = [search_path stringByAppendingPathComponent: path_hint]; + if([fileManager fileExistsAtPath: framework_test_path isDirectory: &isDirectory] && isDirectory) { + framework_load_path = framework_test_path; + } + } + } + + return framework_load_path; +} + +NSString * +_bundle_path_for_framework(VALUE mOSX, NSString *path_hint) +{ + NSString *framework_load_path = nil; // The result we are looking for. + + BOOL isDirectory = false; + if([path_hint isAbsolutePath] + && [[NSFileManager defaultManager] fileExistsAtPath: path_hint isDirectory: &isDirectory] + && isDirectory) + { + // If the path is an absolute path to a directory, then we assume its a + // framework and return it. + framework_load_path = path_hint; + } else { + // If the path hint is one of the shortcut paths, this will resolve the + // shortcut and return the proper path. + framework_load_path = _find_shortcut_load_path(path_hint); + + // If it's not found yet, consult the application load paths that + // are part of the Ruby environment. + if(nil == framework_load_path) { + framework_load_path = _find_application_load_path(mOSX, path_hint); + } + + // Still not found? Try the standard library locations + if(nil == framework_load_path) { + NSString *search_path = nil; + NSArray *paths_to_search = NSSearchPathForDirectoriesInDomains( NSLibraryDirectory, NSUserDomainMask | NSLocalDomainMask | NSSystemDomainMask, true); + NSEnumerator *enumerator = [paths_to_search objectEnumerator]; + while( (nil == framework_load_path) && nil != (search_path = [enumerator nextObject])) + { + framework_load_path = _find_framework_in_directory(search_path, path_hint); + } + } + + // If the framework still hasn't been found then we look inside the + // Developer tree for it. + if(nil == framework_load_path) + { + NSString *search_path = nil; + NSArray *paths_to_search = NSSearchPathForDirectoriesInDomains(NSDeveloperDirectory, NSUserDomainMask | NSLocalDomainMask | NSSystemDomainMask, YES); + NSEnumerator *enumerator = [paths_to_search objectEnumerator]; + while( (nil == framework_load_path) && nil != (search_path = [enumerator nextObject])) + { + framework_load_path = _find_framework_in_directory(search_path, path_hint); + } + } + } + + return framework_load_path; +} + +static VALUE +osx__bundle_path_for_framework(VALUE mOSX, VALUE ruby_path_hint) +{ + // This is a Ruby interface routine for _bundle_path_for_framework. + // It does little more than handle translation of the arguments and + // return values. + + VALUE retVal = Qnil; + + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + NSString *path_hint = nsstring_for_ruby_path(ruby_path_hint); + NSString *bundle_path = _bundle_path_for_framework(mOSX, path_hint); + if(bundle_path) + { + const char *export_path = [bundle_path fileSystemRepresentation]; + retVal = rb_str_new2(export_path); + } + + [pool release]; + return retVal; +} + +// Forward declaration of _load_subframeworks for recursive calling +static void _load_subframeworks(VALUE mOSX, NSBundle *umbrella_framework); + +static VALUE _load_framework(VALUE mOSX, NSString *framework_load_path) +{ + VALUE retVal = Qfalse; + + // Load a given framework and import its bridge support symbols + const char *path_as_cstr = [framework_load_path fileSystemRepresentation]; + + NSBundle *framework_as_bundle = [NSBundle bundleWithPath: framework_load_path]; + if(nil != framework_as_bundle) + { + + // If the framework has already been loaded then require_framework returns false. + // But we go ahead and try to read the bridge support information. + if([framework_as_bundle isLoaded]) { + retVal = Qfalse; + } else { + // The framework was not loaded so try to load it. + NSError *error = nil; + if(![framework_as_bundle loadAndReturnError: &error]) + { + rb_raise(rb_eRuntimeError, "Framework at path `%s' could not be loaded: %s", + path_as_cstr, [[error description] UTF8String]); + } else { + retVal = Qtrue; + } + } + + rb_funcall(mOSX, rb_intern("load_bridge_support_signatures"), 1, rb_str_new2(path_as_cstr)); + + // Load the contents of subframeworks as well. + _load_subframeworks(mOSX, framework_as_bundle); + } else { + rb_raise(rb_eRuntimeError, "The directory at path `%s' is not a framework or could not be loaded", path_as_cstr); + } + + return retVal; +} + +static void _load_subframeworks_in_path(VALUE mOSX, NSString *base_path) +{ + // Given a base path that is assumed to be a folder, find all the framework + // items in that path and try to load them. This can end up recursively + // calling this routine. + NSError *file_error = nil; + + NSArray *items_in_base = [[NSFileManager defaultManager] contentsOfDirectoryAtPath: base_path error: &file_error]; + if( nil == file_error && nil != items_in_base) + { + NSString *path_to_load = nil; + NSEnumerator *file_enumerator = [items_in_base objectEnumerator]; + while(nil != (path_to_load = [file_enumerator nextObject])) + { + if([[path_to_load pathExtension] isEqualToString: @"framework"]) + { + _load_framework(mOSX, [base_path stringByAppendingPathComponent: path_to_load]); + } + } + } +} + +static void _load_subframeworks(VALUE mOSX, NSBundle *umbrella_framework) +{ + // Load both the shared and private frameworks of the umbrella framework + // (if any) + _load_subframeworks_in_path(mOSX, [umbrella_framework privateFrameworksPath]); + _load_subframeworks_in_path(mOSX, [umbrella_framework sharedFrameworksPath]); +} + +static VALUE +osx_require_framework(VALUE mOSX, VALUE ruby_path_hint) +{ + VALUE retVal = Qfalse; + + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + // Try to find the path to a framework given the hint. If one is found + // try to load it. + NSString *path_hint = nsstring_for_ruby_path(ruby_path_hint); + NSString *framework_load_path = _bundle_path_for_framework(mOSX, path_hint); + if(framework_load_path) + { + retVal = _load_framework(mOSX, framework_load_path); + } else { + [pool release]; + pool = nil; + + // Note, this raises an ArgumntError to be consistent with the previous RubyCocoa code, however + // in MacRuby the code that performs this same function raises a RuntimeError. + rb_raise(rb_eArgError, "framework `%s' could not be found", RSTRING_PTR(ruby_path_hint)); + } + + [pool release]; + return retVal; +} + +static VALUE +osx_get_bridgesupport_search_paths(VALUE mOSX) +{ + VALUE pathsArray = rb_ary_new(); + + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + // Get the list of the usual domain search paths and append BridgeSupport to + // them construct a ruby array along the way. These paths will be used by the + // Ruby side of things to locate bridge support files. + NSArray *searchPaths = NSSearchPathForDirectoriesInDomains( NSLibraryDirectory, NSUserDomainMask | NSLocalDomainMask | NSSystemDomainMask, true); + NSEnumerator *enumerator = [searchPaths objectEnumerator]; + NSString *pathToAdd; + + while(nil != (pathToAdd = [enumerator nextObject])) + { + NSString *completePath = [pathToAdd stringByAppendingPathComponent: BRIDGE_SUPPORT_NAME]; + rb_ary_push(pathsArray, rb_str_new2([completePath fileSystemRepresentation])); + } + + // When the initialization routines in RBRuntime.m initialize RubyCocoa for + // an applicaiton, or other type of bundle, they put the application's private + // bridge support directories in a Ruby constant named RUBYCOCOA_SIGN_PATHS. + // We prefix the system search paths in the pathsArray with the values from + // RUBYCOCOA_SIGN_PATHS if it is defined. + RB_ID constant_name = rb_intern("RUBYCOCOA_SIGN_PATHS"); + if(rb_const_defined(mOSX, constant_name)) { + VALUE app_paths_array = rb_const_get(mOSX, constant_name); + Check_Type(app_paths_array, T_ARRAY); + + pathsArray = rb_ary_plus(pathsArray, app_paths_array); + } + + [pool release]; + + return pathsArray; +} + void initialize_bridge_support (VALUE mOSX) { @@ -2047,4 +2364,13 @@ initialize_bridge_support (VALUE mOSX) rb_define_module_function(mOSX, "lookup_informal_protocol_method_type", osx_lookup_informal_protocol_method_type, 2); + + rb_define_module_function(mOSX, "require_framework", + osx_require_framework, 1); + + rb_define_module_function(mOSX, "get_bridgesupport_search_paths", + osx_get_bridgesupport_search_paths, 0); + + rb_define_module_function(mOSX, "_bundle_path_for_framework", + osx__bundle_path_for_framework, 1); } diff --git a/framework/src/ruby/osx/objc/oc_import.rb b/framework/src/ ruby/osx/objc/oc_import.rb index 78229d1..cff1b71 100644 --- a/framework/src/ruby/osx/objc/oc_import.rb +++ b/framework/src/ruby/osx/objc/oc_import.rb @@ -8,94 +8,9 @@ require 'osx/objc/oc_wrapper' module OSX - - FRAMEWORK_PATHS = [ - '/System/Library/Frameworks', - '/Library/Frameworks' - ] - - SIGN_PATHS = [ - '/System/Library/BridgeSupport', - '/Library/BridgeSupport' - ] - - PRE_SIGN_PATHS = - if path = ENV['BRIDGE_SUPPORT_PATH'] - path.split(':') - else - [] - end - - FRAMEWORK_PATHS.concat(RUBYCOCOA_FRAMEWORK_PATHS) - - if path = ENV['HOME'] - FRAMEWORK_PATHS << File.join(ENV['HOME'], 'Library', 'Frameworks') - SIGN_PATHS << File.join(ENV['HOME'], 'Library', 'BridgeSupport') - end - - # A name-to-path cache for the frameworks we support that are buried into umbrella frameworks. - QUICK_FRAMEWORKS = { - 'CoreGraphics' => '/System/Library/Frameworks/ ApplicationServices.framework/Frameworks/CoreGraphics.framework', - 'PDFKit' => '/System/Library/Frameworks/Quartz.framework/ Frameworks/PDFKit.framework', - 'QuartzComposer' => '/System/Library/Frameworks/Quartz.framework/ Frameworks/QuartzComposer.framework', - 'ImageKit' => '/System/Library/Frameworks/Quartz.framework/ Frameworks/ImageKit.framework' - } - - def _bundle_path_for_framework(framework) - if framework[0] == ?/ - [OSX::NSBundle.bundleWithPath(framework), framework] - elsif path = QUICK_FRAMEWORKS[framework] - [OSX::NSBundle.bundleWithPath(path), path] - else - path = FRAMEWORK_PATHS.map { |dir| - File.join(dir, "#{framework}.framework") - }.find { |path| - File.exist?(path) - } - if path - [OSX::NSBundle.bundleWithPath(path), path] - end - end - end - module_function :_bundle_path_for_framework - - # The OSX::require_framework method imports Mac OS X frameworks and uses the - # BridgeSupport metadata to add Ruby entry points for the framework's Classes, - # methods, and Constants into the OSX module. - # - # The framework parameter is a reference to the framework that should be - # imported. This may be a full path name to a particular framework, a shortcut, - # or a framework name. The shortcuts are the keys listed in the - # <tt>QUICK_FRAMEWORKS</tt> hash. - # - # If a framework name (with no path) is given, then the method searches a number - # of directories. Those directories (in search order) are: - # 1. /System/Library/Frameworks - # 2. /Library/Frameworks - # 3. Any directories in the RUBYCOCOA_FRAMEWORK_PATHS array, if defined - # 4. ENV['HOME']/Library/Frameworks, if the HOME environment variable is defined - # - # When using the search paths, the <tt>.framework</tt> file type extension should - # be omitted from the framework name passed to the method. - # - # If the method loads the framework successfully, it returns <tt>true</tt>. - # If the framework was already loaded the method returns <tt>false</ tt>. - # If the method is unable to locate, or unable to load the framework then it - # raises an <tt>ArgumentError</tt>. - def require_framework(framework) - return false if framework_loaded?(framework) - bundle, path = _bundle_path_for_framework(framework) - bundle.oc_load - if not bundle.isLoaded? then - raise ArgumentError, "Can't load framework '#{framework}'" - end - load_bridge_support_signatures(path) - return true - end - module_function :require_framework - def framework_loaded?(framework) - bundle, path = _bundle_path_for_framework(framework) + path = _bundle_path_for_framework(framework) + bundle = NSBundle.bundleWithPath(path); unless bundle.nil? loaded = bundle.isLoaded if loaded then @@ -135,35 +50,28 @@ module OSX module_function :__load_bridge_support_file__ def load_bridge_support_signatures(framework) - # First, look into the pre paths. - fname = framework[0] == ?/ ? File.basename(framework, '.framework') : framework - PRE_SIGN_PATHS.each { |dir| return true if __load_bridge_support_file__(dir, fname) } - - # A path to a framework, let's search for a BridgeSupport file inside the Resources folder. - if framework[0] == ?/ - path = File.join(framework, 'Resources', 'BridgeSupport') + # strip the framework path down to it's base file name. + fname = (framework[0] == ?/) ? File.basename(framework, '.framework') : framework + + # The environment variable BRIDGE_SUPPORT_PATH can contain a list of + # directories that should be searched for bridge support files before the + # standard loctions are examined. + bridge_support_env = ENV['BRIDGE_SUPPORT_PATH']; + bridge_support_paths = bridge_support_env ? bridge_support_env.split(':') : [] + bridge_support_paths.each { |dir| return true if __load_bridge_support_file__(dir, fname) } + + # search the framework itself to see if it has BridgeSupport in its + # resources area + path = _bundle_path_for_framework(framework); + if path && File.exist?(path) + path = File.join(path, 'Resources', 'BridgeSupport') return true if __load_bridge_support_file__(path, fname) - framework = fname - end - - # Let's try to localize the framework and see if it contains the metadata. - FRAMEWORK_PATHS.each do |dir| - path = File.join(dir, "#{framework}.framework") - if File.exist?(path) - path = File.join(path, 'Resources', 'BridgeSupport') - return true if __load_bridge_support_file__(path, fname) - end - end - - # Try the app/bundle specific and RubyCocoa.framework metadata directories. - RUBYCOCOA_SIGN_PATHS.each do |path| - if File.exist?(path) then - return true if __load_bridge_support_file__(path, fname) - end end - # We can still look into the general metadata directories. - SIGN_PATHS.each { |dir| return true if __load_bridge_support_file__(dir, fname) } + # Get the bridge support folders in the currently loading bundle, as well + # as those in the file domain areas (~/Library/BridgeSupport, / Library/BridgeSupport + # /System/Library/BridgeSupport, etc). + get_bridgesupport_search_paths.each { |dir| return true if __load_bridge_support_file__(dir, fname) } # Damnit! warn "Can't find signatures file for #{framework}" if OSX._debug? On 19 apr 2008, at 05:11, Scott Thompson wrote: > My apologies for needing remedial hand-holding on this. I'm new to > git and it's got me a bit confused. > > I've made my changes in support of require_framework and I'd like to > submit them now. I'm afraid, however, that after reading a lot of > documentation on git, I'm not sure how to do that. > > I used "git" to clone down a copy of the source. Then I branched the > code, made my changes in the branch, but I'm unsure how to proceed. > My git status says something like: > > # On branch rst_require_framework > # Changes to be committed: > # (use "git reset HEAD <file>..." to unstage) > # > # modified: framework/src/objc/BridgeSupport.m > # modified: framework/src/ruby/osx/objc/oc_import.rb > # > # Changed but not updated: > # (use "git add <file>..." to update what will be committed) > # > # modified: framework/RubyCocoa.xcodeproj/project.pbxproj > # > [snip -- untracked files left off for brevity] > > So the two files I've changed are there in my branch and I'm ready to > commit them. What's not clear to me is how I make those changes > available to you guys so you can review them and put them in the > "real" repository. > > My understanding is that when I did the git clone, I got both a > repository and a working copy. If I commit it should change the > repository, but how do I forward those changes to the community at > large? > > Scott > > _______________________________________________ > Rubycocoa-devel mailing list > Rubyc****@lists***** > http://lists.sourceforge.jp/mailman/listinfo/rubycocoa-devel