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

Fix: detect ICU symbols' suffix from .so file at compile time #3

Merged
merged 9 commits into from
May 7, 2017
59 changes: 54 additions & 5 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,12 +1,61 @@
---
language: crystal
dist: trusty
sudo: required

before_install:
- sudo apt-get -qq update
- sudo apt-get install -y pkg-config libicu52
matrix:
allow_failures: # FIXME: temporary fix, see https://github.com/olbat/icu.cr/issues/2
- os: osx
include:
# Linux
- os: linux
dist: precise
- os: linux
dist: trusty
- os: linux
dist: xenial
- os: linux
dist: trusty
env: GENERATE_LIB=1 # regenerate the binding
# OSX
- os: osx
osx_image: xcode8.3
- os: osx
osx_image: xcode8.3
env: GENERATE_LIB=1 # regenerate the binding

before_install: |
case $TRAVIS_OS_NAME in
linux)
sudo apt-get -qq update
if [[ -n $GENERATE_LIB ]]; then
sudo apt-get install -y llvm-3.5-dev libclang-3.5-dev
fi
sudo apt-get install -y libicu-dev
;;
osx)
brew update

# FIXME: ugly fix for the CI's image, crystal-lang should not be reinstalled
rm -f /usr/local/bin/shards
brew install crystal-lang

if [[ -n $GENERATE_LIB ]]; then
# install LLVM and Clang 3.7
brew install [email protected]
# FIXME: ugly fix since the libclang.dylib is not symlinked correctly
brew list --verbose [email protected] | grep "\.dylib$" | xargs -n1 -I{} ln -sf {} $(brew --prefix)/lib
fi

brew install icu4c
brew link --force icu4c
;;
*)
exit 1;;
esac

before_script:
- "[ -z $GENERATE_LIB ] || make"

script:
- crystal tool format --check
- crystal tool format --check src spec
- crystal spec
21 changes: 21 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
CRBIN=crystal
LGBIN=lib/libgen/bin/libgen
LGCONF=lib.yml
GENSRC=src/lib_generator.cr

generate_lib: $(LGBIN) $(LGCONF)
rm -rf src/lib_icu
$(LGBIN) $(LGCONF)
$(CRBIN) run $(GENSRC)

$(LGBIN): lib/libgen
cd lib/libgen && $(CRBIN) deps build

lib/libgen: shard.yml
$(CRBIN) deps install

clean:
rm -rf lib
rm -rf .crystal

.PHONY: clean
4 changes: 2 additions & 2 deletions lib.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
name: LibICU
ldflags: "-licuio -licui18n -liculx -licule -licuuc -licudata"
packages: "icu-uc icu-i18n icu-io icu-lx icu-le"
ldflags: "-licuio -licui18n -licuuc -licudata"
packages: "icu-uc icu-i18n icu-io"
destdir: src/lib_icu/
rename:
rules:
Expand Down
7 changes: 6 additions & 1 deletion shard.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ description: |
A binding to the ICU library

libraries:
libicu: "~> 52.0" # FIXME: see https://github.com/olbat/icu.cr/issues/1
libicu: ">= 4.8"

development_dependencies:
libgen:
github: olbat/libgen
#version: ~> 0.1

license: GPLv3
2 changes: 2 additions & 0 deletions spec/currencies_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ describe "ICU::Currencies" do
end
end

{% if compare_versions(LibICU::VERSION, "49.0.0") >= 0 %}
describe "numeric_code" do
it "returns the code associated to a currency" do
ICU::Currencies.numeric_code("EUR").should eq(978)
Expand All @@ -24,4 +25,5 @@ describe "ICU::Currencies" do
end
end
end
{% end %}
end
20 changes: 20 additions & 0 deletions spec/icu_info_spec.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
require "./spec_helper"
require "../src/icu_info"

describe "icu_version" do
it "returns the version of the ICU lib" do
icu_version().should match(/^[0-9][0-9\.]*$/)
end
end

describe "icu_sem_version" do
it "returns the version of the ICU lib in semantic versioning format" do
icu_sem_version().should match(/^[0-9]+\.[0-9]+\.[0-9]+$/)
end
end

describe "icu_so_symbols_suffix" do
it "returns the version suffix added to symbols in so files of the ICU lib" do
icu_so_symbols_suffix().should match(/^[0-9_]*$/)
end
end
2 changes: 2 additions & 0 deletions spec/region_spec.cr
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
require "./spec_helper"

{% if compare_versions(LibICU::VERSION, "52.0.0") >= 0 %}
describe "ICU::Region" do
describe "initialize" do
it "creates a new region from a code" do
Expand Down Expand Up @@ -57,3 +58,4 @@ describe "ICU::Region" do
end
end
end
{% end %}
2 changes: 2 additions & 0 deletions src/icu/currencies.cr
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,11 @@ class ICU::Currencies
ICU.uchars_to_string(buff, len)
end

