environment/lib/make.rb
Artur Gurgul 57cc223477 save
2025-08-05 16:07:34 +00:00

226 lines
6.4 KiB
Ruby

require 'yaml'
require 'tmpdir'
require 'system'
require 'ostruct'
require 'fileutils'
require 'open3'
# make for: the user, system, package
# as regular user, if dependencies provided
# user: $HOME/.local
# as super user
# system: /
# package: /pkg/$name/$version/
module Make
def self.rostruct(obj)
case obj
when Hash
OpenStruct.new(obj.transform_values { |v| rostruct(v) })
when Array
obj.map { |v| rostruct(v) }
else
obj
end
end
class Context
attr_accessor :name, :use_cache, :environment, :steps, :packages, :repository
attr_accessor :target, :archive, :source_type
def initialize(options: OpenStruct.new)
@target = options.target || :user
@name = options.name
@use_cache = options.use_cache || false
#System.detect_os
# rbenv: brew install rbenv && rbenv install 3.0.0
# asdf: asdf install ruby 3.0.0
makefile_path = "#{ENV["DAT_ROOT"]}/recipes/#{@name}.yml"
unless File.file?(makefile_path)
makefile_path = "#{ENV["DAT_ROOT"]}/recipes/#{@name}/#{System.detect_os}.yml"
end
puts "recipe at: #{makefile_path}"
makefile = YAML.load_file(makefile_path)
# puts makefile_path
# puts makefile
@packages = makefile["packages"] || []
project_sources = {
repository: makefile["repository"] != nil,
archive: makefile["archive"] != nil
}.select { |_, v| v }
if project_sources.size == 1
@source_type = project_sources.keys.first
else
raise "Exactly one type of source: 'repository' or 'archive'"
end
puts @source_type
@repository = Make.rostruct(makefile["repository"] || OpenStruct.new)
@archive = Make.rostruct(makefile["archive"] || OpenStruct.new)
@steps = makefile["steps"] || []
@environment = ENV.to_h.merge(
"PREFIX" => get_prefix,
"CPUS" => System.cpus.to_s,
"SUDO" => (@target == :user ? "" : "sudo")
)
environment = makefile["environment"]
if environment != nil
@environment = environment.merge(@environment)
end
end
def get_prefix
sys_prefix = case System.os_name
when :macos
"/usr"
else
""
end
case @target
when :user
"#{ENV["HOME"]}/.local"
when :package
"#{sys_prefix}/pkg/#{@name}/#{@repository.branch}"
when :system
"#{sys_prefix}/"
else
raise "Unknown target: #{@target.inspect}"
end
end
# Root project directory
def rpd
puts @repository
if @use_cache
path = "#{ENV["HOME"]}/.cache/dat/build/#{@name}/#{@repository.branch}"
FileUtils.mkdir_p(path)
yield path
else
Dir.mktmpdir do |tmp_path|
yield tmp_path
end
end
end
def local_repo
"#{ENV["HOME"]}/.cache/dat/repo/#{@name}.git"
end
def to_s
vars = instance_variables.map do |var|
"#{var.to_s.delete('@')}: #{instance_variable_get(var).inspect}"
end
"Context(#{vars.join(', ')})"
end
end
class Builder
attr_accessor :context, :cwd
def initialize(context)
@context = context
end
def execute(command)
if command.strip.start_with?("cd ")
eval_cmd = command.sub(/^cd /, 'echo ')
# new_dir = `#{eval_cmd}`.strip
new_dir, stderr, status = Open3.capture3(@context.environment, eval_cmd)
begin
puts "Dir: #{Dir.pwd}"
# new_dir = File.expand_path(new_dir)
new_dir = new_dir.strip
puts "Exists #{new_dir} => #{Dir.exist?(new_dir)}"
Dir.chdir(new_dir)
puts "Changed directory to #{Dir.pwd}"
rescue Errno::ENOENT
puts "Directory not found: #{new_dir}"
end
else
system(@context.environment, command)
end
end
def build
install
@context.rpd do | path |
@cwd = path
case @context.source_type
when :repository
checkout
when :archive
download_and_extract
end
puts "path: #{path}"
Dir.chdir(path)
@context.steps.each do |command|
# system(env, command)
execute(command)
# need to be refreshed
# rehash # hash -r # https://chatgpt.com/c/6880b3d6-b190-8330-9623-0458254d2881
end
end
end
def download_and_extract
archive = @context.archive
puts archive
exit -1
end
def checkout
repo_path = @context.local_repo
repo_url = @context.repository.url
branch = @context.repository.branch
puts "Local bare git repo path: #{repo_path}"
if Dir.exist?(repo_path) && !Dir.empty?(repo_path)
puts "Bare repo exists, fetching updates..."
Dir.chdir(repo_path) do
system("git fetch origin")
end
else
puts "Cloning bare repository..."
FileUtils.mkdir_p(repo_path)
system("git clone --bare #{repo_url} #{repo_path}")
end
system("git --git-dir=#{repo_path} --work-tree=#{@cwd} checkout -f #{branch}")
end
def install
System.install(context.packages)
end
end
# dat make -t pkg --cache --name dry-run
# dat make --name dry-run
def self.command(options)
context = Context.new(options: options)
builder = Builder.new(context)
builder.build
end
end