pycall 1.3.1 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile CHANGED
@@ -1,31 +1,93 @@
1
- require "bundler"
2
- Bundler::GemHelper.install_tasks
1
+ require "bundler/gem_helper"
2
+ require "rake/clean"
3
3
 
4
- require "rake"
5
- require "rake/extensiontask"
6
- require "rspec/core/rake_task"
4
+ base_dir = File.join(File.dirname(__FILE__))
5
+
6
+ helper = Bundler::GemHelper.new(base_dir)
7
+ helper.install
8
+ spec = helper.gemspec
9
+
10
+ def run_extconf(build_dir, extension_dir, *arguments)
11
+ cd(build_dir) do
12
+ ruby(File.join(extension_dir, "extconf.rb"), *arguments)
13
+ end
14
+ end
15
+
16
+ def make_command
17
+ if RUBY_PLATFORM =~ /mswin/
18
+ "nmake"
19
+ else
20
+ ENV["MAKE"] || find_make
21
+ end
22
+ end
23
+
24
+ def find_make
25
+ candidates = ["gmake", "make"]
26
+ paths = ENV.fetch("PATH", "").split(File::PATH_SEPARATOR)
27
+ exeext = RbConfig::CONFIG["EXEEXT"]
28
+ candidates.each do |candidate|
29
+ paths.each do |path|
30
+ cmd = File.join(path, "#{candidate}#{exeext}")
31
+ return cmd if File.executable?(cmd)
32
+ end
33
+ end
34
+ end
7
35
 
8
36
  Dir[File.expand_path('../tasks/**/*.rake', __FILE__)].each {|f| load f }
9
37
 
10
- gem_spec = eval(File.read('pycall.gemspec'))
11
- Rake::ExtensionTask.new('pycall', gem_spec) do |ext|
12
- ext.lib_dir = File.join(*['lib', ENV['FAT_DIR']].compact)
13
- ext.cross_compile = true
14
- ext.cross_platform = %w[x86-mingw32 x64-mingw32]
15
- ext.cross_compiling do |s|
16
- s.files.concat %w[lib/2.2/pycall.so lib/2.3/pycall.so lib/2.4/pycall.so]
38
+ spec.extensions.each do |extension|
39
+ extension_dir = File.join(base_dir, File.dirname(extension))
40
+ build_dir = ENV["BUILD_DIR"]
41
+ if build_dir
42
+ build_dir = File.join(build_dir, "pycall")
43
+ directory build_dir
44
+ else
45
+ build_dir = extension_dir
46
+ end
47
+
48
+ makefile = File.join(build_dir, "Makefile")
49
+ file makefile => build_dir do
50
+ run_extconf(build_dir, extension_dir)
51
+ end
52
+
53
+ CLOBBER << makefile
54
+ CLOBBER << File.join(build_dir, "mkmf.log")
55
+
56
+ desc "Configure"
57
+ task configure: makefile
58
+
59
+ desc "Compile"
60
+ task compile: makefile do
61
+ cd(build_dir) do
62
+ sh(make_command)
63
+ end
64
+ end
65
+
66
+ task :clean do
67
+ cd(build_dir) do
68
+ sh(make_command, "clean") if File.exist?("Makefile")
69
+ end
17
70
  end
18
71
  end
19
72
 
20
- Rake::ExtensionTask.new('pycall/spec_helper')
73
+ require "rake/extensiontask"
74
+ Rake::ExtensionTask.new("pycall/spec_helper")
21
75
 
22
- desc "Compile binaries for mingw platform using rake-compiler-dock"
23
- task 'build:mingw' do
24
- require 'rake_compiler_dock'
25
- RakeCompilerDock.sh "bundle && rake cross native gem RUBY_CC_VERSION=2.1.6:2.2.2:2.3.0:2.4.0"
76
+ desc "Run tests"
77
+ task :test do
78
+ cd(base_dir) do
79
+ ruby("test/run-test.rb")
80
+ end
26
81
  end
27
82
 
28
- RSpec::Core::RakeTask.new(:spec)
83
+ task default: :test
84
+
85
+ require "rspec/core/rake_task"
86
+ RSpec::Core::RakeTask.new(:spec) do |t|
87
+ ext_dir = File.join(base_dir, "ext/pycall")
88
+ t.ruby_opts = "-I#{ext_dir}"
89
+ t.verbose = true
90
+ end
29
91
 
