tpkg 2.2.4 → 2.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,12 +1,14 @@
1
1
  #!/usr/bin/ruby -w
2
2
  ##############################################################################
3
3
  # tpkg package management system
4
- # Copyright 2009, 2010, 2011 AT&T Interactive
5
4
  # License: MIT (http://www.opensource.org/licenses/mit-license.php)
6
5
  ##############################################################################
7
6
 
8
- # Ensure we can find tpkg.rb
9
- $:.unshift File.join(File.dirname(__FILE__), "..", "lib")
7
+ # When run from the source repository or from an unpacked copy of the
8
+ # distribution we want to find the local library, even if there's a copy
9
+ # installed on the system. The installed copy is likely older, and the API
10
+ # may be out of sync with this executable.
11
+ $:.unshift(File.expand_path('../lib', File.dirname(__FILE__)))
10
12
 
11
13
  # This script expects one argument, which is the tpkg.xml file
12
14
  # that you want to convert to yml format.
@@ -1,6 +1,5 @@
1
1
  ##############################################################################
2
2
  # tpkg package management system
3
- # Copyright 2009, 2010, 2011 AT&T Interactive
4
3
  # License: MIT (http://www.opensource.org/licenses/mit-license.php)
5
4
  ##############################################################################
6
5
 
@@ -20,27 +19,30 @@ Silently.silently do
20
19
  require 'facter'
21
20
  end
22
21
  require 'digest/sha2' # Digest::SHA256#hexdigest, etc.
23
- require 'uri' # URI
24
- require 'net/http' # Net::HTTP
25
- require 'net/https' # Net::HTTP#use_ssl, etc.
26
- require 'time' # Time#httpdate
27
- require 'rexml/document' # REXML::Document
22
+ require 'etc' # Etc.getpwnam, getgrnam
28
23
  require 'fileutils' # FileUtils.cp, rm, etc.
29
- require 'tempfile' # Tempfile
30
24
  require 'find' # Find
31
- require 'etc' # Etc.getpwnam, getgrnam
25
+ require 'net/http' # Net::HTTP
26
+ require 'net/https' # Net::HTTP#use_ssl, etc.
32
27
  require 'openssl' # OpenSSL
33
28
  require 'open3' # Open3
34
29
  require 'set' # Enumerable#to_set
30
+ require 'rexml/document' # REXML::Document
31
+ require 'stringio' # StringIO
32
+ require 'tempfile' # Tempfile
33
+ require 'time' # Time#httpdate
34
+ require 'uri' # URI
35
35
  require 'yaml' # YAML
36
36
  end
37
- require 'tpkg/versiontype' # Version
38
37
  require 'tpkg/deployer'
39
38
  require 'tpkg/metadata'
39
+ require 'tpkg/versiontype' # Version
40
+
41
+ OpenSSLCipherError = OpenSSL::Cipher.const_defined?(:CipherError) ? OpenSSL::Cipher::CipherError : OpenSSL::CipherError
40
42
 
41
43
  class Tpkg
42
44
 
43
- VERSION = '2.2.4'
45
+ VERSION = '2.3.0'
44
46
 
45
47
  GENERIC_ERR = 1
46
48
  POSTINSTALL_ERR = 2
@@ -82,17 +84,40 @@ class Tpkg
82
84
  if !ENV['PATH']
83
85
  raise "tpkg cannot run because the PATH env variable is not set."
84
86
  end
85
- ENV['PATH'].split(':').each do |path|
87
+ ENV['PATH'].split(File::PATH_SEPARATOR).each do |path|
86
88
  TARNAMES.each do |tarname|
