crew 49.6 KB
Newer Older
1
#!/usr/bin/env ruby
Cassandra Watergate's avatar
Cassandra Watergate committed
2
require_relative '../lib/color'
Yan Couto's avatar
Yan Couto committed
3 4

# Disallow sudo
5
abort 'Chromebrew should not be run as root.'.lightred if Process.uid == 0
Yan Couto's avatar
Yan Couto committed
6

7 8 9
require 'find'
require 'net/http'
require 'uri'
10
require 'digest/sha2'
11
require 'json'
12
require 'fileutils'
Cassandra Watergate's avatar
Cassandra Watergate committed
13
require 'securerandom'
14
require 'tmpdir'
Cassandra Watergate's avatar
Cassandra Watergate committed
15 16
require_relative '../lib/const'
require_relative '../lib/util'
17

Yan Couto's avatar
Yan Couto committed
18 19
# Add lib to LOAD_PATH
$LOAD_PATH.unshift "#{CREW_LIB_PATH}lib"
20 21

DOC = <<DOCOPT
22
Chromebrew - Package manager for Chrome OS http://skycocker.github.io/chromebrew/
23 24

Usage:
25 26
  crew build [options] [-k|--keep] <name> ...
  crew const [options] [<name> ...]
satmandu's avatar
satmandu committed
27
  crew deps [options] <name> ...
28 29
  crew download [options] <name> ...
  crew files [options] <name> ...
30
  crew help [<command>]
31 32 33 34 35 36
  crew install [options] [-k|--keep] [-s|--build-from-source] [-S|--recursive-build] <name> ...
  crew list [options] (available|installed|compatible|incompatible)
  crew postinstall [options] <name> ...
  crew reinstall [options] [-k|--keep] [-s|--build-from-source] [-S|--recursive-build] <name> ...
  crew remove [options] <name> ...
  crew search [options] [<name> ...]
37
  crew update [options] [<compatible>]
38
  crew upgrade [options] [-k|--keep] [-s|--build-from-source] [<name> ...]
39
  crew whatprovides [options] <pattern> ...
40

41 42
  -c --color              Use colors even if standard out is not a tty.
  -d --no-color           Disable colors even if standard out is a tty.
