Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Link C/C++ stdlib statically for binary gems #127

Merged
merged 7 commits into from
Jun 5, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
29 changes: 26 additions & 3 deletions Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,27 @@ task default: :test

require 'rake/extensiontask'
gem_spec = Gem::Specification.load("sassc.gemspec")

# HACK: Prevent rake-compiler from overriding required_ruby_version,
# because the shared library here is Ruby-agnostic.
# See https://github.com/rake-compiler/rake-compiler/issues/153
module FixRequiredRubyVersion
def required_ruby_version=(*); end
end
Gem::Specification.send(:prepend, FixRequiredRubyVersion)

Rake::ExtensionTask.new('libsass', gem_spec) do |ext|
ext.name = 'libsass'
ext.ext_dir = '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_config_options << '--disable-march-tune-native'

ext.cross_compiling do |spec|
spec.files.reject! { |path| File.fnmatch?('ext/*', path) }
end
Expand All @@ -18,11 +33,19 @@ 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)

88 changes: 67 additions & 21 deletions ext/extconf.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,30 +10,76 @@
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}
# Set to true when building binary gems
if enable_config('static-stdlib', false)
$LDFLAGS << ' -static-libgcc -static-libstdc++'
end

# Set to false when building binary gems
if enable_config('march-tune-native', true)
$CFLAGS << ' -march=native -mtune=native'
$CXXFLAGS << ' -march=native -mtune=native'
end

if enable_config('lto', true)
$CFLAGS << ' -flto'
$CXXFLAGS << ' -flto'
$LDFLAGS << ' -flto'
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

MakeMakefile::LINK_SO << "\nstrip -x $@"

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

install: libsass.#{dl_ext}
cp libsass.#{dl_ext} '$(LIBSASS_OUT)'
# 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

clean:
$(MAKE) -C '$(SUB_DIR)' clean
rm -f '$(LIBSASS_OUT)' libsass.#{dl_ext}
if RUBY_ENGINE == 'jruby' &&
Gem::Version.new(RUBY_ENGINE_VERSION) < Gem::Version.new('9.2.8.0')
# COUTFLAG is not set correctly on jruby<9.2.8.0
# See https://github.com/jruby/jruby/issues/5749
MakeMakefile.send(:remove_const, :COUTFLAG)
MakeMakefile::COUTFLAG = '-o $(empty)'

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

.PHONY: clean install
MAKEFILE
create_makefile 'sassc/libsass'
12 changes: 2 additions & 10 deletions lib/sassc/native.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,8 @@ module SassC
module Native
extend FFI::Library

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

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