autobuild 1.2.15 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -5,6 +5,38 @@ require 'autobuild/exceptions'
5
5
  # various RCS into the package source directory. A list of patches to apply
6
6
  # after the import can be given in the +:patches+ option.
7
7
  class Autobuild::Importer
8
+ # Instances of the Importer::Status class represent the status of a current
9
+ # checkout w.r.t. the remote repository.
10
+ class Status
11
+ # Remote and local are at the same point
12
+ UP_TO_DATE = 0
13
+ # Local contains all data that remote has, but has new commits
14
+ ADVANCED = 1
15
+ # Next update will require a merge
16
+ NEEDS_MERGE = 2
17
+ # Next update will be simple (no merge)
18
+ SIMPLE_UPDATE = 3
19
+
20
+ # The update status
21
+ attr_accessor :status
22
+ # True if there is code in the working copy that is not committed
23
+ attr_accessor :uncommitted_code
24
+
25
+ # An array of strings that represent commits that are in the remote
26
+ # repository and not in this one (would be merged by an update)
27
+ attr_accessor :remote_commits
28
+ # An array of strings that represent commits that are in the local
29
+ # repository and not in the remote one (would be pushed by an update)
30
+ attr_accessor :local_commits
31
+
32
+ def initialize
33
+ @status = -1
34
+ @uncommitted_code = false
35
+ @remote_commits = Array.new
36
+ @local_commits = Array.new
37
+ end
38
+ end
39
+
8
40
  # Creates a new Importer object. The options known to Importer are:
9
41
  # [:patches] a list of patch to apply after import
10
42
  #
@@ -20,10 +52,13 @@ class Autobuild::Importer
20
52
  srcdir = package.srcdir
21
53
  if File.directory?(srcdir)
22
54
  if Autobuild.do_update
55
+ Autobuild.progress "updating #{package.name}"
23
56
  update(package)
24
57
  patch(package)
25
58
  else
26
- puts "Not updating #{package.name}"
59
+ if Autobuild.verbose
60
+ puts " not updating #{package.name}"
61
+ end
27
62
  return
28
63
  end
29
64
 
@@ -31,6 +66,7 @@ class Autobuild::Importer
31
66
  raise ConfigException, "#{srcdir} exists but is not a directory"
32
67
  else
33
68
  begin
69
+ Autobuild.progress "checking out #{package.name}"
34
70
  checkout(package)
35
71
  patch(package)
36
72
  rescue Autobuild::Exception
@@ -68,6 +104,10 @@ class Autobuild::Importer
68
104
  end
69
105
  end
70
106
 
107
+ if !patches.empty?
108
+ Autobuild.progress "patching #{package.name}"
109
+ end
110
+
71
111
  # Do not be smart, remove all already applied patches
72
112
  # and then apply the new ones
73
113
  begin
@@ -11,6 +11,35 @@ module Autobuild
11
11
  # - import
12
12
  # - prepare
13
13
  # - build & install
14
+ #
15
+ # In the first stage checks the source out and/or updates it.
16
+ #
17
+ # In the second stage, packages create their dependency structure to handle
18
+ # specific build systems. For instance, it is there that build systems like
19
+ # CMake are handled so that reconfiguration happens if needed. In the same
20
+ # way, it is there that code generation will happen as well.
21
+ #
22
+ # Finally, the build stage actually calls the package's build targets (of
23
+ # the form "package_name-build", which will trigger the build if needed.
24
+ #
25
+ # <b>Autodetecting dependencies</b>
26
+ # There are two sides in dependency autodetection. The first side is that
27
+ # packages must declare what they provide. One example is the handling of
28
+ # pkgconfig dependencies: packages must declare that they provide a
29
+ # pkgconfig definition. This side of the autodetection must be done just
30
+ # after the package's import, by overloading the #import method:
31
+ #
32
+ # def import
33
+ # super
34
+ #
35
+ # # Do autodetection and call Package#provides
36
+ # end
37
+ #
38
+ # Note that, in most cases, the overloaded import method *must* begin with
39
+ # "super".
40
+ #
41
+ # The other side is the detection itself. That must be done by overloading
42
+ # the #prepare method.
14
43
  class Package
