Skip to content

Commit 82caf8e

Browse files
committed
Link C/C++ stlib statically for binary gems
Also: 1. Compile via `mkmf` instead of libsass's own Makefile. This is necessary to make cross-compilation work (rake-compiler hooks into mkmf). 2. Do not use per-ruby versions of `libsass.so`. It is unnecessary, because `libsass.so` is Ruby-agnostic (the FFI gem is used instead to use it from any Ruby version). 3. Add VERSION file to the non-precompiled gem. 4. Clean before every build.
1 parent 266c904 commit 82caf8e

File tree

6 files changed

+97
-35
lines changed

6 files changed

+97
-35
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,4 @@
1515
*.gem
1616
mkmf.log
1717
vendor/bundle
18+
/ext/Makefile

Rakefile

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,19 +10,36 @@ Rake::ExtensionTask.new('libsass', gem_spec) do |ext|
1010
ext.lib_dir = 'lib/sassc'
1111
ext.cross_compile = true
1212
ext.cross_platform = %w[x86-mingw32 x64-mingw32 x86-linux x86_64-linux]
13+
14+
# Link C++ stdlib statically when building binary gems.
15+
ext.cross_config_options << '--enable-static-stdlib'
16+
1317
ext.cross_compiling do |spec|
1418
spec.files.reject! { |path| File.fnmatch?('ext/*', path) }
19+
20+
21+
# Reset the required ruby version requirements.
22+
# This is set by rake-compiler, but the shared library here is Ruby-agnostic.
23+
spec.required_ruby_version = gem_spec.required_ruby_version
1524
end
1625
end
1726

1827
desc 'Compile all native gems via rake-compiler-dock (Docker)'
1928
task 'gem:native' do
2029
require 'rake_compiler_dock'
21-
RakeCompilerDock.sh "bundle && gem i rake --no-document && "\
22-
"rake cross native gem MAKE='nice make -j`nproc`' "\
23-
"RUBY_CC_VERSION=2.6.0:2.5.0:2.4.0:2.3.0"
30+
31+
# The RUBY_CC_VERSION here doesn't matter for the final package.
32+
# Only one version should be specified, as the shared library is Ruby-agnostic.
33+
#
34+
# g++-multilib is installed for 64->32-bit cross-compilation.
35+
RakeCompilerDock.sh "sudo apt-get install -y g++-multilib && bundle && gem i rake --no-document && "\
36+
"rake clean && rake cross native gem MAKE='nice make -j`nproc`' "\
37+
"RUBY_CC_VERSION=2.6.0 CLEAN=1"
2438
end
2539

40+
CLEAN.include 'tmp', 'pkg', 'lib/sassc/libsass.so', 'ext/libsass/VERSION',
41+
'ext/*.{o,so,bundle}', 'ext/Makefile'
42+
2643
desc "Run all tests"
2744
task test: 'compile:libsass' do
2845
$LOAD_PATH.unshift('lib', 'test')

ext/depend

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# Replaces default mkmf dependencies. Default mkmf dependencies include all libruby headers.
2+
# We don't need libruby and some of these headers are missing on JRuby (breaking compilation there).
3+
$(OBJS): $(HDRS)
4+

ext/extconf.rb

Lines changed: 54 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -10,30 +10,63 @@
1010
fail 'Could not fetch libsass'
1111
end
1212

13-
# Only needed because rake-compiler expects `.bundle` on macOS:
14-
# https://github.com/rake-compiler/rake-compiler/blob/9f15620e7db145d11ae2fc4ba032367903f625e3/features/support/platform_extension_helpers.rb#L5
15-
dl_ext = (RUBY_PLATFORM =~ /darwin/ ? 'bundle' : 'so')
13+
require 'mkmf'
1614

17-
File.write 'Makefile', <<-MAKEFILE
18-
ifndef DESTDIR
19-
LIBSASS_OUT = #{gem_root}/lib/sassc/libsass.#{dl_ext}
20-
else
21-
LIBSASS_OUT = $(DESTDIR)$(PREFIX)/libsass.#{dl_ext}
22-
endif
15+
$CXXFLAGS << ' -std=c++11'
2316

24-
SUB_DIR := #{libsass_dir}
17+
# Link stdlib statically when building binary gems.
18+
if enable_config('static-stdlib')
19+
$LDFLAGS << ' -static-libgcc -static-libstdc++'
20+
end
21+
22+
# Disable noisy compilation warnings.
23+
$warnflags = ''
24+
$CFLAGS.gsub!(/[\s+](-ansi|-std=[^\s]+)/, '')
25+
26+
dir_config 'libsass'
27+
28+
libsass_version = Dir.chdir(libsass_dir) do
29+
if File.exist?('VERSION')
30+
File.read('VERSION').chomp
31+
elsif File.exist?('.git')
32+
ver = %x[git describe --abbrev=4 --dirty --always --tags].chomp
33+
File.write('VERSION', ver)
34+
ver
35+
end
36+
end
37+
38+
if libsass_version
39+
libsass_version_def = %Q{ -DLIBSASS_VERSION='"#{libsass_version}"'}
40+
$CFLAGS << libsass_version_def
41+
$CXXFLAGS << libsass_version_def
42+
end
43+
44+
$INCFLAGS << " -I$(srcdir)/libsass/include"
45+
$VPATH << "$(srcdir)/libsass/src"
46+
Dir.chdir(__dir__) do
47+
$VPATH += Dir['libsass/src/*/'].map { |p| "$(srcdir)/#{p}" }
48+
$srcs = Dir['libsass/src/**/*.{c,cpp}']
49+
end
2550