30
- task :default => :spec
92
+ task default: :spec
31
93
  task spec: :compile
@@ -0,0 +1,77 @@
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "code",
5
+ "execution_count": 1,
6
+ "id": "burning-montreal",
7
+ "metadata": {},
8
+ "outputs": [
9
+ {
10
+ "data": {
11
+ "text/plain": [
12
+ "<module 'folium' from '/opt/brew/Cellar/python@3.9/3.9.2/Frameworks/Python.framework/Versions/3.9/lib/python3.9/site-packages/folium/__init__.py'>"
13
+ ]
14
+ },
15
+ "execution_count": 1,
16
+ "metadata": {},
17
+ "output_type": "execute_result"
18
+ }
19
+ ],
20
+ "source": [
21
+ "require \"pycall\"\n",
22
+ "folium = PyCall.import_module(\"folium\")"
23
+ ]
24
+ },
25
+ {
26
+ "cell_type": "code",
27
+ "execution_count": 7,
28
+ "id": "resistant-agriculture",
29
+ "metadata": {
30
+ "scrolled": false
31
+ },
32
+ "outputs": [
33
+ {
34
+ "data": {
35
+ "text/html": [
36
+ "<div style=\"width:100%;\"><div style=\"position:relative;width:100%;height:0;padding-bottom:60%;\"><span style=\"color:#565656\">Make this Notebook Trusted to load map: File -> Trust Notebook</span><iframe src=\"about:blank\" style=\"position:absolute;width:100%;height:100%;left:0;top:0;border:none !important;\" data-html=%3C%21DOCTYPE%20html%3E%0A%3Chead%3E%20%20%20%20%0A%20%20%20%20%3Cmeta%20http-equiv%3D%22content-type%22%20content%3D%22text/html%3B%20charset%3DUTF-8%22%20/%3E%0A%20%20%20%20%0A%20%20%20%20%20%20%20%20%3Cscript%3E%0A%20%20%20%20%20%20%20%20%20%20%20%20L_NO_TOUCH%20%3D%20false%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20L_DISABLE_3D%20%3D%20false%3B%0A%20%20%20%20%20%20%20%20%3C/script%3E%0A%20%20%20%20%0A%20%20%20%20%3Cstyle%3Ehtml%2C%20body%20%7Bwidth%3A%20100%25%3Bheight%3A%20100%25%3Bmargin%3A%200%3Bpadding%3A%200%3B%7D%3C/style%3E%0A%20%20%20%20%3Cstyle%3E%23map%20%7Bposition%3Aabsolute%3Btop%3A0%3Bbottom%3A0%3Bright%3A0%3Bleft%3A0%3B%7D%3C/style%3E%0A%20%20%20%20%3Cscript%20src%3D%22https%3A//cdn.jsdelivr.net/npm/leaflet%401.6.0/dist/leaflet.js%22%3E%3C/script%3E%0A%20%20%20%20%3Cscript%20src%3D%22https%3A//code.jquery.com/jquery-1.12.4.min.js%22%3E%3C/script%3E%0A%20%20%20%20%3Cscript%20src%3D%22https%3A//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/js/bootstrap.min.js%22%3E%3C/script%3E%0A%20%20%20%20%3Cscript%20src%3D%22https%3A//cdnjs.cloudflare.com/ajax/libs/Leaflet.awesome-markers/2.0.2/leaflet.awesome-markers.js%22%3E%3C/script%3E%0A%20%20%20%20%3Clink%20rel%3D%22stylesheet%22%20href%3D%22https%3A//cdn.jsdelivr.net/npm/leaflet%401.6.0/dist/leaflet.css%22/%3E%0A%20%20%20%20%3Clink%20rel%3D%22stylesheet%22%20href%3D%22https%3A//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css%22/%3E%0A%20%20%20%20%3Clink%20rel%3D%22stylesheet%22%20href%3D%22https%3A//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap-theme.min.css%22/%3E%0A%20%20%20%20%3Clink%20rel%3D%22stylesheet%22%20href%3D%22https%3A//maxcdn.bootstrapcdn.com/font-awesome/4.6.3/css/font-awesome.min.css%22/%3E%0A%20%20%20%20%3Clink%20rel%3D%22stylesheet%22%20href%3D%22https%3A//cdnjs.cloudflare.com/ajax/libs/Leaflet.awesome-markers/2.0.2/leaflet.awesome-markers.css%22/%3E%0A%20%20%20%20%3Clink%20rel%3D%22stylesheet%22%20href%3D%22https%3A//cdn.jsdelivr.net/gh/python-visualization/folium/folium/templates/leaflet.awesome.rotate.min.css%22/%3E%0A%20%20%20%20%0A%20%20%20%20%20%20%20%20%20%20%20%20%3Cmeta%20name%3D%22viewport%22%20content%3D%22width%3Ddevice-width%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20initial-scale%3D1.0%2C%20maximum-scale%3D1.0%2C%20user-scalable%3Dno%22%20/%3E%0A%20%20%20%20%20%20%20%20%20%20%20%20%3Cstyle%3E%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23map_5fca0053c20247f19cc1284357d59a29%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20position%3A%20relative%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20width%3A%20100.0%25%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20height%3A%20100.0%25%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20left%3A%200.0%25%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20top%3A%200.0%25%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20%3C/style%3E%0A%20%20%20%20%20%20%20%20%0A%3C/head%3E%0A%3Cbody%3E%20%20%20%20%0A%20%20%20%20%0A%20%20%20%20%20%20%20%20%20%20%20%20%3Cdiv%20class%3D%22folium-map%22%20id%3D%22map_5fca0053c20247f19cc1284357d59a29%22%20%3E%3C/div%3E%0A%20%20%20%20%20%20%20%20%0A%3C/body%3E%0A%3Cscript%3E%20%20%20%20%0A%20%20%20%20%0A%20%20%20%20%20%20%20%20%20%20%20%20var%20map_5fca0053c20247f19cc1284357d59a29%20%3D%20L.map%28%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22map_5fca0053c20247f19cc1284357d59a29%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20center%3A%20%5B35.68053684909772%2C%20139.75749875116222%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20crs%3A%20L.CRS.EPSG3857%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20zoom%3A%2015%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20zoomControl%3A%20true%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20preferCanvas%3A%20false%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20%29%3B%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%0A%0A%20%20%20%20%20%20%20%20%0A%20%20%20%20%0A%20%20%20%20%20%20%20%20%20%20%20%20var%20tile_layer_a0bac7f0648a4b10b642992e43f1cdab%20%3D%20L.tileLayer%28%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22https%3A//%7Bs%7D.tile.openstreetmap.org/%7Bz%7D/%7Bx%7D/%7By%7D.png%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7B%22attribution%22%3A%20%22Data%20by%20%5Cu0026copy%3B%20%5Cu003ca%20href%3D%5C%22http%3A//openstreetmap.org%5C%22%5Cu003eOpenStreetMap%5Cu003c/a%5Cu003e%2C%20under%20%5Cu003ca%20href%3D%5C%22http%3A//www.openstreetmap.org/copyright%5C%22%5Cu003eODbL%5Cu003c/a%5Cu003e.%22%2C%20%22detectRetina%22%3A%20false%2C%20%22maxNativeZoom%22%3A%2018%2C%20%22maxZoom%22%3A%2018%2C%20%22minZoom%22%3A%200%2C%20%22noWrap%22%3A%20false%2C%20%22opacity%22%3A%201%2C%20%22subdomains%22%3A%20%22abc%22%2C%20%22tms%22%3A%20false%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20%29.addTo%28map_5fca0053c20247f19cc1284357d59a29%29%3B%0A%20%20%20%20%20%20%20%20%0A%20%20%20%20%0A%20%20%20%20%20%20%20%20%20%20%20%20var%20marker_b70f6fe03da84c00b40a5540bf220d8d%20%3D%20L.marker%28%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%5B35.67400045350403%2C%20139.75593234124372%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7B%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20%29.addTo%28map_5fca0053c20247f19cc1284357d59a29%29%3B%0A%20%20%20%20%20%20%20%20%0A%20%20%20%20%0A%20%20%20%20%20%20%20%20var%20popup_b529c15361c6471ca5b708097315db55%20%3D%20L.popup%28%7B%22maxWidth%22%3A%20%22100%25%22%7D%29%3B%0A%0A%20%20%20%20%20%20%20%20%0A%20%20%20%20%20%20%20%20%20%20%20%20var%20html_ceaac4d79b8942afbefdcd504a795de8%20%3D%20%24%28%60%3Cdiv%20id%3D%22html_ceaac4d79b8942afbefdcd504a795de8%22%20style%3D%22width%3A%20100.0%25%3B%20height%3A%20100.0%25%3B%22%3E%E6%97%A5%E6%AF%94%E8%B0%B7%E5%85%AC%E5%9C%92%3C/div%3E%60%29%5B0%5D%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20popup_b529c15361c6471ca5b708097315db55.setContent%28html_ceaac4d79b8942afbefdcd504a795de8%29%3B%0A%20%20%20%20%20%20%20%20%0A%0A%20%20%20%20%20%20%20%20marker_b70f6fe03da84c00b40a5540bf220d8d.bindPopup%28popup_b529c15361c6471ca5b708097315db55%29%0A%20%20%20%20%20%20%20%20%3B%0A%0A%20%20%20%20%20%20%20%20%0A%20%20%20%20%0A%3C/script%3E onload=\"this.contentDocument.open();this.contentDocument.write( decodeURIComponent(this.getAttribute('data-html')));this.contentDocument.close();\" allowfullscreen webkitallowfullscreen mozallowfullscreen></iframe></div></div>"
37
+ ],
38
+ "text/plain": [
39
+ "<folium.folium.Map object at 0x165c3bc10>"
40
+ ]
41
+ },
42
+ "execution_count": 7,
43
+ "metadata": {},
44
+ "output_type": "execute_result"
45
+ }
46
+ ],
47
+ "source": [
48
+ "map = folium.Map.new(location: [35.68053684909772, 139.75749875116222], zoom_start: 15)\n",
49
+ "folium.Marker.new([35.67400045350403, 139.75593234124372], popup: \"日比谷公園\").add_to(map)\n",
50
+ "map"
51
+ ]
52
+ },
53
+ {
54
+ "cell_type": "code",
55
+ "execution_count": null,
56
+ "id": "angry-giant",
57
+ "metadata": {},
58
+ "outputs": [],
59
+ "source": []
60
+ }
61
+ ],
62
+ "metadata": {
63
+ "kernelspec": {
64
+ "display_name": "Ruby 3.0.0",
65
+ "language": "ruby",
66
+ "name": "ruby"
67
+ },
68
+ "language_info": {
69
+ "file_extension": ".rb",
70
+ "mimetype": "application/x-ruby",
71
+ "name": "ruby",
72
+ "version": "3.0.0"
73
+ }
74
+ },
75
+ "nbformat": 4,
76
+ "nbformat_minor": 5
77
+ }
@@ -663,7 +663,7 @@ Py_ssize_t pycall_python_hexversion(void);
663
663
 