15
44
  @@packages = {}
16
45
  @@provides = {}
@@ -23,6 +52,9 @@ module Autobuild
23
52
  # set the installation directory. If a relative path is given,
24
53
  # it is relative to Autobuild.prefix. Defaults to ''
25
54
  attr_writer :prefix
55
+ # Sets the log directory. If no value is set, the package will use
56
+ # Autobuild.logdir
57
+ attr_writer :logdir
26
58
 
27
59
  # Sets importer object for this package. Defined for backwards compatibility.
28
60
  # Use the #importer attribute instead
@@ -44,7 +76,9 @@ module Autobuild
44
76
  # has finished. The path is absolute
45
77
  #
46
78
  # A package is sucessfully built when it is installed
47
- def installstamp; "#{Autobuild.logdir}/#{name}-#{STAMPFILE}" end
79
+ def installstamp
80
+ File.join(@logdir || Autobuild.logdir, "#{name}-#{STAMPFILE}")
81
+ end
48
82
 
49
83
  def initialize(spec)
50
84
  @dependencies = Array.new
@@ -66,17 +100,8 @@ module Autobuild
66
100
 
67
101
  @doc_dir ||= 'doc'
68
102
  @doc_target_dir ||= name
69
-
70
- # Declare the installation stampfile
71
- file installstamp do
72
- Dir.chdir(srcdir) do
73
- Autobuild.apply_post_install(name, @post_install)
74
- end
75
- end
76
- # Add dependencies declared in spec
77
- depends_on *depends if depends
78
103
 
79
- # Define the import task
104
+ # Define the default tasks
80
105
  task "#{name}-import" do import end
81
106
  task :import => "#{name}-import"
82
107
 
@@ -84,7 +109,7 @@ module Autobuild
84
109
  task "#{name}-prepare" => "#{name}-import" do prepare end
85
110
  task :prepare => "#{name}-prepare"
86
111
 
87
- task "#{name}-build" => ["#{name}-prepare", installstamp]
112
+ task "#{name}-build" => "#{name}-prepare"
88
113
  task :build => "#{name}-build"
89
114
 
90
115
  task(name) do
@@ -96,10 +121,34 @@ module Autobuild
96
121
  end
97
122
  end
98
123
  task :default => name
124
+
125
+ # The dependencies will be declared in the prepare phase, so save
126
+ # them there for now
127
+ @spec_dependencies = depends
99
128
  end
100
129
 
130
+ # Call the importer if there is one. Autodetection of "provides" should
131
+ # be done there as well. See the documentation of Autobuild::Package for
132
+ # more information.
101
133
  def import; @importer.import(self) if @importer end
102
- def prepare; end
134
+ # Create all the dependencies required to reconfigure and/or rebuild the
135
+ # package when required. The package's build target is called
136
+ # "package_name-build".
137
+ def prepare
138
+ super if defined? super
139
+
140
+ task "#{name}-build" => installstamp
141
+ # Declare the installation stampfile
142
+ file installstamp do
143
+ Dir.chdir(srcdir) do
144
+ Autobuild.apply_post_install(name, @post_install)
145
+ end
146
+ end
147
+ # Add dependencies declared in spec
148
+ depends_on *@spec_dependencies if @spec_dependencies
149
+
150
+ Autobuild.update_environment prefix
151
+ end
103
152
 
104
153
  # Directory in which the documentation target will have generated the
105
154
  # documentation (if any). The interpretation of relative directories
@@ -141,6 +190,9 @@ module Autobuild
141
190
  raise
142
191
  else
143
192
  STDERR.puts "W: failed to generate documentation for #{name}"
193
+ if e.kind_of?(SubcommandFailed)
194
+ STDERR.puts "W: see #{e.logfile} for more details"
195
+ end
144
196
  end
145
197
  end
146
198
  end
@@ -183,7 +235,9 @@ module Autobuild
183
235
  end
