autobuild 1.2.15 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,3 +1,17 @@
1
+ == Version 1.3.0
2
+ * major update of the user interaction. Autobuild is now much less verbose, and
3
+ (hopefully) displays more useful error messages in most cases.
4
+ * make the library easier to use to build other tools (i.e. not so much
5
+ autobuild-centered anymore)
6
+ * add the :branch and :tag targets to the git importer. The git importer should
7
+ now be used as:
8
+ Autobuild.git(URL, :branch => branch_name)
9
+ OR
10
+ Autobuild.git(URL, :tag => tag_name_or_commit_id)
11
+
12
+ This puts it in line with the other importers which have a
13
+ Autobuild.importer_type(URL, option_hash) form.
14
+
1
15
  == Version 1.2.15
2
16
  * fix branch switching in the git importer: the build will not fail anymore if
3
17
  autobuild has to switch to an already existing branch
data/Rakefile CHANGED
@@ -1,17 +1,17 @@
1
1
  require 'hoe'
2
2
  require 'lib/autobuild'
3
3
 
4
- Hoe.new('autobuild', Autobuild::VERSION) do |p|
5
- p.author = "Sylvain Joyeux"
6
- p.email = "sylvain.joyeux@m4x.org"
4
+ Hoe.spec 'autobuild' do
5
+ developer "Sylvain Joyeux", "sylvain.joyeux@m4x.org"
7
6
 
8
- p.summary = 'Rake-based utility to build and install multiple packages with dependencies'
9
- p.description = p.paragraphs_of('README.txt', 2..5).join("\n\n")
10
- p.url = p.paragraphs_of('README.txt', 1).first.split(/\n/)[1..-1].map { |s| s.gsub('* ', '') }
11
- p.changes = p.paragraphs_of('Changes.txt', 0).join("\n\n")
7
+ self.summary = 'Rake-based utility to build and install multiple packages with dependencies'
8
+ self.description = self.paragraphs_of('README.txt', 2..5).join("\n\n")
9
+ self.url = self.paragraphs_of('README.txt', 1).first.split(/\n/)[1..-1].map { |s| s.gsub('* ', '') }
10
+ self.changes = self.paragraphs_of('Changes.txt', 0).join("\n\n")
12
11
 
13
- p.extra_deps << ['rake', '>= 0.7.0']
14
- p.extra_deps << 'rmail'
15
- p.extra_deps << 'daemons'
12
+ self.extra_deps <<
13
+ ['rake', '>= 0.7.0'] <<
14
+ ['rmail', '>= 1.0'] <<
15
+ ['daemons', '>= 0.0']
16
16
  end
17
17
 
@@ -15,6 +15,10 @@ DEFAULT_HTTP_PORT = 2000
15
15
  conffile, *packages = Autobuild.commandline(ARGV)
16
16
  Autobuild.packages = packages
17
17
 
18
+ Autobuild.env_set 'PATH', *(ENV['PATH'].split(":"))
19
+ Autobuild.env_set 'RUBYLIB', *(ENV['PATH'].split(":"))
20
+ Autobuild.env_set 'PKG_CONFIG_PATH', *(ENV['PATH'].split(":"))
21
+
18
22
  # make conffile an absolute path since daemonize mode makes
19
23
  # / the current directory
20
24
  conffile = File.expand_path(conffile, Dir.pwd)
@@ -28,34 +32,10 @@ begin
28
32
  Reporting.report do
29
33
  load conffile
30
34
 
31
- if Autobuild.mail[:to]
32
- if !Autobuild::HAS_RMAIL
33
- STDERR.puts "RMail is not available. Mail notification is disabled"
34
- else
35
- Reporting << MailReporter.new(Autobuild.mail)
36
- end
37
- end
38
-
39
- if Autobuild.only_doc
40
- targets = ['doc']
41
- else
42
- targets = ['import']
43
- targets += ['prepare', 'build'] if Autobuild.do_build
44
- targets << 'doc' if Autobuild.do_doc
45
- end
46
- targets.each do |phase|
47
- packages = Autobuild.packages
48
- packages = Autobuild.default_packages if packages.empty?
49
-
50
- if packages.empty?
51
- Rake::Task[phase].invoke
52
- else
53
- packages.each do |pkg|
54
- Rake::Task["#{pkg}-#{phase}"].invoke
55
- end
56
- end
57
- end
35
+ packages = Autobuild.packages
36
+ packages = Autobuild.default_packages if packages.empty?
58
37
 