87
- if File.executable?(File.join(path, tarname))
88
- IO.popen("#{File.join(path, tarname)} --version 2>/dev/null") do |pipe|
89
- pipe.each_line do |line|
89
+ tarpath = nil
90
+ if RUBY_PLATFORM == 'i386-mingw32'
91
+ # Turns out that File.join is fairly pointless, at least as far
92
+ # as Windows compatibility. Ruby always uses '/' as the path
93
+ # separator, even on Windows. I.e. File::SEPARATOR is '/' on
94
+ # Windows (yes, really!). File::ALT_SEPARATOR is '\' but
95
+ # File.join and its ilk will never use it. The forward slash
96
+ # works fine for API-level file operations, Windows will accept
97
+ # either forward slashes or backslashes at the API level. But
98
+ # you can't execute a path with forward slashes, apparently due
99
+ # to some backwards-compatibility thing with cmd.exe. Sigh.
100
+ tarpath = path.gsub('/', '\\') + '\\' + tarname + '.exe'
101
+ else
102
+ tarpath = File.join(path, tarname)
103
+ end
104
+ if File.executable?(tarpath)
105
+ # Particularly on Windows it is possible that the path contains
106
+ # spaces. I.e. C:\Program Files (x86)\GnuWin32\bin\bsdtar.exe
107
+ # It looks like that needs to be wrapped in quotes to execute
108
+ # properly.
109
+ if tarpath.include?(' ')
110
+ tarpath = '"' + tarpath + '"'
111
+ end
112
+ Open3.popen3("#{tarpath} --version") do |stdin, stdout, stderr|
113
+ stdin.close
114
+ stdout.each_line do |line|
90
115
  if line.include?('GNU tar')
91
116
  @@tarinfo[:type] = 'gnu'
92
- @@tar = File.join(path, tarname)
117
+ @@tar = tarpath
93
118
  elsif line.include?('bsdtar')
94
119
  @@tarinfo[:type] = 'bsd'
95
- @@tar = File.join(path, tarname)
120
+ @@tar = tarpath
96
121
  end
97
122
  if line =~ /(?:(\d+)\.)?(?:(\d+)\.)?(\*|\d+)/
98
123
  @@tarinfo[:version] = [$1, $2, $3].compact.join(".")
@@ -103,7 +128,6 @@ class Tpkg
103
128
  end
104
129
  end
105
130
  end
106
- # Raise an exception if we didn't find a suitable tar
107
131
  raise "Unable to find GNU tar or bsdtar in PATH"
108
132
  end
109
133
  end
@@ -163,10 +187,13 @@ class Tpkg
163
187
  File.chown(st.uid, st.gid, tmpfile.path)
164
188
  rescue Errno::EPERM
165
189
  raise if Process.euid == 0
190
+ rescue Errno::EINVAL
191
+ raise if RUBY_PLATFORM != 'i386-cygwin'
166
192
  end
167
193
  tmpfile.write(MAGIC)
168
194
  tmpfile.write(salt)
169
- tmpfile.write(c.update(IO.read(filename)) + c.final)
195
+ content = IO.read(filename)
196
+ tmpfile.write(c.update(content) + c.final) unless content.empty?
170
197
  tmpfile.close
171
198
  File.rename(tmpfile.path, filename)
172
199
  end
@@ -207,8 +234,11 @@ class Tpkg
207
234
  File.chown(st.uid, st.gid, tmpfile.path)
208
235
  rescue Errno::EPERM
209
236
  raise if Process.euid == 0
237
+ rescue Errno::EINVAL
238
+ raise if RUBY_PLATFORM != 'i386-cygwin'
210
239
  end
211
- tmpfile.write(c.update(file.read) + c.final)
240
+ content = file.read
241
+ tmpfile.write(c.update(content) + c.final) unless content.empty?
212
242
  tmpfile.close
213
243
  File.rename(tmpfile.path, filename)
214
244
  end
@@ -433,22 +463,12 @@ class Tpkg
433
463
  # blocks) and see if tar succeeds in listing a file.
434
464
  1.upto(10) do |numblocks|
435
465
  tarblocks = File.read(package_file, 512*numblocks)