184
236
  end
185
237
 
186
- # This package depends on +packages+
238
+ # This package depends on +packages+. It means that its build will
239
+ # always be triggered after the packages listed in +packages+ are built
240
+ # and installed.
187
241
  def depends_on(*packages)
188
242
  packages.each do |p|
189
243
  p = p.to_s
@@ -198,7 +252,8 @@ module Autobuild
198
252
  end
199
253
  end
200
254
 
201
- # Declare that this package provides +packages+
255
+ # Declare that this package provides +packages+. In effect, the names
256
+ # listed in +packages+ are aliases for this package.
202
257
  def provides(*packages)
203
258
  packages.each do |p|
204
259
  p = p.to_s
@@ -51,8 +51,6 @@ module Autobuild
51
51
  @configureflags = []
52
52
 
53
53
  super
54
-
55
- Autobuild.update_environment(prefix)
56
54
  end
57
55
 
58
56
  def install_doc(relative_to = builddir)
@@ -63,6 +61,7 @@ module Autobuild
63
61
  def with_doc(target = 'doc')
64
62
  doc_task do
65
63
  Dir.chdir(builddir) do
64
+ Autobuild.progress "generating documentation for #{name}"
66
65
  Subprocess.run(name, 'doc', Autobuild.tool(:make), target)
67
66
  yield if block_given?
68
67
  end
@@ -120,6 +119,8 @@ module Autobuild
120
119
  end
121
120
 
122
121
  def prepare
122
+ super
123
+
123
124
  configureflags.flatten!
124
125
 
125
126
  # Check if config.status has been generated with the
@@ -146,7 +147,7 @@ module Autobuild
146
147
  configure
147
148
  end
148
149
 
149
- source_tree srcdir do |pkg|
150
+ Autobuild.source_tree srcdir do |pkg|
150
151
  pkg.exclude << Regexp.new("^#{Regexp.quote(builddir)}")
151
152
  end
152
153
  file buildstamp => [ srcdir, "#{builddir}/config.status" ] do
@@ -157,10 +158,10 @@ module Autobuild
157
158
 
158
159
  file installstamp => buildstamp do
159
160
  install
160
- Autobuild.update_environment(prefix)
161
161
  end
162
- end
163
162
 
163
+ Autobuild.update_environment(prefix)
164
+ end
164
165
 
165
166
  private
166
167
  # Adds a target to rebuild the autotools environment
@@ -180,6 +181,7 @@ module Autobuild
180
181
  using[:autogen] = %w{autogen autogen.sh}.find { |f| File.exists?(f) }
181
182
  end
182
183
 
184
+ Autobuild.progress "generating build system for #{name}"
183
185
  if using[:autogen]
184
186
  Subprocess.run(name, 'configure', File.expand_path(using[:autogen]))
185
187
  else
@@ -221,6 +223,7 @@ module Autobuild
221
223
  command = [ "#{srcdir}/configure", "--no-create", "--prefix=#{prefix}" ]
222
224
  command += Array[*configureflags]
223
225
 
226
+ Autobuild.progress "configuring build system for #{name}"
224
227
  Subprocess.run(name, 'configure', *command)
225
228
  }
226
229
  end
@@ -228,18 +231,21 @@ module Autobuild
228
231
  # Do the build in builddir
229
232
  def build
230
233
  Dir.chdir(builddir) {
234
+ Autobuild.progress "building #{name}"
231
235
  Subprocess.run(name, 'build', './config.status')
232
236
  Subprocess.run(name, 'build', Autobuild.tool(:make))
233
237
  }
234
- touch_stamp(buildstamp)
238
+ Autobuild.touch_stamp(buildstamp)
235
239
  end
236
240
 
237
241
  # Install the result in prefix
238
242
  def install
239
243
  Dir.chdir(builddir) {
244
+ Autobuild.progress "installing #{name}"
240
245
  Subprocess.run(name, 'install', Autobuild.tool(:make), 'install')
241
246
  }