38
+ Autobuild.apply(packages)
59
39
  Reporting.success
60
40
  end
61
41
  rescue ThreadError
@@ -1,8 +1,25 @@
1
- require 'autobuild/config'
2
- require 'autobuild/reporting'
3
- require 'autobuild/package'
4
-
5
1
  module Autobuild
6
- VERSION = "1.2.15" unless defined? Autobuild::VERSION
2
+ VERSION = "1.3.0" unless defined? Autobuild::VERSION
7
3
  end
8
4
 
5
+ require 'autobuild/config'
6
+ require 'autobuild/configurable'
7
+ require 'autobuild/environment'
8
+ require 'autobuild/exceptions'
9
+ require 'autobuild/import/cvs'
10
+ require 'autobuild/import/darcs'
11
+ require 'autobuild/importer'
12
+ require 'autobuild/import/git'
13
+ require 'autobuild/import/svn'
14
+ require 'autobuild/import/tar'
15
+ require 'autobuild/packages/autotools'
16
+ require 'autobuild/packages/cmake'
17
+ require 'autobuild/packages/genom'
18
+ require 'autobuild/packages/import'
19
+ require 'autobuild/packages/orogen'
20
+ require 'autobuild/packages/pkgconfig'
21
+ require 'autobuild/pkgconfig'
22
+ require 'autobuild/reporting'
23
+ require 'autobuild/subcommand'
24
+ require 'autobuild/timestamps'
25
+
@@ -201,5 +201,37 @@ module Autobuild
201
201
  args[0..-1]
202
202
  end
203
203
  end
204
+
205
+ def self.apply(packages, buildname = "autobuild")
206
+ if Autobuild.mail[:to]
207
+ if !Autobuild::HAS_RMAIL
208
+ STDERR.puts "RMail is not available. Mail notification is disabled"
209
+ else
210
+ Reporting << MailReporter.new(Autobuild.mail)
211
+ end
212
+ end
213
+
214
+ if Autobuild.only_doc
215
+ phases = ['doc']
216
+ else
217
+ phases = ['import']
218
+ phases += ['prepare', 'build'] if Autobuild.do_build
219
+ phases << 'doc' if Autobuild.do_doc
220
+ end
221
+ phases.each do |phase|
222
+ # We create a dummy task listing what needs to be done, and then we
223
+ # call it
224
+ targets = if packages.empty?
225
+ phase
226
+ else
227
+ packages.map { |pkg| "#{pkg}-#{phase}" }
228
+ end
229
+
230
+ task "#{buildname}-#{phase}" => targets
231
+ end
232
+ phases.each do |phase|
233
+ Rake::Task["#{buildname}-#{phase}"].invoke
234
+ end
235
+ end
204
236
  end
205
237
 
@@ -63,13 +63,15 @@ module Autobuild
63
63
  end
64
64
 
65
65
  def prepare
66
+ super
67
+
66
68
  file configurestamp do
67
69
  ensure_dependencies_installed
68
70
  configure
69
71
  end
70
72
  task "#{name}-prepare" => configurestamp
71
73
 
72
- source_tree srcdir do |pkg|
74
+ Autobuild.source_tree srcdir do |pkg|
73
75
  pkg.exclude << Regexp.new("^#{Regexp.quote(builddir)}")
74
76
  end
75
77
 
@@ -87,7 +89,7 @@ module Autobuild
87
89
 
88
90
  # Configure the builddir directory before starting make
89
91
  def configure
90
- touch_stamp(configurestamp)
92
+ Autobuild.touch_stamp(configurestamp)
91
93
  end
92
94
 
93
95
  # Do the build in builddir
