Skip to content

Commit

Permalink
feat: MiniPortile.activate_mkmf with support for pkg-config
Browse files Browse the repository at this point in the history
which will set $LDFLAGS and $CFLAGS according to a set of pkg-config
files.

This should allow gems with C extensions that use mini_portile to pass
in the pkg-config files for all their build flags.

Also, accumulate directories in PKG_CONFIG_PATH so we can resolve
package references when using multiple files.
  • Loading branch information
flavorjones committed Sep 12, 2023
1 parent f9212d1 commit 22e03ca
Show file tree
Hide file tree
Showing 7 changed files with 198 additions and 14 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ Gemfile.lock
pkg
ports
tmp
mkmf.log
16 changes: 8 additions & 8 deletions examples/Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ require 'rbconfig'
$: << File.expand_path(File.join(File.dirname(__FILE__), "../lib"))
require "mini_portile2"

require "mkmf"

recipes = []
recipe_hooks = {}

Expand Down Expand Up @@ -130,12 +128,11 @@ yaml.files = [{
url: "https://github.com/yaml/libyaml/releases/download/0.2.5/yaml-0.2.5.tar.gz",
sha256: "c642ae9b75fee120b2d96c712538bd2cf283228d2337df2cf2988e3c02678ef4",
}]
recipes.push(yaml)
recipes.unshift(yaml)
recipe_hooks["yaml"] = lambda do |recipe|
conf = pkg_config(File.join(recipe.path, "lib", "pkgconfig", "yaml-0.1.pc"))
puts "pkg_config: #{conf.inspect}"
MiniPortile.activate_mkmf(pkgconf: File.join(recipe.path, "lib", "pkgconfig", "yaml-0.1.pc"))

expected = "-L" + MiniPortile.native_path(File.join(recipe.path, "lib"))
expected = "-L" + File.join(recipe.path, "lib")
$LDFLAGS.split.include?(expected) or raise(<<~MSG)
assertion failed: LDFLAGS not updated correctly:
#{$LDFLAGS}
Expand All @@ -158,9 +155,10 @@ namespace :ports do
desc "Install port #{recipe.name} #{recipe.version}"
task recipe.name => ["ports"] do |t|
recipe.cook
recipe.activate
if hook = recipe_hooks[recipe.name]
hook.call(recipe)
else
recipe.activate
end
end

Expand All @@ -173,7 +171,9 @@ namespace :ports do
puts "Artifacts of '#{recipe.name}' in '#{recipe.path}'"
end
puts "LIBRARY_PATH: #{ENV['LIBRARY_PATH'].inspect}"
puts "LDFLAGS: #{ENV['LDFLAGS'].inspect}, #{$LDFLAGS.inspect}"
puts "LDFLAGS: #{ENV['LDFLAGS'].inspect}"
puts "$LDFLAGS: #{$LDFLAGS.inspect}"
puts "$CFLAGS: #{$CFLAGS.inspect}"
end
end

Expand Down
65 changes: 64 additions & 1 deletion lib/mini_portile2/mini_portile.rb
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,33 @@ def self.posix_path(path)
end
end

def self.activate_mkmf(pkgconf: nil)
if !pkgconf
raise NotImplementedError, "MiniPortile.activate_mkmf does not (yet) work without pkg-config files. If you want this functionality, please open an issue! ♥ ♥ ♥"
end

unless File.exist?(pkgconf)
raise RuntimeError, "pkg-config file '#{pkgconf}' does not exist"
end

require "mkmf"

MakeMakefile.message "Activating #{File.basename(pkgconf)} in #{File.dirname(pkgconf)}\n"

# on macos, pkg-config will not return --cflags without this
ENV["PKG_CONFIG_ALLOW_SYSTEM_CFLAGS"] = "t"

# append to PKG_CONFIG_PATH as we go, so later pkg-config files can depend on earlier ones
ENV["PKG_CONFIG_PATH"] = [ENV["PKG_CONFIG_PATH"], File.dirname(pkgconf)].compact.join(File::PATH_SEPARATOR)

cflags = minimal_pkg_config(pkgconf, "cflags")
$CFLAGS << " " << cflags
$CXXFLAGS << " " << cflags

ldflags = minimal_pkg_config(pkgconf, "libs", "static")
$LDFLAGS << " " << ldflags
end