242
- touch_stamp(installstamp)
247
+ Autobuild.touch_stamp(installstamp)
248
+ Autobuild.update_environment(prefix)
243
249
  end
244
250
  end
245
251
  end
@@ -39,6 +39,7 @@ module Autobuild
39
39
  def with_doc(target = 'doc')
40
40
  doc_task do
41
41
  Dir.chdir(builddir) do
42
+ Autobuild.progress "generating documentation for #{name}"
42
43
  Subprocess.run(name, 'doc', Autobuild.tool(:make), target)
43
44
  yield if block_given?
44
45
  end
@@ -65,6 +66,15 @@ module Autobuild
65
66
  end
66
67
  end
67
68
 
69
+ def import
70
+ super
71
+
72
+ Dir.glob(File.join(srcdir, "*.pc.in")) do |file|
73
+ file = File.basename(file, ".pc.in")
74
+ provides "pkgconfig/#{file}"
75
+ end
76
+ end
77
+
68
78
  def prepare
69
79
  super
70
80
 
@@ -115,6 +125,7 @@ module Autobuild
115
125
  end
116
126
  command << srcdir
117
127
 
128
+ Autobuild.progress "generating and configuring build system for #{name}"
118
129
  Subprocess.run(name, 'configure', *command)
119
130
  super
120
131
  end
@@ -123,18 +134,21 @@ module Autobuild
123
134
  # Do the build in builddir
124
135
  def build
125
136
  Dir.chdir(builddir) do
137
+ Autobuild.progress "building #{name}"
126
138
  if always_reconfigure || !File.file?('Makefile')
127
139
  Subprocess.run(name, 'build', Autobuild.tool(:cmake), '.')
128
140
  end
129
141
  Subprocess.run(name, 'build', Autobuild.tool(:make))
130
142
  end
131
- touch_stamp(buildstamp)
143
+ Autobuild.touch_stamp(buildstamp)
132
144
  end
133
145
 
134
146
  # Install the result in prefix
135
147
  def install
136
148
  Dir.chdir(builddir) do
149
+ Autobuild.progress "installing #{name}"
137
150
  Subprocess.run(name, 'install', Autobuild.tool(:make), 'install')
151
+ Autobuild.update_environment prefix
138
152
  end
139
153
  super
140
154
  end
@@ -89,10 +89,10 @@ module Autobuild
89
89
  genom_pkg = PkgConfig.new('genom')
90
90
 
91
91
  includedir = File.join(genom_pkg.includedir, 'genom')
92
- source_tree includedir
92
+ Autobuild.source_tree includedir
93
93
 
94
94
  canvasdir = File.join(genom_pkg.prefix, "share", "genom", genom_pkg.version);;
95
- source_tree canvasdir
95
+ Autobuild.source_tree canvasdir
96
96
 
97
97
  binary = File.join(genom_pkg.exec_prefix, "bin", "genom")
98
98
  file binary
@@ -121,6 +121,7 @@ module Autobuild
121
121
  file genomstamp => genom_dependencies
122
122
  file genomstamp => srcdir do
123
123
  Dir.chdir(srcdir) do
124
+ Autobuild.progress "generating GenoM files for #{package.name}"
124
125
  Subprocess.run(name, 'genom', *cmdline)
125
126
  end
126
127
  end
@@ -130,6 +131,7 @@ module Autobuild
130
131
  # configure does not depend on the .gen file
131
132
  # since the generation takes care of rebuilding configure
132
133
  # if .gen has changed
134
+ Autobuild.progress "generating build system for #{package.name}"
133
135
  Dir.chdir(srcdir) { Subprocess.run(name, 'genom', File.expand_path('autogen')) }
134
136
  end
135
137
 
@@ -16,15 +16,20 @@ module Autobuild
16
16
  def initialize(*args)
17
17
  @exclude = []
18
18
  super
19
+ end
20
+
21
+ def prepare
22
+ super
23
+
19
24
  exclude << Regexp.new("^#{Regexp.quote(installstamp)}")
20
25
 