@@ -96,7 +98,7 @@ module Autobuild
96
98
 
97
99
  # Install the result in prefix
98
100
  def install
99
- touch_stamp(installstamp)
101
+ Autobuild.touch_stamp(installstamp)
100
102
  end
101
103
  end
102
104
  end
@@ -1,37 +1,68 @@
1
1
  module Autobuild
2
- ## Adds an element to a path-like variable
2
+ @environment = Hash.new
3
+ class << self
4
+ attr_reader :environment
5
+ end
6
+
7
+ # Set a new environment variable
8
+ def self.env_set(name, *values)
9
+ environment.delete(name)
10
+ env_add(name, *values)
11
+ end
12
+ # Adds a new value to an environment variable
13
+ def self.env_add(name, *values)
14
+ set = environment[name]
15
+ if !set
16
+ set = Array.new
17
+ elsif !set.respond_to?(:to_ary)
18
+ set = [set]
19
+ end
20
+
21
+ values.concat(set)
22
+ @environment[name] = values
23
+ ENV[name] = values.join(":")
24
+ end
25
+
26
+ def self.env_add_path(name, path, *paths)
27
+ if File.directory?(path)
28
+ oldpath = environment[name]
29
+ if !oldpath || !oldpath.include?(path)
30
+ env_add(name, path)
31
+ if name == 'RUBYLIB'
32
+ $LOAD_PATH.unshift path
33
+ end
34
+ end
35
+ end
36
+ if !paths.empty?
37
+ env_add_path(name, *paths)
38
+ end
39
+ end
40
+
41
+ # DEPRECATED: use env_add_path instead
3
42
  def self.pathvar(path, varname)
4
43
  if File.directory?(path)
5
44
  if block_given?
6
45
  return unless yield(path)
7
46
  end
8
-
9
- oldpath = ENV[varname]
10
- if oldpath.nil? || oldpath.empty?
11
- ENV[varname] = path
12
- elsif ENV[varname] !~ /(^|:)#{Regexp.quote(path)}($|:)/
13
- ENV[varname] = "#{path}:#{oldpath}"
14
- end
47
+ env_add_path(varname, path)
15
48
  end
16
49
  end
17
50
 
18
- ## Updates the environment when a new prefix has been added
19
- # TODO: modularize that
51
+ # Updates the environment when a new prefix has been added
20
52
  def self.update_environment(newprefix)
21
- pathvar("#{newprefix}/bin", 'PATH')
22
- pathvar("#{newprefix}/lib/pkgconfig", 'PKG_CONFIG_PATH')
23
- pathvar("#{newprefix}/lib/ruby/1.8", 'RUBYLIB')
24
- pathvar("#{newprefix}/lib", 'RUBYLIB') do |path|
25
- if File.directory?("#{path}/ruby")
26
- false
27
- else
28
- !Dir["#{path}/**/*.rb"].empty?
29
- end
53
+ env_add_path('PATH', "#{newprefix}/bin")
54
+ env_add_path('PKG_CONFIG_PATH', "#{newprefix}/lib/pkgconfig")
55
+
56
+ # Validate the new rubylib path
57
+ new_rubylib = "#{newprefix}/lib"
58
+ if !File.directory?(File.join(new_rubylib, "ruby")) && !Dir["#{new_rubylib}/**/*.rb"].empty?
59
+ env_add_path('RUBYLIB', new_rubylib)
30
60
  end
31
61
 
32
62
  require 'rbconfig'
33
63
  ruby_arch = File.basename(Config::CONFIG['archdir'])
34
- pathvar("#{newprefix}/lib/ruby/1.8/#{ruby_arch}", 'RUBYLIB')
64
+ env_add_path("RUBYLIB", "#{newprefix}/lib/ruby/1.8")
65
+ env_add_path("RUBYLIB", "#{newprefix}/lib/ruby/1.8/#{ruby_arch}")
35
66
  end
36
67
  end
37
68
 
@@ -16,7 +16,7 @@ module Autobuild
16
16
  alias :exception_message :to_s
17
17
  def to_s
18
18
  if target && phase