26-
libsass.#{dl_ext}:#{' clean' if ENV['CLEAN']}
27-
$(MAKE) -C '$(SUB_DIR)' lib/libsass.so
28-
cp '$(SUB_DIR)/lib/libsass.so' libsass.#{dl_ext}
29-
strip -x libsass.#{dl_ext}
51+
MakeMakefile::LINK_SO << "\nstrip -x $@"
3052

31-
install: libsass.#{dl_ext}
32-
cp libsass.#{dl_ext} '$(LIBSASS_OUT)'
53+
# Don't link libruby.
54+
$LIBRUBYARG = nil
3355

34-
clean:
35-
$(MAKE) -C '$(SUB_DIR)' clean
36-
rm -f '$(LIBSASS_OUT)' libsass.#{dl_ext}
56+
# Disable .def file generation for mingw, as it defines an
57+
# `Init_libsass` export which we don't have.
58+
MakeMakefile.send(:remove_const, :EXPORT_PREFIX)
59+
MakeMakefile::EXPORT_PREFIX = nil
60+
61+
if RUBY_PLATFORM == 'java'
62+
# COUTFLAG is not set correctly on jruby
63+
# See https://github.com/jruby/jruby/issues/5749
64+
MakeMakefile.send(:remove_const, :COUTFLAG)
65+
MakeMakefile::COUTFLAG = '-o $(empty)'
66+
67+
# CCDLFLAGS is not set correctly on jruby
68+
# See https://github.com/jruby/jruby/issues/5751
69+
$CXXFLAGS << ' -fPIC'
70+
end
3771

38-
.PHONY: clean install
39-
MAKEFILE
72+
create_makefile 'sassc/libsass'

lib/sassc/native.rb

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,7 @@ module Native
1010
gem_root = spec.gem_dir
1111

1212
dl_ext = (RUBY_PLATFORM =~ /darwin/ ? 'bundle' : 'so')
13-
ruby_version_so_path = "#{gem_root}/lib/sassc/#{RUBY_VERSION[/\d+.\d+/]}/libsass.#{dl_ext}"
14-
if File.exist?(ruby_version_so_path)
15-
ffi_lib ruby_version_so_path
16-
else
17-
ffi_lib "#{gem_root}/lib/sassc/libsass.#{dl_ext}"
18-
end
13+
ffi_lib "#{gem_root}/lib/sassc/libsass.#{dl_ext}"
1914

2015
require_relative "native/sass_value"
2116

sassc.gemspec

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,18 +40,30 @@ Gem::Specification.new do |spec|
4040
gem_dir = File.expand_path(File.dirname(__FILE__)) + "/"
4141

4242
libsass_dir = File.join(gem_dir, 'ext', 'libsass')
43-
if !File.directory?(libsass_dir)
44-
$stderr.puts "Error: ext/libsass not checked out. Please run:\n\n"\
45-
" git submodule update --init"
46-
exit 1
43+
if !File.directory?(libsass_dir) ||
44+
# '.', '..', and possibly '.git' from a failed checkout:
45+
Dir.entries(libsass_dir).size <= 3
46+
Dir.chdir(__dir__) { system('git submodule update --init') } or
47+
fail 'Could not fetch libsass'
48+
end
49+
50+
# Write a VERSION file for non-binary gems (for `SassC::Native.version`).
51+
if !File.exist?(File.join(libsass_dir, 'VERSION'))
52+
libsass_version = Dir.chdir(libsass_dir) do
53+
%x[git describe --abbrev=4 --dirty --always --tags].chomp
54+
end
55+
File.write(File.join(libsass_dir, 'VERSION'), libsass_version)
4756
end
4857

4958
Dir.chdir(libsass_dir) do
5059
submodule_relative_path = File.join('ext', 'libsass')
60+
skip_re = %r{(^("?test|docs|script)/)|\.md$|\.yml$}
61+
only_re = %r{\.[ch](pp)?$}
5162
`git ls-files`.split($\).each do |filename|
52-
next if filename =~ %r{(^("?test|docs|script)/)|\.md$|\.yml$}
63+
next if filename =~ skip_re || filename !~ only_re
5364
spec.files << File.join(submodule_relative_path, filename)
5465
end
66+
spec.files << File.join(submodule_relative_path, 'VERSION')
5567
end
5668

5769
end

0 commit comments

Comments
 (0)