21
- source_tree(srcdir) do |pkg|
26
+ Autobuild.source_tree(srcdir) do |pkg|
22
27
  pkg.exclude.concat exclude
23
28
  exclude.freeze
24
29
  end
25
30
 
26
31
  file installstamp => srcdir do
27
- touch_stamp installstamp
32
+ Autobuild.touch_stamp installstamp
28
33
  end
29
34
  end
30
35
  end
@@ -3,11 +3,141 @@ module Autobuild
3
3
  Orogen.new(opts, &proc)
4
4
  end
5
5
 
6
+
7
+ # This discards everything but the calls to import_types_from,
8
+ # using_task_library and using_toolkit. This is used to automatically
9
+ # discover the dependencies without resorting to an actual build
10
+ class FakeOrogenEnvironment
11
+ class BlackHole
12
+ def initialize(*args)
13
+ end
14
+ def method_missing(*args)
15
+ self
16
+ end
17
+ def self.method_missing(*args)
18
+ self
19
+ end
20
+ def self.const_missing(*args)
21
+ self
22
+ end
23
+ end
24
+ StaticDeployment = BlackHole
25
+ StaticDeployment::Logger = BlackHole
26
+ TaskContext = BlackHole
27
+
28
+ class FakeDeployment
29
+ attr_reader :env
30
+ def initialize(env, name)
31
+ @env = env
32
+ env.provides << "pkgconfig/orogen-#{name}"
33
+ end
34
+ def add_default_logger
35
+ env.using_task_library 'logger'
36
+ BlackHole
37
+ end
38
+ def task(*args)
39
+ method_missing(*args)
40
+ end
41
+ def const_missing(*args)
42
+ BlackHole
43
+ end
44
+ def method_missing(*args)
45
+ BlackHole
46
+ end
47
+ def self.const_missing(*args)
48
+ BlackHole
49
+ end
50
+ end
51
+
52
+ def self.orocos_target
53
+ user_target = ENV['OROCOS_TARGET']
54
+ if @orocos_target
55
+ @orocos_target.dup
56
+ elsif user_target && !user_target.empty?
57
+ user_target
58
+ else
59
+ 'gnulinux'
60
+ end
61
+ end
62
+
63
+ attr_reader :project_name, :dependencies, :provides
64
+ def self.load(file)
65
+ FakeOrogenEnvironment.new.load(file)
66
+ end
67
+
68
+ def initialize
69
+ @dependencies = Array.new
70
+ @provides = Array.new
71
+ end
72
+
73
+ def load(file)
74
+ Kernel.eval(File.read(file), binding)
75
+ self
76
+ end
77
+
78
+ def name(name)
79
+ @project_name = name
80
+ nil
81
+ end
82
+ def using_library(*names)
83
+ @dependencies.concat(names)
84
+ nil
85
+ end
86
+ def using_toolkit(*names)
87
+ names = names.map { |n| "#{n}-toolkit-#{FakeOrogenEnvironment.orocos_target}" }
88
+ @dependencies.concat(names)
89
+ nil
90
+ end
91
+ def using_task_library(*names)
92
+ names = names.map { |n| "#{n}-tasks-#{FakeOrogenEnvironment.orocos_target}" }
93
+ @dependencies.concat(names)
94
+ nil
95
+ end
96
+
97
+ def static_deployment(name = nil, &block)
98
+ deployment("test_#{project_name}", &block)
99
+ end
100
+ def deployment(name, &block)
101
+ deployment = FakeDeployment.new(self, name)
102
+ deployment.instance_eval(&block) if block
103
+ deployment
104
+ end
105
+
106
+ def self.const_missing(*args)
107
+ BlackHole
108
+ end
109
+ def const_missing(*args)
110
+ BlackHole
111
+ end
112
+ def method_missing(*args)
113
+ BlackHole
114
+ end
115
+ end
116
+
117
+ # This class represents packages generated by orogen. oroGen is a
118
+ # specification and code generation tool for the Orocos/RTT integration
119
+ # framework. See http://doudou.github.com/orogen for more information.
120
+ #
121
+ # This class extends the CMake package class to handle the code generation
122
+ # step. Moreover, it will load the orogen specification and automatically
123
+ # add the relevant pkg-config dependencies as dependencies.
124
+ #
125
+ # This requires that the relevant packages define the pkg-config definitions
126
+ # they install in the pkgconfig/ namespace. It means that a "driver/camera"
127
+ # package (for instance) that installs a "camera.pc" file will have to
128
+ # provide the "pkgconfig/camera" virtual package. This is done automatically
129
+ # by the CMake package handler if the source contains a camera.pc.in file,
130
+ # but can also be done manually with a call to Package#provides:
131
+ #
132
+ # pkg.provides "pkgconfig/camera"
133
+ #
6
134
  class Orogen < CMake