19
- "#{target}: failed in #{phase} phase\n #{super}"
19
+ "#{target}: failed in #{phase} phase\n #{super}"
20
20
  elsif target
21
21
  "#{target}: #{super}"
22
22
  else
@@ -56,10 +56,29 @@ module Autobuild
56
56
  @status = status
57
57
  end
58
58
 
59
+ ERROR_LINES = 10
60
+
59
61
  def to_s
60
- prefix = "#{super}\n command '#{command}' failed"
61
- prefix << ": " + @orig_message if @orig_message
62
- prefix << "\n see #{File.basename(logfile)} for details\n"
62
+ prefix = super
63
+ if @orig_message
64
+ prefix << "\n #{@orig_message}"
65
+ end
66
+ prefix << "\n see #{logfile} for details"
67
+
68
+ # If we do not have a status, it means an error occured in the
69
+ # launching process. More importantly, it means we already have a
70
+ # proper explanation for it. Don't display the logfile at all.
71
+ if status
72
+ lines = File.readlines(logfile)
73
+ if lines.size > ERROR_LINES
74
+ lines = lines[-ERROR_LINES, ERROR_LINES]
75
+ end
76
+ prefix << "\n last #{lines.size} lines are:\n\n"
77
+ lines.each do |l|
78
+ prefix << " #{l}"
79
+ end
80
+ end
81
+ prefix
63
82
  end
64
83
  end
65
84
  end
@@ -62,10 +62,10 @@ module Autobuild
62
62
  cvsroot = @root
63
63
 
64
64
  FileUtils.mkdir_p(head) if !File.directory?(head)
65
- Dir.chdir(head) {
65
+ Dir.chdir(head) do
66
66
  options = [ @program, '-d', cvsroot, 'co', '-d', tail ] + @options_co + [ modulename ]
67
67
  Subprocess.run(package.name, :import, *options)
68
- }
68
+ end
69
69
  end
70
70
  end
71
71
 
@@ -1,6 +1,7 @@
1
1
  require 'fileutils'
2
2
  require 'autobuild/subcommand'
3
3
  require 'autobuild/importer'
4
+ require 'utilrb/kernel/options'
4
5
 
5
6
  module Autobuild
6
7
  class Git < Importer
@@ -13,12 +14,47 @@ module Autobuild
13
14
  # Autobuild.programs['git'] = 'my_git_tool'
14
15
  def initialize(repository, branch = nil, options = {})
15
16
  @repository = repository.to_str
16
- @branch = branch || 'master'
17
- super(options)
17
+
18
+ if branch.respond_to?(:to_hash)
19
+ options = branch.to_hash
20
+ branch = nil
21
+ end
22
+
23
+ if branch
24
+ STDERR.puts "WARN: the git importer now expects you to provide the branch as a named option"
25
+ STDERR.puts "WARN: this form is deprecated:"
26
+ STDERR.puts "WARN: Autobuild.git 'git://github.com/doudou/autobuild.git', 'master'"
27
+ STDERR.puts "WARN: and should be replaced by"
28
+ STDERR.puts "WARN: Autobuild.git 'git://github.com/doudou/autobuild.git', :branch => 'master'"
29
+ end
30
+
31
+ gitopts, common = Kernel.filter_options options, :branch => nil, :tag => nil, :commit => nil
32
+ if gitopts[:branch] && branch
33
+ raise ConfigException, "git branch specified with both the option hash and the explicit parameter"
34
+ end
35
+ branch = gitopts[:branch] || branch || 'master'
36
+ tag = gitopts[:tag]
37
+ commit = gitopts[:commit]
38
+
39
+ if (branch && commit) || (branch && commit) || (tag && commit)
40
+ raise ConfigException, "you can specify only a branch, tag or commit but not two or three at the same time"
41
+ end
42
+ @branch = branch
43
+ @commit = tag || commit
44
+ super(common)
18
45
  end
19
46
 
20
47
  attr_accessor :repository
48
+
49
+ # The branch this importer is tracking
50
+ #
51
+ # If set, tag has to be nil.
21
52
  attr_accessor :branch
