pycall 1.3.1 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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