Skip to content

Commit 4f98263

Browse files
authored
Merge pull request #127 from glebm/static
Link C/C++ stdlib statically for binary gems
2 parents 3a26ea4 + e1e44d2 commit 4f98263

File tree

6 files changed

+117
-39
lines changed

6 files changed

+117
-39
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: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,27 @@ task default: :test
44

55
require 'rake/extensiontask'
66
gem_spec = Gem::Specification.load("sassc.gemspec")
7+
8+
# HACK: Prevent rake-compiler from overriding required_ruby_version,
9+
# because the shared library here is Ruby-agnostic.
10+
# See https://github.com/rake-compiler/rake-compiler/issues/153
11+
module FixRequiredRubyVersion
12+
def required_ruby_version=(*); end
13+
end
14+
Gem::Specification.send(:prepend, FixRequiredRubyVersion)
15+
716
Rake::ExtensionTask.new('libsass', gem_spec) do |ext|
817
ext.name = 'libsass'
918
ext.ext_dir = 'ext'
1019
ext.lib_dir = 'lib/sassc'
1120
ext.cross_compile = true
1221
ext.cross_platform = %w[x86-mingw32 x64-mingw32 x86-linux x86_64-linux]
22+
23+
# Link C++ stdlib statically when building binary gems.
24+
ext.cross_config_options << '--enable-static-stdlib'
25+
26+
ext.cross_config_options << '--disable-march-tune-native'
27+
1328
ext.cross_compiling do |spec|
1429
spec.files.reject! { |path| File.fnmatch?('ext/*', path) }
1530
end
@@ -18,11 +33,19 @@ end
1833
desc 'Compile all native gems via rake-compiler-dock (Docker)'
1934
task 'gem:native' do
2035
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"
36+
37+
# The RUBY_CC_VERSION here doesn't matter for the final package.
38+
# Only one version should be specified, as the shared library is Ruby-agnostic.
39+
#
40+
# g++-multilib is installed for 64->32-bit cross-compilation.
41+
RakeCompilerDock.sh "sudo apt-get install -y g++-multilib && bundle && gem i rake --no-document && "\
42+
"rake clean && rake cross native gem MAKE='nice make -j`nproc`' "\
43+
"RUBY_CC_VERSION=2.6.0 CLEAN=1"
2444
end
2545

46+
CLEAN.include 'tmp', 'pkg', 'lib/sassc/libsass.so', 'ext/libsass/VERSION',
47+
'ext/*.{o,so,bundle}', 'ext/Makefile'
48+
2649
desc "Run all tests"
2750
task test: 'compile:libsass' do
2851
$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: 67 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -10,30 +10,76 @@
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+
# Set to true when building binary gems
18+
if enable_config('static-stdlib', false)
19+
$LDFLAGS << ' -static-libgcc -static-libstdc++'
20+
end
21+
22+
# Set to false when building binary gems
23+
if enable_config('march-tune-native', true)
24+
$CFLAGS << ' -march=native -mtune=native'
25+
$CXXFLAGS << ' -march=native -mtune=native'
26+
end
27+
28+
if enable_config('lto', true)
29+
$CFLAGS << ' -flto'
30+
$CXXFLAGS << ' -flto'
31+
$LDFLAGS << ' -flto'
32+
end
33+
34+
# Disable noisy compilation warnings.
35+
$warnflags = ''
36+
$CFLAGS.gsub!(/[\s+](-ansi|-std=[^\s]+)/, '')
37+
38+
dir_config 'libsass'
39+
40+
libsass_version = Dir.chdir(libsass_dir) do
41+
if File.exist?('VERSION')
42+
File.read('VERSION').chomp
43+
elsif File.exist?('.git')
44+
ver = %x[git describe --abbrev=4 --dirty --always --tags].chomp
45+
File.write('VERSION', ver)
46+
ver
47+
end
48+
end
49+
50+
if libsass_version
51+
libsass_version_def = %Q{ -DLIBSASS_VERSION='"#{libsass_version}"'}
52+
$CFLAGS << libsass_version_def
53+
$CXXFLAGS << libsass_version_def
54+
end
55+
56+
$INCFLAGS << " -I$(srcdir)/libsass/include"
57+
$VPATH << "$(srcdir)/libsass/src"
58+
Dir.chdir(__dir__) do
59+
$VPATH += Dir['libsass/src/*/'].map { |p| "$(srcdir)/#{p}" }
60+
$srcs = Dir['libsass/src/**/*.{c,cpp}']
61+
end
62+
63+
MakeMakefile::LINK_SO << "\nstrip -x $@"
2564

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}
65+
# Don't link libruby.
66+
$LIBRUBYARG = nil
3067

31-
install: libsass.#{dl_ext}
32-
cp libsass.#{dl_ext} '$(LIBSASS_OUT)'
68+
# Disable .def file generation for mingw, as it defines an
69+
# `Init_libsass` export which we don't have.
70+
MakeMakefile.send(:remove_const, :EXPORT_PREFIX)
71+
MakeMakefile::EXPORT_PREFIX = nil
3372

34-
clean:
35-
$(MAKE) -C '$(SUB_DIR)' clean
36-
rm -f '$(LIBSASS_OUT)' libsass.#{dl_ext}
73+
if RUBY_ENGINE == 'jruby' &&
74+
Gem::Version.new(RUBY_ENGINE_VERSION) < Gem::Version.new('9.2.8.0')
75+
# COUTFLAG is not set correctly on jruby<9.2.8.0
76+
# See https://github.com/jruby/jruby/issues/5749
77+
MakeMakefile.send(:remove_const, :COUTFLAG)
78+
MakeMakefile::COUTFLAG = '-o $(empty)'
79+
80+
# CCDLFLAGS is not set correctly on jruby<9.2.8.0
81+
# See https://github.com/jruby/jruby/issues/5751
82+
$CXXFLAGS << ' -fPIC'
83+
end
3784

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

lib/sassc/native.rb

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,8 @@ module SassC
66
module Native
77
extend FFI::Library
88

9-
spec = Gem.loaded_specs["sassc"]
10-
gem_root = spec.gem_dir
11-
12-
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
9+
dl_ext = (RbConfig::CONFIG['host_os'] =~ /darwin/ ? 'bundle' : 'so')
10+
ffi_lib File.expand_path("libsass.#{dl_ext}", __dir__)
1911

2012
require_relative "native/sass_value"
2113

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)