43
  -k --keep               Keep the `CREW_BREW_DIR` (#{CREW_BREW_DIR}) directory.
44
  -L --license            Display the crew license.
45
  -s --build-from-source  Build from source even if pre-compiled binary exists.
46
  -S --recursive-build    Build from source, including all dependencies, even if pre-compiled binaries exist.
47
  -v --verbose            Show extra information.
48
  -V --version            Display the crew version.
49
  -h --help               Show this screen.
50

51
version #{CREW_VERSION}
52
DOCOPT
53

Cassandra Watergate's avatar
Cassandra Watergate committed
54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
CREW_LICENSE = <<~LICENSESTRING
  Copyright (C) 2021 Chromebrew Authors

  This program is free software: you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation, version 3 of the License.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program. If not, see https://www.gnu.org/licenses/gpl-3.0.html.
LICENSESTRING

70 71
# Set XZ_OPT environment variable for build command.
# If CREW_XZ_OPT is defined, use it by default.  Use `-7e`, otherwise.
72
ENV["XZ_OPT"] = ENV['CREW_XZ_OPT'] || "-7e -T #{CREW_NPROC}"
73

74 75 76
# If CURL environment variable exists use it in lieu of curl.
CURL = ENV['CURL'] || 'curl'

77
# All available crew commands.
satmandu's avatar
satmandu committed
78
@cmds = ["build", "const", "deps", "download", "files", "help", "install", "list", "postinstall", "reinstall", "remove", "search", "update", "upgrade", "whatprovides"]
79

80
# Parse arguments using docopt
Cassandra Watergate's avatar
Cassandra Watergate committed
81
require_relative '../lib/docopt'
82 83
begin
  args = Docopt::docopt(DOC)
84
  args['<name>'] = args['<name>'].map { |arg| arg.gsub('-','_') } if args['<name>']
85
rescue Docopt::Exit => e
86
  if ARGV[0] then
Cassandra Watergate's avatar
Cassandra Watergate committed
87 88
    case ARGV[0]
    when '-V', '--version'
89
      puts CREW_VERSION
90
      exit 0
Cassandra Watergate's avatar
Cassandra Watergate committed
91 92 93
    when '-L', '--license'
      puts CREW_LICENSE
      exit 0
94 95 96 97
    end
    if ARGV[0] != '-h' and ARGV[0] != '--help' then
      puts "Could not understand \"crew #{ARGV.join(' ')}\".".lightred
      # Looking for similar commands
98 99
      if not @cmds.include?(ARGV[0]) then
        similar = @cmds.select {|word| edit_distance(ARGV[0], word) < 4}
100
        if not similar.empty? then
101
          puts 'Did you mean?'
102 103
          similar.each {|sug| puts "  #{sug}"}
        end
Ed Reel's avatar
Ed Reel committed
104
      end
Yan Couto's avatar
Yan Couto committed
105 106
    end
  end
107 108 109 110
  puts e.message
  exit 1
end

111
String.use_color = args["--color"] || !args["--no-color"]
112
@opt_keep = args["--keep"]
113
@opt_verbose = args["--verbose"]
114 115

if @opt_verbose then
supechicken's avatar
supechicken committed
116 117
  @fileutils_verbose = true
  @verbose = 'v'
118
  @short_verbose = '-v'
119
else
supechicken's avatar
supechicken committed
120
  @fileutils_verbose = false
121
  @verbose = ''
122
  @short_verbose = ''
123 124
end

125
@opt_src = args["--build-from-source"]
126
@opt_recursive = args["--recursive-build"]
127

128
@device = JSON.parse(File.read(CREW_CONFIG_PATH + 'device.json'), symbolize_names: true)
129 130
#symbolize also values
@device.each do |key, elem|
131 132 133
  @device[key] = @device[key].to_sym rescue @device[key]
end

134 135
def print_package(pkgPath, extra = false)
  pkgName = File.basename pkgPath, '.rb'
136 137 138 139 140
  begin
    set_package pkgName, pkgPath
  rescue => e
    puts "Error with #{pkgName}.rb: #{e}".red unless e.to_s.include?('uninitialized constant')
  end
141 142 143
  print_current_package extra
end

144
def print_current_package(extra = false)
Ed Reel's avatar
Ed Reel committed
145
  status = ''
146 147
  status = 'installed' if @device[:installed_packages].any? do |elem| elem[:name] == @pkg.name end
  status = 'incompatible' unless @device[:compatible_packages].any? do |elem| elem[:name] == @pkg.name end
Ed Reel's avatar
Ed Reel committed
148 149 150 151 152 153
  case status
  when 'installed'
    print @pkg.name.lightgreen
  when 'incompatible'
    print @pkg.name.lightred
  else
154
    print @pkg.name.lightblue
Ed Reel's avatar
Ed Reel committed
155
  end
156
  print ": #{@pkg.description}".lightblue if @pkg.description
Ed Reel's avatar
Ed Reel committed
157 158 159
  if extra
    puts ""
    puts @pkg.homepage if @pkg.homepage
160 161
    puts "Version: #{@pkg.version}"
    print "License: #{@pkg.license}" if @pkg.license
Ed Reel's avatar
Ed Reel committed
162 163 164 165
  end
  puts ""
end

166 167 168 169 170 171
def set_package(pkgName, pkgPath)
  begin
    require_relative pkgPath
  rescue SyntaxError => e
    puts "#{e.class}: #{e.message}".red
  end
172
  @pkg = Object.const_get(pkgName.capitalize)
173
  @pkg.build_from_source = true if @opt_recursive
174
  @pkg.name = pkgName
175 176
end

177
def list_packages
178 179
  Dir[CREW_PACKAGES_PATH + '*.rb'].each do |filename|
    print_package filename
180 181 182
  end
end

183
def list_available
184
  Dir[CREW_PACKAGES_PATH + '*.rb'].each do |filename|
Ed Reel's avatar
Ed Reel committed
185 186
    notInstalled = true
    pkgName = File.basename filename, '.rb'
187
    notInstalled = false if File.exist? CREW_META_PATH + pkgName + '.filelist'
Ed Reel's avatar
Ed Reel committed
188
    if notInstalled
189 190 191 192 193 194
      begin
        set_package pkgName, filename
      rescue => e
        puts "Error with #{pkgName}.rb: #{e}".red unless e.to_s.include?('uninitialized constant')
      end
      if @pkg.compatibility&.include? 'all' or @pkg.compatibility&.include? ARCH
Ed Reel's avatar
Ed Reel committed
195 196 197
        puts pkgName
      end
    end
198 199 200 201
  end
end

def list_installed
202 203 204 205 206 207 208 209 210 211 212 213 214 215 216
  unless @opt_verbose
    Dir[CREW_META_PATH + '*.directorylist'].sort.map do |f|
      File.basename(f, '.directorylist').lightgreen
    end
  else
    @installed_packages = []
    @device[:installed_packages].each do |package|
      search package[:name], true
      @installed_packages.append(package[:name] + ' ' + package[:version].to_s)
    end
    @sorted_installed_packages = @installed_packages.sort
    @sorted_installed_packages.unshift('======= =======')
    @sorted_installed_packages.unshift('Package Version')
    @first_col_width = @sorted_installed_packages.map(&:split).map(&:first).max_by(&:size).size + 2
    @sorted_installed_packages.map(&:strip).each do |line|
217
      puts "%-#{@first_col_width}s%s".lightgreen % line.split
218 219
    end
    puts
Ed Reel's avatar
Ed Reel committed
220
  end
221 222
end

Ed Reel's avatar
Ed Reel committed
223
def list_compatible(compat = true)
224 225
  Dir[CREW_PACKAGES_PATH + '*.rb'].each do |filename|
    pkgName = File.basename filename, '.rb'
226
    if @device[:compatible_packages].any? do |elem| elem[:name] == pkgName end
227 228 229 230 231 232
      if compat
        if ( File.exist? CREW_META_PATH + pkgName + '.filelist' )
          puts pkgName.lightgreen
        else
          puts pkgName
        end
Ed Reel's avatar
Ed Reel committed
233
      end
234
    else
235
      puts pkgName.lightred unless compat
Ed Reel's avatar
Ed Reel committed
236 237 238 239 240
    end
  end
end

def generate_compatible
241
  puts 'Generating compatible packages...'.orange if @opt_verbose
Ed Reel's avatar
Ed Reel committed
242
  @device[:compatible_packages] = []
243 244
  Dir[CREW_PACKAGES_PATH + '*.rb'].each do |filename|
    pkgName = File.basename filename, '.rb'
245 246 247 248 249
    begin
      set_package pkgName, filename
    rescue => e
      puts "Error with #{pkgName}.rb: #{e}".red unless e.to_s.include?('uninitialized constant')
    end
250
    puts "Checking #{pkgName} for compatibility.".orange if @opt_verbose
251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267
    # If compatibility property does not exist, check if a binary package
    # exists, and if not, see if at least a source url exists.
    @compatibility = true
    @binary_url = ''
    @url = ''
    if @pkg.compatibility.nil?
      @binary_url = @pkg.get_binary_url(@device[:architecture])
      @url = @pkg.get_url(@device[:architecture])
      if !@binary_url
        puts "#{pkgName} is missing compatibility information".red
        #puts "url: #{@url}".green
        # If no source package is available, then package is not compatible.
        @compatibility = false unless @url
        puts "#{pkgName} compatibility is #{@compatibility}" if @opt_verbose
      end
    end
    if ( @pkg.compatibility&.include? 'all' or @pkg.compatibility&.include? ARCH or @pkg.compatibility.nil? ) and @compatibility
268
      #add to compatible packages
269
      puts "Adding #{pkgName} to compatible packages.".lightgreen if @opt_verbose
270
      @device[:compatible_packages].push(name: @pkg.name)
271 272
    else
      puts "#{pkgName} is not a compatible package.".lightred if @opt_verbose
Ed Reel's avatar
Ed Reel committed
273 274 275 276 277 278
    end
  end
  File.open(CREW_CONFIG_PATH + 'device.json', 'w') do |file|
    output = JSON.parse @device.to_json
    file.write JSON.pretty_generate(output)
  end
279
  puts 'Generating compatible packages done.'.orange if @opt_verbose
Ed Reel's avatar
Ed Reel committed
280 281
end

282
def search (pkgName, silent = false)
283
  pkgPath = CREW_PACKAGES_PATH + pkgName + '.rb'
284 285 286 287 288
  begin
    return set_package(pkgName, pkgPath) if File.exist?(pkgPath)
  rescue => e
    puts "Error with #{pkgName}.rb: #{e}".red unless e.to_s.include?('uninitialized constant')
  end
289
  abort "Package #{pkgName} not found. :(".lightred unless silent
290 291
end

292 293
def regexp_search(pkgPat)
  re = Regexp.new(pkgPat, true)
Ed Reel's avatar
Ed Reel committed
294
  results = Dir[CREW_PACKAGES_PATH + '*.rb'].sort \
295
    .select  { |f| File.basename(f, '.rb') =~ re } \
Yan Couto's avatar
Yan Couto committed
296
    .each    { |f| print_package(f, @opt_verbose) }
297
  if results.empty?
298 299
    Dir[CREW_PACKAGES_PATH + '*.rb'].each do |packagePath|
      packageName = File.basename packagePath, '.rb'
300 301 302 303 304
      begin
        set_package packageName, packagePath
      rescue => e
        puts "Error with #{pkgName}.rb: #{e}".red unless e.to_s.include?('uninitialized constant')
      end
305 306 307
      if ( @pkg.description =~ /#{pkgPat}/i )
        print_current_package @opt_verbose
        results.push(packageName)
308 309 310
      end
    end
  end
311
  abort "Package #{pkgPat} not found. :(".lightred if results.empty?
312 313
end

314
def help(pkgName)
315
  case pkgName
Ed Reel's avatar
Ed Reel committed
316
  when "build"
317
    puts "Build package(s)."
318
    puts "Usage: crew build [-k|--keep] [-v|--verbose] <package1> [<package2> ...]"
319 320
    puts "Build package(s) from source and place the archive and checksum in the current working directory."
    puts "If `-k` or `--keep` is present, the `CREW_BREW_DIR` (#{CREW_BREW_DIR}) directory will remain."
321
    puts "If `-v` or `--verbose` is present, extra information will be displayed."
Ed Reel's avatar
Ed Reel committed
322 323 324 325
  when "const"
    puts "Display constant(s)."
    puts "Usage: crew const [<const1> <const2> ...]"
    puts "If no constants are provided, all constants will be displayed."
satmandu's avatar
satmandu committed
326 327 328
  when "deps"
    puts "Display dependencies of package(s)."
    puts "Usage: crew deps <package1> [<package2> ...]"
Ed Reel's avatar
Ed Reel committed
329
  when "download"
330
    puts "Download package(s)."
331
    puts "Usage: crew download [-v|--verbose] <package1> [<package2> ...]"
332
    puts "Download package(s) to `CREW_BREW_DIR` (#{CREW_BREW_DIR}), but don't install."
333
    puts "If `-v` or `--verbose` is present, extra information will be displayed."
Ed Reel's avatar
Ed Reel committed
334 335 336 337
  when "files"
    puts "Display installed files of package(s)."
    puts "Usage: crew files <package1> [<package2> ...]"
    puts "The package(s) must be currently installed."
Ed Reel's avatar
Ed Reel committed
338
  when "install"
339
    puts "Install package(s)."
340
    puts "Usage: crew install [-k|--keep] [-s|--build-from-source] [-S|--recursive-build] [-v|--verbose] <package1> [<package2> ...]"
341 342 343
    puts "The package(s) must have a valid name.  Use `crew search <pattern>` to search for packages to install."
    puts "If `-k` or `--keep` is present, the `CREW_BREW_DIR` (#{CREW_BREW_DIR}) directory will remain."
    puts "If `-s` or `--build-from-source` is present, the package(s) will be compiled instead of installed via binary."
344
    puts "If `-S` or `--recursive-build` is present, the package(s), including all dependencies, will be compiled instead of installed via binary."
345
    puts "If `-v` or `--verbose` is present, extra information will be displayed."
346
  when "list"
Ed Reel's avatar
Ed Reel committed
347
    puts "List packages"
348
    puts "Usage: crew list [-v|--verbose] available|installed|compatible|incompatible"
349 350 351 352
  when "postinstall"
    puts "Display postinstall messages of package(s)."
    puts "Usage: crew postinstall <package1> [<package2> ...]"
    puts "The package(s) must be currently installed."
Ed Reel's avatar
Ed Reel committed
353 354
  when "reinstall"
    puts "Remove and install package(s)."
355 356 357 358
    puts "Usage: crew reinstall [-k|--keep] [-s|--build-from-source] [-S|--recursive-build] [-v|--verbose] <package1> [<package2> ...]"
    puts "If `-k` or `--keep` is present, the `CREW_BREW_DIR` (#{CREW_BREW_DIR}) directory will remain."
    puts "If `-s` or `--build-from-source` is present, the package(s) will be compiled instead of installed via binary."
    puts "If `-S` or `--recursive-build` is present, the package(s), including all dependencies, will be compiled instead of installed via binary."
Ed Reel's avatar
Ed Reel committed
359
    puts "If `-v` or `--verbose` is present, extra information will be displayed."
Ed Reel's avatar
Ed Reel committed
360
  when "remove"
361
    puts "Remove package(s)."
362
    puts "Usage: crew remove [-v|--verbose] <package1> [<package2> ...]"
363
    puts "The package(s) must be currently installed."
364
    puts "If `-v` or `--verbose` is present, extra information will be displayed."
Ed Reel's avatar
Ed Reel committed
365
  when "search"
366 367 368
    puts "Look for package(s)."
    puts "Usage: crew search [-v|--verbose] [<pattern> ...]"
    puts "If <pattern> is omitted, all packages will be returned."
Ed Reel's avatar
Ed Reel committed
369 370
    puts "If the package color is " + "green".lightgreen + ", it means the package is installed."
    puts "If the package color is " + "red".lightred + ", it means the architecture is not supported."
371
    puts "The <pattern> string can also contain regular expressions."
372
    puts "If `-v` or `--verbose` is present, homepage, version and license will be displayed."
373
    puts "Examples:"
374
    puts "  crew search ^lib".lightblue + " will display all packages that start with `lib`."
Ed Reel's avatar
Ed Reel committed
375 376
    puts "  crew search audio".lightblue + " will display all packages with `audio` in the name."
    puts "  crew search | grep -i audio".lightblue + " will display all packages with `audio` in the name or description."
377
    puts "  crew search git -v".lightblue + " will display packages with `git` in the name along with homepage, version and license."
Ed Reel's avatar
Ed Reel committed
378 379 380
  when "update"
    puts "Update crew."
    puts "Usage: crew update"
381
    puts "This only updates crew itself.  Use `crew upgrade` to update packages."
382 383
    puts "Usage: crew update compatible"
    puts "This updates the crew package compatibility list."
Ed Reel's avatar
Ed Reel committed
384 385
  when "upgrade"
    puts "Update package(s)."
386
    puts "Usage: crew upgrade [-v|--verbose] [-s|--build-from-source] <package1> [<package2> ...]"
387 388
    puts "If package(s) are omitted, all packages will be updated.  Otherwise, specific package(s) will be updated."
    puts "Use `crew update` to update crew itself."
389
    puts "If `-s` or `--build-from-source` is present, the package(s) will be compiled instead of upgraded via binary."
390
    puts "If `-v` or `--verbose` is present, extra information will be displayed."
Ed Reel's avatar
Ed Reel committed
391 392
  when "whatprovides"
    puts "Determine which package(s) contains file(s)."
393 394
    puts "Usage: crew whatprovides <pattern> ..."
    puts "The <pattern> is a search string which can contain regular expressions."
Ed Reel's avatar
Ed Reel committed
395
  else
396
    puts "Available commands: #{@cmds.join(', ')}"
Ed Reel's avatar
Ed Reel committed
397 398 399
  end
end

400
def const(var)
Ed Reel's avatar
Ed Reel committed
401 402
  if var
    value = eval(var)
403
    puts "#{var}=#{value}"
Ed Reel's avatar
Ed Reel committed
404
  else
405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434
    @ruby_default_constants = %w[
      ARGF
      ARGV
      CROSS_COMPILING
      DOC
      ENV
      GC
      IO
      JSON
      OpenSSL
      Q
      R
      RUBY_COPYRIGHT
      RUBY_DESCRIPTION
      RUBY_ENGINE
      RUBY_ENGINE_VERSION
      RUBYGEMS_ACTIVATION_MONITOR
      RUBY_PATCHLEVEL
      RUBY_PLATFORM
      RUBY_RELEASE_DATE
      RUBY_REVISION
      RUBY_VERSION
      RubyVM
      S
      STDERR
      STDIN
      STDOUT
      StringIO
      TOPLEVEL_BINDING
      URI
Ed Reel's avatar
Ed Reel committed
435
    ]
436 437 438 439 440 441 442
    # Get all constants
    @constants = Module.constants.select {|e| e =~ /[[:upper:]]$/}
    # Reject all constants which match the default list
    @constants = @constants.map(&:to_s).reject{ |e| @ruby_default_constants.find{ |f| /\A#{e}\z/ =~ f }}
    # Print a sorted list of the remaining constants used by crew.
    @constants.sort.each { |var|
      value = eval(var.to_s)
443
      puts "#{var}=#{value}"
Ed Reel's avatar
Ed Reel committed
444 445 446 447
    }
  end
end

448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470
def human_size (bytes)
  kilobyte = 1024.0
  megabyte = kilobyte * kilobyte
  gigabyte = megabyte * kilobyte
  if bytes < kilobyte
    units = 'B'
    size = bytes
  end
  if bytes >= kilobyte and bytes < megabyte
    units = 'KB'
    size = bytes / kilobyte
  end
  if bytes >= megabyte and bytes < gigabyte
    units = 'MB'
    size = bytes / megabyte
  end
  if bytes >= gigabyte
    units = 'GB'
    size = bytes / gigabyte
  end
  return sprintf('%.2f', size.to_s) + units
end

Ed Reel's avatar
Ed Reel committed
471
def files (pkgName)
472
  filelist = "#{CREW_META_PATH}#{pkgName}.filelist"
473
  if File.exist? filelist
Ed Reel's avatar
Ed Reel committed
474
    system "sort #{filelist}"
475 476 477
    lines = File.readlines(filelist).size
    size = 0
    File.readlines(filelist).each do |filename|
478
      size += File.size(filename.chomp) if File.exist? filename.chomp
479 480
    end
    humansize = human_size(size)
481
    puts "Total found: #{lines}".lightgreen
482
    puts "Disk usage: #{humansize}".lightgreen
483 484
  else
    puts "Package #{pkgName} is not installed. :(".lightred
Ed Reel's avatar
Ed Reel committed
485 486 487
  end
end

488
def whatprovides (regexPat)
489 490 491
  # Use grep version command to ascertain whether we have a working grep.
  unless system('grep -V > /dev/null 2>&1')
    abort 'Grep is not working. Please install it with \'crew install grep\''.lightred
Ed Reel's avatar
Ed Reel committed
492
  end
493 494 495
  fileArray = []
  @grepresults =  %x[grep "#{regexPat}" #{CREW_META_PATH}*.filelist].chomp.gsub('.filelist','').gsub(':',': ').gsub(CREW_META_PATH,'').split(/$/).map(&:strip)
  @grepresults.each { |fileLine| fileArray.push(fileLine) }
496
  unless fileArray.empty?
497 498 499
    fileArray.sort.each do |item|
      puts item
    end
Ed Reel's avatar
Ed Reel committed
500
    puts "\nTotal found: #{fileArray.length}".lightgreen
501
  end
Ed Reel's avatar
Ed Reel committed
502 503
end

504
def update
505
  abort "'crew update' is used to update crew itself. Use 'crew upgrade <package1> [<package2> ...]' to update specific packages.".orange if @pkgName
506 507 508 509 510
  @crew_testing_repo = ENV['CREW_TESTING_REPO']
  @crew_testing_branch = ENV['CREW_TESTING_BRANCH']
  @crew_testing = ENV['CREW_TESTING']
  @crew_testing = 0 if @crew_testing_repo.nil? || @crew_testing_repo.empty?
  @crew_testing = 0 if @crew_testing_branch.nil? || @crew_testing_branch.empty?
511 512
  #update package lists
  Dir.chdir CREW_LIB_PATH do
513
    if @crew_testing == '1'
satmandu's avatar
satmandu committed
514
      puts "Updating crew from testing repository..."
515 516 517 518 519 520 521 522
      system "git remote add testing #{@crew_testing_repo} 2>/dev/null || \
        git remote set-url testing #{@crew_testing_repo}"
      system "git fetch testing #{@crew_testing_branch}"
      system "git reset --hard testing/#{@crew_testing_branch}"
    else
      system 'git fetch origin master'
      system 'git reset --hard origin/master'
    end
523
  end
Cassandra Watergate's avatar
Cassandra Watergate committed
524

525
  puts 'Package lists, crew, and library updated.'
526

Ed Reel's avatar
Ed Reel committed
527 528
  #update compatible packages
  generate_compatible
529
  #check for outdated installed packages
530
  puts 'Checking for package updates...'
531 532 533 534 535 536

  canBeUpdated = 0
  @device[:installed_packages].each do |package|
    search package[:name], true
    if package[:version] != @pkg.version
      canBeUpdated += 1
537
      puts @pkg.name + ' could be updated from ' + package[:version] + ' to ' + @pkg.version
538 539
    end
  end
540

541
  if canBeUpdated > 0
542
    puts
543
    puts "Run `crew upgrade` to update all packages or `crew upgrade <package1> [<package2> ...]` to update specific packages."
544
  else
Ed Reel's avatar
Ed Reel committed
545
    puts "Your software is up to date.".lightgreen
546 547 548 549 550 551 552 553 554 555 556
  end
end

def upgrade
  if @pkgName
    currentVersion = nil
    @device[:installed_packages].each do |package|
      if package[:name] == @pkg.name
        currentVersion = package[:version]
      end
    end
557

558 559
    if currentVersion != @pkg.version
      puts "Updating #{@pkg.name}..."
560
      @pkg.in_upgrade = true
561
      resolve_dependencies_and_install
562
      @pkg.in_upgrade = false
563
    else
Ed Reel's avatar
Ed Reel committed
564
      puts "#{@pkg.name} is already up to date.".lightgreen
565 566
    end
  else
567
    # Make an installed packages list belong to the dependency order
568
    dependencies = []
569
    @device[:installed_packages].each do |package|
570 571 572 573
      # skip package if it is dependent other packages previously checked
      next if dependencies.include? package[:name]
      # add package itself
      dependencies = [ package[:name] ].concat(dependencies)
574
      # expand dependencies and add it to the dependencies list
575
      search package[:name], true
576
      @dependencies = []
577 578 579 580 581 582 583 584 585 586
      exp_dep = expand_dependencies
      dependencies = exp_dep.concat(dependencies)
    end
    dependencies.uniq!

    # Check version number of installed package and make a target list
    toBeUpdated = []
    dependencies.each do |dep|
      package = @device[:installed_packages].find {|pkg| pkg[:name] == dep}
      next unless package
587 588 589 590 591
      search package[:name], true
      if package[:version] != @pkg.version
        toBeUpdated.push(package[:name])
      end
    end
592

satmandu's avatar
satmandu committed
593 594 595 596 597 598 599 600 601 602 603 604 605 606
    # Adjust package install ordering for upgrades.
    CREW_FIRST_PACKAGES.each do |pkg|
      # Add package to beginning.
      toBeUpdated.insert(0,toBeUpdated.delete(pkg)) if toBeUpdated.include? pkg
    end
    CREW_LAST_PACKAGES.each do |pkg|
      if toBeUpdated.include? pkg
        # Add package to beginning.
        toBeUpdated.insert(0,toBeUpdated.delete(pkg))
        # Now rotate first package to last package.
        toBeUpdated=toBeUpdated.rotate(1)
      end
    end

607
    unless toBeUpdated.empty?
608
      puts 'Updating packages...'
609 610
      toBeUpdated.each do |package|
        search package
611
        print_current_package
612
        puts "Updating " + @pkg.name + "..." if @opt_verbose
613
        @pkg.in_upgrade = true
614
        resolve_dependencies_and_install
615
        @pkg.in_upgrade = false
616
      end
Ed Reel's avatar
Ed Reel committed
617
      puts "Packages have been updated.".lightgreen
618
    else
Ed Reel's avatar
Ed Reel committed
619
      puts "Your software is already up to date.".lightgreen
620 621 622 623
    end
  end
end

624
def download
625
  url = @pkg.get_url(@device[:architecture])
626
  source = @pkg.is_source?(@device[:architecture])
627

628 629 630 631 632
  uri = URI.parse url
  filename = File.basename(uri.path)
  sha256sum = @pkg.get_sha256(@device[:architecture])
  @extract_dir = @pkg.get_extract_dir

633
  if !url
634
    abort "No precompiled binary or source is available for #{@device[:architecture]}.".lightred
635
  elsif !source
636
    puts "Precompiled binary available, downloading..."
637 638
  elsif @pkg.build_from_source
    puts "Downloading source..."
satmandu's avatar
satmandu committed
639 640
  elsif uri =~ /^SKIP$/i
    puts "Skipping source download..."
641
  else
642
    puts "No precompiled binary available for your platform, downloading source..."
643
  end
644

645
  Dir.chdir CREW_BREW_DIR do
646 647 648

    case File.basename(filename)
    # Sources that download with curl
649
    when /\.zip$/i, /\.(tar(\.(gz|bz2|xz|lz))?|tgz|tbz|tpxz|txz)$/i, /\.deb$/i, /\.AppImage$/i
satmandu's avatar
satmandu committed
650
      # Recall file from cache if requested
651
      if CREW_CACHE_ENABLED
652
        puts "Looking for #{@pkg.name} archive in cache".orange if @opt_verbose
653
        cachefile = CREW_CACHE_DIR + filename
satmandu's avatar
satmandu committed
654 655 656 657
        if ! File.exist?(cachefile)
          puts 'Cannot find cached archive. 😔 Will download.'.lightred
          cachefile = ''
        else
658 659
          puts "#{@pkg.name.capitalize} archive file exists in cache".lightgreen if @opt_verbose
          if Digest::SHA256.hexdigest( File.read(cachefile) ) == sha256sum || sha256sum =~ /^SKIP$/i
satmandu's avatar
satmandu committed
660 661
            begin
              # Hard link cached file if possible.
662
              FileUtils.ln cachefile, CREW_BREW_DIR, force: true, verbose: @fileutils_verbose unless File.identical?(cachefile,CREW_BREW_DIR + '/' + filename)
satmandu's avatar
satmandu committed
663 664 665
              puts "Archive hard linked from cache".green if @opt_verbose
            rescue
              # Copy cached file if hard link fails.
666
              FileUtils.cp cachefile, CREW_BREW_DIR, verbose: @fileutils_verbose unless File.identical?(cachefile,CREW_BREW_DIR + '/' + filename)
satmandu's avatar
satmandu committed
667 668
              puts "Archive copied from cache".green if @opt_verbose
            end
669 670
            puts "Archive found in cache".lightgreen
            return {source: source, filename: filename}
satmandu's avatar
satmandu committed
671
          else
672
            puts 'Cached archive checksum mismatch. 😔 Will download.'.lightred
satmandu's avatar
satmandu committed
673
            cachefile = ''
674
          end
satmandu's avatar
satmandu committed
675 676
        end
      end
satmandu's avatar
satmandu committed
677
      # Download file if not cached.
678
      system "#{CURL} --retry 3 -#{@verbose}#LC - --insecure \'#{url}\' --output #{filename}"
679
      abort 'Checksum mismatch. 😔 Try again.'.lightred unless
680 681
        Digest::SHA256.hexdigest( File.read(filename) ) == sha256sum || sha256sum =~ /^SKIP$/i
      puts "#{@pkg.name.capitalize} archive downloaded.".lightgreen
satmandu's avatar
satmandu committed
682 683 684 685 686 687 688 689 690 691 692 693
      # Stow file in cache if requested (and if file is not from cache).
      if CREW_CACHE_ENABLED and cachefile.to_s.empty?
        begin
          # Hard link to cache if possible.
          FileUtils.ln filename, CREW_CACHE_DIR, verbose: @fileutils_verbose
          puts "Archive hard linked to cache".green if @opt_verbose
        rescue
          # Copy to cache if hard link fails.
          FileUtils.cp filename, CREW_CACHE_DIR, verbose: @fileutils_verbose
          puts "Archive copied to cache".green if @opt_verbose
        end
        puts 'Archive copied to cache.'.lightgreen
694
      end
satmandu's avatar
satmandu committed
695
      return {source: source, filename: filename}
satmandu's avatar
satmandu committed
696

697 698 699 700
    # Sources that download with git
    when /\.git$/i
      # Recall repository from cache if requested
      if CREW_CACHE_ENABLED
701 702 703 704 705 706 707 708
        if @pkg.git_branch.nil? || @pkg.git_branch.empty?
          cachefile = CREW_CACHE_DIR + filename + @pkg.git_hashtag + '.tar.xz'
          puts "cachefile is #{cachefile}".orange if @opt_verbose
        else
          # Use to the day granularity for a branch timestamp.
          cachefile = CREW_CACHE_DIR + filename + @pkg.git_branch.gsub(/[^0-9A-Za-z.\-]/, '_') + Time.now.strftime("%m%d%Y") + '.tar.xz'
          puts "cachefile is #{cachefile}".orange if @opt_verbose
        end
709
        if File.file?(cachefile)
710
          if system "cd #{CREW_CACHE_DIR} && sha256sum -c #{cachefile}.sha256"
711 712 713 714
            FileUtils.mkdir @extract_dir
            system "tar x#{@verbose}f #{cachefile} -C #{@extract_dir}"
            return {source: source, filename: filename}
          else
715
            puts 'Cached git repository checksum mismatch. 😔 Will download.'.lightred
716 717
          end
        else
718
          puts 'Cannot find cached git repository. 😔 Will download.'.lightred
719 720 721 722 723 724 725 726 727
        end
      end
      # Download via git
      Dir.mkdir @extract_dir
      Dir.chdir @extract_dir do
        system 'git init'
        system 'git config advice.detachedHead false'
        system 'git config init.defaultBranch master'
        system "git remote add origin #{@pkg.source_url}", exception: true
satmandu's avatar
satmandu committed
728 729 730 731 732 733 734 735 736 737
        unless @pkg.git_branch.nil? || @pkg.git_branch.empty?
          # Leave a message because this step can be slow.
          puts "Downloading src from a git branch. This may take a while..."
          system "git remote set-branches origin #{@pkg.git_branch}", exception: true
          system "git fetch --progress origin #{@pkg.git_branch}", exception: true
          system "git checkout #{@pkg.git_hashtag}", exception: true
        else
          system "git fetch --depth 1 origin #{@pkg.git_hashtag}", exception: true
          system 'git checkout FETCH_HEAD'
        end
738
        system 'git submodule update --init --recursive'
739
        puts 'Repository downloaded.'.lightgreen
740 741 742
      end
      # Stow file in cache if requested
      if CREW_CACHE_ENABLED
743 744 745 746 747 748 749
        puts 'Caching downloaded git repo...'
        Dir.chdir "#{@extract_dir}" do
          system "tar c#{@verbose}Jf #{cachefile} \
            $(find -mindepth 1 -maxdepth 1 -printf '%P\n')"
        end
        system "sha256sum #{cachefile} > #{cachefile}.sha256"
        puts 'Git repo cached.'.lightgreen
750
      end
satmandu's avatar
satmandu committed
751 752
    when /^SKIP$/i
      Dir.mkdir @extract_dir
satmandu's avatar
satmandu committed
753
    end
754
  end
755 756 757
  return {source: source, filename: filename}
end

758
def unpack(meta)
759 760
  target_dir = nil
  Dir.chdir CREW_BREW_DIR do
761
    FileUtils.mkdir_p @extract_dir, verbose: @fileutils_verbose
762 763
    case File.basename meta[:filename]
    when /\.zip$/i
764
      puts "Unpacking archive using 'unzip', this may take a while..."
James Larrowe's avatar
James Larrowe committed
765
      _verbopt = @opt_verbose ? '-v' : '-qq'
766
      system 'unzip', _verbopt, '-d', @extract_dir, meta[:filename], exception: true
767
    when /\.(tar(\.(gz|bz2|xz|lz))?|tgz|tbz|txz)$/i
768
      puts "Unpacking archive using 'tar', this may take a while..."
769
      system "tar x#{@verbose}f #{meta[:filename]} -C #{@extract_dir}", exception: true
770
    when /\.deb$/i
771
      puts "Unpacking archive using 'ar', this may take a while..."
772 773 774 775 776 777 778 779 780 781 782 783
      
      suffix = `ar -t #{meta[:filename]}`.scan(/data.(.*)/)[0][0]
      case suffix
      when 'tar.xz'
        tar_opt = '--xz'
      when 'tgz', 'tar.gz'
        tar_opt = '--gzip'
      when 'bzip2'
        tar_opt = '--bzip2'
      end

      system "ar -p #{meta[:filename]} data.#{suffix} | tar x#{@verbose} #{tar_opt} -C #{@extract_dir}", exception: true
784 785
    when /\.AppImage$/i
      puts "Unpacking 'AppImage' archive, this may take a while..."
786
      FileUtils.chmod 0o755, meta[:filename], verbose: @fileutils_verbose
787 788 789
      Dir.chdir @extract_dir do
        system "../#{meta[:filename]} --appimage-extract", exception: true
      end
790 791 792 793 794 795
    when /\.tpxz$/i
      unless File.exist?("#{CREW_PREFIX}/bin/pixz")
        abort 'Pixz is needed for this install. Please install it with \'crew install pixz\''.lightred
      end
      puts "Unpacking 'tpxz' archive using 'tar', this may take a while..."
      system "tar -Ipixz -x#{@verbose}f #{meta[:filename]} -C #{@extract_dir}", exception: true
supechicken's avatar
supechicken committed
796
    end
797 798
    if meta[:source] == true
      # Check the number of directories in the archive
799 800
      entries = Dir["#{@extract_dir}/*"]
      entries = Dir[@extract_dir] if entries.empty?
801
      if entries.empty?
Ed Reel's avatar
Ed Reel committed
802
        abort "Empty archive: #{meta[:filename]}".lightred
803
      elsif entries.length == 1 && File.directory?(entries.first)
804 805 806 807
        # Use `extract_dir/dir_in_archive` if there is only one directory.
        target_dir = entries.first
      else
        # Use `extract_dir` otherwise
808
        target_dir = @extract_dir
809 810 811
      end
    else
      # Use `extract_dir` for binary distribution
812
      target_dir = @extract_dir
813 814 815 816 817
    end
  end
  return CREW_BREW_DIR + target_dir
end

818
def build_and_preconfigure(target_dir)
819
  Dir.chdir target_dir do
820
    puts 'Building from source, this may take a while...'
821

822
    # Rename *.la files temporarily to *.la_tmp to avoid
823 824 825
    # libtool: link: '*.la' is not a valid libtool archive.
    # See https://gnunet.org/faq-la-files and
    # https://stackoverflow.com/questions/42963653/libquadmath-la-is-not-a-valid-libtool-archive-when-configuring-openmpi-with-g
826
    puts 'Rename all *.la files to *.la_tmp'.lightblue
827

828
    system "find #{CREW_LIB_PREFIX} -type f -name *.la -print0 | xargs --null -I{} mv #{@short_verbose} {} {}_tmp"
829

830
    @pkg.in_build = true
831
    @pkg.patch
832
    @pkg.prebuild
833
    @pkg.build
834
    @pkg.in_build = false
835
    # wipe crew destdir
supechicken's avatar
supechicken committed
836
    FileUtils.rm_rf Dir.glob("#{CREW_DEST_DIR}/*"), verbose: @fileutils_verbose
837
    puts 'Preconfiguring package...'
838
    @pkg.install
839 840 841

    # Rename all *.la_tmp back to *.la to avoid
    # cannot access '*.la': No such file or directory
842
    puts 'Rename all *.la_tmp files back to *.la'.lightblue
843
    system "find #{CREW_LIB_PREFIX} -type f -name '*.la_tmp' -exec sh -c 'mv #{@short_verbose} \"$1\" \"${1%.la_tmp}.la\"' _  {} \\;"
844 845 846
  end
end

847 848 849
def pre_flight
  puts 'Performing pre-flight checks...'
  @pkg.preflight
850 851
end

852
def pre_install(dest_dir)
853
  Dir.chdir dest_dir do
854
    puts 'Performing pre-install...'
855 856 857 858
    @pkg.preinstall
  end
end

859 860 861 862 863 864
def post_install
  Dir.mktmpdir do |post_install_tempdir|
    Dir.chdir post_install_tempdir do
      puts "Performing post-install for #{@pkg.name}...".lightblue
      @pkg.postinstall
    end
865 866 867
  end
end

868
def compress_doc(dir)
869
  # check whether crew should compress
870
  return if CREW_NOT_COMPRESS || ENV['CREW_NOT_COMPRESS'] || !File.exist?("#{CREW_PREFIX}/bin/compressdoc")
871 872 873

  if Dir.exist? dir
    system "find #{dir} -type f ! -perm -200 | xargs -r chmod u+w"
874
    system "compressdoc --gzip -9 #{@short_verbose} #{dir}"
875 876 877
  end
end

878
def prepare_package(destdir)
879
  Dir.chdir destdir do
880
    # Avoid /usr/local/share/info/dir{.gz} file conflict:
Cassandra Watergate's avatar
Cassandra Watergate committed
881 882 883
    # The install-info program maintains a directory of installed
    # info documents in /usr/share/info/dir for the use of info
    # readers. This file must not be included in packages other
884 885 886
    # than install-info.
    # https://www.debian.org/doc/debian-policy/ch-docs.html#info-documents
    FileUtils.rm "#{CREW_DEST_PREFIX}/share/info/dir" if File.exist?("#{CREW_DEST_PREFIX}/share/info/dir")
887 888
    
    # Remove all perl module files which will conflict
889
    if @pkg.name =~ /^perl_/
890 891 892
      puts "Removing .packlist and perllocal.pod files to avoid conflicts with other perl packages.".orange
      system "find #{CREW_DEST_DIR} -type f \\( -name '.packlist' -o -name perllocal.pod \\) -delete" 
    end
893

894
    # compress manual files
895 896 897 898
    compress_doc "#{CREW_DEST_PREFIX}/man"
    compress_doc "#{CREW_DEST_PREFIX}/info"
    compress_doc "#{CREW_DEST_PREFIX}/share/man"
    compress_doc "#{CREW_DEST_PREFIX}/share/info"
899

Ed Reel's avatar
Ed Reel committed
900
    # create file list
901 902 903
    system 'find . -type f > ../filelist'
    system 'find . -type l >> ../filelist'
    system 'cut -c2- ../filelist > filelist'
904

905
    # check for conflicts with other installed files
906
    puts "Checking for conflicts with files from installed packages..."
907
    conflicts = []
satmandu's avatar
satmandu committed
908 909 910 911 912
    @conflictscmd = "grep --exclude #{CREW_META_PATH}#{@pkg.name}.filelist \
      -Fxf filelist #{CREW_META_PATH}*.filelist | tr ':' ' ' | \
      sed 's,.filelist,,g' | sed 's,#{CREW_META_PATH},,g'"
    conflicts << %x[#{@conflictscmd}].chomp.split(" ")
    conflicts.reject!(&:empty?)
913 914 915 916 917 918 919 920
    unless conflicts.empty?
      puts "Unable to complete this build since there is a conflict with the same file in another package.".lightred
      pp = ''
      conflicts.each do |p, f|
        puts "\n#{p} package conflicts below:".lightred if p != pp
        puts f.lightred
        pp = p
      end
921
      abort unless ENV['CREW_CONFLICTS_ONLY_ADVISORY'] == '1'
922 923
    end

Ed Reel's avatar
Ed Reel committed
924
    # create directory list
925 926 927
    system 'find . -type d > ../dlist'
    system 'cut -c2- ../dlist > dlistcut'
    system 'tail -n +2 dlistcut > dlist'
928 929

    # remove temporary files
supechicken's avatar
supechicken committed
930
    FileUtils.rm_rf ['dlistcut', '../dlist', '../filelist'], verbose: @fileutils_verbose
931 932 933

    strip_dir destdir 

934 935 936
    # make hard linked files symlinks and use upx on executables
    shrink_dir destdir

937 938 939
  end
end

940
def strip_find_files(find_cmd, strip_option = "")
941
  # check whether crew should strip
942
  return if CREW_NOT_STRIP || ENV['CREW_NOT_STRIP'] || !File.exist?("#{CREW_PREFIX}/bin/llvm-strip")
943 944 945

  # run find_cmd and strip only ar or ELF files
  system "#{find_cmd} | xargs -r chmod u+w"
946
  system "#{find_cmd} | xargs -r sh -c 'for i in \"$0\" \"$@\"; do case \"$(head -c 4 $i)\" in ?ELF|\!?ar) echo \"$i\";; esac ; done' | xargs -r llvm-strip #{strip_option}"
947 948
end

949
def strip_dir(dir)
950 951 952 953 954 955 956 957
  unless CREW_NOT_STRIP || ENV['CREW_NOT_STRIP']
    Dir.chdir dir do
      # Strip libraries with -S
      puts "Stripping libraries..."
      strip_find_files "find . -type f  \\( -name 'lib*.a' -o -name 'lib*.so*' \\) -print", "-S"

      # Strip binaries but not compressed archives
      puts "Stripping binaries..."
958
      extensions = [ 'bz2', 'gz', 'lha', 'lz', 'lzh', 'rar', 'tar', 'tbz', 'tgz', 'tpxz', 'txz', 'xz', 'Z', 'zip' ]
959 960 961
      inames = extensions.join(' ! -iname *\.')
      strip_find_files "find . -type f ! -iname *\.#{inames} -perm /111 -print | sed -e '/lib.*\.a$/d' -e '/lib.*\.so/d'"
    end
962 963 964
  end
end

965 966 967 968 969 970 971 972 973 974 975 976 977
def shrink_dir(dir)
  # We might also want a package option to avoid using these tools
  # on specific packages such as sommelier & xwayland.
  if ENV['CREW_SHRINK_ARCHIVE'] == '1'
    Dir.chdir dir do
      if File.exist?("#{CREW_PREFIX}/bin/rdfind")
        puts "Using rdfind to find duplicate or hard linked files."
        system "#{CREW_PREFIX}/bin/rdfind -removeidentinode true -makesymlinks true -makeresultsfile false ."
      end
      if File.exist?("#{CREW_PREFIX}/bin/symlinks")
        puts "Using symlinks tool to make absolute symlinks relative"
        system 'symlinks -cr .' if File.exist?("#{CREW_PREFIX}/bin/symlinks")
      end
978 979 980 981 982
      # Issues with non-x86_64 in compressing libraries, so just compress
      # non-libraries. Also note that one needs to use "upx -d" on a
      # compressed file to use ldd.
      # sommelier also isn't happy when sommelier and xwayland are compressed
      # so don't compress those packages.
983
      if File.exist?("#{CREW_PREFIX}/bin/upx")
984 985 986 987 988 989 990 991 992 993 994
        # Logic here is to find executable binaries, compress after making
        # a backup, then expand the compressed file with upx. If the
        # expansion doesn't error out then it is ok to delete the backup.
        @execfiles = %x[find . -executable -type f ! \\( -name \"*.so*\" -o -name \"*.a\" \\) -exec sh -c \"file -i \'{}\' | grep -q \'executable; charset=binary\'\" \\; -print].chomp
        unless @execfiles.empty? or @execfiles.nil?
          puts "Using upx to shrink binaries."
          @execfiles.each_line do |execfile|
            system "upx --best -k --overlay=skip #{execfile} && \
            \( upx -t #{execfile} && rm #{execfile}.~ || mv #{execfile}.~ #{execfile}\)"
          end
        end
995 996 997 998 999
      end
    end
  end
end

1000
def install_package(pkgdir)
1001
  Dir.chdir pkgdir do
1002
    # install filelist, dlist and binary files
1003
    puts 'Performing install...'
1004

1005 1006
    FileUtils.mv 'dlist', CREW_META_PATH + @pkg.name + '.directorylist', verbose: @fileutils_verbose
    FileUtils.mv 'filelist', CREW_META_PATH + @pkg.name + '.filelist', verbose: @fileutils_verbose
1007

1008
    if Dir.exists? "#{pkgdir}/home" then
supechicken's avatar
supechicken committed
1009
      system "tar -c#{@verbose}f - ./usr/* ./home/* | (cd /; tar xp --keep-directory-symlink -f -)"
satmandu's avatar
satmandu committed
1010
    elsif Dir.exists? "#{pkgdir}/usr" then
supechicken's avatar
supechicken committed
1011
      system "tar -c#{@verbose}f - ./usr/* | (cd /; tar xp --keep-directory-symlink -f -)"
satmandu's avatar
satmandu committed
1012
    end
1013 1014 1015
  end
end

1016
def resolve_dependencies_and_install
1017
  @resolve_dependencies_and_install = 1
1018 1019 1020
  unless @pkg.is_fake?
    # Process preflight block to see if package should even
    # be downloaded or installed.
1021
    pre_flight
1022
  end
1023 1024
  begin
    origin = @pkg.name
1025

1026
    @to_postinstall = []
1027
    resolve_dependencies
1028

1029
    search origin, true
1030
    install
1031 1032 1033 1034 1035
    @to_postinstall.append(@pkg.name)
    @to_postinstall.each do |dep|
      search dep
      post_install
    end
1036
  rescue InstallError => e
Ed Reel's avatar
Ed Reel committed
1037
    abort "#{@pkg.name} failed to install: #{e.to_s}".lightred
1038
  ensure
1039
    # cleanup
1040
    unless @opt_keep
1041 1042
      FileUtils.rm_rf Dir.glob("#{CREW_BREW_DIR}/*")
      FileUtils.mkdir_p "#{CREW_BREW_DIR}/dest" # this is a little ugly, feel free to find a better way
Michał Siwek's avatar
Michał Siwek committed
1043
    end
1044
  end
1045
  puts "#{@pkg.name.capitalize} installed!".lightgreen
1046
  @resolve_dependencies_and_install = 0
1047 1048
end

1049
def expand_dependencies
1050
  def push_dependencies
1051
    if @pkg.is_binary?(@device[:architecture]) ||
1052
      (!@pkg.in_upgrade && !@pkg.build_from_source && @device[:installed_packages].any? { |pkg| pkg[:name] == @pkg.name })
1053
      # retrieve name of dependencies that doesn't contain :build tag
1054
      check_deps = @pkg.dependencies.select {|k, v| !v.include?(:build)}.map {|k, v| k}
1055
    else
1056
      # retrieve name of all dependencies
1057
      check_deps = @pkg.dependencies.map {|k, v| k}
1058
    end
1059
    # check all dependencies recursively
1060
    check_deps.each do |dep|
1061
      # build unique dependencies list
satmandu's avatar
satmandu committed
1062
      unless @dependencies&.include?(dep) || dep == @pkgName
1063 1064 1065 1066
        @dependencies << dep
        search dep, true
        push_dependencies
      end
1067 1068
    end
  end
1069
  push_dependencies
1070 1071 1072
end

def resolve_dependencies
1073
  abort "Package #{@pkg.name} is not compatible with your device architecture (#{ARCH}) :/".lightred unless @device[:compatible_packages].any? do |elem| elem[:name] == @pkg.name end
1074 1075 1076 1077 1078 1079 1080 1081 1082

  @dependencies = []
  if @pkg.build_from_source
    # make sure all buildessential packages are installed
    pkgname = @pkg.name
    search 'buildessential', true
    expand_dependencies
    search pkgname, true
  end
1083
  expand_dependencies
1084 1085

  # leave only not installed packages in dependencies
1086
  @dependencies.select! {|name| @device[:installed_packages].none? {|pkg| pkg[:name] == name}}
1087

1088
  return if @dependencies.empty?
1089

1090
  puts 'The following packages also need to be installed: '
1091

1092
  deps = @dependencies
satmandu's avatar
satmandu committed
1093 1094 1095 1096
  # populate arrays with common elements
  begin_packages = deps & CREW_FIRST_PACKAGES
  end_packages = deps & CREW_LAST_PACKAGES

1097
  @dependencies.each do |dep|
1098 1099 1100
    depends = nil
    File.open("#{CREW_PACKAGES_PATH}#{dep}.rb") do |f|
      f.each_line do |line|
1101 1102
        found = line[/depends_on/] if line.ascii_only?
        if found
1103 1104 1105 1106 1107 1108 1109
          depends = true
          break
        end
      end
    end
    # if a dependency package has no other dependencies, push to the front
    begin_packages.push dep unless depends
1110
  end
satmandu's avatar
satmandu committed
1111 1112 1113 1114
  # Remove elements in another array
  deps -= begin_packages
  deps -= end_packages

1115
  @dependencies = (begin_packages + deps + end_packages).uniq
1116

1117
  @dependencies.each do |dep|
1118
    print dep + ' '
1119
  end
1120

1121 1122
  puts
  print 'Do you agree? [Y/n] '
1123 1124
  response = STDIN.getc
  case response
1125 1126
  when 'n'
    abort 'No changes made.'
supechicken's avatar
supechicken committed
1127
  when "\n", "y", "Y"
1128
    puts 'Proceeding...'
1129
    proceed = true
1130
  else
1131
    puts "I don't understand `#{response}`. :(".lightred
1132
    abort 'No changes made.'
1133 1134 1135
  end

  if proceed
1136
    @dependencies.each do |dep|
1137
      search dep
1138
      print_current_package
1139 1140
      install
    end
1141 1142 1143 1144 1145 1146 1147 1148
    if @resolve_dependencies_and_install == 1 or @resolve_dependencies_and_build == 1
      @to_postinstall = @dependencies
    else
      @dependencies.each do |dep|
        search dep
        post_install
      end
    end
1149
  end
1150 1151 1152
end

def install
1153
  if !@pkg.in_upgrade && @device[:installed_packages].any? { |pkg| pkg[:name] == @pkg.name }
Ed Reel's avatar
Ed Reel committed
1154
    puts "Package #{@pkg.name} already installed, skipping...".lightgreen
1155 1156 1157 1158
    return
  end

  unless @pkg.is_fake?
1159
    meta = download
1160 1161
    target_dir = unpack meta
    if meta[:source] == true
1162

1163 1164 1165
      # build from source and place binaries at CREW_DEST_DIR
      # CREW_DEST_DIR contains usr/local/... hierarchy
      build_and_preconfigure target_dir
1166

1167
      # prepare filelist and dlist at CREW_DEST_DIR
1168
      prepare_package CREW_DEST_DIR
1169

1170 1171 1172 1173 1174
      # use CREW_DEST_DIR
      dest_dir = CREW_DEST_DIR
    else
      # use extracted binary directory
      dest_dir = target_dir
1175
    end
1176
  end
1177

1178 1179
  # remove it just before the file copy
  if @pkg.in_upgrade
1180
    puts 'Removing since upgrade or reinstall...'
1181 1182
    remove @pkg.name
  end
1183

1184
  unless @pkg.is_fake?
1185 1186 1187 1188
    # perform pre-install process
    pre_install dest_dir

    # perform install process
1189
    install_package dest_dir
1190

1191 1192 1193 1194
    unless @resolve_dependencies_and_install == 1 or @resolve_dependencies_and_build == 1
      # perform post-install process
      post_install
    end
1195
  end
Yan Couto's avatar
Yan Couto committed
1196

1197
  #add to installed packages
1198
  @device[:installed_packages].push(name: @pkg.name, version: @pkg.version)
1199
  File.open(CREW_CONFIG_PATH + 'device.json', 'w') do |file|
1200 1201 1202
    output = JSON.parse @device.to_json
    file.write JSON.pretty_generate(output)
  end
1203
  # Update shared library cache after install is complete.
1204 1205
  system "echo #{CREW_LIB_PREFIX} > #{CREW_PREFIX}/etc/ld.so.conf"
  system "#{CREW_PREFIX}/sbin/ldconfig -f #{CREW_PREFIX}/etc/ld.so.conf -C #{CREW_PREFIX}/etc/ld.so.cache"
1206 1207
end

1208
def resolve_dependencies_and_build
1209 1210
  @resolve_dependencies_and_build = 1
  @to_postinstall = []
1211 1212 1213 1214 1215
  begin
    origin = @pkg.name

    # mark current package as which is required to compile from source
    @pkg.build_from_source = true
1216
    resolve_dependencies
1217 1218 1219 1220
    @to_postinstall.each do |dep|
      search dep
      post_install
    end
1221 1222 1223
    search origin, true
    build_package Dir.pwd
  rescue InstallError => e
Ed Reel's avatar
Ed Reel committed
1224
    abort "#{@pkg.name} failed to build: #{e.to_s}".lightred
1225 1226
  ensure
    #cleanup
1227
    unless @opt_keep
supechicken's avatar
supechicken committed
1228 1229
      FileUtils.rm_rf Dir.glob("#{CREW_BREW_DIR}/*"), verbose: @fileutils_verbose
      FileUtils.mkdir_p CREW_BREW_DIR + '/dest', verbose: @fileutils_verbose #this is a little ugly, feel free to find a better way
1230 1231
    end
  end
1232
  puts "#{@pkg.name} is built!".lightgreen
1233
  @resolve_dependencies_and_build = 0
1234 1235
end

1236
def build_package(pwd)
1237 1238
  abort 'It is not possible to build a fake package'.lightred if @pkg.is_fake?
  abort 'It is not possible to build without source'.lightred if !@pkg.is_source?(@device[:architecture])
1239 1240

  # download source codes and unpack it
1241
  meta = download
1242 1243 1244 1245 1246 1247 1248 1249 1250
  target_dir = unpack meta

  # build from source and place binaries at CREW_DEST_DIR
  build_and_preconfigure target_dir

  # call check method here.  this check method is called by this function only,
  # therefore it is possible place time consuming tests in the check method.
  if Dir.exist? target_dir
    Dir.chdir target_dir do
1251
      puts 'Checking...'
1252 1253 1254 1255 1256 1257 1258 1259
      @pkg.check
    end
  end

  # prepare filelist and dlist at CREW_DEST_DIR
  prepare_package CREW_DEST_DIR

  # build package from filelist, dlist and binary files in CREW_DEST_DIR
1260
  puts 'Archiving...'
1261 1262 1263
  archive_package pwd
end

1264
def archive_package(pwd)
1265
  unless ENV['CREW_USE_PIXZ'] == '0' || !File.exist?("#{CREW_PREFIX}/bin/pixz") 
1266 1267 1268 1269 1270 1271
    puts "Using pixz to compress archive."
    pkg_name = "#{@pkg.name}-#{@pkg.version}-chromeos-#{@device[:architecture]}.tpxz"
    Dir.chdir CREW_DEST_DIR do
      # Use smaller blocks with "-f0.25" to make random access faster.
      system "tar c#{@verbose} * | pixz -f0.25 -9 > #{pwd}/#{pkg_name}"
    end
1272 1273 1274 1275 1276
  else
    pkg_name = "#{@pkg.name}-#{@pkg.version}-chromeos-#{@device[:architecture]}.tar.xz"
    Dir.chdir CREW_DEST_DIR do
      system "tar c#{@verbose}Jf #{pwd}/#{pkg_name} *"
    end
1277
  end
supechicken's avatar
supechicken committed
1278
  system "sha256sum #{pwd}/#{pkg_name} > #{pwd}/#{pkg_name}.sha256"
1279 1280
end

1281
def remove(pkgName)
1282

lyxell's avatar
lyxell committed
1283
  #make sure the package is actually installed
1284
  unless @device[:installed_packages].any? { |pkg| pkg[:name] == pkgName } || File.exist?("#{CREW_META_PATH}#{pkgName}.filelist")
Ed Reel's avatar
Ed Reel committed
1285
    puts "Package #{pkgName} isn't installed.".lightred
1286
    return
1287
  end
1288

1289
  #if the filelist exists, remove the files and directories installed by the package
1290
  if File.file?("#{CREW_META_PATH}#{pkgName}.filelist")
1291
    Dir.chdir CREW_CONFIG_PATH do
1292 1293 1294

      #remove all files installed by the package
      File.open("meta/#{pkgName}.filelist").each_line do |line|
1295
        begin
1296
          puts 'Removing file ' + line.chomp + ''.lightred if @opt_verbose
1297
          File.unlink line.chomp
1298 1299
        rescue => exception #swallow exception
        end
1300 1301
      end

1302 1303
      #remove all directories installed by the package
      File.readlines("meta/#{pkgName}.directorylist").reverse.each do |line|
1304
        begin
1305
          puts 'Removing directory ' + line.chomp + ''.lightred if @opt_verbose
1306
          Dir.rmdir line.chomp
1307 1308
        rescue => exception #swallow exception
        end
1309 1310
      end

1311 1312 1313
      #remove the file and directory list
      File.unlink "meta/#{pkgName}.filelist"
      File.unlink "meta/#{pkgName}.directorylist"
lyxell's avatar
lyxell committed
1314

1315
    end
1316 1317 1318
  end

  #remove from installed packages
1319
  puts 'Removing package ' + pkgName + "".lightred if @opt_verbose
1320
  @device[:installed_packages].each do |elem|
1321
    @device[:installed_packages].delete elem if elem[:name] == pkgName
1322
  end
lyxell's avatar
lyxell committed
1323 1324

  #update the device manifest
1325
  File.open(CREW_CONFIG_PATH + 'device.json', 'w') do |file|
1326 1327 1328
    out = JSON.parse @device.to_json
    file.write JSON.pretty_generate(out)
  end
lyxell's avatar
lyxell committed
1329

1330
  search pkgName, true
Ed Reel's avatar
Ed Reel committed
1331
  @pkg.remove
lyxell's avatar
lyxell committed
1332

Ed Reel's avatar
Ed Reel committed
1333
  puts "#{pkgName.capitalize} removed!".lightgreen
1334 1335
end

1336
def build_command(args)
1337
  args["<name>"].each do |name|
1338
    @pkgName = name
1339
    search @pkgName
1340
    print_current_package @opt_verbose
1341
    resolve_dependencies_and_build
Ed Reel's avatar
Ed Reel committed
1342
  end
1343 1344
end

1345
def const_command(args)
Ed Reel's avatar
Ed Reel committed
1346 1347 1348 1349 1350 1351 1352 1353 1354
  unless args["<name>"].empty?
    args["<name>"].each do |name|
      const name
    end
  else
    const nil
  end
end

satmandu's avatar
satmandu committed
1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374
def deps_command(args)
  args["<name>"].each do |name|
    @dependencies = []
    @pkgName = name
    search @pkgName
    print_current_package
    expand_dependencies
    puts @dependencies
  end
end

def download_command(args)
  args["<name>"].each do |name|
    @pkgName = name
    search @pkgName
    print_current_package @opt_verbose
    download
  end
end

1375
def files_command(args)
Ed Reel's avatar
Ed Reel committed
1376
  args["<name>"].each do |name|
1377
    @pkgName = name
Ed Reel's avatar
Ed Reel committed
1378
    search @pkgName
1379
    print_current_package
Ed Reel's avatar
Ed Reel committed
1380 1381 1382 1383
    files name
  end
end

1384
def help_command(args)
1385 1386
  if args["<command>"]
    help args["<command>"]
1387
  else
1388
    puts "Usage: crew help <command>"
1389
    help nil
1390
  end
1391 1392
end

1393
def install_command(args)
1394
  args["<name>"].each do |name|
1395
    @pkgName = name
1396
    search @pkgName
1397
    print_current_package true
1398
    @pkg.build_from_source = true if @opt_src or @opt_recursive
1399 1400
    resolve_dependencies_and_install
  end
1401 1402
end

1403
def list_command(args)
1404 1405 1406
  if args['available']
    list_available
  elsif args['installed']
1407
    puts list_installed
Ed Reel's avatar
Ed Reel committed
1408 1409 1410 1411
  elsif args['compatible']
    list_compatible true
  elsif args['incompatible']
    list_compatible false
1412 1413 1414
  end
end

1415
def postinstall_command(args)
1416
  args["<name>"].each do |name|
1417
    @pkgName = name
1418 1419
    search @pkgName, true
    if @device[:installed_packages].any? do |elem| elem[:name] == @pkgName end
1420 1421
      @pkg.postinstall
    else
1422
      puts "Package #{@pkgName} is not installed. :(".lightred
1423 1424 1425 1426
    end
  end
end

1427
def reinstall_command(args)
Ed Reel's avatar
Ed Reel committed
1428
  args["<name>"].each do |name|
1429
    @pkgName = name
Ed Reel's avatar
Ed Reel committed
1430
    search @pkgName
1431
    print_current_package
1432
    @pkg.build_from_source = true if @opt_src or @opt_recursive
1433 1434 1435 1436 1437
    if @pkgName
      @pkg.in_upgrade = true
      resolve_dependencies_and_install
      @pkg.in_upgrade = false
    end
Ed Reel's avatar
Ed Reel committed
1438 1439 1440
  end
end

1441
def remove_command(args)
1442
  args["<name>"].each do |name|
1443
    remove name
1444
  end
1445 1446
end

1447
def search_command(args)
1448
  args["<name>"].each do |name|
1449
    regexp_search name
1450 1451
  end.empty? and begin
    list_packages
1452
  end
1453
end
1454

1455 1456 1457 1458 1459 1460
def update_command(args)
  if args['<compatible>']
    generate_compatible
  else
    update
  end
1461 1462
end

1463
def upgrade_command(args)
1464
  args["<name>"].each do |name|
1465
    @pkgName = name
1466
    search @pkgName
1467
    print_current_package
1468
    @pkg.build_from_source = true if @opt_src
1469 1470 1471
    upgrade
  end.empty? and begin
    upgrade
1472
  end
1473 1474
end

1475
def whatprovides_command(args)
1476
  args["<pattern>"].each do |name|
1477
    whatprovides name
1478
  end
1479
end
1480

1481
def is_command(name)
1482 1483 1484 1485 1486
  return false if name =~ /^[-<]/
  return true
end

command_name = args.find { |k, v| v && is_command(k) } [0]
1487
function = command_name + '_command'
1488
send(function, args)