436
- # Open3.popen3("#{find_tar} -tf - #{@@taroptions}") do |stdin, stdout, stderr|
437
- # stdin.write(tarblocks)
438
- # stdin.close
439
- # toplevel = stdout.read
440
- # end
441
- # Unfortunately popen3 doesn't provide a mechanism for determining the
442
- # success or failure of the command until ruby 1.9. ($? is never
443
- # accurately set for popen3, the mechanism in ruby 1.9 for getting the
444
- # exit status for popen3 is unique to popen3.) So we're left with this,
445
- # which it rather Unix-specific.
446
- IO.popen("#{find_tar} -tf - #{@@taroptions} 2> /dev/null", 'r+') do |pipe|
447
- pipe.write(tarblocks)
448
- pipe.close_write
449
- toplevel = pipe.read
450
- end
451
- if $?.success?
466
+ Open3.popen3("#{find_tar} -tf - #{@@taroptions}") do |stdin, stdout, stderr|
467
+ stdin.write(tarblocks)
468
+ stdin.close
469
+ toplevel = stdout.read
470
+ end
471
+ if !toplevel.empty?
452
472
  break
453
473
  else
454
474
  toplevel = nil
@@ -711,6 +731,13 @@ class Tpkg
711
731
  # Extract 7 from 7.1-RELEASE, for example
712
732
  fbver = Facter['operatingsystemrelease'].value
713
733
  osver = fbver.split('.').first
734
+ elsif Facter['operatingsystem'] &&
735
+ Facter['operatingsystem'].value == 'windows'
736
+ # Extract 6.1 from 6.1.7601, for example
737
+ # That seems like the right level to split at
738
+ # based on http://en.wikipedia.org/wiki/Ver_(command)
739
+ winver = Facter['operatingsystemrelease'].value
740
+ osver = winver.split('.')[0,2].join('.')
714
741
  elsif Facter['operatingsystemrelease'] &&
715
742
  Facter['operatingsystemrelease'].value &&
716
743
  !Facter['operatingsystemrelease'].value.empty?
@@ -772,6 +799,14 @@ class Tpkg
772
799
  same_min_ver_req = true
773
800
  end
774
801
  end
802
+ if req[:version_greater_than]
803
+ pkgver = Version.new(metadata[:version])
804
+ reqver = Version.new(req[:version_greater_than])
805
+ if pkgver <= reqver
806
+ puts "Package fails version_greater_than (#{pkgver} <= #{reqver})" if @@debug
807
+ result = false
808
+ end
809
+ end
775
810
  if req[:maximum_version]
776
811
  pkgver = Version.new(metadata[:version])
777
812
  reqver = Version.new(req[:maximum_version])
@@ -782,6 +817,14 @@ class Tpkg
782
817
  same_max_ver_req = true
783
818
  end
784
819
  end
820
+ if req[:version_less_than]
821
+ pkgver = Version.new(metadata[:version])
822
+ reqver = Version.new(req[:version_less_than])
823
+ if pkgver >= reqver
824
+ puts "Package fails version_less_than (#{pkgver} >= #{reqver})" if @@debug
825
+ result = false
826
+ end
827
+ end
785
828
  if same_min_ver_req && req[:minimum_package_version]
786
829
  pkgver = Version.new(metadata[:package_version])
787
830
  reqver = Version.new(req[:minimum_package_version])
@@ -790,6 +833,14 @@ class Tpkg
790
833
  result = false
791
834
  end
792
835
  end
836
+ if same_min_ver_req && req[:package_version_greater_than]
837
+ pkgver = Version.new(metadata[:package_version])
838
+ reqver = Version.new(req[:package_version_greater_than])
839
+ if pkgver <= reqver
840
+ puts "Package fails package_version_greater_than (#{pkgver} <= #{reqver})" if @@debug
841
+ result = false
842
+ end
843
+ end
793
844
  if same_max_ver_req && req[:maximum_package_version]
794
845
  pkgver = Version.new(metadata[:package_version])
795
846
  reqver = Version.new(req[:maximum_package_version])
@@ -798,6 +849,14 @@ class Tpkg
798
849
  result = false
799
850
  end
800
851
  end
