xbps-otterb-build (4973B)
1 #!/usr/bin/env ruby 2 # frozen_string_literal: true 3 4 require "fileutils" 5 require "open3" 6 require "etc" 7 8 class Builder 9 REPO = "https://repo-default.voidlinux.org/current" 10 DEPS = %w[ 11 cmake 12 make 13 gcc 14 qt5-devel 15 qt5-svg-devel 16 qt5-multimedia-devel 17 qt5-declarative-devel 18 qt5-webkit-devel 19 hunspell-devel 20 gstreamer1-devel 21 libxml2-devel 22 qt5-tools 23 ] 24 .freeze 25 26 def initialize(argv) 27 @outdir = nil 28 @chroot = "/var/chroot/otter-build" 29 @keep = false 30 @quiet = false 31 parse(argv) 32 validate 33 end 34 35 def run 36 bootstrap 37 mount_vfs 38 prep_chroot 39 install_deps 40 build 41 extract 42 ensure 43 unmount_vfs 44 remove_chroot unless @keep 45 msg("done.") 46 end 47 48 private 49 50 def parse(argv) 51 i = 0 52 while i < argv.length 53 case argv[i] 54 when "-o" 55 @outdir = argv[i += 1] 56 when "-c" 57 @chroot = argv[i += 1] 58 when "-k" 59 @keep = true 60 when "-q" 61 @quiet = true 62 when "-h", "--help" 63 usage(code: 0) 64 else 65 usage 66 end 67 68 i += 1 69 end 70 end 71 72 def usage_lines 73 [ 74 "Usage: #{File.basename($0)} [-o DIR] [-c DIR] [-k] [-q]", 75 " -o DIR output directory (default: invoking user home)", 76 " -c DIR chroot directory (default: /var/chroot/otter-build)", 77 " -k keep chroot after build", 78 " -q quiet" 79 ] 80 end 81 82 def usage(code: 1) 83 usage_lines.each { |l| puts(l) } 84 exit(code) 85 end 86 87 def validate 88 unless Process.uid.zero? 89 usage_lines.each { |l| puts(l) } 90 die("must run as root") 91 end 92 93 @src = Dir.pwd 94 die("no CMakeLists.txt in #{@src}") unless File.file?(File.join(@src, "CMakeLists.txt")) 95 96 inv = ENV["SUDO_USER"] || ENV["DOAS_USER"] 97 if inv 98 @pw = Etc.getpwnam(inv) rescue nil 99 die("cannot resolve user #{inv}") unless @pw 100 @outdir ||= @pw.dir 101 else 102 @outdir ||= File.expand_path("~") 103 end 104 end 105 106 def msg(s) = puts(s) unless @quiet 107 108 def die(s) 109 $stderr.puts("#{File.basename($0)}: #{s}") 110 exit(1) 111 end 112 113 def run_cmd(cmd, fatal: true) 114 msg(cmd) 115 _o, e, st = Open3.capture3(cmd) 116 die("#{cmd}: #{e.strip}") if !st.success? && fatal 117 st.success? 118 end 119 120 def chroot_cmd(script) 121 msg("chroot #{@chroot} /bin/bash -c '#{script}'") 122 die("chroot command failed") unless system("chroot #{@chroot} /bin/bash -c '#{script}'") 123 end 124 125 def bootstrap 126 FileUtils.mkdir_p(@chroot) 127 return if File.executable?(File.join(@chroot, "bin/bash")) 128 129 msg("bootstrapping chroot...") 130 kdir = File.join(@chroot, "var/db/xbps/keys") 131 FileUtils.mkdir_p(kdir) 132 Dir.glob("/var/db/xbps/keys/*").each { |f| FileUtils.cp(f, kdir) } 133 134 run_cmd("xbps-install -Syu -R #{REPO} -r #{@chroot} base-voidstrap", fatal: false) 135 die("bootstrap failed") unless File.executable?(File.join(@chroot, "bin/bash")) 136 msg("bootstrap ok") 137 end 138 139 def mount_vfs 140 mounts = { 141 File.join(@chroot, "proc") => ["-t", "proc", "proc"], 142 File.join(@chroot, "sys") => ["-t", "sysfs", "sys"], 143 File.join(@chroot, "dev") => ["--bind", "/dev"] 144 } 145 mounts.each do |dst, opts| 146 FileUtils.mkdir_p(dst) 147 next if system("mountpoint -q #{dst} 2>/dev/null") 148 system("mount #{opts.join(" ")} #{dst}") 149 end 150 end 151 152 def unmount_vfs 153 %w[dev sys proc].each { |n| system("umount #{File.join(@chroot, n)} 2>/dev/null") } 154 end 155 156 def prep_chroot 157 FileUtils.cp("/etc/resolv.conf", File.join(@chroot, "etc/resolv.conf")) 158 kdir = File.join(@chroot, "var/db/xbps/keys") 159 FileUtils.mkdir_p(kdir) 160 Dir.glob("/var/db/xbps/keys/*").each { |f| FileUtils.cp(f, kdir) } 161 end 162 163 def install_deps 164 chroot_cmd("xbps-install -Syu") 165 chroot_cmd("xbps-install -S #{DEPS.join(" ")}") 166 end 167 168 def build 169 dst = File.join(@chroot, "src/otter-browser") 170 FileUtils.rm_rf(dst) 171 FileUtils.mkdir_p(File.dirname(dst)) 172 system("cp -a #{@src} #{dst}") || die("copy failed") 173 174 cm = File.join(dst, "CMakeLists.txt") 175 txt = File.read(cm) 176 unless txt.include?("FATAL_ON_MISSING_REQUIRED_PACKAGES") 177 die("CMakeLists.txt: FATAL_ON_MISSING_REQUIRED_PACKAGES not found") 178 end 179 180 txt.gsub!("FATAL_ON_MISSING_REQUIRED_PACKAGES", "") 181 File.write(cm, txt) 182 183 script = "set -e; cd /src/otter-browser; rm -rf build; mkdir build; cd build; cmake -DENABLE_QT5=ON ../; make -j$(nproc)" 184 chroot_cmd(script) 185 end 186 187 def extract 188 bin = File.join(@chroot, "src/otter-browser/build/otter-browser") 189 die("binary not found") unless File.file?(bin) 190 191 out = File.join(@outdir, "otter-browser") 192 system("cp #{bin} #{out}") || die("copy binary failed") 193 FileUtils.chmod("+x", out) 194 195 if @pw 196 begin 197 File.chown(@pw.uid, @pw.gid, out) 198 rescue => e 199 msg("chown failed: #{e}") 200 end 201 end 202 203 msg("binary: #{out}") 204 end 205 206 def remove_chroot 207 msg("removing #{@chroot}...") 208 FileUtils.rm_rf(@chroot) 209 end 210 end 211 212 Builder.new(ARGV).run