diff --git a/.gitignore b/.gitignore index 65492a94..74855a98 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,4 @@ *.gem mkmf.log vendor/bundle +/ext/Makefile diff --git a/Rakefile b/Rakefile index f682dc4e..226e18af 100644 --- a/Rakefile +++ b/Rakefile @@ -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') diff --git a/ext/depend b/ext/depend new file mode 100644 index 00000000..940eda67 --- /dev/null +++ b/ext/depend @@ -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) + diff --git a/ext/extconf.rb b/ext/extconf.rb index b0370870..42427b80 100644 --- a/ext/extconf.rb +++ b/ext/extconf.rb @@ -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' diff --git a/lib/sassc/native.rb b/lib/sassc/native.rb index 16e6aaef..78cfe367 100644 --- a/lib/sassc/native.rb +++ b/lib/sassc/native.rb @@ -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" diff --git a/sassc.gemspec b/sassc.gemspec index 6c16e2e7..becd5e40 100644 --- a/sassc.gemspec +++ b/sassc.gemspec @@ -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