852
+ if same_max_ver_req && req[:package_version_less_than]
853
+ pkgver = Version.new(metadata[:package_version])
854
+ reqver = Version.new(req[:package_version_less_than])
855
+ if pkgver >= reqver
856
+ puts "Package fails package_version_less_than (#{pkgver} >= #{reqver})" if @@debug
857
+ result = false
858
+ end
859
+ end
801
860
  # The empty? check ensures that a package with no operatingsystem
802
861
  # field matches all clients.
803
862
  if metadata[:operatingsystem] &&
@@ -1005,37 +1064,100 @@ class Tpkg
1005
1064
  # foo
1006
1065
  # foo=1.0
1007
1066
  # foo=1.0=1
1008
- # foo-1.0-1.tpkg
1009
- def self.parse_request(request, installed_dir = nil)
1010
- # FIXME: Add support for <, <=, >, >=
1067
+ # foo>1.0
1068
+ # foo<=1.0=2
1069
+ # foo<=1.0>=3
1070
+ # foo=1.0<=6
1071
+ # foo-1.0-1.tpkg
1072
+ def self.parse_request(request)
1011
1073
  req = {}
1012
- parts = request.split('=')
1013
-
1074
+ # Note that the ordering in the regex is important. <= and >= have to
1075
+ # appear before others so that they match rather than two separate matches
1076
+ # for the '>' and '=' characters. I.e. '1>=2'.split(/(>=|>|=)/) ==
1077
+ # ['1', '>=', '2'] but '1>=2'.split(/(>|=|>=)/) == ['1', '>', '=', '2']
1078
+ parts = request.split(/(<=|>=|<|>|=)/)
1079
+
1014
1080
  # upgrade/remove/query options could take package filenames
1015
1081
  # We're assuming that the filename ends in .tpkg and has a version number that starts
1016
1082
  # with a digit. For example: foo-1.0.tpkg, foo-bar-1.0-1.tpkg
1017
1083
  if request =~ /\.tpkg$/
1018
1084
  req = {:filename => request, :name => request.split(/-\d/)[0]}
1019
- elsif parts.length > 2 && parts[-2] =~ /^[\d\.]/ && parts[-1] =~ /^[\d\.]/
1020
- package_version = parts.pop
1021
- version = parts.pop
1022
- req[:name] = parts.join('-')
1023
- req[:minimum_version] = version
1024
- req[:maximum_version] = version
1025
- req[:minimum_package_version] = package_version
1026
- req[:maximum_package_version] = package_version
1027
- elsif parts.length > 1 && parts[-1] =~ /^[\d\.]/
1028
- version = parts.pop
1029
- req[:name] = parts.join('-')
1030
- req[:minimum_version] = version
1031
- req[:maximum_version] = version
1032
1085
  else
1033
- req[:name] = parts.join('-')
1086
+ if parts.length > 4 && parts[-3] =~ /^[\d\.]/ && parts[-1] =~ /^[\d\.]/
1087
+ package_version = parts.pop
1088
+ package_version_sign = parts.pop
1089
+ version = parts.pop
1090
+ version_sign = parts.pop
1091
+
1092
+ case version_sign
1093
+ when '<'
1094
+ # E.g. foo<1.0
1095
+ req[:version_less_than] = version
1096
+ when '<='
1097
+ # E.g. foo<=1.0
1098
+ req[:maximum_version] = version
1099
+ when '='
1100
+ # E.g. foo=1.0
1101
+ req[:minimum_version] = version
1102
+ req[:maximum_version] = version
1103
+ when '>'
1104
+ # E.g. foo>1.0
1105
+ req[:version_greater_than] = version
1106
+ when '>='
1107
+ # E.g. foo>=1.0
1108
+ req[:minimum_version] = version
1109
+ end
1110
+
1111
+ case package_version_sign
1112
+ when '<'
1113
+ # E.g. foo=1.0<2.0
1114
+ req[:package_version_less_than] = package_version
1115
+ when '<='
1116
+ # E.g. foo=1.0<=2.0
1117
+ req[:maximum_package_version] = package_version
1118
+ when '='
1119
+ # E.g. foo=1.0=2.0
1120
+ req[:minimum_package_version] = package_version
1121
+ req[:maximum_package_version] = package_version
1122
+ when '>'
1123
+ # E.g. foo=1.0>2.0
1124
+ req[:package_version_greater_than] = package_version
1125
+ when '>='
1126
+ # E.g. foo=1.0>=2.0
1127
+ req[:minimum_package_version] = package_version
1128
+ end
1129
+ elsif parts.length > 1 && parts[-1] =~ /^[\d\.]/
1130
+ version = parts.pop
1131
+ version_sign = parts.pop
1132
+ if version_sign == '=' && version.include?('*')
1133
+ req[:allowed_versions] = version
1134
+ else
1135
+ case version_sign
1136
+ when '<'
1137
+ # E.g. foo<1.0
1138
+ req[:version_less_than] = version
1139
+ when '<='
1140
+ # E.g. foo<=1.0
1141
+ req[:maximum_version] = version
1142
+ when '='
1143
+ # E.g. foo=1.0
1144
+ req[:minimum_version] = version
1145
+ req[:maximum_version] = version
1146
+ when '>'
1147
+ # E.g. foo>1.0
1148
+ req[:version_greater_than] = version
1149
+ when '>='
1150
+ # E.g. foo>=1.0
1151
+ req[:minimum_version] = version
1152
+ end
1153
+ end
1154
+ end
1155
+ req[:name] = parts.join('')
1034
1156
  end
