autoproj 2.12.0 → 2.15.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/lint.yml +25 -0
- data/.github/workflows/test.yml +30 -0
- data/.rubocop.yml +79 -91
- data/.rubocop_todo.yml +1473 -0
- data/Gemfile +9 -9
- data/Rakefile +24 -24
- data/autoproj.gemspec +22 -22
- data/bin/alocate +4 -4
- data/bin/alog +6 -7
- data/bin/amake +4 -4
- data/bin/aup +4 -4
- data/bin/autoproj +2 -2
- data/bin/autoproj_bootstrap +186 -183
- data/bin/autoproj_bootstrap.in +7 -8
- data/bin/autoproj_install +185 -182
- data/bin/autoproj_install.in +6 -7
- data/lib/autoproj/aruba_minitest.rb +6 -11
- data/lib/autoproj/autobuild.rb +5 -6
- data/lib/autoproj/autobuild_extensions/archive_importer.rb +10 -11
- data/lib/autoproj/autobuild_extensions/dsl.rb +61 -44
- data/lib/autoproj/autobuild_extensions/git.rb +27 -26
- data/lib/autoproj/autobuild_extensions/package.rb +23 -22
- data/lib/autoproj/autobuild_extensions/svn.rb +1 -2
- data/lib/autoproj/base.rb +1 -1
- data/lib/autoproj/bash_completion.rb +5 -6
- data/lib/autoproj/build_option.rb +22 -24
- data/lib/autoproj/cli/base.rb +27 -27
- data/lib/autoproj/cli/bootstrap.rb +14 -16
- data/lib/autoproj/cli/build.rb +18 -10
- data/lib/autoproj/cli/cache.rb +51 -8
- data/lib/autoproj/cli/clean.rb +10 -10
- data/lib/autoproj/cli/commit.rb +7 -8
- data/lib/autoproj/cli/doc.rb +2 -2
- data/lib/autoproj/cli/envsh.rb +1 -2
- data/lib/autoproj/cli/exec.rb +60 -20
- data/lib/autoproj/cli/inspection_tool.rb +18 -13
- data/lib/autoproj/cli/locate.rb +30 -41
- data/lib/autoproj/cli/log.rb +7 -7
- data/lib/autoproj/cli/main.rb +217 -205
- data/lib/autoproj/cli/main_doc.rb +22 -21
- data/lib/autoproj/cli/main_global.rb +44 -19
- data/lib/autoproj/cli/main_plugin.rb +18 -18
- data/lib/autoproj/cli/main_test.rb +28 -27
- data/lib/autoproj/cli/manifest.rb +7 -7
- data/lib/autoproj/cli/osdeps.rb +12 -11
- data/lib/autoproj/cli/patcher.rb +2 -3
- data/lib/autoproj/cli/query.rb +17 -18
- data/lib/autoproj/cli/reconfigure.rb +1 -2
- data/lib/autoproj/cli/reset.rb +9 -12
- data/lib/autoproj/cli/show.rb +48 -55
- data/lib/autoproj/cli/status.rb +56 -44
- data/lib/autoproj/cli/switch_config.rb +5 -6
- data/lib/autoproj/cli/tag.rb +12 -11
- data/lib/autoproj/cli/test.rb +7 -7
- data/lib/autoproj/cli/update.rb +104 -51
- data/lib/autoproj/cli/utility.rb +14 -12
- data/lib/autoproj/cli/version.rb +42 -40
- data/lib/autoproj/cli/versions.rb +14 -15
- data/lib/autoproj/cli/watch.rb +33 -37
- data/lib/autoproj/cli/which.rb +16 -20
- data/lib/autoproj/cli.rb +4 -2
- data/lib/autoproj/configuration.rb +78 -85
- data/lib/autoproj/default.osdeps +29 -3
- data/lib/autoproj/environment.rb +42 -23
- data/lib/autoproj/exceptions.rb +9 -3
- data/lib/autoproj/find_workspace.rb +20 -25
- data/lib/autoproj/git_server_configuration.rb +40 -44
- data/lib/autoproj/gitorious.rb +1 -1
- data/lib/autoproj/installation_manifest.rb +64 -29
- data/lib/autoproj/local_package_set.rb +13 -11
- data/lib/autoproj/manifest.rb +145 -135
- data/lib/autoproj/metapackage.rb +2 -6
- data/lib/autoproj/ops/atomic_write.rb +7 -6
- data/lib/autoproj/ops/build.rb +4 -6
- data/lib/autoproj/ops/cache.rb +64 -53
- data/lib/autoproj/ops/cached_env.rb +7 -6
- data/lib/autoproj/ops/configuration.rb +511 -506
- data/lib/autoproj/ops/import.rb +90 -61
- data/lib/autoproj/ops/install.rb +179 -175
- data/lib/autoproj/ops/loader.rb +77 -76
- data/lib/autoproj/ops/main_config_switcher.rb +36 -45
- data/lib/autoproj/ops/phase_reporting.rb +4 -4
- data/lib/autoproj/ops/snapshot.rb +250 -247
- data/lib/autoproj/ops/tools.rb +76 -78
- data/lib/autoproj/ops/watch.rb +6 -6
- data/lib/autoproj/ops/which.rb +17 -14
- data/lib/autoproj/options.rb +13 -2
- data/lib/autoproj/os_package_installer.rb +102 -92
- data/lib/autoproj/os_package_query.rb +7 -13
- data/lib/autoproj/os_package_resolver.rb +189 -140
- data/lib/autoproj/os_repository_installer.rb +4 -4
- data/lib/autoproj/os_repository_resolver.rb +8 -6
- data/lib/autoproj/package_definition.rb +12 -13
- data/lib/autoproj/package_managers/apt_dpkg_manager.rb +46 -31
- data/lib/autoproj/package_managers/bundler_manager.rb +156 -118
- data/lib/autoproj/package_managers/debian_version.rb +25 -21
- data/lib/autoproj/package_managers/emerge_manager.rb +2 -3
- data/lib/autoproj/package_managers/gem_manager.rb +68 -77
- data/lib/autoproj/package_managers/homebrew_manager.rb +3 -4
- data/lib/autoproj/package_managers/manager.rb +8 -3
- data/lib/autoproj/package_managers/pacman_manager.rb +2 -3
- data/lib/autoproj/package_managers/pip_manager.rb +37 -27
- data/lib/autoproj/package_managers/pkg_manager.rb +3 -4
- data/lib/autoproj/package_managers/port_manager.rb +2 -3
- data/lib/autoproj/package_managers/shell_script_manager.rb +66 -36
- data/lib/autoproj/package_managers/unknown_os_manager.rb +5 -8
- data/lib/autoproj/package_managers/yum_manager.rb +12 -15
- data/lib/autoproj/package_managers/zypper_manager.rb +11 -14
- data/lib/autoproj/package_manifest.rb +66 -53
- data/lib/autoproj/package_selection.rb +187 -187
- data/lib/autoproj/package_set.rb +128 -114
- data/lib/autoproj/python.rb +285 -0
- data/lib/autoproj/query_base.rb +20 -14
- data/lib/autoproj/reporter.rb +19 -19
- data/lib/autoproj/repository_managers/apt.rb +101 -67
- data/lib/autoproj/repository_managers/unknown_os_manager.rb +3 -3
- data/lib/autoproj/shell_completion.rb +16 -13
- data/lib/autoproj/source_package_query.rb +29 -36
- data/lib/autoproj/system.rb +32 -21
- data/lib/autoproj/test.rb +131 -106
- data/lib/autoproj/variable_expansion.rb +10 -10
- data/lib/autoproj/vcs_definition.rb +53 -37
- data/lib/autoproj/version.rb +1 -1
- data/lib/autoproj/workspace.rb +162 -117
- data/lib/autoproj/zsh_completion.rb +8 -9
- data/lib/autoproj.rb +53 -53
- data/samples/autoproj/init.rb +1 -2
- metadata +62 -72
- data/.travis.yml +0 -22
data/lib/autoproj/ops/loader.rb
CHANGED
@@ -1,77 +1,81 @@
|
|
1
1
|
module Autoproj
|
2
2
|
module Ops
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
3
|
+
class Loader
|
4
|
+
# The path w.r.t. which we should resolve relative paths
|
5
|
+
#
|
6
|
+
# @return [String]
|
7
|
+
attr_reader :root_dir
|
8
|
+
# @return [Array<String>] information about what is being loaded
|
9
|
+
attr_reader :file_stack
|
10
10
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
11
|
+
def initialize(root_dir)
|
12
|
+
@root_dir = root_dir
|
13
|
+
@file_stack = Array.new
|
14
|
+
@loaded_autobuild_files = Set.new
|
15
|
+
end
|
16
16
|
|
17
|
-
|
18
|
-
|
19
|
-
|
17
|
+
def in_package_set(pkg_set, path)
|
18
|
+
path = File.expand_path(path, root_dir) if path
|
19
|
+
@file_stack.push([pkg_set, path])
|
20
|
+
yield
|
21
|
+
ensure
|
22
|
+
@file_stack.pop
|
20
23
|
end
|
21
|
-
@file_stack.push([pkg_set, path])
|
22
|
-
yield
|
23
|
-
ensure
|
24
|
-
@file_stack.pop
|
25
|
-
end
|
26
24
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
25
|
+
def filter_load_exception(error, package_set, path)
|
26
|
+
raise error if Autoproj.verbose
|
27
|
+
|
28
|
+
rx_path = Regexp.quote(path)
|
29
|
+
unless (error_line = error.backtrace.find { |l| l =~ /#{rx_path}/ })
|
30
|
+
raise error
|
31
|
+
end
|
32
|
+
|
33
|
+
if (line_number = Integer(/#{rx_path}:(\d+)/.match(error_line)[1]))
|
32
34
|
line_number = "#{line_number}:"
|
33
35
|
end
|
34
36
|
|
35
37
|
if package_set.local?
|
36
|
-
raise ConfigError.new(path),
|
38
|
+
raise ConfigError.new(path),
|
39
|
+
"#{path}:#{line_number} #{error.message}", error.backtrace
|
37
40
|
else
|
38
|
-
raise ConfigError.new(path),
|
41
|
+
raise ConfigError.new(path),
|
42
|
+
"#{File.basename(path)}(package_set=#{package_set.name}):"\
|
43
|
+
"#{line_number} #{error.message}", error.backtrace
|
39
44
|
end
|
40
|
-
else
|
41
|
-
raise error
|
42
45
|
end
|
43
|
-
end
|
44
46
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
47
|
+
# Returns the information about the file that is currently being loaded
|
48
|
+
#
|
49
|
+
# The return value is [package_set, path], where +package_set+ is the
|
50
|
+
# PackageSet instance and +path+ is the path of the file w.r.t. the autoproj
|
51
|
+
# root directory
|
52
|
+
def current_file
|
53
|
+
unless (file = @file_stack.last)
|
54
|
+
raise ArgumentError, "not in a #in_package_set context"
|
55
|
+
end
|
56
|
+
|
52
57
|
file
|
53
|
-
else raise ArgumentError, "not in a #in_package_set context"
|
54
58
|
end
|
55
|
-
end
|
56
59
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
60
|
+
# The PackageSet object representing the package set that is currently being
|
61
|
+
# loaded
|
62
|
+
def current_package_set
|
63
|
+
current_file.first
|
64
|
+
end
|
62
65
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
66
|
+
# Load a definition file from a package set
|
67
|
+
#
|
68
|
+
# If any error is detected, the backtrace will be filtered so that it is
|
69
|
+
# easier to understand by the user. Moreover, if +source+ is non-nil, the
|
70
|
+
# package set name will be mentionned.
|
71
|
+
#
|
72
|
+
# @param [PackageSet] pkg_set
|
73
|
+
# @param [Array<String>] path
|
74
|
+
def load(pkg_set, *path)
|
75
|
+
path = File.join(*path)
|
76
|
+
relative_path =
|
77
|
+
File.expand_path(path).gsub(/^#{Regexp.quote(root_dir)}\//, "")
|
78
|
+
in_package_set(pkg_set, relative_path) do
|
75
79
|
Kernel.load path
|
76
80
|
rescue Interrupt
|
77
81
|
raise
|
@@ -81,30 +85,27 @@ module Autoproj
|
|
81
85
|
filter_load_exception(e, pkg_set, path)
|
82
86
|
end
|
83
87
|
end
|
84
|
-
end
|
85
88
|
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
load(pkg_set, *path)
|
89
|
+
# Load a definition file from a package set if the file is present
|
90
|
+
#
|
91
|
+
# (see load)
|
92
|
+
def load_if_present(pkg_set, *path)
|
93
|
+
path = File.join(*path)
|
94
|
+
load(pkg_set, *path) if File.file?(path)
|
93
95
|
end
|
94
|
-
end
|
95
96
|
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
97
|
+
def import_autobuild_file(package_set, path)
|
98
|
+
return if @loaded_autobuild_files.include?(path)
|
99
|
+
|
100
|
+
load(package_set, path)
|
101
|
+
@loaded_autobuild_files << path
|
102
|
+
end
|
100
103
|
end
|
101
|
-
end
|
102
104
|
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
105
|
+
# @deprecated use Autoproj.workspace, or better make sure all ops classes
|
106
|
+
# get their own workspace object as argument
|
107
|
+
def self.loader
|
108
|
+
Autoproj.workspace
|
109
|
+
end
|
108
110
|
end
|
109
111
|
end
|
110
|
-
|
@@ -16,10 +16,9 @@ module Autoproj
|
|
16
16
|
EXPECTED_ROOT_ENTRIES = [".", "..", "autoproj_bootstrap",
|
17
17
|
".autoproj", "bootstrap.sh", ENV_FILENAME].to_set
|
18
18
|
|
19
|
-
|
20
19
|
def check_root_dir_empty?
|
21
|
-
(ENV[
|
22
|
-
(ENV[
|
20
|
+
(ENV["AUTOPROJ_NONINTERACTIVE"] != "1") &&
|
21
|
+
(ENV["AUTOPROJ_BOOTSTRAP_IGNORE_NONEMPTY_DIR"] != "1")
|
23
22
|
end
|
24
23
|
|
25
24
|
# Verifies that {#root_dir} contains only expected entries, to make
|
@@ -28,23 +27,21 @@ module Autoproj
|
|
28
27
|
# If the environment variable AUTOPROJ_BOOTSTRAP_IGNORE_NONEMPTY_DIR
|
29
28
|
# is set to 1, the check is skipped
|
30
29
|
def check_root_dir_empty
|
31
|
-
require
|
30
|
+
require "set"
|
32
31
|
curdir_entries = Dir.entries(ws.root_dir).map { |p| File.basename(p) }.to_set -
|
33
|
-
|
32
|
+
EXPECTED_ROOT_ENTRIES
|
34
33
|
return if curdir_entries.empty?
|
35
34
|
|
36
|
-
|
35
|
+
loop do
|
37
36
|
print "The current directory is not empty, continue bootstrapping anyway ? [yes] "
|
38
37
|
STDOUT.flush
|
39
38
|
answer = STDIN.readline.chomp
|
40
|
-
if answer == "no"
|
41
|
-
raise Interrupt, "Interrupted by user"
|
42
|
-
end
|
39
|
+
raise Interrupt, "Interrupted by user" if answer == "no"
|
43
40
|
|
44
41
|
if answer == "" || answer == "yes"
|
45
42
|
# Set this environment variable since we might restart
|
46
43
|
# autoproj later on.
|
47
|
-
ENV[
|
44
|
+
ENV["AUTOPROJ_BOOTSTRAP_IGNORE_NONEMPTY_DIR"] = "1"
|
48
45
|
return
|
49
46
|
else
|
50
47
|
STDOUT.puts "invalid answer. Please answer 'yes' or 'no'"
|
@@ -62,29 +59,27 @@ module Autoproj
|
|
62
59
|
# @param [Array<String>] reuse set of autoproj roots that are being reused
|
63
60
|
# @raise ConfigError
|
64
61
|
def validate_autoproj_current_root(reuse)
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
end
|
75
|
-
end
|
62
|
+
return unless (current_root = ENV["AUTOPROJ_CURRENT_ROOT"])
|
63
|
+
return if (current_root == ws.root_dir) || reuse.include?(current_root)
|
64
|
+
|
65
|
+
Autoproj.error "the env.sh from #{ENV['AUTOPROJ_CURRENT_ROOT']} seem to already be sourced"
|
66
|
+
Autoproj.error "start a new shell and try to bootstrap again"
|
67
|
+
Autoproj.error ""
|
68
|
+
Autoproj.error "you are allowed to boostrap from another autoproj installation"
|
69
|
+
Autoproj.error "only if you reuse it with the --reuse flag"
|
70
|
+
raise Autobuild::Exception, ""
|
76
71
|
end
|
77
72
|
|
78
|
-
MAIN_CONFIGURATION_TEMPLATE = File.expand_path(File.join("..", "..", "..", "samples",
|
73
|
+
MAIN_CONFIGURATION_TEMPLATE = File.expand_path(File.join("..", "..", "..", "samples", "autoproj"), File.dirname(__FILE__))
|
79
74
|
|
80
75
|
def bootstrap(buildconf_info,
|
81
|
-
|
82
|
-
|
76
|
+
check_root_dir_empty: check_root_dir_empty?,
|
77
|
+
reuse: Array.new)
|
83
78
|
self.check_root_dir_empty if check_root_dir_empty
|
84
79
|
validate_autoproj_current_root(reuse)
|
85
80
|
|
86
81
|
ws.config.validate_ruby_executable
|
87
|
-
ws.config.set
|
82
|
+
ws.config.set "reused_autoproj_installations", reuse, true
|
88
83
|
ws.env.export_env_sh(shell_helpers: ws.config.shell_helpers?)
|
89
84
|
|
90
85
|
# If we are not getting the installation setup from a VCS, copy the template
|
@@ -97,10 +92,10 @@ module Autoproj
|
|
97
92
|
manifest_url = buildconf_info.first
|
98
93
|
Autoproj.message("autoproj: downloading manifest file #{manifest_url}", :bold)
|
99
94
|
manifest_data =
|
100
|
-
begin open(manifest_url
|
95
|
+
begin open(manifest_url, &:read)
|
101
96
|
rescue
|
102
97
|
# Delete the autoproj directory
|
103
|
-
FileUtils.rm_rf
|
98
|
+
FileUtils.rm_rf "autoproj"
|
104
99
|
raise ConfigError.new, "cannot read file / URL #{manifest_url}, did you mean 'autoproj bootstrap VCSTYPE #{manifest_url}' ?"
|
105
100
|
end
|
106
101
|
|
@@ -122,9 +117,11 @@ module Autoproj
|
|
122
117
|
if args.first =~ /^(\w+)=/
|
123
118
|
# First argument is an option string, we are simply setting the
|
124
119
|
# options without changing the type/url
|
125
|
-
type
|
120
|
+
type = vcs.type
|
121
|
+
url = vcs.url
|
126
122
|
else
|
127
|
-
type
|
123
|
+
type = args.shift
|
124
|
+
url = args.shift
|
128
125
|
end
|
129
126
|
options = args
|
130
127
|
|
@@ -133,7 +130,7 @@ module Autoproj
|
|
133
130
|
if vcs.type == type && vcs.url == url
|
134
131
|
vcs = vcs.to_hash
|
135
132
|
options.each do |opt|
|
136
|
-
opt_name, opt_value = opt.split(
|
133
|
+
opt_name, opt_value = opt.split("=")
|
137
134
|
vcs[opt_name.to_sym] = opt_value
|
138
135
|
end
|
139
136
|
# Validate the VCS definition, but save the hash as-is
|
@@ -145,10 +142,10 @@ module Autoproj
|
|
145
142
|
else
|
146
143
|
# We will have to delete the current autoproj directory. Ask the user.
|
147
144
|
opt = Autoproj::BuildOption.new("delete current config", "boolean",
|
148
|
-
|
149
|
-
|
145
|
+
Hash[default: "false",
|
146
|
+
doc: "delete the current configuration ? (required to switch)"], nil)
|
150
147
|
|
151
|
-
return
|
148
|
+
return unless opt.ask(nil)
|
152
149
|
|
153
150
|
do_switch_config(true, type, url, *options)
|
154
151
|
ws.config.save
|
@@ -163,9 +160,7 @@ module Autoproj
|
|
163
160
|
vcs_def[:url] = VCSDefinition.to_absolute_url(url, ws.root_dir)
|
164
161
|
options.each do |opt|
|
165
162
|
name, value = opt.split("=")
|
166
|
-
if value =~ /^\d+$/
|
167
|
-
value = Integer(value)
|
168
|
-
end
|
163
|
+
value = Integer(value) if value =~ /^\d+$/
|
169
164
|
|
170
165
|
vcs_def[name] = value
|
171
166
|
end
|
@@ -193,7 +188,8 @@ module Autoproj
|
|
193
188
|
ops.update_configuration_repository(
|
194
189
|
vcs,
|
195
190
|
"autoproj main configuration",
|
196
|
-
config_dir
|
191
|
+
config_dir
|
192
|
+
)
|
197
193
|
|
198
194
|
# If the new tree has a configuration file, load it but override
|
199
195
|
# the already known parameters once it is loaded
|
@@ -207,9 +203,9 @@ module Autoproj
|
|
207
203
|
# options will not be reused
|
208
204
|
#
|
209
205
|
# TODO: cleanup the options to only keep the relevant ones
|
210
|
-
vcs_def = Hash[
|
206
|
+
vcs_def = Hash["type" => type, "url" => url]
|
211
207
|
options.each do |opt|
|
212
|
-
opt_name, opt_val = opt.split
|
208
|
+
opt_name, opt_val = opt.split "="
|
213
209
|
vcs_def[opt_name] = opt_val
|
214
210
|
end
|
215
211
|
# Validate the option hash, just in case
|
@@ -217,7 +213,6 @@ module Autoproj
|
|
217
213
|
# Save the new options
|
218
214
|
ws.config.set "manifest_source", vcs_def.dup, true
|
219
215
|
ws.config.save
|
220
|
-
|
221
216
|
rescue Exception => e
|
222
217
|
Autoproj.error "switching configuration failed: #{e.message}"
|
223
218
|
if backup_name
|
@@ -227,12 +222,8 @@ module Autoproj
|
|
227
222
|
end
|
228
223
|
raise
|
229
224
|
ensure
|
230
|
-
if backup_name
|
231
|
-
FileUtils.rm_rf backup_name
|
232
|
-
end
|
225
|
+
FileUtils.rm_rf backup_name if backup_name
|
233
226
|
end
|
234
227
|
end
|
235
228
|
end
|
236
229
|
end
|
237
|
-
|
238
|
-
|
@@ -15,13 +15,13 @@ module Autoproj
|
|
15
15
|
|
16
16
|
dump = JSON.dump(
|
17
17
|
"#{@name}_report" => {
|
18
|
-
|
19
|
-
|
18
|
+
"timestamp" => Time.now,
|
19
|
+
"packages" => info
|
20
20
|
}
|
21
21
|
)
|
22
22
|
|
23
23
|
FileUtils.mkdir_p File.dirname(@path)
|
24
|
-
File.open(@path,
|
24
|
+
File.open(@path, "w") do |io|
|
25
25
|
io.write dump
|
26
26
|
end
|
27
27
|
end
|
@@ -37,7 +37,7 @@ module Autoproj
|
|
37
37
|
@incremental_report.concat(
|
38
38
|
"#{prefix}\"#{autobuild_package.name}\": #{JSON.dump(new_metadata)}"
|
39
39
|
)
|
40
|
-
File.open(@path,
|
40
|
+
File.open(@path, "w") do |io|
|
41
41
|
io.write "{ \"#{@name}_report\": "\
|
42
42
|
"{\"timestamp\": #{JSON.dump(Time.now)}, \"packages\": {"
|
43
43
|
io.write(@incremental_report)
|