{% if compare_versions(LibICU::VERSION, "49.0.0") >= 0 %}
def self.numeric_code(currency : String) : Int32
num = LibICU.ucurr_get_numeric_code(ICU.string_to_uchars(currency))
raise ICU::Error.new(%(Unknown currency "#{currency}")) if num == 0
num
end
{% end %}
end
2 changes: 2 additions & 0 deletions src/icu/region.cr
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
{% if compare_versions(LibICU::VERSION, "52.0.0") >= 0 %}
class ICU::Region
@uregion : LibICU::URegion
@code : String?
Expand Down Expand Up @@ -47,3 +48,4 @@ class ICU::Region
regions.map { |r| self.class.new(r) }
end
end
{% end %}
101 changes: 101 additions & 0 deletions src/icu_info.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
require "xml"
require "c/dlfcn"

PKGNAME = "icu-uc"
TESTFUNC = "u_init"
{% if flag?(:darwin) %}
SOFILE = "libicuuc.dylib"
{% elsif flag?(:windows) %}
SOFILE = "libicuuc.dll"
{% else %}
SOFILE = "libicuuc.so"
{% end %}

def icu_version : String
version = nil
if system("command -v icuinfo > /dev/null")
icuinfo = `icuinfo -v`
begin
doc = XML.parse(icuinfo)
if params = doc.first_element_child
if params.name == "icuSystemParams" # ICU >= 49.0
params.children.select(&.element?).each do |param|
if param.name == "param" && param["name"]? == "version"
version = param.content.strip
break
end
end
elsif params.name == "ICUINFO" # ICU < 49.0
if v = params.content.match(/Compiled-Version\s*:\s*([0-9\.]+)/)
version = v[1]
end
end
end
rescue XML::Error
end
else
STDERR.puts %(WARNING: cannot find the "icuinfo" tool in PATH)
{% if flag?(:darwin) %}
STDERR.puts %(\t(on OSX, please check that you've run "brew link icu4c"))
{% end %}
end

if !version && system("command -v pkg-config > /dev/null")
version = `pkg-config --modversion #{PKGNAME}`.strip
end

abort("cannot find ICU version", 3) if !version || version.empty?

version
end

def icu_sem_version : String
# convert ICU version to semantic versioning
version = icu_version()
v = version.split(".")
if v.size == 3
version
elsif v.size > 3
v[0..2].join(".")
else
3.times.map { |i| v[i]? || "0" }.join(".")
end
end

def icu_possible_suffixes(version : String) : Array(String)
v = version.split(".")
[
"",
"_#{version}",
"_#{v[0]}",
"_#{v[0]}#{v[1]}",
"_#{v[0]}_#{v[1]}",
"_#{v[0][0]}_#{v[0][1]? || v[1][0]}",
].uniq
end

def icu_so_symbols_suffix
suffixes = icu_possible_suffixes(icu_version())

handle = LibC.dlopen(SOFILE, LibC::RTLD_LAZY)
abort("cannot load the #{SOFILE} file", 1) if handle.null?

begin
suffixes.each do |suffix|
LibC.dlsym(handle, "#{TESTFUNC}#{suffix}")
return suffix if LibC.dlerror.null?
end
abort("cannot find so symbols suffix", 2)
ensure
LibC.dlclose(handle)
end
end

case ARGV[0]?
when "--version"
puts icu_version()
when "--sem-version"
puts icu_sem_version()
when "--so-symbols-suffix"
puts icu_so_symbols_suffix()
end
34 changes: 34 additions & 0 deletions src/lib_generator.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
require "compiler/crystal/tools/formatter"

LIB_DIR = "lib_icu"
ICU_INFO = "icu_info.cr"

Dir[File.join(File.dirname(__FILE__), LIB_DIR, "**", "*.cr")].each do |file|
abort "cannot read #{file}" unless File.readable?(file)
src = File.read(file)
src = Crystal.format(src)

# run the suffix finder program to initialize the SYMS_SUFFIX constant
# in the common lib file
if File.basename(file) == "#{LIB_DIR}.cr"
constdef = <<-EOS
VERSION={{ run("../#{ICU_INFO}", "--sem-version").strip.stringify }}
SYMS_SUFFIX={{ run("../#{ICU_INFO}", "--so-symbols-suffix").strip.stringify }}
{% end %}

{% begin %}

EOS
else
constdef = ""
end

# add macro blocks in the body of every lib files
src = src.gsub(/^(lib \w+)\n(.+)end$/m, "\\1\n {% begin %}\n#{constdef}\\2 {% end %}\nend")

# replace the current suffix by the SYMS_SUFFIX constant (macro)
src = src.gsub(/^(\s*fun\s+\w+\s*=\s*\w+?)(_[0-9_]+)?([\(\s:]|$)/m, "\\1{{SYMS_SUFFIX.id}}\\3")

abort "cannot write #{file}" unless File.writable?(file)
File.write(file, Crystal.format(src))
end
Loading