1035
1157
  req[:type] = :tpkg
1036
1158
  req
1037
1159
  end
1038
-
1160
+
1039
1161
  # deploy_options is used for configuration the deployer. It is a map of option_names => option_values. Possible
1040
1162
  # options are: use-ssh-key, deploy-as, worker-count, abort-on-fail
1041
1163
  #
@@ -1355,17 +1477,25 @@ class Tpkg
1355
1477
  name = metadata_yml[:name]
1356
1478
  metadata[name] = [] if !metadata[name]
1357
1479
  metadata[name] << metadata_yml
1358
- elsif File.directory?(source)
1359
- if !File.exists?(File.join(source, 'metadata.yml'))
1360
- warn "Warning: the source directory #{source} has no metadata.yml file. Try running tpkg -x #{source} first."
1361
- next
1480
+ elsif source[0,1] == File::SEPARATOR || File.directory?(source)
1481
+ if File.directory?(source)
1482
+ if !File.exists?(File.join(source, 'metadata.yml'))
1483
+ warn "Source directory #{source} has no metadata.yml file. Try running tpkg -x #{source} first."
1484
+ next
1485
+ end
1486
+ metadata_contents = File.read(File.join(source, 'metadata.yml'))
1487
+ Metadata::get_pkgs_metadata_from_yml_doc(metadata_contents, metadata, source)
1488
+ else
1489
+ warn "Source directory #{source} does not exist, skipping."
1362
1490
  end
1363
-
1364
- metadata_contents = File.read(File.join(source, 'metadata.yml'))
1365
- Metadata::get_pkgs_metadata_from_yml_doc(metadata_contents, metadata, source)
1366
1491
  else
1367
1492
  uri = http = localdate = remotedate = localdir = localpath = nil
1368
1493
 
1494
+ if !URI.parse(source).absolute?
1495
+ warn "Source #{source} is not a file, directory, or absolute URI, skipping"
1496
+ next
1497
+ end
1498
+
1369
1499
  uri = URI.join(source, 'metadata.yml')
1370
1500
  http = gethttp(uri)
1371
1501
 
@@ -1880,6 +2010,10 @@ class Tpkg
1880
2010
  end
1881
2011
  pkgs
1882
2012
  end
2013
+
2014
+ # Returns an array (possibly empty) of the packages that meet the given
2015
+ # requirement. If the given requirement is nil or not specified then all
2016
+ # installed packages are returned.
1883
2017
  def installed_packages_that_meet_requirement(req=nil)
1884
2018
  pkgs = []
1885
2019
  if req && req[:type] == :native
@@ -2334,6 +2468,7 @@ class Tpkg
2334
2468
  File.chmod(0644, tmpfile.path)