664
664
  void pycall_Py_DecRef(PyObject *);
665
665
 
666
- RUBY_EXTERN const rb_data_type_t pycall_pyptr_data_type;
666
+ extern const rb_data_type_t pycall_pyptr_data_type;
667
667
  size_t pycall_pyptr_memsize(void const *);
668
668
  void pycall_pyptr_free(void *);
669
669
 
@@ -356,11 +356,16 @@ PyRuby_getattro_with_gvl(PyRubyObject *pyro, PyObject *pyobj_name)
356
356
 
357
357
  VALUE cPyRubyPtr;
358
358
 
359
- const rb_data_type_t pycall_pyrubyptr_data_type = {
359
+ static rb_data_type_t pycall_pyrubyptr_data_type = {
360
360
  "PyCall::PyRubyPtr",
361
361
  { 0, pycall_pyptr_free, pycall_pyptr_memsize, },
362
362
  #ifdef RUBY_TYPED_FREE_IMMEDIATELY
363
- &pycall_pyptr_data_type, 0, RUBY_TYPED_FREE_IMMEDIATELY
363
+ # if defined _WIN32 && !defined __CYGWIN__
364
+ 0,
365
+ # else
366
+ &pycall_pyptr_data_type,
367
+ # endif
368
+ 0, RUBY_TYPED_FREE_IMMEDIATELY
364
369
  #endif
365
370
  };
366
371
 
@@ -462,6 +467,10 @@ pycall_init_ruby_wrapper(void)
462
467
 
463
468
  /* PyCall::PyRubyPtr */
464
469
 
470
+ #if defined _WIN32 && !defined __CYGWIN__
471
+ pycall_pyrubyptr_data_type.parent = &pycall_pyptr_data_type;
472
+ #endif
473
+
465
474
  cPyRubyPtr = rb_define_class_under(mPyCall, "PyRubyPtr", cPyPtr);
466
475
  rb_define_alloc_func(cPyRubyPtr, pycall_pyruby_allocate);
467
476
  rb_define_method(cPyRubyPtr, "__ruby_object_id__", pycall_pyruby_get_ruby_object_id, 0);
data/lib/pycall.rb CHANGED
@@ -58,6 +58,17 @@ module PyCall
58
58
  LibPython::Helpers.hasattr?(obj.__pyptr__, name)
59
59
  end
60
60
 
61
+ def same?(left, right)
62
+ case left
63
+ when PyObjectWrapper
64
+ case right
65
+ when PyObjectWrapper
66
+ return left.__pyptr__ == right.__pyptr__
67
+ end
68
+ end
69
+ false
70
+ end
71
+
61
72
  def import_module(name)
62
73
  LibPython::Helpers.import_module(name)
63
74
  end
data/lib/pycall/init.rb CHANGED
@@ -30,20 +30,15 @@ module PyCall
30
30
  remove_method :const_missing
31
31
  end
32
32
 
33
- ENV['PYTHONPATH'] = [ File.expand_path('../python', __FILE__), ENV['PYTHONPATH'] ].compact.join(File::PATH_SEPARATOR)
34
-
35
33
  LibPython.instance_variable_set(:@handle, LibPython::Finder.find_libpython(python))
36
34
  class << LibPython
37
35
  undef_method :handle
38
36
  attr_reader :handle
39
37
  end
40
38
 
41
- begin
42
- major, minor, _ = RUBY_VERSION.split('.')
43
- require "#{major}.#{minor}/pycall.so"
44
- rescue LoadError
45
- require 'pycall.so'
46
- end
39
+ require 'pycall.so'
40
+
41
+ PyCall.sys.path.append(File.expand_path('../python', __FILE__))
47
42
 
48
43
  require 'pycall/dict'
49
44
  require 'pycall/list'
@@ -1,5 +1,6 @@
1
1
  require 'pycall/error'
2
2
  require 'fiddle'
3
+ require 'pathname'
3
4
 
4
5
  module PyCall
5
6
  module LibPython
@@ -39,60 +40,102 @@ module PyCall
39
40
  def find_libpython(python = nil)
40
41
  debug_report("find_libpython(#{python.inspect})")
41
42
  python, python_config = find_python_config(python)
42
-
43
- # Try LIBPYTHON environment variable first.
44
- if (libpython = ENV['LIBPYTHON'])
45
- if File.file?(libpython)
43
+ suffix = python_config[:SHLIB_SUFFIX]
44
+
45
+ use_conda = (ENV.fetch("CONDA_PREFIX", nil) == File.dirname(python_config[:executable]))
46
+ python_home = if !ENV.key?("PYTHONHOME") || use_conda
47
+ python_config[:PYTHONHOME]
48
+ else
49
+ ENV["PYTHONHOME"]
50
+ end
51
+ ENV["PYTHONHOME"] = python_home
52
+
53
+ candidate_paths(python_config) do |path|
54
+ debug_report("Candidate: #{path}")
55
+ normalized = normalize_path(path, suffix)
56
+ if normalized
57
+ debug_report("Trying to dlopen: #{normalized}")
46
58
  begin
47
- return dlopen(libpython)
59
+ return dlopen(normalized)
48
60
  rescue Fiddle::DLError
49
- debug_report "#{$!.class}: #{$!.message}"
50
- else
51
- debug_report "Success to dlopen #{libpython.inspect} from ENV['LIBPYTHON']"
61
+ debug_report "dlopen(#{normalized.inspect}) => #{$!.class}: #{$!.message}"
52
62
  end
63
+ else
64
+ debug_report("Not found.")
53
65
  end
54
- warn "WARNING(#{self}.#{__method__}) Ignore the wrong libpython location specified in ENV['LIBPYTHON']."
55
66
  end
67
+ end
56
68
 
57
- # Find libpython (we hope):
58
- set_PYTHONHOME(python_config)
59
- libs = make_libs(python_config)
60
- libpaths = make_libpaths(python_config)
61
- multiarch = python_config[:MULTIARCH] || python_config[:multiarch]
62
- libs.each do |lib|
63
- libpaths.each do |libpath|
64
- libpath_libs = [ File.join(libpath, lib) ]
65
- libpath_libs << File.join(libpath, multiarch, lib) if multiarch
66
- libpath_libs.each do |libpath_lib|
67
- [ libpath_lib, "#{libpath_lib}.#{LIBSUFFIX}" ].each do |fullname|
68
- unless File.file? fullname
69
- debug_report "Unable to find #{fullname}"
70
- next
71
- end
72
- begin
73
- return dlopen(libpath_lib)
74
- rescue Fiddle::DLError
75
- debug_report "#{$!.class}: #{$!.message}"
76
- else
77
- debug_report "Success to dlopen #{libpaht_lib}"
78
- end
79
- end
80
- end
81
- end
69
+ def candidate_names(python_config)
70
+ names = []
71
+ names << python_config[:LDLIBRARY] if python_config[:LDLIBRARY]
72
+ suffix = python_config[:SHLIB_SUFFIX]
73
+ if python_config[:LIBRARY]
74
+ ext = File.extname(python_config[:LIBRARY])
75
+ names << python_config[:LIBRARY].delete_suffix(ext) + suffix
76
+ end
77
+ dlprefix = if windows? then "" else "lib" end
78
+ sysdata = {
79
+ v_major: python_config[:version_major],
80
+ VERSION: python_config[:VERSION],
81
+ ABIFLAGS: python_config[:ABIFLAGS],
82
+ }
83
+ [
84
+ "python%{VERSION}%{ABIFLAGS}" % sysdata,
85
+ "python%{VERSION}" % sysdata,
86
+ "python%{v_major}" % sysdata,
87
+ "python"
88
+ ].each do |stem|
89
+ names << "#{dlprefix}#{stem}#{suffix}"
82
90
  end
83
91
 
84
- # Find libpython in the system path
85
- libs.each do |lib|
86
- begin
87
- return dlopen(lib)
88
- rescue Fiddle::DLError
89
- debug_report "#{$!.class}: #{$!.message}"
90
- else
91
- debug_report "Success to dlopen #{lib}"
92
+ names.compact!
93
+ names.uniq!
94
+
95
+ debug_report("candidate_names: #{names}")
96
+ return names
97
+ end
98
+
99
+ def candidate_paths(python_config)
100
+ # The candidate library that linked by executable
101
+ yield python_config[:linked_libpython]
102
+
103
+ lib_dirs = make_libpaths(python_config)
104
+ lib_basenames = candidate_names(python_config)
105
+
106
+ # candidates by absolute paths
107
+ lib_dirs.each do |dir|
108
+ lib_basenames.each do |name|
109
+ yield File.join(dir, name)
92
110
  end
93
111
  end
94
112
 
95
- raise ::PyCall::PythonNotFound
113
+ # library names for searching in system library paths
114
+ lib_basenames.each do |name|
115
+ yield name
116
+ end
117
+ end
118
+
119
+ def normalize_path(path, suffix, apple_p=apple?)
120
+ return nil if path.nil?
121
+ case
122
+ when path.nil?,
123
+ Pathname.new(path).relative?
124
+ nil
125
+ when File.exist?(path)
126
+ File.realpath(path)
127
+ when File.exist?(path + suffix)
128
+ File.realpath(path + suffix)
129
+ when apple_p
130
+ normalize_path(remove_suffix_apple(path), ".so", false)
131
+ else
132
+ nil
133
+ end
134
+ end
135
+
136
+ # Strip off .so or .dylib
137
+ def remove_suffix_apple(path)
138
+ path.sub(/\.(?:dylib|so)\z/, '')
96
139
  end
97
140
 
98
141
  def investigate_python_config(python)
@@ -119,47 +162,25 @@ module PyCall
119
162
  File.expand_path('../../python/investigator.py', __FILE__)
120
163
  end
121
164
 
122
- def set_PYTHONHOME(python_config)
123
- if !ENV.has_key?('PYTHONHOME')
124
- case RUBY_PLATFORM
125
- when /mingw32/, /cygwin/, /mswin/
126
- ENV['PYTHONHOME'] = python_config[:exec_prefix]
127
- else
128
- ENV['PYTHONHOME'] = python_config.values_at(:prefix, :exec_prefix).join(':')
129
- end
130
- end
131
- end
165
+ def make_libpaths(python_config)
166
+ libpaths = python_config.values_at(:LIBPL, :srcdir, :LIBDIR)
132
167
 
133
- def make_libs(python_config)
134
- libs = []
135
- %i(INSTSONAME LDLIBRARY).each do |key|
136
- lib = python_config[key]
137
- libs << lib << File.basename(lib) if lib
138
- end
139
- if (lib = python_config[:LIBRARY])
140
- libs << File.basename(lib, File.extname(lib))
168
+ if windows?
169
+ libpaths << File.dirname(python_config[:executable])
170
+ else
171
+ libpaths << File.expand_path('../../lib', python_config[:executable])
141
172
  end
142
173
 
143
- v = python_config[:VERSION]
144
- libs << "#{LIBPREFIX}python#{v}" << "#{LIBPREFIX}python"
145
- libs.uniq!
146
-
147
- debug_report "libs: #{libs.inspect}"
148
- return libs
149
- end
150
-
151
- def make_libpaths(python_config)
152
- executable = python_config[:executable]
153
- libpaths = [ python_config[:LIBDIR] ]
154
- if Fiddle::WINDOWS
155
- libpaths << File.dirname(executable)
156
- else
157
- libpaths << File.expand_path('../../lib', executable)
174
+ if apple?
175
+ libpaths << python_config[:PYTHONFRAMEWORKPREFIX]
158
176
  end
159
- libpaths << python_config[:PYTHONFRAMEWORKPREFIX]
177
+
160
178
  exec_prefix = python_config[:exec_prefix]
161
- libpaths << exec_prefix << File.join(exec_prefix, 'lib')
179
+ libpaths << exec_prefix
180
+ libpaths << File.join(exec_prefix, 'lib')
181
+
162
182
  libpaths.compact!
183
+ libpaths.uniq!
163
184
 
164
185
  debug_report "libpaths: #{libpaths.inspect}"
165
186
  return libpaths
@@ -167,6 +188,14 @@ module PyCall
167
188
 
168
189
  private
169
190
 
191
+ def windows?
192
+ Fiddle::WINDOWS
193
+ end
194
+
195
+ def apple?
196
+ RUBY_PLATFORM.include?("darwin")
197
+ end
198
+
170
199
  def dlopen(libname)
171
200
  Fiddle.dlopen(libname).tap do |handle|
172
201
  debug_report("dlopen(#{libname.inspect}) = #{handle.inspect}") if handle
@@ -185,3 +214,22 @@ module PyCall
185
214
  end
186
215
  end
187
216
  end
217
+
218
+ if __FILE__ == $0
219
+ require "pp"
220
+ python, python_config = PyCall::LibPython::Finder.find_python_config
221
+
222
+ puts "python_config:"
223
+ pp python_config
224
+
225
+ puts "\ncandidate_names:"
226
+ p PyCall::LibPython::Finder.candidate_names(python_config)
227
+
228
+ puts "\nlib_dirs:"
229
+ p PyCall::LibPython::Finder.make_libpaths(python_config)
230
+
231
+ puts "\ncandidate_paths:"
232
+ PyCall::LibPython::Finder.candidate_paths(python_config) do |path|
233
+ puts "- #{path}"
234
+ end
235
+ end