53
+ # The commit we are pointing to. It can be either a commit ID or a tag
54
+ # name.
55
+ #
56
+ # If set, branch has to be nil.
57
+ attr_reader :commit
22
58
 
23
59
  # True if it is allowed to merge remote updates automatically. If false
24
60
  # (the default), the import will fail if the updates do not resolve as
@@ -28,22 +64,122 @@ module Autobuild
28
64
  # Set the merge flag. See #merge?
29
65
  def merge=(flag); @merge = flag end
30
66
 
67
+ # Raises ConfigException if the current directory is not a git
68
+ # repository
69
+ def validates_git_repository
70
+ if !File.directory?('.git')
71
+ raise ConfigException, "#{package.srcdir} is not a git repository"
72
+ end
73
+ end
74
+
75
+ # Fetches updates from the remote repository. Returns the remote commit
76
+ # ID on success, nil on failure. Expects the current directory to be the
77
+ # package's source directory.
78
+ def fetch_remote(package)
79
+ Dir.chdir(package.srcdir) do
80
+ validates_git_repository
81
+
82
+ Subprocess.run(package.name, :import, Autobuild.tool('git'), 'fetch', repository, branch)
83
+ if File.readable?( File.join('.git', 'FETCH_HEAD') )
84
+ fetch_commit = File.readlines( File.join('.git', 'FETCH_HEAD') ).
85
+ delete_if { |l| l =~ /not-for-merge/ }
86
+ if !fetch_commit.empty?
87
+ fetch_commit.first.split(/\s+/).first
88
+ end
89
+ end
90
+ end
91
+ end
92
+
93
+ # Returns a Importer::Status object that represents the status of this
94
+ # package w.r.t. the root repository
95
+ def status(package)
96
+ Dir.chdir(package.srcdir) do
97
+ remote_commit = fetch_remote(package)
98
+ if !remote_commit
99
+ return
100
+ end
101
+
102
+ status = merge_status(remote_commit)
103
+ `git diff --quiet`
104
+ if $?.exitstatus != 0
105
+ status.uncommitted_code = true
106
+ end
107
+ status
108
+ end
109
+ end
110
+
111
+ # Checks if the current branch is the target branch. Expects that the
112
+ # current directory is the package's directory
113
+ def on_target_branch?
114
+ current_branch = `git symbolic-ref HEAD`.chomp
115
+ current_branch == "refs/heads/#{branch}"
116
+ end
117
+
118
+ class Status < Importer::Status
119
+ attr_reader :fetch_commit
120
+ attr_reader :head_commit
121
+ attr_reader :common_commit
122
+
123
+ def initialize(status, remote_commit, local_commit, common_commit)
124
+ super()
125
+ @status = status
126
+ @fetch_commit = fetch_commit
127
+ @head_commit = head_commit
128
+ @common_commit = common_commit
129
+
130
+ if remote_commit != common_commit
131
+ @remote_commits = log(common_commit, remote_commit)
132
+ end
133
+ if local_commit != common_commit
134
+ @local_commits = log(common_commit, local_commit)
135
+ end
136
+ end
137
+
138
+ def needs_update?
139
+ status == Status::NEEDS_MERGE || status == Status::SIMPLE_UPDATE
140
+ end
141
+
142
+ def log(from, to)
143
+ `git log --pretty=format:"%h %cr %cn %s" #{from}..#{to}`.chomp.split("\n")
144
+ end
145
+ end
146
+
147
+ def merge_status(fetch_commit)
148
+ common_commit = `git merge-base HEAD #{fetch_commit}`.chomp
149
+ head_commit = `git rev-parse #{branch}`.chomp
150
+
151
+ status = if common_commit != fetch_commit
152
+ if common_commit == head_commit
153
+ Status::SIMPLE_UPDATE
154
+ else
155
+ Status::NEEDS_MERGE
156
+ end
157
+ else
158
+ if common_commit == head_commit
159
+ Status::UP_TO_DATE
160
+ else
161
+ Status::ADVANCED
162
+ end
163
+ end
164
+
165
+ Status.new(status, fetch_commit, head_commit, common_commit)
166
+ end
167
+
31
168
  def update(package)
