kompo 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.devcontainer/Dockerfile +2 -4
- data/.devcontainer/devcontainer.json +2 -2
- data/.standard.yml +2 -0
- data/CHANGELOG.md +21 -0
- data/Gemfile +10 -2
- data/Gemfile.lock +115 -3
- data/README.md +117 -10
- data/Rakefile +12 -2
- data/docs/document.md.ja +31 -0
- data/exe/kompo +45 -14
- data/lib/fs.c.erb +6 -0
- data/lib/kompo/cache.rb +65 -0
- data/lib/kompo/kompo_ignore.rb +40 -0
- data/lib/kompo/tasks/build_native_gem.rb +191 -0
- data/lib/kompo/tasks/bundle_install.rb +224 -0
- data/lib/kompo/tasks/cargo_path.rb +59 -0
- data/lib/kompo/tasks/check_stdlibs.rb +58 -0
- data/lib/kompo/tasks/collect_dependencies.rb +101 -0
- data/lib/kompo/tasks/copy_gemfile.rb +46 -0
- data/lib/kompo/tasks/copy_project_files.rb +89 -0
- data/lib/kompo/tasks/find_native_extensions.rb +89 -0
- data/lib/kompo/tasks/homebrew.rb +83 -0
- data/lib/kompo/tasks/install_deps.rb +365 -0
- data/lib/kompo/tasks/install_ruby.rb +427 -0
- data/lib/kompo/tasks/kompo_vfs_path.rb +144 -0
- data/lib/kompo/tasks/kompo_vfs_version_check.rb +56 -0
- data/lib/kompo/tasks/make_fs_c.rb +202 -0
- data/lib/kompo/tasks/make_main_c.rb +65 -0
- data/lib/kompo/tasks/packing.rb +235 -0
- data/lib/kompo/tasks/ruby_build_path.rb +54 -0
- data/lib/kompo/tasks/work_dir.rb +84 -0
- data/lib/kompo/version.rb +2 -1
- data/lib/kompo.rb +47 -420
- data/lib/main.c.erb +28 -15
- data/rbs_collection.lock.yaml +116 -0
- data/rbs_collection.yaml +19 -0
- metadata +72 -8
- data/lib/kompo/kompo_fs.rb +0 -15
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'fileutils'
|
|
4
|
+
|
|
5
|
+
module Kompo
|
|
6
|
+
# Copy project files (entrypoint and additional files) to working directory
|
|
7
|
+
class CopyProjectFiles < Taski::Task
|
|
8
|
+
exports :entrypoint_path, :additional_paths
|
|
9
|
+
|
|
10
|
+
def run
|
|
11
|
+
work_dir = WorkDir.path
|
|
12
|
+
project_dir = Taski.args.fetch(:project_dir, Taski.env.working_directory) || Taski.env.working_directory
|
|
13
|
+
entrypoint = Taski.args.fetch(:entrypoint, 'main.rb')
|
|
14
|
+
files = Taski.args.fetch(:files, [])
|
|
15
|
+
|
|
16
|
+
# Copy entrypoint (preserve relative path structure)
|
|
17
|
+
src_entrypoint = File.expand_path(File.join(project_dir, entrypoint))
|
|
18
|
+
raise "Entrypoint not found: #{src_entrypoint}" unless File.exist?(src_entrypoint)
|
|
19
|
+
|
|
20
|
+
# Validate source is inside project_dir
|
|
21
|
+
real_project_dir = File.realpath(project_dir)
|
|
22
|
+
real_src = File.realpath(src_entrypoint)
|
|
23
|
+
unless real_src.start_with?(real_project_dir + File::SEPARATOR) || real_src == real_project_dir
|
|
24
|
+
raise "Entrypoint path escapes project directory: #{entrypoint}"
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
@entrypoint_path = File.join(work_dir, entrypoint)
|
|
28
|
+
FileUtils.mkdir_p(File.dirname(@entrypoint_path))
|
|
29
|
+
FileUtils.cp(src_entrypoint, @entrypoint_path)
|
|
30
|
+
puts "Copied entrypoint: #{entrypoint}"
|
|
31
|
+
|
|
32
|
+
# Copy additional files/directories
|
|
33
|
+
@additional_paths = []
|
|
34
|
+
files.each do |file|
|
|
35
|
+
src = File.expand_path(File.join(project_dir, file))
|
|
36
|
+
next unless File.exist?(src)
|
|
37
|
+
|
|
38
|
+
# Validate source is inside project_dir
|
|
39
|
+
real_src = File.realpath(src)
|
|
40
|
+
unless real_src.start_with?(real_project_dir + File::SEPARATOR) || real_src == real_project_dir
|
|
41
|
+
warn "Skipping path that escapes project directory: #{file}"
|
|
42
|
+
next
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
dest = File.join(work_dir, file)
|
|
46
|
+
|
|
47
|
+
# Validate destination is inside work_dir
|
|
48
|
+
real_work_dir = File.realpath(work_dir)
|
|
49
|
+
expanded_dest = File.expand_path(dest)
|
|
50
|
+
unless expanded_dest.start_with?(real_work_dir + File::SEPARATOR) || expanded_dest == real_work_dir
|
|
51
|
+
warn "Skipping path that would escape work directory: #{file}"
|
|
52
|
+
next
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
if File.directory?(src)
|
|
56
|
+
FileUtils.mkdir_p(dest)
|
|
57
|
+
FileUtils.cp_r(src, File.dirname(dest))
|
|
58
|
+
else
|
|
59
|
+
FileUtils.mkdir_p(File.dirname(dest))
|
|
60
|
+
FileUtils.cp(src, dest)
|
|
61
|
+
end
|
|
62
|
+
@additional_paths << dest
|
|
63
|
+
puts "Copied: #{file}"
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def clean
|
|
68
|
+
work_dir = WorkDir.path
|
|
69
|
+
return unless work_dir && Dir.exist?(work_dir)
|
|
70
|
+
|
|
71
|
+
real_work_dir = File.realpath(work_dir)
|
|
72
|
+
files = Taski.args.fetch(:files, [])
|
|
73
|
+
|
|
74
|
+
# Clean up copied files (with path validation)
|
|
75
|
+
if @entrypoint_path
|
|
76
|
+
expanded_path = File.expand_path(@entrypoint_path)
|
|
77
|
+
FileUtils.rm_f(@entrypoint_path) if expanded_path.start_with?(real_work_dir + File::SEPARATOR)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
files.each do |file|
|
|
81
|
+
path = File.join(work_dir, file)
|
|
82
|
+
expanded_path = File.expand_path(path)
|
|
83
|
+
# Only delete if path is inside work_dir
|
|
84
|
+
FileUtils.rm_rf(path) if expanded_path.start_with?(real_work_dir + File::SEPARATOR) && File.exist?(path)
|
|
85
|
+
end
|
|
86
|
+
puts 'Cleaned up project files'
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'open3'
|
|
4
|
+
|
|
5
|
+
module Kompo
|
|
6
|
+
# Find native gem extensions that need to be built
|
|
7
|
+
# Exports:
|
|
8
|
+
# - extensions: Array of hashes with extension info (dir_name, gem_ext_name, is_rust)
|
|
9
|
+
class FindNativeExtensions < Taski::Task
|
|
10
|
+
exports :extensions
|
|
11
|
+
|
|
12
|
+
def run
|
|
13
|
+
@extensions = []
|
|
14
|
+
|
|
15
|
+
# Skip if no Gemfile
|
|
16
|
+
unless CopyGemfile.gemfile_exists
|
|
17
|
+
puts 'No Gemfile, no native extensions to find'
|
|
18
|
+
return
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
ruby_version = InstallRuby.ruby_version
|
|
22
|
+
ruby_build_path = InstallRuby.ruby_build_path
|
|
23
|
+
ruby_install_dir = InstallRuby.ruby_install_dir
|
|
24
|
+
bundle_ruby_dir = BundleInstall.bundle_ruby_dir
|
|
25
|
+
|
|
26
|
+
# Get Init functions already defined in libruby-static
|
|
27
|
+
builtin_init_funcs = get_builtin_init_functions(ruby_install_dir)
|
|
28
|
+
|
|
29
|
+
# Find all native extensions in installed gems
|
|
30
|
+
extconf_files = Dir.glob(File.join(bundle_ruby_dir, 'gems/**/extconf.rb'))
|
|
31
|
+
|
|
32
|
+
extconf_files.each do |extconf_path|
|
|
33
|
+
dir_name = File.dirname(extconf_path)
|
|
34
|
+
gem_ext_name = File.basename(dir_name)
|
|
35
|
+
|
|
36
|
+
# Skip if Init function is already defined in libruby-static
|
|
37
|
+
# This catches gems like prism that are compiled into Ruby core
|
|
38
|
+
init_func = "Init_#{gem_ext_name}"
|
|
39
|
+
if builtin_init_funcs.include?(init_func)
|
|
40
|
+
puts "skip: #{gem_ext_name} is already built into Ruby (#{init_func} found in libruby-static)"
|
|
41
|
+
next
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Skip if this extension is part of Ruby standard library
|
|
45
|
+
ruby_std_lib = dir_name.split('/').drop_while { |p| p != 'ext' }.join('/')
|
|
46
|
+
ruby_ext_objects = Dir.glob(File.join(ruby_build_path, "ruby-#{ruby_version}", 'ext', '**', '*.o'))
|
|
47
|
+
if ruby_ext_objects.any? { |o| o.include?(ruby_std_lib) }
|
|
48
|
+
puts "skip: #{gem_ext_name} is included in Ruby standard library"
|
|
49
|
+
next
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
cargo_toml = File.join(dir_name, 'Cargo.toml')
|
|
53
|
+
is_rust = File.exist?(cargo_toml)
|
|
54
|
+
|
|
55
|
+
@extensions << {
|
|
56
|
+
dir_name: dir_name,
|
|
57
|
+
gem_ext_name: gem_ext_name,
|
|
58
|
+
is_rust: is_rust,
|
|
59
|
+
cargo_toml: is_rust ? cargo_toml : nil
|
|
60
|
+
}
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
puts "Found #{@extensions.size} native extensions to build"
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
private
|
|
67
|
+
|
|
68
|
+
# Extract Init_ function names from libruby-static using nm
|
|
69
|
+
def get_builtin_init_functions(ruby_install_dir)
|
|
70
|
+
lib_dir = File.join(ruby_install_dir, 'lib')
|
|
71
|
+
static_lib = Dir.glob(File.join(lib_dir, 'libruby*-static.a')).first
|
|
72
|
+
return Set.new unless static_lib
|
|
73
|
+
|
|
74
|
+
# Use nm to extract defined Init_ symbols (T = text/code section)
|
|
75
|
+
# Use Open3.capture2 with array form to avoid shell injection
|
|
76
|
+
output, status = Open3.capture2('nm', static_lib, err: File::NULL)
|
|
77
|
+
return Set.new unless status.success?
|
|
78
|
+
|
|
79
|
+
# Filter lines matching " T _?Init_" pattern and extract the symbol name
|
|
80
|
+
# macOS prefixes symbols with underscore, Linux does not
|
|
81
|
+
symbols = output.lines.select { |line| line.match?(/ T _?Init_/) }
|
|
82
|
+
symbols.map do |line|
|
|
83
|
+
# Third whitespace-separated field is the symbol name
|
|
84
|
+
symbol = line.split[2]
|
|
85
|
+
symbol&.delete_prefix('_')
|
|
86
|
+
end.compact.to_set
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'open3'
|
|
4
|
+
|
|
5
|
+
module Kompo
|
|
6
|
+
# Section to get the Homebrew path.
|
|
7
|
+
# Switches implementation based on whether Homebrew is already installed.
|
|
8
|
+
class HomebrewPath < Taski::Section
|
|
9
|
+
interfaces :path
|
|
10
|
+
|
|
11
|
+
# Common Homebrew installation paths
|
|
12
|
+
COMMON_BREW_PATHS = ['/opt/homebrew/bin/brew', '/usr/local/bin/brew'].freeze
|
|
13
|
+
|
|
14
|
+
def impl
|
|
15
|
+
homebrew_installed? ? Installed : Install
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Use existing Homebrew installation
|
|
19
|
+
class Installed < Taski::Task
|
|
20
|
+
def run
|
|
21
|
+
# First check PATH
|
|
22
|
+
brew_in_path, = Open3.capture2('which', 'brew', err: File::NULL)
|
|
23
|
+
brew_in_path = brew_in_path.chomp
|
|
24
|
+
@path = if brew_in_path.empty?
|
|
25
|
+
# Fallback to common installation paths
|
|
26
|
+
HomebrewPath::COMMON_BREW_PATHS.find { |p| File.executable?(p) }
|
|
27
|
+
else
|
|
28
|
+
brew_in_path
|
|
29
|
+
end
|
|
30
|
+
puts "Homebrew path: #{@path}"
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Install Homebrew
|
|
35
|
+
class Install < Taski::Task
|
|
36
|
+
MARKER_FILE = File.expand_path('~/.kompo_installed_homebrew')
|
|
37
|
+
|
|
38
|
+
def run
|
|
39
|
+
puts 'Homebrew not found. Installing...'
|
|
40
|
+
system('/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"')
|
|
41
|
+
|
|
42
|
+
brew_in_path, = Open3.capture2('which', 'brew', err: File::NULL)
|
|
43
|
+
@path = brew_in_path.chomp
|
|
44
|
+
if @path.empty?
|
|
45
|
+
# Check common installation paths
|
|
46
|
+
HomebrewPath::COMMON_BREW_PATHS.each do |p|
|
|
47
|
+
if File.executable?(p)
|
|
48
|
+
@path = p
|
|
49
|
+
break
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
raise 'Failed to install Homebrew' if @path.nil? || @path.empty?
|
|
55
|
+
|
|
56
|
+
# Mark that kompo installed Homebrew
|
|
57
|
+
File.write(MARKER_FILE, @path)
|
|
58
|
+
puts "Homebrew installed at: #{@path}"
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def clean
|
|
62
|
+
# Only uninstall if kompo installed it
|
|
63
|
+
return unless File.exist?(MARKER_FILE)
|
|
64
|
+
|
|
65
|
+
puts 'Uninstalling Homebrew (installed by kompo)...'
|
|
66
|
+
system('NONINTERACTIVE=1 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/uninstall.sh)"')
|
|
67
|
+
File.delete(MARKER_FILE) if File.exist?(MARKER_FILE)
|
|
68
|
+
puts 'Homebrew uninstalled'
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
private
|
|
73
|
+
|
|
74
|
+
def homebrew_installed?
|
|
75
|
+
# Check if brew is in PATH
|
|
76
|
+
brew_in_path, = Open3.capture2('which', 'brew', err: File::NULL)
|
|
77
|
+
return true unless brew_in_path.chomp.empty?
|
|
78
|
+
|
|
79
|
+
# Check common installation paths
|
|
80
|
+
COMMON_BREW_PATHS.any? { |p| File.executable?(p) }
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'English'
|
|
4
|
+
module Kompo
|
|
5
|
+
# Section to handle platform-specific dependencies.
|
|
6
|
+
# Switches implementation based on the current platform.
|
|
7
|
+
# Exports lib_paths for linker flags (e.g., "-L/usr/local/lib")
|
|
8
|
+
class InstallDeps < Taski::Section
|
|
9
|
+
interfaces :lib_paths
|
|
10
|
+
|
|
11
|
+
def impl
|
|
12
|
+
macos? ? ForMacOS : ForLinux
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# macOS implementation - installs dependencies via Homebrew
|
|
16
|
+
class ForMacOS < Taski::Task
|
|
17
|
+
def run
|
|
18
|
+
# HomebrewPath.path triggers Homebrew installation if not present
|
|
19
|
+
@lib_paths = [
|
|
20
|
+
InstallGmp.lib_path,
|
|
21
|
+
InstallOpenssl.lib_path,
|
|
22
|
+
InstallReadline.lib_path,
|
|
23
|
+
InstallLibyaml.lib_path,
|
|
24
|
+
InstallZlib.lib_path,
|
|
25
|
+
InstallLibffi.lib_path
|
|
26
|
+
].compact.join(' ')
|
|
27
|
+
|
|
28
|
+
puts 'All Homebrew dependencies installed'
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# GMP library installation Section
|
|
32
|
+
class InstallGmp < Taski::Section
|
|
33
|
+
interfaces :lib_path
|
|
34
|
+
|
|
35
|
+
def impl
|
|
36
|
+
brew = HomebrewPath.path
|
|
37
|
+
system("#{brew} list #{BREW_NAME} > /dev/null 2>&1") ? Installed : Install
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
BREW_NAME = 'gmp'
|
|
41
|
+
MARKER_FILE = File.expand_path('~/.kompo_installed_gmp')
|
|
42
|
+
|
|
43
|
+
class Installed < Taski::Task
|
|
44
|
+
def run
|
|
45
|
+
brew = HomebrewPath.path
|
|
46
|
+
prefix = `#{brew} --prefix #{BREW_NAME} 2>/dev/null`.chomp
|
|
47
|
+
@lib_path = "-L#{prefix}/lib" if $CHILD_STATUS.success? && !prefix.empty?
|
|
48
|
+
puts "#{BREW_NAME} is already installed"
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
class Install < Taski::Task
|
|
53
|
+
def run
|
|
54
|
+
brew = HomebrewPath.path
|
|
55
|
+
puts "Installing #{BREW_NAME}..."
|
|
56
|
+
system("#{brew} install #{BREW_NAME}") or raise "Failed to install #{BREW_NAME}"
|
|
57
|
+
File.write(MARKER_FILE, 'installed')
|
|
58
|
+
|
|
59
|
+
prefix = `#{brew} --prefix #{BREW_NAME} 2>/dev/null`.chomp
|
|
60
|
+
@lib_path = "-L#{prefix}/lib" if $CHILD_STATUS.success? && !prefix.empty?
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def clean
|
|
64
|
+
return unless File.exist?(MARKER_FILE)
|
|
65
|
+
|
|
66
|
+
brew = HomebrewPath.path
|
|
67
|
+
puts "Uninstalling #{BREW_NAME} (installed by kompo)..."
|
|
68
|
+
system("#{brew} uninstall #{BREW_NAME}")
|
|
69
|
+
File.delete(MARKER_FILE) if File.exist?(MARKER_FILE)
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# OpenSSL library installation Section
|
|
75
|
+
class InstallOpenssl < Taski::Section
|
|
76
|
+
interfaces :lib_path
|
|
77
|
+
|
|
78
|
+
def impl
|
|
79
|
+
brew = HomebrewPath.path
|
|
80
|
+
system("#{brew} list #{BREW_NAME} > /dev/null 2>&1") ? Installed : Install
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
BREW_NAME = 'openssl@3'
|
|
84
|
+
MARKER_FILE = File.expand_path('~/.kompo_installed_openssl')
|
|
85
|
+
|
|
86
|
+
class Installed < Taski::Task
|
|
87
|
+
def run
|
|
88
|
+
brew = HomebrewPath.path
|
|
89
|
+
prefix = `#{brew} --prefix #{BREW_NAME} 2>/dev/null`.chomp
|
|
90
|
+
@lib_path = "-L#{prefix}/lib" if $CHILD_STATUS.success? && !prefix.empty?
|
|
91
|
+
puts "#{BREW_NAME} is already installed"
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
class Install < Taski::Task
|
|
96
|
+
def run
|
|
97
|
+
brew = HomebrewPath.path
|
|
98
|
+
puts "Installing #{BREW_NAME}..."
|
|
99
|
+
system("#{brew} install #{BREW_NAME}") or raise "Failed to install #{BREW_NAME}"
|
|
100
|
+
File.write(MARKER_FILE, 'installed')
|
|
101
|
+
|
|
102
|
+
prefix = `#{brew} --prefix #{BREW_NAME} 2>/dev/null`.chomp
|
|
103
|
+
@lib_path = "-L#{prefix}/lib" if $CHILD_STATUS.success? && !prefix.empty?
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def clean
|
|
107
|
+
return unless File.exist?(MARKER_FILE)
|
|
108
|
+
|
|
109
|
+
brew = HomebrewPath.path
|
|
110
|
+
puts "Uninstalling #{BREW_NAME} (installed by kompo)..."
|
|
111
|
+
system("#{brew} uninstall #{BREW_NAME}")
|
|
112
|
+
File.delete(MARKER_FILE) if File.exist?(MARKER_FILE)
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Readline library installation Section
|
|
118
|
+
class InstallReadline < Taski::Section
|
|
119
|
+
interfaces :lib_path
|
|
120
|
+
|
|
121
|
+
def impl
|
|
122
|
+
brew = HomebrewPath.path
|
|
123
|
+
system("#{brew} list #{BREW_NAME} > /dev/null 2>&1") ? Installed : Install
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
BREW_NAME = 'readline'
|
|
127
|
+
MARKER_FILE = File.expand_path('~/.kompo_installed_readline')
|
|
128
|
+
|
|
129
|
+
class Installed < Taski::Task
|
|
130
|
+
def run
|
|
131
|
+
brew = HomebrewPath.path
|
|
132
|
+
prefix = `#{brew} --prefix #{BREW_NAME} 2>/dev/null`.chomp
|
|
133
|
+
@lib_path = "-L#{prefix}/lib" if $CHILD_STATUS.success? && !prefix.empty?
|
|
134
|
+
puts "#{BREW_NAME} is already installed"
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
class Install < Taski::Task
|
|
139
|
+
def run
|
|
140
|
+
brew = HomebrewPath.path
|
|
141
|
+
puts "Installing #{BREW_NAME}..."
|
|
142
|
+
system("#{brew} install #{BREW_NAME}") or raise "Failed to install #{BREW_NAME}"
|
|
143
|
+
File.write(MARKER_FILE, 'installed')
|
|
144
|
+
|
|
145
|
+
prefix = `#{brew} --prefix #{BREW_NAME} 2>/dev/null`.chomp
|
|
146
|
+
@lib_path = "-L#{prefix}/lib" if $CHILD_STATUS.success? && !prefix.empty?
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def clean
|
|
150
|
+
return unless File.exist?(MARKER_FILE)
|
|
151
|
+
|
|
152
|
+
brew = HomebrewPath.path
|
|
153
|
+
puts "Uninstalling #{BREW_NAME} (installed by kompo)..."
|
|
154
|
+
system("#{brew} uninstall #{BREW_NAME}")
|
|
155
|
+
File.delete(MARKER_FILE) if File.exist?(MARKER_FILE)
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# libyaml library installation Section
|
|
161
|
+
class InstallLibyaml < Taski::Section
|
|
162
|
+
interfaces :lib_path
|
|
163
|
+
|
|
164
|
+
def impl
|
|
165
|
+
brew = HomebrewPath.path
|
|
166
|
+
system("#{brew} list #{BREW_NAME} > /dev/null 2>&1") ? Installed : Install
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
BREW_NAME = 'libyaml'
|
|
170
|
+
MARKER_FILE = File.expand_path('~/.kompo_installed_libyaml')
|
|
171
|
+
|
|
172
|
+
class Installed < Taski::Task
|
|
173
|
+
def run
|
|
174
|
+
brew = HomebrewPath.path
|
|
175
|
+
prefix = `#{brew} --prefix #{BREW_NAME} 2>/dev/null`.chomp
|
|
176
|
+
@lib_path = "-L#{prefix}/lib" if $CHILD_STATUS.success? && !prefix.empty?
|
|
177
|
+
puts "#{BREW_NAME} is already installed"
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
class Install < Taski::Task
|
|
182
|
+
def run
|
|
183
|
+
brew = HomebrewPath.path
|
|
184
|
+
puts "Installing #{BREW_NAME}..."
|
|
185
|
+
system("#{brew} install #{BREW_NAME}") or raise "Failed to install #{BREW_NAME}"
|
|
186
|
+
File.write(MARKER_FILE, 'installed')
|
|
187
|
+
|
|
188
|
+
prefix = `#{brew} --prefix #{BREW_NAME} 2>/dev/null`.chomp
|
|
189
|
+
@lib_path = "-L#{prefix}/lib" if $CHILD_STATUS.success? && !prefix.empty?
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
def clean
|
|
193
|
+
return unless File.exist?(MARKER_FILE)
|
|
194
|
+
|
|
195
|
+
brew = HomebrewPath.path
|
|
196
|
+
puts "Uninstalling #{BREW_NAME} (installed by kompo)..."
|
|
197
|
+
system("#{brew} uninstall #{BREW_NAME}")
|
|
198
|
+
File.delete(MARKER_FILE) if File.exist?(MARKER_FILE)
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
# zlib library installation Section
|
|
204
|
+
class InstallZlib < Taski::Section
|
|
205
|
+
interfaces :lib_path
|
|
206
|
+
|
|
207
|
+
def impl
|
|
208
|
+
brew = HomebrewPath.path
|
|
209
|
+
system("#{brew} list #{BREW_NAME} > /dev/null 2>&1") ? Installed : Install
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
BREW_NAME = 'zlib'
|
|
213
|
+
MARKER_FILE = File.expand_path('~/.kompo_installed_zlib')
|
|
214
|
+
|
|
215
|
+
class Installed < Taski::Task
|
|
216
|
+
def run
|
|
217
|
+
brew = HomebrewPath.path
|
|
218
|
+
prefix = `#{brew} --prefix #{BREW_NAME} 2>/dev/null`.chomp
|
|
219
|
+
@lib_path = "-L#{prefix}/lib" if $CHILD_STATUS.success? && !prefix.empty?
|
|
220
|
+
puts "#{BREW_NAME} is already installed"
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
class Install < Taski::Task
|
|
225
|
+
def run
|
|
226
|
+
brew = HomebrewPath.path
|
|
227
|
+
puts "Installing #{BREW_NAME}..."
|
|
228
|
+
system("#{brew} install #{BREW_NAME}") or raise "Failed to install #{BREW_NAME}"
|
|
229
|
+
File.write(MARKER_FILE, 'installed')
|
|
230
|
+
|
|
231
|
+
prefix = `#{brew} --prefix #{BREW_NAME} 2>/dev/null`.chomp
|
|
232
|
+
@lib_path = "-L#{prefix}/lib" if $CHILD_STATUS.success? && !prefix.empty?
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
def clean
|
|
236
|
+
return unless File.exist?(MARKER_FILE)
|
|
237
|
+
|
|
238
|
+
brew = HomebrewPath.path
|
|
239
|
+
puts "Uninstalling #{BREW_NAME} (installed by kompo)..."
|
|
240
|
+
system("#{brew} uninstall #{BREW_NAME}")
|
|
241
|
+
File.delete(MARKER_FILE) if File.exist?(MARKER_FILE)
|
|
242
|
+
end
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
# libffi library installation Section
|
|
247
|
+
class InstallLibffi < Taski::Section
|
|
248
|
+
interfaces :lib_path
|
|
249
|
+
|
|
250
|
+
def impl
|
|
251
|
+
brew = HomebrewPath.path
|
|
252
|
+
system("#{brew} list #{BREW_NAME} > /dev/null 2>&1") ? Installed : Install
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
BREW_NAME = 'libffi'
|
|
256
|
+
MARKER_FILE = File.expand_path('~/.kompo_installed_libffi')
|
|
257
|
+
|
|
258
|
+
class Installed < Taski::Task
|
|
259
|
+
def run
|
|
260
|
+
brew = HomebrewPath.path
|
|
261
|
+
prefix = `#{brew} --prefix #{BREW_NAME} 2>/dev/null`.chomp
|
|
262
|
+
@lib_path = "-L#{prefix}/lib" if $CHILD_STATUS.success? && !prefix.empty?
|
|
263
|
+
puts "#{BREW_NAME} is already installed"
|
|
264
|
+
end
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
class Install < Taski::Task
|
|
268
|
+
def run
|
|
269
|
+
brew = HomebrewPath.path
|
|
270
|
+
puts "Installing #{BREW_NAME}..."
|
|
271
|
+
system("#{brew} install #{BREW_NAME}") or raise "Failed to install #{BREW_NAME}"
|
|
272
|
+
File.write(MARKER_FILE, 'installed')
|
|
273
|
+
|
|
274
|
+
prefix = `#{brew} --prefix #{BREW_NAME} 2>/dev/null`.chomp
|
|
275
|
+
@lib_path = "-L#{prefix}/lib" if $CHILD_STATUS.success? && !prefix.empty?
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
def clean
|
|
279
|
+
return unless File.exist?(MARKER_FILE)
|
|
280
|
+
|
|
281
|
+
brew = HomebrewPath.path
|
|
282
|
+
puts "Uninstalling #{BREW_NAME} (installed by kompo)..."
|
|
283
|
+
system("#{brew} uninstall #{BREW_NAME}")
|
|
284
|
+
File.delete(MARKER_FILE) if File.exist?(MARKER_FILE)
|
|
285
|
+
end
|
|
286
|
+
end
|
|
287
|
+
end
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
# Linux implementation - checks dependencies using pkg-config
|
|
291
|
+
class ForLinux < Taski::Task
|
|
292
|
+
def run
|
|
293
|
+
unless pkg_config_available?
|
|
294
|
+
puts '[WARNING] pkg-config not found. Skipping dependency check.'
|
|
295
|
+
puts 'Install pkg-config to enable automatic dependency verification.'
|
|
296
|
+
@lib_paths = ''
|
|
297
|
+
return
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
check_dependencies
|
|
301
|
+
@lib_paths = collect_lib_paths
|
|
302
|
+
|
|
303
|
+
puts 'All required development libraries are installed.'
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
REQUIRED_LIBS = {
|
|
307
|
+
'openssl' => { pkg_config: 'openssl', apt: 'libssl-dev', yum: 'openssl-devel' },
|
|
308
|
+
'readline' => { pkg_config: 'readline', apt: 'libreadline-dev', yum: 'readline-devel' },
|
|
309
|
+
'zlib' => { pkg_config: 'zlib', apt: 'zlib1g-dev', yum: 'zlib-devel' },
|
|
310
|
+
'libyaml' => { pkg_config: 'yaml-0.1', apt: 'libyaml-dev', yum: 'libyaml-devel' },
|
|
311
|
+
'libffi' => { pkg_config: 'libffi', apt: 'libffi-dev', yum: 'libffi-devel' }
|
|
312
|
+
}.freeze
|
|
313
|
+
|
|
314
|
+
private
|
|
315
|
+
|
|
316
|
+
def pkg_config_available?
|
|
317
|
+
system('which pkg-config > /dev/null 2>&1')
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
def check_dependencies
|
|
321
|
+
missing = REQUIRED_LIBS.reject do |_, info|
|
|
322
|
+
system("pkg-config --exists #{info[:pkg_config]} 2>/dev/null")
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
raise build_error_message(missing) unless missing.empty?
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
def collect_lib_paths
|
|
329
|
+
pkg_names = REQUIRED_LIBS.values.map { |info| info[:pkg_config] }
|
|
330
|
+
paths = pkg_names.flat_map do |pkg|
|
|
331
|
+
`pkg-config --libs-only-L #{pkg} 2>/dev/null`.chomp.split
|
|
332
|
+
end
|
|
333
|
+
paths.uniq.join(' ')
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
def build_error_message(missing)
|
|
337
|
+
lib_names = missing.keys.join(', ')
|
|
338
|
+
apt_packages = missing.values.map { |info| info[:apt] }.join(' ')
|
|
339
|
+
yum_packages = missing.values.map { |info| info[:yum] }.join(' ')
|
|
340
|
+
|
|
341
|
+
<<~MSG
|
|
342
|
+
Missing required development libraries: #{lib_names}
|
|
343
|
+
|
|
344
|
+
Please install them using your package manager:
|
|
345
|
+
|
|
346
|
+
Ubuntu/Debian:
|
|
347
|
+
sudo apt-get update
|
|
348
|
+
sudo apt-get install -y #{apt_packages}
|
|
349
|
+
|
|
350
|
+
RHEL/CentOS/Fedora:
|
|
351
|
+
sudo yum install -y #{yum_packages}
|
|
352
|
+
# or: sudo dnf install -y #{yum_packages}
|
|
353
|
+
|
|
354
|
+
After installing, run kompo again.
|
|
355
|
+
MSG
|
|
356
|
+
end
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
private
|
|
360
|
+
|
|
361
|
+
def macos?
|
|
362
|
+
RUBY_PLATFORM.include?('darwin')
|
|
363
|
+
end
|
|
364
|
+
end
|
|
365
|
+
end
|