2335
2469
  File.rename(tmpfile.path, localpath)
2336
2470
  rescue
2471
+ # FIXME: should include original exception message to help user debug
2337
2472
  raise "Unable to download and/or verify the package."
2338
2473
  end
2339
2474
 
@@ -2620,6 +2755,8 @@ class Tpkg
2620
2755
  end
2621
2756
  rescue Errno::EPERM
2622
2757
  raise if Process.euid == 0
2758
+ rescue Errno::EINVAL
2759
+ raise if RUBY_PLATFORM != 'i386-cygwin'
2623
2760
  end
2624
2761
  if File.symlink?(f)
2625
2762
  if default_perms
@@ -2644,7 +2781,13 @@ class Tpkg
2644
2781
  stat = info[:stat]
2645
2782
  file_path = info[:normalized]
2646
2783
  File.chmod(stat.mode, file_path)
2647
- File.chown(stat.uid, stat.gid, file_path)
2784
+ begin
2785
+ File.chown(stat.uid, stat.gid, file_path)
2786
+ rescue Errno::EPERM
2787
+ raise if Process.euid == 0
2788
+ rescue Errno::EINVAL
2789
+ raise if RUBY_PLATFORM != 'i386-cygwin'
2790
+ end
2648
2791
  end
2649
2792
 
2650
2793
  # Handle any decryption, ownership/permissions, and other issues for specific files
@@ -2681,6 +2824,8 @@ class Tpkg
2681
2824
  end
2682
2825
  rescue Errno::EPERM
2683
2826
  raise if Process.euid == 0
2827
+ rescue Errno::EINVAL
2828
+ raise if RUBY_PLATFORM != 'i386-cygwin'
2684
2829
  end
2685
2830
  end
2686
2831
  if tpkgfile[:posix][:perms]
@@ -2710,7 +2855,7 @@ class Tpkg
2710
2855
  begin
2711
2856
  Tpkg::decrypt(metadata[:name], working_path, options[:passphrase], *([tpkgfile[:encrypt][:algorithm]].compact))
2712
2857
  break
2713
- rescue OpenSSL::CipherError
2858
+ rescue OpenSSLCipherError
2714
2859
  @@passphrase = nil
2715
2860
  if i == 3
2716
2861
  raise "Incorrect passphrase."
@@ -2937,6 +3082,8 @@ class Tpkg
2937
3082
  else
2938
3083
  raise e
2939
3084
  end
3085
+ rescue Errno::EINVAL
3086
+ raise if RUBY_PLATFORM != 'i386-cygwin'
2940
3087
  end
2941
3088
  # Insert the contents of the current crontab file
2942
3089
  File.open(destination[:file]) { |file| tmpfile.write(file.read) }
@@ -3029,6 +3176,8 @@ class Tpkg
3029
3176
  else
3030
3177
  raise
3031
3178
  end
3179
+ rescue Errno::EINVAL
3180
+ raise if RUBY_PLATFORM != 'i386-cygwin'
3032
3181
  end
3033
3182
  # Remove section associated with this package
3034
3183
  skip = false
@@ -3267,7 +3416,8 @@ class Tpkg
3267
3416
  # requirements << { :name => 'bar' }, packages['bar'] = { :source => 'http://server/pkgs/bar-2.3.pkg' }
3268
3417
  # requirements << { :name => 'blat', :minimum_version => '0.5', :maximum_version => '0.5' }, packages['blat'] populated with available packages meeting that requirement
3269
3418
  # Note: the requirements and packages arguments are modified by this method
3270
- def parse_requests(requests, requirements, packages)
3419
+ # FIXME: This method has a terrible API, can we fix it?
3420
+ def parse_requests(requests, requirements, packages, options = {})
3271
3421
  newreqs = []
3272
3422
 
3273
3423
  requests.each do |request|
@@ -3313,14 +3463,16 @@ class Tpkg
3313
3463
  else # basic package specs ('foo' or 'foo=1.0')
3314
3464
  puts "parse_requests request looks like package spec" if @@debug
3315
3465
 
