autoproj 2.8.8 → 2.9.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/bin/alog +5 -3
- data/lib/autoproj.rb +2 -0
- data/lib/autoproj/cli/base.rb +8 -1
- data/lib/autoproj/cli/build.rb +5 -3
- data/lib/autoproj/cli/locate.rb +5 -1
- data/lib/autoproj/cli/osdeps.rb +1 -0
- data/lib/autoproj/cli/status.rb +13 -4
- data/lib/autoproj/cli/update.rb +9 -5
- data/lib/autoproj/default.osdeps +14 -0
- data/lib/autoproj/manifest.rb +7 -9
- data/lib/autoproj/ops/build.rb +48 -2
- data/lib/autoproj/ops/configuration.rb +18 -0
- data/lib/autoproj/os_package_installer.rb +27 -0
- data/lib/autoproj/os_repository_installer.rb +53 -0
- data/lib/autoproj/os_repository_resolver.rb +137 -0
- data/lib/autoproj/package_managers/apt_dpkg_manager.rb +70 -10
- data/lib/autoproj/package_managers/bundler_manager.rb +9 -2
- data/lib/autoproj/package_managers/debian_version.rb +123 -0
- data/lib/autoproj/package_managers/manager.rb +9 -0
- data/lib/autoproj/package_managers/pip_manager.rb +4 -0
- data/lib/autoproj/package_selection.rb +36 -10
- data/lib/autoproj/package_set.rb +39 -4
- data/lib/autoproj/repository_managers/apt.rb +289 -0
- data/lib/autoproj/repository_managers/manager.rb +26 -0
- data/lib/autoproj/repository_managers/unknown_os_manager.rb +30 -0
- data/lib/autoproj/test.rb +1 -0
- data/lib/autoproj/version.rb +1 -1
- data/lib/autoproj/workspace.rb +21 -7
- metadata +8 -2
@@ -0,0 +1,137 @@
|
|
1
|
+
require 'tempfile'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
module Autoproj
|
5
|
+
# Manager for OS repository provided by package sets
|
6
|
+
class OSRepositoryResolver
|
7
|
+
# All the information contained in all the OSrepos files
|
8
|
+
attr_reader :all_definitions
|
9
|
+
|
10
|
+
# The operating system
|
11
|
+
attr_accessor :operating_system
|
12
|
+
|
13
|
+
def self.load(file)
|
14
|
+
raise ArgumentError, "no such file or directory: #{file}" unless File.file?(file)
|
15
|
+
|
16
|
+
error_t = if defined? Psych::SyntaxError
|
17
|
+
[ArgumentError, Psych::SyntaxError]
|
18
|
+
else
|
19
|
+
ArgumentError
|
20
|
+
end
|
21
|
+
|
22
|
+
result = new
|
23
|
+
file = File.expand_path(file)
|
24
|
+
begin
|
25
|
+
data = YAML.safe_load(File.read(file)) || {}
|
26
|
+
verify_definitions(data)
|
27
|
+
rescue *error_t => e
|
28
|
+
raise ConfigError.new, "error in #{file}: #{e.message}", e.backtrace
|
29
|
+
end
|
30
|
+
result.merge(new(data, file))
|
31
|
+
result
|
32
|
+
end
|
33
|
+
|
34
|
+
def initialize(defs = [], file = nil, operating_system: nil)
|
35
|
+
@operating_system = operating_system
|
36
|
+
@all_definitions = Set.new
|
37
|
+
if file
|
38
|
+
defs.each do |def_|
|
39
|
+
all_definitions << [[file], def_]
|
40
|
+
end
|
41
|
+
else
|
42
|
+
defs.each do |def_|
|
43
|
+
all_definitions << [[], def_]
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def merge(info)
|
49
|
+
@all_definitions += info.all_definitions
|
50
|
+
end
|
51
|
+
|
52
|
+
def definitions
|
53
|
+
all_definitions.map(&:last).uniq
|
54
|
+
end
|
55
|
+
|
56
|
+
def all_entries
|
57
|
+
definitions.map do |distribution|
|
58
|
+
distribution.values.map do |release|
|
59
|
+
release.map(&:values)
|
60
|
+
end
|
61
|
+
end.flatten.uniq
|
62
|
+
end
|
63
|
+
|
64
|
+
def entry_matches?(entry, identifiers)
|
65
|
+
!(entry.keys.first.split(',').map(&:strip) & identifiers).empty?
|
66
|
+
end
|
67
|
+
|
68
|
+
def resolved_entries
|
69
|
+
os_name, os_version = operating_system
|
70
|
+
os_version << 'default' unless os_version.include?('default')
|
71
|
+
|
72
|
+
distribution_filtered = definitions.select do |entry|
|
73
|
+
entry_matches?(entry, os_name)
|
74
|
+
end.map(&:values).flatten
|
75
|
+
|
76
|
+
release_filtered = distribution_filtered.select { |entry| entry_matches?(entry, os_version) }
|
77
|
+
release_filtered.map(&:values).flatten.uniq
|
78
|
+
end
|
79
|
+
|
80
|
+
# OS repos definitions must follow the format:
|
81
|
+
#
|
82
|
+
# - distribution:
|
83
|
+
# - release:
|
84
|
+
# - key1: value1
|
85
|
+
# key2: value2
|
86
|
+
#
|
87
|
+
# The key, value pairs are OS dependent, and will be verified/parsed by the
|
88
|
+
# corresponding 'repository manager'. Thus, for a debian-like distribution
|
89
|
+
# one would have something like:
|
90
|
+
#
|
91
|
+
# - ubuntu:
|
92
|
+
# - xenial:
|
93
|
+
# - type: repo
|
94
|
+
# repo: 'deb http://br.archive.ubuntu.com/ubuntu/ xenial main restricted'
|
95
|
+
def self.verify_definitions(array, path = [])
|
96
|
+
verify_type(array, Array, path)
|
97
|
+
array.each do |entry|
|
98
|
+
verify_type(entry, Hash, path)
|
99
|
+
verify_os(entry, (path + [entry]))
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def self.verify_os(hash, path = [])
|
104
|
+
verify_type(hash, Hash, path)
|
105
|
+
hash.each do |key, value|
|
106
|
+
verify_type(key, String, path)
|
107
|
+
verify_release(value, (path + [value]))
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def self.verify_release(array, path = [])
|
112
|
+
verify_type(array, Array, path)
|
113
|
+
array.each do |releases|
|
114
|
+
verify_type(releases, Hash, path)
|
115
|
+
|
116
|
+
releases.values.each do |entries|
|
117
|
+
verify_type(entries, Array, path)
|
118
|
+
verify_entries(entries, path + entries)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def self.verify_entries(array, path = [])
|
124
|
+
verify_type(array, Array, path)
|
125
|
+
array.each do |entry|
|
126
|
+
verify_type(entry, Hash, path)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def self.verify_type(obj, type, path = [])
|
131
|
+
return if obj.is_a?(type)
|
132
|
+
|
133
|
+
raise ArgumentError, "invalid osrepos definition in #{path.join('/')}: "\
|
134
|
+
"expected a #{type}, found a #{obj.class}"
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'autoproj/package_managers/debian_version'
|
2
|
+
|
1
3
|
module Autoproj
|
2
4
|
module PackageManagers
|
3
5
|
# Package manager interface for systems that use APT and dpkg for
|
@@ -8,15 +10,36 @@ class AptDpkgManager < ShellScriptManager
|
|
8
10
|
def initialize(ws, status_file = "/var/lib/dpkg/status")
|
9
11
|
@status_file = status_file
|
10
12
|
@installed_packages = nil
|
13
|
+
@installed_versions = nil
|
11
14
|
super(ws, true,
|
12
15
|
%w{apt-get install},
|
13
16
|
%w{DEBIAN_FRONTEND=noninteractive apt-get install -y})
|
14
17
|
end
|
15
18
|
|
16
|
-
def
|
19
|
+
def configure_manager
|
20
|
+
super
|
21
|
+
ws.config.declare 'apt_dpkg_update', 'boolean',
|
22
|
+
default: 'yes',
|
23
|
+
doc: ['Would you like autoproj to keep apt packages up-to-date?']
|
24
|
+
keep_uptodate?
|
25
|
+
end
|
26
|
+
|
27
|
+
def keep_uptodate?
|
28
|
+
ws.config.get('apt_dpkg_update')
|
29
|
+
end
|
30
|
+
|
31
|
+
def keep_uptodate=(flag)
|
32
|
+
ws.config.set('apt_dpkg_update', flag, true)
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.parse_package_status(installed_packages, installed_versions, paragraph)
|
17
36
|
if paragraph =~ /^Status: install ok installed$/
|
18
37
|
if paragraph =~ /^Package: (.*)$/
|
19
|
-
|
38
|
+
package_name = $1
|
39
|
+
installed_packages << package_name
|
40
|
+
if paragraph =~ /^Version: (.*)$/
|
41
|
+
installed_versions[package_name] = DebianVersion.new($1)
|
42
|
+
end
|
20
43
|
end
|
21
44
|
if paragraph =~ /^Provides: (.*)$/
|
22
45
|
installed_packages.merge($1.split(',').map(&:strip))
|
@@ -26,6 +49,7 @@ def self.parse_package_status(installed_packages, paragraph)
|
|
26
49
|
|
27
50
|
def self.parse_dpkg_status(status_file)
|
28
51
|
installed_packages = Set.new
|
52
|
+
installed_versions = {}
|
29
53
|
dpkg_status = File.read(status_file)
|
30
54
|
dpkg_status << "\n"
|
31
55
|
|
@@ -36,17 +60,53 @@ def self.parse_dpkg_status(status_file)
|
|
36
60
|
|
37
61
|
while paragraph_end = dpkg_status.scan_until(/Package: /)
|
38
62
|
paragraph = "Package: #{paragraph_end[0..-10]}"
|
39
|
-
parse_package_status(installed_packages, paragraph)
|
63
|
+
parse_package_status(installed_packages, installed_versions, paragraph)
|
64
|
+
end
|
65
|
+
parse_package_status(installed_packages, installed_versions, "Package: #{dpkg_status.rest}")
|
66
|
+
[installed_packages, installed_versions]
|
67
|
+
end
|
68
|
+
|
69
|
+
def self.parse_apt_cache_paragraph(paragraph)
|
70
|
+
version = '0'
|
71
|
+
if paragraph =~ /^Package: (.*)$/
|
72
|
+
package_name = $1
|
73
|
+
if paragraph =~ /^Version: (.*)$/
|
74
|
+
version = $1
|
75
|
+
end
|
76
|
+
end
|
77
|
+
[package_name, version]
|
78
|
+
end
|
79
|
+
|
80
|
+
def self.parse_packages_versions(packages)
|
81
|
+
packages_versions = {}
|
82
|
+
apt_cache_show = `apt-cache show --no-all-versions #{packages.join(' ')}`
|
83
|
+
apt_cache_show = StringScanner.new(apt_cache_show)
|
84
|
+
if !apt_cache_show.scan(/Package: /)
|
85
|
+
return packages_versions
|
86
|
+
end
|
87
|
+
|
88
|
+
while paragraph_end = apt_cache_show.scan_until(/Package: /)
|
89
|
+
paragraph = "Package: #{paragraph_end[0..-10]}"
|
90
|
+
package_name, version = parse_apt_cache_paragraph(paragraph)
|
91
|
+
packages_versions[package_name] = DebianVersion.new(version)
|
40
92
|
end
|
41
|
-
|
42
|
-
|
93
|
+
package_name, version = parse_apt_cache_paragraph("Package: #{apt_cache_show.rest}")
|
94
|
+
packages_versions[package_name] = DebianVersion.new(version)
|
95
|
+
packages_versions
|
96
|
+
end
|
97
|
+
|
98
|
+
def updated?(package, available_version)
|
99
|
+
# Consider up-to-date if the package is provided by another package (purely virtual)
|
100
|
+
# Ideally, we should check the version of the package that provides it
|
101
|
+
return true unless available_version && @installed_versions[package]
|
102
|
+
|
103
|
+
(available_version <= @installed_versions[package])
|
43
104
|
end
|
44
105
|
|
45
106
|
# On a dpkg-enabled system, checks if the provided package is installed
|
46
107
|
# and returns true if it is the case
|
47
108
|
def installed?(package_name, filter_uptodate_packages: false, install_only: false)
|
48
|
-
@installed_packages
|
49
|
-
|
109
|
+
@installed_packages, @installed_versions = self.class.parse_dpkg_status(status_file) unless @installed_packages && @installed_versions
|
50
110
|
if package_name =~ /^(\w[a-z0-9+-.]+)/
|
51
111
|
@installed_packages.include?($1)
|
52
112
|
else
|
@@ -54,11 +114,12 @@ def installed?(package_name, filter_uptodate_packages: false, install_only: fals
|
|
54
114
|
false
|
55
115
|
end
|
56
116
|
end
|
57
|
-
|
117
|
+
|
58
118
|
def install(packages, filter_uptodate_packages: false, install_only: false)
|
119
|
+
packages_versions = self.class.parse_packages_versions(packages)
|
59
120
|
if filter_uptodate_packages || install_only
|
60
121
|
packages = packages.find_all do |package_name|
|
61
|
-
!installed?(package_name)
|
122
|
+
!installed?(package_name) || (keep_uptodate? && !updated?(package_name, packages_versions[package_name]))
|
62
123
|
end
|
63
124
|
end
|
64
125
|
|
@@ -71,4 +132,3 @@ def install(packages, filter_uptodate_packages: false, install_only: false)
|
|
71
132
|
end
|
72
133
|
end
|
73
134
|
end
|
74
|
-
|
@@ -213,7 +213,7 @@ def self.run_bundler(ws, *commandline, gem_home: nil, gemfile: nil)
|
|
213
213
|
'GEM_PATH' => nil,
|
214
214
|
'BUNDLE_GEMFILE' => gemfile,
|
215
215
|
'RUBYOPT' => nil,
|
216
|
-
'RUBYLIB' =>
|
216
|
+
'RUBYLIB' => rubylib_for_bundler
|
217
217
|
]
|
218
218
|
ws.run 'autoproj', 'osdeps',
|
219
219
|
Autobuild.tool('bundle'), *commandline,
|
@@ -388,6 +388,11 @@ def discover_rubylib
|
|
388
388
|
end
|
389
389
|
end
|
390
390
|
|
391
|
+
def self.rubylib_for_bundler
|
392
|
+
rx = Regexp.new("/gems/#{Regexp.quote("bundler-#{Bundler::VERSION}")}/")
|
393
|
+
$LOAD_PATH.grep(rx).join(File::PATH_SEPARATOR)
|
394
|
+
end
|
395
|
+
|
391
396
|
def discover_bundle_rubylib(silent_errors: false)
|
392
397
|
require 'bundler'
|
393
398
|
gemfile = File.join(ws.prefix_dir, 'gems', 'Gemfile')
|
@@ -398,7 +403,9 @@ def discover_bundle_rubylib(silent_errors: false)
|
|
398
403
|
env = ws.env.resolved_env
|
399
404
|
Tempfile.open 'autoproj-rubylib' do |io|
|
400
405
|
result = Bundler.clean_system(
|
401
|
-
Hash['GEM_HOME' => env['GEM_HOME'], 'GEM_PATH' => env['GEM_PATH'],
|
406
|
+
Hash['GEM_HOME' => env['GEM_HOME'], 'GEM_PATH' => env['GEM_PATH'],
|
407
|
+
'BUNDLE_GEMFILE' => gemfile, 'RUBYOPT' => nil,
|
408
|
+
'RUBYLIB' => self.class.rubylib_for_bundler],
|
402
409
|
Autobuild.tool('ruby'), '-rbundler/setup', '-e', 'puts $LOAD_PATH',
|
403
410
|
out: io, **silent_redirect)
|
404
411
|
|
@@ -0,0 +1,123 @@
|
|
1
|
+
module Autoproj
|
2
|
+
module PackageManagers
|
3
|
+
class DebianVersion
|
4
|
+
attr_reader :version
|
5
|
+
attr_reader :epoch
|
6
|
+
attr_reader :upstream_version
|
7
|
+
attr_reader :debian_revision
|
8
|
+
|
9
|
+
include Comparable
|
10
|
+
|
11
|
+
def initialize(version)
|
12
|
+
@version = version
|
13
|
+
parse_version
|
14
|
+
end
|
15
|
+
|
16
|
+
def split
|
17
|
+
[epoch, upstream_version, debian_revision]
|
18
|
+
end
|
19
|
+
|
20
|
+
def <=>(b)
|
21
|
+
(0..2).inject(0) do |result, i|
|
22
|
+
return result unless result == 0
|
23
|
+
normalize(compare_fragments(self.split[i], b.split[i]))
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.compare(a, b)
|
28
|
+
new(a)<=>new(b)
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def normalize(value)
|
34
|
+
return -1 if value < 0
|
35
|
+
return 1 if value > 0
|
36
|
+
return 0 if value == 0
|
37
|
+
end
|
38
|
+
|
39
|
+
# Reference: https://www.debian.org/doc/debian-policy/ch-controlfields.html#version
|
40
|
+
def parse_version
|
41
|
+
@epoch = '0'
|
42
|
+
@debian_revision = '0'
|
43
|
+
|
44
|
+
@upstream_version = @version.split(':')
|
45
|
+
if @upstream_version.size > 1
|
46
|
+
@epoch = @upstream_version.first
|
47
|
+
@upstream_version = @upstream_version[1..-1].join(':')
|
48
|
+
else
|
49
|
+
@upstream_version = @upstream_version.first
|
50
|
+
end
|
51
|
+
|
52
|
+
@upstream_version = @upstream_version.split('-')
|
53
|
+
if @upstream_version.size > 1
|
54
|
+
@debian_revision = @upstream_version.last
|
55
|
+
@upstream_version = @upstream_version[0..-2].join('-')
|
56
|
+
else
|
57
|
+
@upstream_version = @upstream_version.first
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def alpha?(look_ahead)
|
62
|
+
look_ahead =~ /[[:alpha:]]/
|
63
|
+
end
|
64
|
+
|
65
|
+
def digit?(look_ahead)
|
66
|
+
look_ahead =~ /[[:digit:]]/
|
67
|
+
end
|
68
|
+
|
69
|
+
def order(c)
|
70
|
+
if digit?(c)
|
71
|
+
return 0
|
72
|
+
elsif alpha?(c)
|
73
|
+
return c.ord
|
74
|
+
elsif c == '~'
|
75
|
+
return -1
|
76
|
+
elsif c
|
77
|
+
return c.ord + 256
|
78
|
+
else
|
79
|
+
return 0
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# Ported from https://github.com/Debian/apt/blob/master/apt-pkg/deb/debversion.cc
|
84
|
+
def compare_fragments(a, b)
|
85
|
+
i = 0
|
86
|
+
j = 0
|
87
|
+
while i != a.size && j != b.size
|
88
|
+
first_diff = 0
|
89
|
+
while i != a.size && j != b.size && (!digit?(a[i]) || !digit?(b[j]))
|
90
|
+
vc = order(a[i])
|
91
|
+
rc = order(b[j])
|
92
|
+
return vc-rc if vc != rc
|
93
|
+
i += 1
|
94
|
+
j += 1
|
95
|
+
end
|
96
|
+
|
97
|
+
i += 1 while a[i] == '0'
|
98
|
+
j += 1 while b[j] == '0'
|
99
|
+
while digit?(a[i]) && digit?(b[j])
|
100
|
+
first_diff = a[i].ord - b[j].ord if first_diff == 0
|
101
|
+
i += 1
|
102
|
+
j += 1
|
103
|
+
end
|
104
|
+
|
105
|
+
return 1 if digit?(a[i])
|
106
|
+
return -1 if digit?(b[j])
|
107
|
+
return first_diff if first_diff != 0
|
108
|
+
end
|
109
|
+
|
110
|
+
return 0 if i == a.size && j == b.size
|
111
|
+
|
112
|
+
if i == a.size
|
113
|
+
return 1 if b[j] == '~'
|
114
|
+
return -1
|
115
|
+
end
|
116
|
+
if j == b.size
|
117
|
+
return -1 if a[i] == '~'
|
118
|
+
return 1
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
@@ -41,6 +41,11 @@ def strict?
|
|
41
41
|
false
|
42
42
|
end
|
43
43
|
|
44
|
+
# If this package manager depends on OS packages, they should be added here
|
45
|
+
def os_dependencies
|
46
|
+
[]
|
47
|
+
end
|
48
|
+
|
44
49
|
# Create a package manager
|
45
50
|
#
|
46
51
|
# @param [Workspace] ws the underlying workspace
|
@@ -56,6 +61,10 @@ def initialize(ws)
|
|
56
61
|
# This is e.g. needed for python pip or rubygems
|
57
62
|
def initialize_environment
|
58
63
|
end
|
64
|
+
|
65
|
+
# Perform additional configuration required for the package manager
|
66
|
+
def configure_manager
|
67
|
+
end
|
59
68
|
end
|
60
69
|
end
|
61
70
|
end
|