autobuild 1.2.15 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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