3316
- # Tpkg::parse_request is a class method and doesn't know where packages are installed.
3317
- # So we have to tell it ourselves.
3318
- req = Tpkg::parse_request(request, @installed_directory)
3466
+ req = Tpkg::parse_request(request)
3319
3467
  newreqs << req
3320
3468
 
3321
3469
  puts "Initializing the list of possible packages for this req" if @@debug
3322
3470
  if !packages[req[:name]]
3323
- packages[req[:name]] = available_packages_that_meet_requirement(req)
3471
+ if !options[:installed_only]
3472
+ packages[req[:name]] = available_packages_that_meet_requirement(req)
3473
+ else
3474
+ packages[req[:name]] = installed_packages_that_meet_requirement(req)
3475
+ end
3324
3476
  end
3325
3477
  end
3326
3478
  end
@@ -3657,7 +3809,7 @@ class Tpkg
3657
3809
  puts "Running '/opt/local/bin/port install #{pkgname}' to install native package" if @@debug
3658
3810
  system("/opt/local/bin/port install #{pkgname}")
3659
3811
  else
3660
- # Fink support would be nice
3812
+ # Fink, Homebrew support would be nice
3661
3813
  raise "No supported native package tool available on #{Tpkg::get_os}"
3662
3814
  end
3663
3815
  else
@@ -3900,8 +4052,8 @@ class Tpkg
3900
4052
  end
3901
4053
 
3902
4054
  if prompt_for_conflicting_files(pkgfile, CHECK_UPGRADE)
3903
- # If the old and new packages have overlapping externals flag them
3904
- # to be skipped so that the external isn't removed and then
4055
+ # If the old and new packages have overlapping externals then flag
4056
+ # them to be skipped so that the external isn't removed and then
3905
4057
  # immediately re-added
3906
4058
  oldpkgs = installed_packages_that_meet_requirement({:name => pkg[:metadata][:name], :type => :tpkg})
3907
4059
  externals_to_skip = []
@@ -3909,7 +4061,7 @@ class Tpkg
3909
4061
  if oldpkgs.all? {|oldpkg| oldpkg[:metadata][:externals] && oldpkg[:metadata][:externals].include?(external)}
3910
4062
  externals_to_skip << external
3911
4063
  end
3912
- end if pkg[:metadata][:externals]
4064
+ end if pkg[:metadata][:externals] && !oldpkgs.empty?
3913
4065
 
3914
4066
  # Remove the old package if we haven't done so
3915
4067
  unless oldpkgs.nil? or oldpkgs.empty? or removed_pkgs.include?(pkg[:metadata][:name])
@@ -3969,7 +4121,7 @@ class Tpkg
3969
4121
  requests.uniq! if requests.is_a?(Array)
3970
4122
  packages_to_remove = []
3971
4123
  requests.each do |request|
3972
- req = Tpkg::parse_request(request, @installed_directory)
4124
+ req = Tpkg::parse_request(request)
3973
4125
  packages_to_remove.concat(installed_packages_that_meet_requirement(req))
3974
4126
  end
3975
4127
  else
@@ -4063,7 +4215,7 @@ class Tpkg
4063
4215
  end
4064
4216
 
4065
4217
  # Stop the services if there's init script
4066
- if !options[:upgrade]
4218
+ if !options[:upgrade] && !options[:skip_remove_stop]
4067
4219
  packages_to_remove.each do |pkg|
4068
4220
  init_scripts_metadata = init_scripts(pkg[:metadata])
4069
4221
  if init_scripts_metadata && !init_scripts_metadata.empty?
@@ -4539,6 +4691,8 @@ class Tpkg
4539
4691
  original_dir = Dir.pwd
4540
4692
 
4541
4693
  workdir = Tpkg::tempdir("tpkg_download")
4694
+ # FIXME: should use begin/ensure to make sure we chdir back when done
4695
+ # But I also wonder why we have to chdir at all...
4542
4696
  Dir.chdir(workdir)
4543
4697
  parse_requests(requests, requirements, packages)
4544
4698
  packages = packages.values.flatten