32
169
  Dir.chdir(package.srcdir) do
33
- if !File.directory?('.git')
34
- raise ConfigException, "#{package.srcdir} is not a git repository"
170
+ validates_git_repository
171
+ fetch_commit = fetch_remote(package)
172
+ if !fetch_commit
173
+ return
35
174
  end
36
175
 
37
- # Fetch the remote reference
38
- Subprocess.run(package.name, :import, Autobuild.tool('git'), 'fetch', repository, branch)
39
- if !File.readable?( File.join('.git', 'FETCH_HEAD') )
176
+ # If we are tracking a commit/tag, just check it out and return
177
+ if commit
178
+ Subprocess.run(package.name, :import, Autobuild.tool('git'), 'checkout', commit)
40
179
  return
41
180
  end
42
181
 
43
- # Check that we are on the right branch, and if not simply
44
- # create it from FETCH_HEAD
45
- current_branch = `git symbolic-ref HEAD`.chomp
46
- if current_branch != "refs/heads/#{branch}"
182
+ if !on_target_branch?
47
183
  # Check if the target branch already exists. If it is the
48
184
  # case, check it out. Otherwise, create it.
49
185
  if File.file?(File.join(".git", "refs", "heads", branch))
@@ -53,21 +189,12 @@ module Autobuild
53
189
  end
54
190
  end
55
191
 
56
- # Now that we are on the right branch, update it
57
- fetch_commit = File.readlines( File.join('.git', 'FETCH_HEAD') ).
58
- delete_if { |l| l =~ /not-for-merge/ }
59
- return if fetch_commit.empty?
60
- fetch_commit = fetch_commit.first.split(/\s+/).first
61
-
62
- common_commit = `git merge-base HEAD #{fetch_commit}`.chomp
63
- head_commit = `git rev-parse #{branch}`.chomp
64
-
65
- if common_commit != fetch_commit
66
- if merge? || common_commit == head_commit
67
- Subprocess.run(package.name, :import, Autobuild.tool('git'), 'merge', fetch_commit)
68
- else
69
- raise "importing the current version would lead to a non fast-forward"
192
+ status = merge_status(fetch_commit)
193
+ if status.needs_update?
194
+ if !merge? && status.status == Status::NEEDS_MERGE
195
+ raise PackageException, "importing the current version would require a merge"
70
196
  end
197
+ Subprocess.run(package.name, :import, Autobuild.tool('git'), 'merge', fetch_commit)
71
198
  end
72
199
  end
73
200
  end
@@ -83,13 +210,20 @@ module Autobuild
83
210
  repository, package.srcdir)
84
211
 
85
212
  Dir.chdir(package.srcdir) do
213
+ # If we are tracking a commit/tag, just check it out
214
+ if commit
215
+ Subprocess.run(package.name, :import, Autobuild.tool('git'),
216
+ 'checkout', commit)
217
+ return
218
+ end
219
+
86
220
  current_branch = `git symbolic-ref HEAD`.chomp
87
221
  if current_branch == "refs/heads/#{branch}"
88
222
  Subprocess.run(package.name, :import, Autobuild.tool('git'),
89
- 'reset', '--hard', "autobuild/#{branch}")
223
+ 'reset', '--hard', "autobuild/#{branch}")
90
224
  else
91
225
  Subprocess.run(package.name, :import, Autobuild.tool('git'),
92
- 'checkout', '-b', branch, "autobuild/#{branch}")
226
+ 'checkout', '-b', branch, "autobuild/#{branch}")
93
227
  end
94
228
  end
95
229
  end
@@ -97,7 +231,7 @@ module Autobuild
97
231
 
98
232
  # Creates a git importer which gets the source for the given repository and branch
99
233
  # URL +source+. The allowed values in +options+ are described in SVN.new.
100
- def self.git(repository, branch, options = {})
234
+ def self.git(repository, branch = nil, options = {})
101
235
  Git.new(repository, branch, options)
102
236
  end
103
237
  end