7
135
  class << self
8
136
  attr_accessor :corba
9
137
  end
10
138
 
139
+ attr_reader :orogen_spec
140
+
11
141
  attr_accessor :corba
12
142
 
13
143
  attr_accessor :orogen_file
@@ -16,18 +146,6 @@ module Autobuild
16
146
  super
17
147
 
18
148
  @orogen_file ||= "#{File.basename(name)}.orogen"
19
-
20
- # Find out where orogen is, and make sure the configurestamp depend
21
- # on it. Ignore if orogen is too old to have a --base-dir option
22
- orogen_root = File.join(`orogen --base-dir`.chomp, 'orogen')
23
- if !orogen_root.empty?
24
- file genstamp => Autobuild.source_tree(orogen_root)
25
- end
26
-
27
- file configurestamp => genstamp
28
- file genstamp => File.join(srcdir, orogen_file) do
29
- regen
30
- end
31
149
  end
32
150
 
33
151
  def depends_on(*packages)
@@ -38,6 +156,43 @@ module Autobuild
38
156
  end
39
157
  end
40
158
 
159
+ def import
160
+ super
161
+
162
+ @orogen_spec = FakeOrogenEnvironment.load(File.join(srcdir, orogen_file))
163
+ provides "pkgconfig/#{orogen_spec.project_name}-toolkit-#{FakeOrogenEnvironment.orocos_target}"
164
+ provides "pkgconfig/#{orogen_spec.project_name}-tasks-#{FakeOrogenEnvironment.orocos_target}"
165
+ orogen_spec.provides.each do |name|
166
+ provides name
167
+ end
168
+ end
169
+
170
+ def prepare
171
+ super
172
+
173
+ # If required, load the component's specification and add
174
+ # dependencies based on the orogen specification.
175
+ orogen_spec.dependencies.each do |pkg_name|
176
+ target = "pkgconfig/#{pkg_name}"
177
+ if Autobuild::Package[target]
178
+ depends_on target
179
+ end
180
+ end
181
+
182
+ # Find out where orogen is, and make sure the configurestamp depend
183
+ # on it. Ignore if orogen is too old to have a --base-dir option
184
+ orogen_root = `orogen --base-dir`.chomp
185
+ if orogen_root.empty?
186
+ raise ConfigException, "cannot find the orogen tool"
187
+ end
188
+ orogen_root = File.join(orogen_root, 'orogen')
189
+ file genstamp => Autobuild.source_tree(orogen_root)
190
+
191
+ file configurestamp => genstamp
192
+ file genstamp => File.join(srcdir, orogen_file) do
193
+ regen
194
+ end
195
+ end
41
196
  def genstamp; File.join(srcdir, '.orogen', 'orogen-stamp') end
42
197
 
43
198
  def regen
@@ -45,9 +200,10 @@ module Autobuild
45
200
  cmdline << '--corba' if corba
46
201
  cmdline << orogen_file
47
202
 
203
+ Autobuild.progress "generating oroGen project #{name}"
48
204
  Dir.chdir(srcdir) do
49
205
  Subprocess.run name, 'orogen', *cmdline
50
- touch_stamp genstamp
206
+ Autobuild.touch_stamp genstamp
51
207
  end
52
208
  end
53
209
  end