Skip to content

Commit

Permalink
Link C/C++ stlib statically for binary gems
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
glebm committed May 29, 2019
1 parent 266c904 commit 82caf8e
Show file tree
Hide file tree
Showing 6 changed files with 97 additions and 35 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,4 @@
*.gem
mkmf.log
vendor/bundle
/ext/Makefile
23 changes: 20 additions & 3 deletions Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,36 @@ Rake::ExtensionTask.new('libsass', gem_spec) do |ext|
ext.lib_dir = 'lib/sassc'
ext.cross_compile = true
ext.cross_platform = %w[x86-mingw32 x64-mingw32 x86-linux x86_64-linux]

# Link C++ stdlib statically when building binary gems.
ext.cross_config_options << '--enable-static-stdlib'

ext.cross_compiling do |spec|
spec.files.reject! { |path| File.fnmatch?('ext/*', path) }


# Reset the required ruby version requirements.
# This is set by rake-compiler, but the shared library here is Ruby-agnostic.
spec.required_ruby_version = gem_spec.required_ruby_version
end
end

desc 'Compile all native gems via rake-compiler-dock (Docker)'
task 'gem:native' do
require 'rake_compiler_dock'
RakeCompilerDock.sh "bundle && gem i rake --no-document && "\
"rake cross native gem MAKE='nice make -j`nproc`' "\
"RUBY_CC_VERSION=2.6.0:2.5.0:2.4.0:2.3.0"

# The RUBY_CC_VERSION here doesn't matter for the final package.
# Only one version should be specified, as the shared library is Ruby-agnostic.
#
# g++-multilib is installed for 64->32-bit cross-compilation.
RakeCompilerDock.sh "sudo apt-get install -y g++-multilib && bundle && gem i rake --no-document && "\
"rake clean && rake cross native gem MAKE='nice make -j`nproc`' "\
"RUBY_CC_VERSION=2.6.0 CLEAN=1"
end

CLEAN.include 'tmp', 'pkg', 'lib/sassc/libsass.so', 'ext/libsass/VERSION',
'ext/*.{o,so,bundle}', 'ext/Makefile'

desc "Run all tests"
task test: 'compile:libsass' do
$LOAD_PATH.unshift('lib', 'test')
Expand Down
4 changes: 4 additions & 0 deletions ext/depend
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Replaces default mkmf dependencies. Default mkmf dependencies include all libruby headers.
# We don't need libruby and some of these headers are missing on JRuby (breaking compilation there).
$(OBJS): $(HDRS)

75 changes: 54 additions & 21 deletions ext/extconf.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,30 +10,63 @@
fail 'Could not fetch libsass'
end

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

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

SUB_DIR := #{libsass_dir}
# Link stdlib statically when building binary gems.
if enable_config('static-stdlib')
$LDFLAGS << ' -static-libgcc -static-libstdc++'
end

# Disable noisy compilation warnings.
$warnflags = ''
$CFLAGS.gsub!(/[\s+](-ansi|-std=[^\s]+)/, '')

dir_config 'libsass'

libsass_version = Dir.chdir(libsass_dir) do
if File.exist?('VERSION')
File.read('VERSION').chomp
elsif File.exist?('.git')
ver = %x[git describe --abbrev=4 --dirty --always --tags].chomp
File.write('VERSION', ver)
ver
end
end

if libsass_version
libsass_version_def = %Q{ -DLIBSASS_VERSION='"#{libsass_version}"'}
$CFLAGS << libsass_version_def
$CXXFLAGS << libsass_version_def
end

$INCFLAGS << " -I$(srcdir)/libsass/include"
$VPATH << "$(srcdir)/libsass/src"
Dir.chdir(__dir__) do
$VPATH += Dir['libsass/src/*/'].map { |p| "$(srcdir)/#{p}" }
$srcs = Dir['libsass/src/**/*.{c,cpp}']
end

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

install: libsass.#{dl_ext}
cp libsass.#{dl_ext} '$(LIBSASS_OUT)'
# Don't link libruby.
$LIBRUBYARG = nil

clean:
$(MAKE) -C '$(SUB_DIR)' clean
rm -f '$(LIBSASS_OUT)' libsass.#{dl_ext}
# Disable .def file generation for mingw, as it defines an
# `Init_libsass` export which we don't have.
MakeMakefile.send(:remove_const, :EXPORT_PREFIX)
MakeMakefile::EXPORT_PREFIX = nil

if RUBY_PLATFORM == 'java'
# COUTFLAG is not set correctly on jruby
# See https://github.com/jruby/jruby/issues/5749
MakeMakefile.send(:remove_const, :COUTFLAG)
MakeMakefile::COUTFLAG = '-o $(empty)'

# CCDLFLAGS is not set correctly on jruby
# See https://github.com/jruby/jruby/issues/5751
$CXXFLAGS << ' -fPIC'
end

.PHONY: clean install
MAKEFILE
create_makefile 'sassc/libsass'
7 changes: 1 addition & 6 deletions lib/sassc/native.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,7 @@ module Native
gem_root = spec.gem_dir

dl_ext = (RUBY_PLATFORM =~ /darwin/ ? 'bundle' : 'so')
ruby_version_so_path = "#{gem_root}/lib/sassc/#{RUBY_VERSION[/\d+.\d+/]}/libsass.#{dl_ext}"
if File.exist?(ruby_version_so_path)
ffi_lib ruby_version_so_path
else
ffi_lib "#{gem_root}/lib/sassc/libsass.#{dl_ext}"
end
ffi_lib "#{gem_root}/lib/sassc/libsass.#{dl_ext}"

require_relative "native/sass_value"

Expand Down
22 changes: 17 additions & 5 deletions sassc.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -40,18 +40,30 @@ Gem::Specification.new do |spec|
gem_dir = File.expand_path(File.dirname(__FILE__)) + "/"

libsass_dir = File.join(gem_dir, 'ext', 'libsass')
if !File.directory?(libsass_dir)
$stderr.puts "Error: ext/libsass not checked out. Please run:\n\n"\
" git submodule update --init"
exit 1
if !File.directory?(libsass_dir) ||
# '.', '..', and possibly '.git' from a failed checkout:
Dir.entries(libsass_dir).size <= 3
Dir.chdir(__dir__) { system('git submodule update --init') } or
fail 'Could not fetch libsass'
end

# Write a VERSION file for non-binary gems (for `SassC::Native.version`).
if !File.exist?(File.join(libsass_dir, 'VERSION'))
libsass_version = Dir.chdir(libsass_dir) do
%x[git describe --abbrev=4 --dirty --always --tags].chomp
end
File.write(File.join(libsass_dir, 'VERSION'), libsass_version)
end

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

end

0 comments on commit 82caf8e

Please sign in to comment.