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