1#!/usr/bin/env python
2# encoding: utf-8
3# daniel.svensson at purplescout.se 2008
4# Thomas Nagy 2016-2018 (ita)
5
6"""
7Support for Ruby extensions. A C/C++ compiler is required::
8
9	def options(opt):
10		opt.load('compiler_c ruby')
11	def configure(conf):
12		conf.load('compiler_c ruby')
13		conf.check_ruby_version((1,8,0))
14		conf.check_ruby_ext_devel()
15		conf.check_ruby_module('libxml')
16	def build(bld):
17		bld(
18			features = 'c cshlib rubyext',
19			source = 'rb_mytest.c',
20			target = 'mytest_ext',
21			install_path = '${ARCHDIR_RUBY}')
22		bld.install_files('${LIBDIR_RUBY}', 'Mytest.rb')
23"""
24
25import os
26from waflib import Errors, Options, Task, Utils
27from waflib.TaskGen import before_method, feature, extension
28from waflib.Configure import conf
29
30@feature('rubyext')
31@before_method('apply_incpaths', 'process_source', 'apply_bundle', 'apply_link')
32def init_rubyext(self):
33	"""
34	Add required variables for ruby extensions
35	"""
36	self.install_path = '${ARCHDIR_RUBY}'
37	self.uselib = self.to_list(getattr(self, 'uselib', ''))
38	if not 'RUBY' in self.uselib:
39		self.uselib.append('RUBY')
40	if not 'RUBYEXT' in self.uselib:
41		self.uselib.append('RUBYEXT')
42
43@feature('rubyext')
44@before_method('apply_link', 'propagate_uselib_vars')
45def apply_ruby_so_name(self):
46	"""
47	Strip the *lib* prefix from ruby extensions
48	"""
49	self.env.cshlib_PATTERN = self.env.cxxshlib_PATTERN = self.env.rubyext_PATTERN
50
51@conf
52def check_ruby_version(self, minver=()):
53	"""
54	Checks if ruby is installed.
55	If installed the variable RUBY will be set in environment.
56	The ruby binary can be overridden by ``--with-ruby-binary`` command-line option.
57	"""
58
59	ruby = self.find_program('ruby', var='RUBY', value=Options.options.rubybinary)
60
61	try:
62		version = self.cmd_and_log(ruby + ['-e', 'puts defined?(VERSION) ? VERSION : RUBY_VERSION']).strip()
63	except Errors.WafError:
64		self.fatal('could not determine ruby version')
65	self.env.RUBY_VERSION = version
66
67	try:
68		ver = tuple(map(int, version.split('.')))
69	except Errors.WafError:
70		self.fatal('unsupported ruby version %r' % version)
71
72	cver = ''
73	if minver:
74		cver = '> ' + '.'.join(str(x) for x in minver)
75		if ver < minver:
76			self.fatal('ruby is too old %r' % ver)
77
78	self.msg('Checking for ruby version %s' % cver, version)
79
80@conf
81def check_ruby_ext_devel(self):
82	"""
83	Check if a ruby extension can be created
84	"""
85	if not self.env.RUBY:
86		self.fatal('ruby detection is required first')
87
88	if not self.env.CC_NAME and not self.env.CXX_NAME:
89		self.fatal('load a c/c++ compiler first')
90
91	version = tuple(map(int, self.env.RUBY_VERSION.split(".")))
92
93	def read_out(cmd):
94		return Utils.to_list(self.cmd_and_log(self.env.RUBY + ['-rrbconfig', '-e', cmd]))
95
96	def read_config(key):
97		return read_out('puts RbConfig::CONFIG[%r]' % key)
98
99	cpppath = archdir = read_config('archdir')
100
101	if version >= (1, 9, 0):
102		ruby_hdrdir = read_config('rubyhdrdir')
103		cpppath += ruby_hdrdir
104		if version >= (2, 0, 0):
105			cpppath += read_config('rubyarchhdrdir')
106		cpppath += [os.path.join(ruby_hdrdir[0], read_config('arch')[0])]
107
108	self.check(header_name='ruby.h', includes=cpppath, errmsg='could not find ruby header file', link_header_test=False)
109
110	self.env.LIBPATH_RUBYEXT = read_config('libdir')
111	self.env.LIBPATH_RUBYEXT += archdir
112	self.env.INCLUDES_RUBYEXT = cpppath
113	self.env.CFLAGS_RUBYEXT = read_config('CCDLFLAGS')
114	self.env.rubyext_PATTERN = '%s.' + read_config('DLEXT')[0]
115
116	# ok this is really stupid, but the command and flags are combined.
117	# so we try to find the first argument...
118	flags = read_config('LDSHARED')
119	while flags and flags[0][0] != '-':
120		flags = flags[1:]
121
122	# we also want to strip out the deprecated ppc flags
123	if len(flags) > 1 and flags[1] == "ppc":
124		flags = flags[2:]
125
126	self.env.LINKFLAGS_RUBYEXT = flags
127	self.env.LINKFLAGS_RUBYEXT += read_config('LIBS')
128	self.env.LINKFLAGS_RUBYEXT += read_config('LIBRUBYARG_SHARED')
129
130	if Options.options.rubyarchdir:
131		self.env.ARCHDIR_RUBY = Options.options.rubyarchdir
132	else:
133		self.env.ARCHDIR_RUBY = read_config('sitearchdir')[0]
134
135	if Options.options.rubylibdir:
136		self.env.LIBDIR_RUBY = Options.options.rubylibdir
137	else:
138		self.env.LIBDIR_RUBY = read_config('sitelibdir')[0]
139
140@conf
141def check_ruby_module(self, module_name):
142	"""
143	Check if the selected ruby interpreter can require the given ruby module::
144
145		def configure(conf):
146			conf.check_ruby_module('libxml')
147
148	:param module_name: module
149	:type  module_name: string
150	"""
151	self.start_msg('Ruby module %s' % module_name)
152	try:
153		self.cmd_and_log(self.env.RUBY + ['-e', 'require \'%s\';puts 1' % module_name])
154	except Errors.WafError:
155		self.end_msg(False)
156		self.fatal('Could not find the ruby module %r' % module_name)
157	self.end_msg(True)
158
159@extension('.rb')
160def process(self, node):
161	return self.create_task('run_ruby', node)
162
163class run_ruby(Task.Task):
164	"""
165	Task to run ruby files detected by file extension .rb::
166
167		def options(opt):
168			opt.load('ruby')
169
170		def configure(ctx):
171			ctx.check_ruby_version()
172
173		def build(bld):
174			bld.env.RBFLAGS = '-e puts "hello world"'
175			bld(source='a_ruby_file.rb')
176	"""
177	run_str = '${RUBY} ${RBFLAGS} -I ${SRC[0].parent.abspath()} ${SRC}'
178
179def options(opt):
180	"""
181	Add the ``--with-ruby-archdir``, ``--with-ruby-libdir`` and ``--with-ruby-binary`` options
182	"""
183	opt.add_option('--with-ruby-archdir', type='string', dest='rubyarchdir', help='Specify directory where to install arch specific files')
184	opt.add_option('--with-ruby-libdir', type='string', dest='rubylibdir', help='Specify alternate ruby library path')
185	opt.add_option('--with-ruby-binary', type='string', dest='rubybinary', help='Specify alternate ruby binary')
186
187