def initialize(name, version, **kwargs)
@name = name
@version = version
Expand Down Expand Up @@ -236,7 +263,13 @@ def cook
return true
end

def activate
def activate(pkgconf: nil)
if pkgconf
# because I don't know if anybody has this use case,
# I just haven't bothered to implement this yet.
raise NotImplementedError, "MiniPortile#activate does not (yet) support pkg-config files. If you want this functionality, please open an issue! ♥ ♥ ♥"
end

lib_path = File.join(port_path, "lib")
vars = {
'PATH' => File.join(port_path, 'bin'),
Expand Down Expand Up @@ -656,4 +689,34 @@ def with_tempfile(filename, full_path)
FileUtils.mkdir_p File.dirname(full_path)
FileUtils.mv temp_file.path, full_path, :force => true
end

def abort_pkg_config(id)
abort("\nCould not configure the build properly (#{id}). Please install either the `pkg-config` utility or the `pkg-config` rubygem.\n\n")
end

#
# this minimal version of pkg_config is based on ruby 29dc9378 (2023-01-09)
#
# specifically with the fix from b90e56e6 to support multiple pkg-config options, and removing
# code paths that aren't helpful for mini-portile's use case of parsing pc files.
#
def self.minimal_pkg_config(pkg, *pcoptions)
if pcoptions.empty?
raise ArgumentError, "no pkg-config options are given"
end

if ($PKGCONFIG ||=
(pkgconfig = MakeMakefile.with_config("pkg-config") {MakeMakefile.config_string("PKG_CONFIG") || "pkg-config"}) &&
MakeMakefile.find_executable0(pkgconfig) && pkgconfig)
pkgconfig = $PKGCONFIG
else
raise RuntimeError, "pkg-config is not found"
end

pcoptions = Array(pcoptions).map { |o| "--#{o}" }
response = MakeMakefile.xpopen([pkgconfig, *pcoptions, pkg], err:[:child, :out], &:read)
raise RuntimeError, response unless $?.success?
response.strip
end
private_class_method :minimal_pkg_config
end
13 changes: 13 additions & 0 deletions test/assets/pkgconf/libxml2/libxml-2.0.pc
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
prefix=/foo/libxml2/2.11.5
exec_prefix=${prefix}
libdir=/foo/libxml2/2.11.5/lib
includedir=${prefix}/include
modules=1

Name: libXML
Version: 2.11.5
Description: libXML library version2.
Requires:
Libs: -L${libdir} -lxml2
Libs.private: -L/foo/zlib/1.3/lib -lz -lm
Cflags: -I${includedir}/libxml2
13 changes: 13 additions & 0 deletions test/assets/pkgconf/libxslt/libexslt.pc
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
prefix=/foo/libxslt/1.1.38
exec_prefix=${prefix}
libdir=/foo/libxslt/1.1.38/lib
includedir=${prefix}/include


Name: libexslt
Version: 0.8.21
Description: EXSLT Extension library
Requires: libxml-2.0, libxslt
Cflags: -I${includedir}
Libs: -L${libdir} -lexslt
Libs.private: -lm
13 changes: 13 additions & 0 deletions test/assets/pkgconf/libxslt/libxslt.pc
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
prefix=/foo/libxslt/1.1.38
exec_prefix=${prefix}
libdir=/foo/libxslt/1.1.38/lib
includedir=${prefix}/include


Name: libxslt
Version: 1.1.38
Description: XSLT library version 2.
Requires: libxml-2.0
Cflags: -I${includedir}
Libs: -L${libdir} -lxslt
Libs.private: -lm
91 changes: 86 additions & 5 deletions test/test_activate.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,18 @@
class TestActivate < TestCase
attr_reader :recipe

LIBXML_PC = File.join(__dir__, "assets", "pkgconf", "libxml2", "libxml-2.0.pc")
LIBXSLT_PC = File.join(__dir__, "assets", "pkgconf", "libxslt", "libxslt.pc")
LIBEXSLT_PC = File.join(__dir__, "assets", "pkgconf", "libxslt", "libexslt.pc")

def setup
super

@save_env = %w[PATH CPATH LIBRARY_PATH LDFLAGS].inject({}) do |env, var|
@save_env = %w[PATH CPATH LIBRARY_PATH LDFLAGS PKG_CONFIG_PATH].inject({}) do |env, var|
env.update(var => ENV[var])
end
$LDFLAGS = nil
$CPPFLAGS = nil
$LDFLAGS = ""
$CFLAGS = ""

FileUtils.rm_rf(["tmp", "ports"]) # remove any previous test files

Expand All @@ -22,8 +26,8 @@ def setup
def teardown
FileUtils.rm_rf(["tmp", "ports"]) # remove any previous test files

$LDFLAGS = nil
$CPPFLAGS = nil
$LDFLAGS = ""
$CFLAGS = ""
@save_env.each do |var, val|
ENV[var] = val
end
Expand Down Expand Up @@ -119,6 +123,83 @@ def test_LDFLAGS_env_var_when_cross_compiling
assert_equal(flag_elements('LDFLAGS').first, "-L#{lib_path}")
end

def test_activate_with_pkgconf_raises_an_exception
assert_raises(NotImplementedError) do
recipe.activate(pkgconf: "fake/path/to/foo.pc")
end
end

def test_activate_mkmf_without_pkgconf_raises_an_exception
assert_raises(NotImplementedError) do
MiniPortile.activate_mkmf
end
end

def test_activate_mkmf_pkgconf_does_not_exist
assert_raises(RuntimeError) do
MiniPortile.activate_mkmf(pkgconf: "nonexistent/foo.pc")
end
end

def test_activate_mkmf_LDFLAGS_global
# can't get pkgconf to install on windows with ruby 2.3
skip if MiniPortile.windows? && RUBY_VERSION < "2.4"

MiniPortile.activate_mkmf(pkgconf: LIBXML_PC)

assert_includes($LDFLAGS.split, "-lxml2")
assert_includes($LDFLAGS.split, "-L/foo/libxml2/2.11.5/lib")
end

def test_activate_mkmf_CFLAGS_global
# can't get pkgconf to install on windows with ruby 2.3
skip if MiniPortile.windows? && RUBY_VERSION < "2.4"

MiniPortile.activate_mkmf(pkgconf: LIBXML_PC)

assert_includes($CFLAGS.split, "-I/foo/libxml2/2.11.5/include/libxml2")
end

def test_activate_mkmf_chains_pkgconf_path
# can't get pkgconf to install on windows with ruby 2.3
skip if MiniPortile.windows? && RUBY_VERSION < "2.4"

(ENV["PKG_CONFIG_PATH"] || "").split(File::PATH_SEPARATOR).tap do |pcpaths|
refute_includes(pcpaths, File.dirname(LIBXML_PC))
refute_includes(pcpaths, File.dirname(LIBXSLT_PC))
end

MiniPortile.activate_mkmf(pkgconf: LIBXML_PC)

ENV["PKG_CONFIG_PATH"].split(File::PATH_SEPARATOR).tap do |pcpaths|
assert_includes(pcpaths, File.dirname(LIBXML_PC))
refute_includes(pcpaths, File.dirname(LIBXSLT_PC))
end

MiniPortile.activate_mkmf(pkgconf: LIBXSLT_PC)

ENV["PKG_CONFIG_PATH"].split(File::PATH_SEPARATOR).tap do |pcpaths|
assert_includes(pcpaths, File.dirname(LIBXML_PC))
assert_includes(pcpaths, File.dirname(LIBXSLT_PC))
end

MiniPortile.activate_mkmf(pkgconf: LIBEXSLT_PC)

$CFLAGS.split.tap do |cflags|
assert_includes(cflags, "-I/foo/libxml2/2.11.5/include/libxml2")
assert_includes(cflags, "-I/foo/libxslt/1.1.38/include")
end
$LDFLAGS.split.tap do |ldflags|
assert_includes(ldflags, "-L/foo/libxml2/2.11.5/lib")
assert_includes(ldflags, "-lxml2")
assert_includes(ldflags, "-L/foo/libxslt/1.1.38/lib")
assert_includes(ldflags, "-lxslt")
assert_includes(ldflags, "-lexslt")
assert_includes(ldflags, "-L/foo/zlib/1.3/lib") # from `--static`
assert_includes(ldflags, "-lz") # from `--static`
end
end

private

def path_elements(varname)
Expand Down

0 comments on commit 22e03ca

Please sign in to comment.