Skip to content

Commit

Permalink
Merge pull request #127 from glebm/static
Browse files Browse the repository at this point in the history
Link C/C++ stdlib statically for binary gems
  • Loading branch information
bolandrm authored Jun 5, 2019
2 parents 3a26ea4 + e1e44d2 commit 4f98263
Show file tree
Hide file tree
Showing 6 changed files with 117 additions and 39 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
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

0 comments on commit 4f98263

Please sign in to comment.