autobuild 0.1

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.
data/README ADDED
@@ -0,0 +1,284 @@
1
+ = Introduction
2
+ == What's autobuild ?
3
+ Autobuild is a builder for a set of software packages. It takes as input a yaml config file as input and
4
+
5
+ * imports the package from a SCM or (optionnaly) updates it
6
+ * configures it (for instance for autoconf-based packages)
7
+ * builds and installs it
8
+
9
+ It takes the dependencies between packages into account in its build process.
10
+
11
+ == Example config explained
12
+
13
+ 1. This section is not used at all by autobuild. It is only here to define references
14
+ used in the reste of the config file
15
+
16
+ variables:
17
+ - &openrobots ':ext:sjoyeux@cvs.laas.fr/cvs/openrobots'
18
+ - &openprs_cvs ':ext:sjoyeux@cvs.laas.fr:/usr/local/openprs/CVS-Repository'
19
+ - &compilers [ "CC=gcc-3.3", "CXX=g++-3.3" ]
20
+ - &global_prefix /home/doudou/laas/openrobots
21
+
22
+ 2. <tt>autobuild-config</tt> holds the configuration of the build tool itself. See the
23
+ <b>Autobuild configuration</b> section for the available configuration options.
24
+
25
+ autobuild-config:
26
+ srcdir: &srcdir /home/doudou/laas/openrobots/tools
27
+ prefix: &prefix /home/doudou/laas/openrobots/build/tools
28
+ clean-log: true
29
+
30
+ mail:
31
+ to: sjoyeux@laas.fr
32
+
33
+ environment:
34
+ PATH: [ "/bin", "/usr/bin" ]
35
+ LD_LIBRARY_PATH:
36
+ PKG_CONFIG_PATH:
37
+
38
+ 3. +programs+ defines the tools autobuild should use instead of the its default values.
39
+ See each the rest of the documentation to know what values are used.
40
+
41
+ programs:
42
+ aclocal: "aclocal-1.9"
43
+ automake: "automake-1.9"
44
+
45
+
46
+ 4. the +packages+ sections is where packages are defined
47
+
48
+ packages:
49
+
50
+ 5. options in <tt>common-config</tt> are merged in each package configuration. See
51
+ <b>Configuring packages</b> for the detailed merging rules.
52
+
53
+ common-config:
54
+ configureflags: *compilers
55
+ prefix: ""
56
+
57
+ 6. Tell autobuild to build the pocolibs package. Two common options here:
58
+ *type*:: the builder to use. Available package types are currently +autotools+,
59
+ +import+ and +genom+ (a tool used at my lab;)). See the <b>Available
60
+ package types</b> section for their configuration.
61
+ *import*:: the importer to use. Source definition for the importer is always in
62
+ the +source+ option.
63
+
64
+ pocolibs:
65
+ type: autotools
66
+ import: cvs
67
+ source: [ *openrobots, pocolibs ]
68
+
69
+
70
+ 7. Autobuild takes the dependencies between packages into account. Just list in +depends+
71
+ the packages which should be installed before this one is built.
72
+
73
+ genom:
74
+ type: autotools
75
+ import: cvs
76
+ source: [ *openrobots, genom ]
77
+ depends: pocolibs
78
+
79
+
80
+
81
+
82
+
83
+ = Configuration file
84
+
85
+ The config file is {a Yaml file}[http://yaml4r.sourceforge.net/cookbook]. It is a hash where
86
+ each element is a section:
87
+
88
+ section1:
89
+ option1: value
90
+ option2: value
91
+ section2:
92
+
93
+ In the documentation, I'll sometime use paths to reference the config options. For instance,
94
+ +option1+ is <tt>section1/option1</tt>. The +to+ option in the configuration example
95
+ is <tt>autobuild-config/mail/to</tt>
96
+
97
+ Autobuild uses three sections:
98
+
99
+ <b>autobuild-config</b>:: main autobuild configuration. See <b>Autobuild configuration</b> below
100
+ *programs*:: the programs used by importers and builders. See each tool
101
+ documentation for the list of used options
102
+ *packages*:: the list of packages to build. See <b>Package configuration</b> below
103
+
104
+
105
+ == Autobuild configuration (<tt>autobuild-config</tt>)
106
+ === Misc options
107
+ *noupdate*:: if true, do not update the packages that are already imported (default: false). You
108
+ can also add the <tt>--noupdate</tt> on the command line
109
+
110
+ === Directories (<tt>autobuild-config/srcdir</tt>, <tt>autobuild-config/prefix</tt> and <tt>autobuild-config/logdir</tt>)
111
+ *srcdir*:: the path where programs are to be imported. See <b>Packages configuration</b> for
112
+ more information on how this option is used.
113
+ *prefix*:: the path where programs are to be installed. See <b>Packages configuration</b> for more
114
+ information on how this option is used.
115
+ *logdir*:: by default, autobuild does not displays the output of each subcommand it launches.
116
+ Insteads, it saves this output in log files. These log files are saved in +logdir+.
117
+ The default value is <tt>prefix/autobuild</tt>
118
+ <b>clean-log</b>:: if we must remove all logfiles before each autobuild run or if we should append to them. The
119
+ default is +true+, that is log files are removed. Note that if mailing is active, all log
120
+ files are attached to notification mail.
121
+
122
+
123
+ === Environment (<tt>autobuild-config/environment</tt>)
124
+ The <tt>autobuild-config/environment</tt> section lists the initial values for
125
+ the environment variables. While packages builders shall update the environment
126
+ variables as needed, you can have to set up a proper initial environment. Arrays
127
+ are converted into strings by joining elements by ':'
128
+
129
+ For instance
130
+ autobuild-config:
131
+ environment:
132
+ PATH: [ "/bin", "/usr/bin" ]
133
+ LD_LIBRARY_PATH:
134
+ PKG_CONFIG_PATH:
135
+
136
+ sets
137
+
138
+ PATH="/bin:/usr/bin"
139
+ LD_LIBRARY_PATH=""
140
+ PKG_CONFIG_PATH=""
141
+
142
+
143
+ === Mail (<tt>autobuild-config/mail</tt>)
144
+ If you want to receive a mail when the build has finished (on success and failures), set
145
+ at least the +to+ option, like this:
146
+
147
+ autobuild-config:
148
+ mail:
149
+ from: autobuild@mymachine.rubyrules.org
150
+ to: myself+autobuild@rubyrules.org
151
+ smtp: localhost
152
+
153
+ Other options are:
154
+ *from*:: what to set in the <tt>From:</tt> field, default is <tt>autobuild@hostname</tt>
155
+ *smtp*:: the stmp server to use. Default is +localhost+
156
+
157
+ All log files relative to the current build are attached to the mail.
158
+
159
+
160
+
161
+ == Configuring packages (<tt>packages/*</tt>)
162
+ === The <tt>packages/common-config</tt> section
163
+ If you want to add common options in each package, just set it them here. Note that the options
164
+ are /merged/ in the package config. It neither replaces the values in the package nor be replaced
165
+ by them. The merging strategy depends on the way the <tt>common-config</tt> is specified:
166
+ * if it is an array, the package option is converted to an array and the common option is appended
167
+ * if it is a string, the package option is converted to a string and the common option is appended
168
+ with a space inserted between the two
169
+ * if it is a boolean value, it is <em>overriden</em> by the package value
170
+ * any other values are forbidden
171
+
172
+ To ease the config file writing, all options that accept arrays also accept a string when there
173
+ is only one element. <b>Do not</b> do that in the <tt>common-config</tt> option.
174
+
175
+ === Package definition
176
+ All subsections of the <tt>packages/</tt> section but <tt>common-config</tt> are package definitions
177
+ The section name is used as the package name. The package definition sets up the importer to get the
178
+ source and the builder to build and install it.
179
+
180
+ *type*:: the package type. See the <b>Available package types</b> sections
181
+
182
+ *srcdir*:: where sources are to be imported. If this is a relative path, it is relative to the global
183
+ <tt>/autobuild-config/srcdir</tt> option. Otherwise, the absolute path is used. If no +srcdir+
184
+ is given, the package name is used, so that the default import dir is
185
+ <em>global srcdir</em>/<em>package_name</em>.
186
+
187
+ *Note* because of most SCM operations, it is forbidden that two packages have the same srcdir.
188
+ Empty srcdir are forbidden for the same reason.
189
+
190
+ *importer*::
191
+ the importer type. For now, only +cvs+ and +svn+ are available. See the <b>Available importers</b>
192
+ section.
193
+
194
+ <b>importer/source</b>::
195
+ where the importer should get the sources. The format of this options depends on
196
+ the importer used.
197
+
198
+ *prefix*:: where the program is to be installed. If this is a relative path, it is relative to the global
199
+ <tt>/autobuild-config/prefix</tt> option. Otherwise, the absolute path is used. If no prefix
200
+ is given, the package name is used, so that the default install dir is
201
+ <em>global prefix</em>/<em>package name</em>
202
+
203
+ *depends*:: the array of packages that should be built and installed before this one is built. <tt>depends: [ foo ]</tt>
204
+ is equivalent to <tt>depends: foo</tt>. To make the use of +depends+ in the <tt>common-config</tt>
205
+ section possible, this package name is automatically removed from the package +depends+ array.
206
+ *provides*:: defines aliases for this package. As for +depends+, an array with only element can be replaced
207
+ by the simple value.
208
+
209
+ == Available importers
210
+ === CVS (<tt>type: cvs</tt>)
211
+ *source*:: the source specification is a [ repository, module ] array
212
+ *cvsup*:: options to add to cvs up. Defaults to '-dP'
213
+ *cvsco*:: options to add to cvs ci, Defaults to -P
214
+
215
+ === Subversion (<tt>type: svn</tt>)
216
+ *source*:: the svn URL. To ease the Yaml nodes reference (the <tt>*ref</tt> form), it can be an array
217
+ which is then converted into a path by joining the elements with '/'. For instance:
218
+
219
+ packages:
220
+ foo:
221
+ import: svn
222
+ source: [ *my_home_repository, /trunk/foo ]
223
+
224
+ *svnup*:: options to add to svn up. Defaults to ''
225
+ *svnco*:: options to add to svn co. Defaults to ''
226
+
227
+ == Available package types (<tt>packages/</tt><em>name</em><tt>/type</tt>)
228
+ === Source only (<tt>type: import</tt>)
229
+ Use +import+ if you need the package sources but don't need to build it. You just need
230
+ to set up the importer and +srcdir+. +prefix+ is ignored.
231
+
232
+ packages:
233
+ bar:
234
+ type: import
235
+ import: cvs
236
+ source: [ *my_repository, "bar" ]
237
+ srcdir: &bar_srcdir bar
238
+
239
+ === Autotools (<tt>type: autotools</tt>)
240
+ Use this to build GNU autotools-based packages. This handles autoconf-only packages as
241
+ well as automake-based packages.
242
+
243
+ ==== Configuration programs
244
+ The <tt>autotools</tt> packages use four programs: *autoheader*, *autoconf*, *aclocal*,
245
+ *automake*. The default values can be overriden in the <tt>/programs</tt> section. For
246
+ instance, to be sure that automake 1.9 is used, you set
247
+
248
+ programs:
249
+ automake: automake-1.9
250
+
251
+ Autobuild tries to detect what tools it should run
252
+ * +aclocal+ and +autoheader+ are always used
253
+ * +autoconf+ is used if there is <tt>configure.ac</tt> or <tt>configure.in</tt> in the import dir
254
+ * +automake+ is used if there is a <tt>Makefile.am</tt> file in the import dir
255
+ * you can force to enable or disable any of these four tools in each package config by setting the tool
256
+ flag to true or false. For instance, if you don't want package +foo+ to use automake, you say
257
+
258
+ packages:
259
+ foo:
260
+ automake: false
261
+
262
+ ==== Build programs
263
+ The only program used during the build phase is +make+. The make command can too be overriden in the
264
+ <tt>programs</tt> section.
265
+
266
+ programs:
267
+ make: gnumake
268
+
269
+ ==== Other options
270
+ *builddir*:: the directory where the build takes place. For now, it has to be a relative path,
271
+ which is added to the import dir. The default value is "build"
272
+ *configureflags*:: array of options to add to the +configure+ command line. To ease the reuse of
273
+ Yaml references (the <tt>*label</tt> form), you can use an array of arrays, in which
274
+ case the second array levels are joined using ''. For example if our +foo+ package
275
+ needs the source of the +bar+ package we added as an import package earlier,
276
+
277
+ configureflags: [ [ --with-bar-source=, *srcdir, '/', *bar_srcdir ] ]
278
+ depends: bar
279
+
280
+ = Copyright and license
281
+ Author:: Sylvain Joyeux <sylvain.joyeux@m4x.org>
282
+ Copyright:: Copyright (c) 2005 Sylvain Joyeux
283
+ License:: GPL
284
+
data/bin/autobuild ADDED
@@ -0,0 +1,81 @@
1
+ #! /usr/bin/ruby -w
2
+
3
+ # :main: README
4
+
5
+ require 'rake'
6
+ require 'ostruct'
7
+ require 'optparse'
8
+
9
+ require 'autobuild/config'
10
+ require 'autobuild/logging'
11
+
12
+ def parse_options(args)
13
+ options = OpenStruct.new
14
+ options.noupdate = false
15
+ options.srcdir = nil
16
+ options.prefix = nil
17
+ options.builddir = "build"
18
+ options.logdir = nil
19
+ $VERBOSE = false
20
+
21
+ parser = OptionParser.new do |opts|
22
+ opts.banner = "Usage: autobuild [options] config.yml"
23
+ opts.separator ""
24
+
25
+ opts.on("--srcdir PATH", "Find or imports sources in PATH") do |p|
26
+ options.srcdir = p
27
+ end
28
+
29
+ opts.on("--prefix PATH", "Packages are installed in PATH") do |p|
30
+ options.prefix = p
31
+ options.logdir = "#{p}/autobuild"
32
+ end
33
+
34
+ opts.on("--logdir", "Where logs are saved (default: <prefix>/autobuild)") do |p|
35
+ options.logdir = p
36
+ end
37
+
38
+ opts.on("--noupdate", "Do not update already checked-out sources") do
39
+ options.noupdate = true
40
+ end
41
+
42
+ opts.on("--verbose", "Display output of commands on stdout") do
43
+ $VERBOSE = true
44
+ end
45
+
46
+ opts.on_tail("-h", "--help", "Show this message") do
47
+ puts opts
48
+ exit
49
+ end
50
+ end
51
+
52
+ parser.parse!(args)
53
+ if !args[0]
54
+ puts parser
55
+ exit
56
+ end
57
+
58
+ [ options, args[0], args[1..-1] ]
59
+ end
60
+
61
+ # Load the command line options
62
+ options, conffile, targets = parse_options(ARGV)
63
+ Config.load(conffile, options)
64
+
65
+ $DEBUG = true
66
+ if $DEBUG
67
+ $trace = true
68
+ end
69
+
70
+ begin
71
+ if targets.empty?
72
+ Task[:default].invoke
73
+ else
74
+ targets.each { |t| Task[t.to_sym].invoke }
75
+ end
76
+ success
77
+ rescue BuildException => error
78
+ error(error, "Error during build of #{error.target}")
79
+ exit(1)
80
+ end
81
+
@@ -0,0 +1,112 @@
1
+ require 'yaml'
2
+ require 'pathname'
3
+
4
+ require 'autobuild/logging'
5
+ require 'autobuild/package'
6
+ require 'autobuild/importer'
7
+
8
+ module Config
9
+ def self.load(conffile, user_options)
10
+ data = YAML.load( File.open(conffile) )
11
+
12
+ get_autobuild_config(data, user_options)
13
+ get_package_config(data)
14
+ rescue ConfigException => error
15
+ error(error, "Error in config file '#{conffile}'")
16
+ exit(1)
17
+ rescue ImportException => error
18
+ error(error, "Error: unable to import #{p}")
19
+ exit(1)
20
+ end
21
+
22
+ def self.get_autobuild_config(data, options)
23
+ $PROGRAMS = (data["programs"] or "make")
24
+
25
+ setup = data["autobuild-config"]
26
+ raise ConfigException, "no autobuild-config block" if !setup
27
+
28
+ $SRCDIR = (options.srcdir or setup["srcdir"])
29
+ $PREFIX = (options.prefix or setup["prefix"])
30
+ if !$SRCDIR || !$PREFIX
31
+ raise ConfigException, "you must at least set srcdir and prefix in the config files"
32
+ end
33
+
34
+ $LOGDIR = (options.logdir or setup["logdir"] or "#{$PREFIX}/autobuild")
35
+
36
+ FileUtils.mkdir_p $SRCDIR if !File.directory?($SRCDIR)
37
+ FileUtils.mkdir_p $LOGDIR if !File.directory?($LOGDIR)
38
+ if setup["clean-log"]
39
+ puts "Cleaning log dir #{$LOGDIR}"
40
+ FileUtils.rm_rf Dir.glob("#{$LOGDIR}/*")
41
+ end
42
+
43
+ $MAIL = setup["mail"]
44
+ $NOUPDATE = (options.noupdate or setup["noupdate"] or false)
45
+
46
+ envvars = setup["environment"]
47
+ envvars.each { |k, v|
48
+ ENV[k] = ( v.to_a.collect { |path| path.to_a.join("") }.join(":") )
49
+ }
50
+ end
51
+
52
+ def self.add_package(name, config)
53
+ # Get the package type
54
+ package_type = config[:type].to_sym
55
+ require "autobuild/packages/#{package_type}"
56
+
57
+ # Build the importer object, if there is one
58
+ import_type = config[:import]
59
+ if import_type
60
+ require "autobuild/import/#{import_type}"
61
+ if !config.has_key?(:source)
62
+ raise ConfigException, "missing 'source' option in the '#{name}' package description"
63
+ end
64
+
65
+ config[:import] = Import.method(import_type).call(config[:source], config)
66
+ end
67
+
68
+ # Set the default dir if needed
69
+ config[:srcdir] ||= name
70
+ config[:prefix] ||= name
71
+
72
+ # Build the rake rules for this package
73
+ Package.build(package_type, name, config)
74
+ end
75
+
76
+ # Get the package config
77
+ def self.get_package_config(data)
78
+ setup = data["packages"]
79
+
80
+ # Get the common config block
81
+ common_config = Hash.new
82
+ setup["common-config"].each { |k, v| common_config[k.to_sym] = v } if setup.has_key?("common-config")
83
+
84
+ setup.each do |p, yml_config|
85
+ next if p == "common-config"
86
+
87
+ # Change keys into symbols
88
+ config = {}
89
+ yml_config.each do |k, v|
90
+ config[k.to_sym] = v
91
+ end
92
+
93
+ # Merge the common config
94
+ config = config.merge(common_config) { |k, v1, v2|
95
+ if v2.respond_to?(:to_ary)
96
+ v1.to_a | v2.to_ary
97
+ elsif v2.respond_to?(:to_str)
98
+ v1.to_s + " " + v2.to_str
99
+ end
100
+ }
101
+ # Remove p -> p dependency which may come from common_config
102
+ if config.has_key?(:depends)
103
+ config[:depends] = config[:depends].to_a.find_all { |el| el != p }
104
+ end
105
+
106
+ add_package(p, config)
107
+ end
108
+ end
109
+
110
+ private_class_method :get_autobuild_config, :get_package_config, :add_package
111
+ end
112
+
@@ -0,0 +1,16 @@
1
+ def pathvar(path, varname)
2
+ if File.directory?(path)
3
+ oldpath = ENV[varname]
4
+ if oldpath.empty?
5
+ ENV[varname] = path
6
+ else
7
+ ENV[varname] = "#{oldpath}:#{path}"
8
+ end
9
+ end
10
+ end
11
+
12
+ def update_environment(newprefix)
13
+ pathvar("#{newprefix}/bin", 'PATH')
14
+ pathvar("#{newprefix}/lib/pkgconfig", 'PKG_CONFIG_PATH')
15
+ end
16
+
@@ -0,0 +1,47 @@
1
+ require 'autobuild/subcommand'
2
+ require 'autobuild/importer'
3
+
4
+ class CVSImporter < Importer
5
+ def initialize(root, name, options)
6
+ @root = root
7
+ @module = name
8
+
9
+ @program = ($PROGRAMS[:cvs] || 'cvs')
10
+ @up = (options[:cvsup] || '-dP')
11
+ @co = (options[:cvsco] || '-P')
12
+ end
13
+
14
+ private
15
+
16
+ def update(package)
17
+ Dir.chdir(package.srcdir) {
18
+ begin
19
+ subcommand(package.target, 'cvs', "#{@program} up #{@up}")
20
+ rescue SubcommandFailed => e
21
+ raise ImportException.new(e), "failed to update #{modulename}"
22
+ end
23
+ }
24
+ end
25
+
26
+ def checkout(package)
27
+ head, tail = File.split(package.srcdir)
28
+ cvsroot = @root
29
+ modulename = @module
30
+
31
+ FileUtils.mkdir_p(head) if !File.directory?(head)
32
+ Dir.chdir(head) {
33
+ begin
34
+ subcommand(package.target, 'cvs', "#{@program} -d #{cvsroot} co #{@co} -d #{tail} #{modulename}")
35
+ rescue SubcommandFailed => e
36
+ raise ImportException.new(e), "failed to check out #{modulename}"
37
+ end
38
+ }
39
+ end
40
+ end
41
+
42
+ module Import
43
+ def self.cvs(source, package_options)
44
+ CVSImporter.new(source[0], source[1], package_options)
45
+ end
46
+ end
47
+
@@ -0,0 +1,39 @@
1
+ require 'autobuild/subcommand'
2
+ require 'autobuild/importer'
3
+
4
+ class SVNImporter < Importer
5
+ def initialize(source, options)
6
+ @source = source.to_a.join("/")
7
+
8
+ @program = ($PROGRAMS[:svn] || 'svn')
9
+ @up = options[:svnup]
10
+ @co = options[:svnco]
11
+ end
12
+
13
+ private
14
+
15
+ def update(package)
16
+ Dir.chdir(package.srcdir) {
17
+ begin
18
+ subcommand(package.target, 'svn', "#{program} up #{@up}")
19
+ rescue SubcommandFailed => e
20
+ raise ImportException.new(e), "failed to update #{modulename}"
21
+ end
22
+ }
23
+ end
24
+
25
+ def checkout(package)
26
+ begin
27
+ subcommand(package.target, 'svn', "#{program} co #{@co} #{@source} #{package.srcdir}")
28
+ rescue SubcommandFailed => e
29
+ raise ImportException.new(e), "failed to check out #{modulename}"
30
+ end
31
+ end
32
+ end
33
+
34
+ module Import
35
+ def self.svn(source, options)
36
+ SVNImporter.new(source, options)
37
+ end
38
+ end
39
+
@@ -0,0 +1,24 @@
1
+ class Importer
2
+ def import(package)
3
+ srcdir = package.srcdir
4
+ if File.directory?(srcdir)
5
+ if $NOUPDATE
6
+ puts "Not updating #{package.target} since noupdate is set"
7
+ return
8
+ end
9
+
10
+ update(package)
11
+
12
+ elsif File.exists?(srcdir)
13
+ raise ImportException, "#{srcdir} exists but is not a directory"
14
+ else
15
+ begin
16
+ checkout(package)
17
+ rescue ImportException => error
18
+ FileUtils.rm_rf package.srcdir
19
+ raise error
20
+ end
21
+ end
22
+ end
23
+ end
24
+
@@ -0,0 +1,105 @@
1
+ require 'rmail'
2
+ require 'rmail/serialize'
3
+ require 'net/smtp'
4
+ require 'socket'
5
+
6
+ class SubcommandFailed < Exception
7
+ attr_reader :target, :command, :logfile, :status
8
+ def initialize(target, command, logfile, status)
9
+ @target = target
10
+ @command = command
11
+ @logfile = logfile
12
+ @status = status
13
+ end
14
+ end
15
+
16
+ class ConfigException < Exception
17
+ def mail?; false end
18
+ end
19
+
20
+ class ImportException < SubcommandFailed
21
+ def mail?; true end
22
+
23
+ def initialize(subcommand)
24
+ super(subcommand.target, subcommand.command, subcommand.logfile, subcommand.status)
25
+ end
26
+ end
27
+ class BuildException < SubcommandFailed
28
+ def mail?; true end
29
+
30
+ def initialize(subcommand)
31
+ super(subcommand.target, subcommand.command, subcommand.logfile, subcommand.status)
32
+ end
33
+ end
34
+
35
+ def success
36
+ message = "Build finished successfully at #{Time.now}"
37
+ puts message
38
+ send_mail("Build success", message) if $MAIL
39
+ end
40
+
41
+ def error(object, place)
42
+ if object.kind_of?(SubcommandFailed)
43
+ body = <<EOF
44
+ #{place}: #{object.message}
45
+ command '#{object.command}' failed with status #{object.status}
46
+ see #{File.basename(object.logfile)} for details
47
+ EOF
48
+
49
+ message = <<EOF
50
+ #{place}: #{object.message}
51
+ command '#{object.command}' failed with status #{object.status}
52
+ see #{object.logfile} for details
53
+ EOF
54
+ else
55
+ body = message = "#{place}: #{object.message}"
56
+ end
57
+
58
+ puts message
59
+ send_mail("Build failed", body) if $MAIL
60
+ end
61
+
62
+ module RMail
63
+ class Message
64
+ def add_file(path, content_type='text/plain')
65
+ part = RMail::Message.new
66
+ part.header.set('Content-Type', content_type)
67
+ part.header.set('Content-Disposition', 'attachment', 'filename' => File.basename(path))
68
+ part.body = ''
69
+ File.open(path) do |file|
70
+ part.body << file.readlines.join("\n")
71
+ end
72
+ self.add_part(part)
73
+ end
74
+ end
75
+ end
76
+
77
+ def send_mail(subject, body)
78
+ from = ($MAIL['from'] || "autobuild@#{Socket.gethostname}")
79
+ to = $MAIL['to']
80
+ smtp = ($MAIL['smtp'] || "localhost" )
81
+
82
+ mail = RMail::Message.new
83
+ mail.header.date = Time.now
84
+ mail.header.from = from
85
+ mail.header.to = to
86
+ mail.header.subject = subject
87
+
88
+ part = RMail::Message.new
89
+ part.header.set('Content-Type', 'text/plain')
90
+ part.body = body
91
+ mail.add_part(part)
92
+
93
+ # Attach log files
94
+ Dir.glob("#{$LOGDIR}/*.log") do |file|
95
+ mail.add_file(file)
96
+ end
97
+
98
+ # Send the mail
99
+ smtp = Net::SMTP.new(smtp, Integer($MAIL['port'] || 25))
100
+ smtp.start {
101
+ smtp.send_mail RMail::Serialize.write('', mail), from, to
102
+ }
103
+ end
104
+
105
+
@@ -0,0 +1,83 @@
1
+ require 'autobuild/timestamps'
2
+ require 'autobuild/environment'
3
+ require 'autobuild/subcommand'
4
+
5
+ class Package
6
+ @@packages = {}
7
+
8
+ attr_reader :dependencies
9
+ attr_reader :target, :srcdir, :prefix
10
+
11
+ def installstamp; "#{prefix}/#{target}-#{STAMPFILE}" end
12
+ def self.[](target); @@packages[target] end
13
+
14
+ def initialize(target, options)
15
+ @target = Package.name2target(target)
16
+ raise ConfigException, "Package #{target} is already defined" if Package[target]
17
+
18
+ @options = options
19
+ @dependencies = Array.new
20
+ @provides = Array.new
21
+
22
+ srcdir, prefix =
23
+ (options[:srcdir] or target.to_s),
24
+ (options[:prefix] or "")
25
+
26
+ srcdir = File.expand_path(srcdir, $SRCDIR)
27
+ prefix = File.expand_path(prefix, $PREFIX)
28
+
29
+ @srcdir, @prefix = srcdir, prefix
30
+ @import = options[:import]
31
+ @import.import(self) if @import
32
+
33
+ file installstamp
34
+ task @target => installstamp
35
+
36
+ @options[:depends].to_a.each { |p| depends_on(p) }
37
+ @options[:provides].to_a.each { |p| provides(p) }
38
+ @@packages[target] = self
39
+ end
40
+
41
+ @@factories = Hash.new
42
+
43
+ def depends_on(p)
44
+ p = Package.name2target(p)
45
+ raise :bla if !(Symbol === p)
46
+ task target => p
47
+ puts "#{target} depends on #{p}"
48
+
49
+ @dependencies << p
50
+ end
51
+
52
+ def provides(p)
53
+ p = Package.name2target(p)
54
+ @@packages[p] = self
55
+ puts "Defining #{p} as an alias to #{target}"
56
+ task p => target
57
+
58
+ @provides << p
59
+ end
60
+
61
+ def self.all; @@packages; end
62
+ def self.name2target(name)
63
+ if name.respond_to?(:to_str)
64
+ name.to_str.gsub(/-\//, '_').to_sym
65
+ elsif name.respond_to?(:to_sym)
66
+ name.to_sym
67
+ else
68
+ raise TypeError, "expected either a symbol or a string, got #{name.class}"
69
+ end
70
+ end
71
+ def self.factory(type, klass)
72
+ @@factories[type] = klass
73
+ end
74
+ def self.build(type, name, options)
75
+ raise ConfigException, "#{type} is not a valide package type" if !@@factories.has_key?(type)
76
+
77
+ target = name2target(name)
78
+ task :default => [ target ]
79
+ raise ConfigException, "there is already a package named #{target}" if Package[target]
80
+ return @@factories[type].new(target, options)
81
+ end
82
+ end
83
+
@@ -0,0 +1,115 @@
1
+ require 'autobuild/timestamps'
2
+ require 'autobuild/environment'
3
+ require 'autobuild/package'
4
+ require 'autobuild/subcommand'
5
+
6
+ class Autotools < Package
7
+ factory :autotools, self
8
+
9
+ attr_reader :builddir
10
+
11
+ def buildstamp;
12
+ "#{builddir}/#{target}-#{STAMPFILE}"
13
+ end
14
+
15
+ def initialize(target, options)
16
+ super(target, options)
17
+
18
+ @builddir = (options[:builddir] || "build")
19
+ raise ConfigException, "Autotools packages need a non-empty builddir" if (@builddir.nil? || @builddir.empty?)
20
+ raise ConfigException, "No support for absolute builddirs" if (Pathname.new(@builddir).absolute?)
21
+ @builddir = File.expand_path(builddir, srcdir)
22
+
23
+ regen_targets
24
+
25
+ file "#{builddir}/config.status" => "#{srcdir}/configure" do
26
+ configure
27
+ end
28
+
29
+ source_tree srcdir, builddir
30
+ file srcdir => dependencies if !dependencies.empty?
31
+ file buildstamp => [ srcdir, "#{builddir}/config.status" ] do
32
+ build
33
+ end
34
+ file installstamp => [ buildstamp ] do
35
+ install
36
+ update_environment(prefix)
37
+ end
38
+ update_environment(prefix)
39
+ end
40
+
41
+ def regen_targets
42
+ conffile = "#{srcdir}/configure"
43
+ if File.exists?("#{conffile}.ac")
44
+ file conffile => [ "#{conffile}.ac" ]
45
+ else
46
+ file conffile => [ "#{conffile}.in" ]
47
+ end
48
+ file conffile do
49
+ Dir.chdir(srcdir) {
50
+ $PROGRAMS["aclocal"] ||= "aclocal"
51
+ $PROGRAMS["autoconf"] ||= "autoconf"
52
+ $PROGRAMS["autoheader"] ||= "autoheader"
53
+ $PROGRAMS["automake"] ||= "automake"
54
+
55
+ begin
56
+ subcommand(target, "configure", $PROGRAMS["aclocal"]) if @options[:aclocal]
57
+ subcommand(target, "configure", $PROGRAMS["autoconf"]) if @options[:autoconf]
58
+ subcommand(target, "configure", $PROGRAMS["autoheader"]) if @options[:autoheader]
59
+ subcommand(target, "configure", $PROGRAMS["automake"]) if @options[:automake]
60
+ rescue SubcommandFailed => e
61
+ raise BuildException.new(e), "failed to build the configure environment of #{target}"
62
+ end
63
+ }
64
+ end
65
+ end
66
+
67
+ def configure
68
+ if File.exists?(builddir) && !File.directory?(builddir)
69
+ raise BuildException, "#{builddir} already exists but is not a directory"
70
+ end
71
+
72
+ FileUtils.mkdir_p builddir if !File.directory?(builddir)
73
+ Dir.chdir(builddir) {
74
+ command = "#{srcdir}/configure --no-create --prefix=#{prefix}"
75
+
76
+ configureflags = @options[:configureflags].to_a.collect { |item|
77
+ item.to_a.join("")
78
+ }.join(" ")
79
+ command += " #{configureflags}" if !configureflags.empty?
80
+
81
+ begin
82
+ subcommand(target, "configure", command)
83
+ rescue SubcommandFailed => e
84
+ raise BuildException.new(e), "failed to configure #{target}"
85
+ end
86
+ }
87
+ end
88
+
89
+ def build
90
+ Dir.chdir(builddir) {
91
+ begin
92
+ subcommand(target, "build", "./config.status")
93
+ $PROGRAMS["make"] ||= "make"
94
+ subcommand(target, "build", $PROGRAMS["make"])
95
+ rescue SubcommandFailed => e
96
+ raise BuildException.new(e), "failed to build #{target}"
97
+ end
98
+ }
99
+ touch_stamp(buildstamp)
100
+ end
101
+
102
+ def install
103
+ Dir.chdir(builddir) {
104
+ make = ($PROGRAMS["make"] or "make")
105
+ begin
106
+ subcommand(target, "install", "#{make} install")
107
+ rescue SubcommandFailed => e
108
+ raise BuildException.new(e), "failed to install #{builddir}"
109
+ end
110
+ }
111
+ touch_stamp(installstamp)
112
+ end
113
+ end
114
+
115
+
@@ -0,0 +1,70 @@
1
+ require 'autobuild/packages/autotools'
2
+
3
+ class GenomModule < Autotools
4
+ def initialize(target, options)
5
+ super(target, options)
6
+ get_requires
7
+ get_provides
8
+ end
9
+
10
+ def genomstamp; "#{srcdir}/.genom/genom-stamp" end
11
+
12
+ def get_requires
13
+ File.open("#{srcdir}/#{target}.gen") do |f|
14
+ f.each_line { |line|
15
+ if line =~ /^require\s*:\s*([\w\-]+(?:\s*,\s*[\w\-]+)*);/
16
+ $1.split(/, /).each { |name|
17
+ depends_on name
18
+ file genomstamp => Package.name2target(name)
19
+ }
20
+ elsif line =~ /^require/
21
+ puts "failed to math #{line}"
22
+ end
23
+ }
24
+ end
25
+ end
26
+
27
+ def get_provides
28
+ File.open("#{srcdir}/configure.ac.user") do |f|
29
+ f.each_line { |line|
30
+ if line =~ /^\s*EXTRA_PKGCONFIG\s*=\s*"?([\w\-]+(?:\s+[\w\-]+)*)"?/
31
+ $1.split(/\s+/).each { |pkg|
32
+ provides pkg
33
+ }
34
+ end
35
+ }
36
+ end
37
+ end
38
+
39
+
40
+ def regen_targets
41
+ file buildstamp => genomstamp
42
+ file genomstamp => [ :genom, "#{srcdir}/#{target}.gen" ] do
43
+ Dir.chdir(srcdir) {
44
+ cmdline = "genom " + @options[:genomflags].to_a.join(" ") + " #{target}"
45
+ begin
46
+ subcommand(target, 'genom', cmdline)
47
+ rescue SubcommandFailed => e
48
+ raise BuildException.new(e), "failed to generate module #{target}"
49
+ end
50
+ }
51
+ end
52
+
53
+ acuser = "#{srcdir}/configure.ac.user"
54
+ if File.exists?(acuser)
55
+ file "#{srcdir}/configure" => acuser do
56
+ # configure does not depend on the .gen file
57
+ # since the generation takes care of rebuilding configure
58
+ # if .gen has changed
59
+ begin
60
+ subcommand(target, 'genom', cmdline)
61
+ rescue SubcommandFailed => e
62
+ raise BuildException.new(e), "failed to generate module #{target}"
63
+ end
64
+ end
65
+ end
66
+ end
67
+
68
+ factory :genom, self
69
+ end
70
+
@@ -0,0 +1,19 @@
1
+ require 'autobuild/timestamps'
2
+ require 'autobuild/package'
3
+
4
+ class ImporterPackage < Package
5
+ def installstamp
6
+ "#{srcdir}/#{STAMPFILE}"
7
+ end
8
+ def initialize(target, options)
9
+ super(target, options)
10
+ source_tree srcdir, installstamp
11
+ file installstamp => srcdir do
12
+ touch_stamp installstamp
13
+ end
14
+ end
15
+
16
+ factory :import, ImporterPackage
17
+ end
18
+
19
+
@@ -0,0 +1,32 @@
1
+ require 'autobuild/logging'
2
+
3
+ def subcommand(target, type, command)
4
+ logname = "#{$LOGDIR}/#{target}-#{type}.log"
5
+ puts "#{target}: running #{command}\n (output goes to #{logname})"
6
+
7
+ status = File.open(logname, "a") { |logfile|
8
+ pid = fork {
9
+ if $VERBOSE
10
+ $stderr.dup.reopen(logfile.dup)
11
+ $stdout.dup.reopen(logfile.dup)
12
+ else
13
+ $stderr.reopen(logfile.dup)
14
+ $stdout.reopen(logfile.dup)
15
+ end
16
+
17
+ if !exec(*command.split(" "))
18
+ raise "Error running command"
19
+ end
20
+ }
21
+ childpid, childstatus = Process.wait2(pid)
22
+ childstatus
23
+ }
24
+
25
+ if status.exitstatus > 0
26
+ raise SubcommandFailed.new(target, command, logname, status.exitstatus)
27
+ return false
28
+ else
29
+ return true
30
+ end
31
+ end
32
+
@@ -0,0 +1,48 @@
1
+ require 'find'
2
+ require 'rake/tasklib'
3
+
4
+ STAMPFILE = "autobuild-stamp"
5
+
6
+ def tree_timestamp(path, *exclude)
7
+ latest = Time.at(0)
8
+ latest_file = ""
9
+ dot = "."[0]
10
+
11
+ exclude.collect! { |e| File.expand_path(e, path) }
12
+ Find.find(path) { |p|
13
+ Find.prune if File.basename(p)[0] == dot
14
+ exclude.each { |pattern|
15
+ Find.prune if File.fnmatch?(pattern, p)
16
+ }
17
+ next if File.directory?(p)
18
+
19
+ p_time = File.mtime(p)
20
+ if latest < p_time
21
+ latest = p_time
22
+ latest_file = p
23
+ end
24
+ }
25
+
26
+ return latest
27
+ end
28
+
29
+ class SourceTreeTask < Task
30
+ attr_accessor :exclude
31
+ def timestamp
32
+ tree_timestamp(name, "*CVS", *@exclude)
33
+ end
34
+ end
35
+ def source_tree(path, exclude, &block)
36
+ task = SourceTreeTask.define_task(path, &block)
37
+ task.exclude = exclude
38
+ end
39
+
40
+ def get_stamp(stampfile)
41
+ return Time.at(0) if !File.exists?(stampfile)
42
+ return File.mtime(stampfile)
43
+ end
44
+
45
+ def touch_stamp(stampfile)
46
+ File.open(stampfile, "w") { |*| }
47
+ end
48
+
metadata ADDED
@@ -0,0 +1,82 @@
1
+ --- !ruby/object:Gem::Specification
2
+ rubygems_version: 0.8.11
3
+ specification_version: 1
4
+ name: autobuild
5
+ version: !ruby/object:Gem::Version
6
+ version: "0.1"
7
+ date: 2005-08-31 00:00:00 +02:00
8
+ summary: Rake-based utility to build and install multiple packages with dependencies
9
+ require_paths:
10
+ - lib
11
+ - lib
12
+ email: sylvain.joyeux@m4x.org
13
+ homepage:
14
+ rubyforge_project:
15
+ description: "autobuild imports, configures, builds and installs software packages (mainly
16
+ C/C++ autotools packages for now) with dependencies. It can be used in
17
+ community-based software development to make sure that nothing is broken in the
18
+ build process of a set of packages."
19
+ autorequire:
20
+ default_executable:
21
+ bindir: bin
22
+ has_rdoc: true
23
+ required_ruby_version: !ruby/object:Gem::Version::Requirement
24
+ requirements:
25
+ -
26
+ - ">"
27
+ - !ruby/object:Gem::Version
28
+ version: 0.0.0
29
+ version:
30
+ platform: ruby
31
+ signing_key:
32
+ cert_chain:
33
+ authors:
34
+ - Sylvain Joyeux
35
+ files:
36
+ - lib/autobuild/config.rb
37
+ - lib/autobuild/importer.rb
38
+ - lib/autobuild/package.rb
39
+ - lib/autobuild/timestamps.rb
40
+ - lib/autobuild/logging.rb
41
+ - lib/autobuild/subcommand.rb
42
+ - lib/autobuild/environment.rb
43
+ - lib/autobuild/import/cvs.rb
44
+ - lib/autobuild/import/svn.rb
45
+ - lib/autobuild/packages/autotools.rb
46
+ - lib/autobuild/packages/genom.rb
47
+ - lib/autobuild/packages/import.rb
48
+ - bin/autobuild
49
+ - README
50
+ test_files: []
51
+ rdoc_options:
52
+ - "--title"
53
+ - Autobuild
54
+ - "--main"
55
+ - README
56
+ extra_rdoc_files:
57
+ - README
58
+ executables:
59
+ - autobuild
60
+ extensions: []
61
+ requirements: []
62
+ dependencies:
63
+ - !ruby/object:Gem::Dependency
64
+ name: rake
65
+ version_requirement:
66
+ version_requirements: !ruby/object:Gem::Version::Requirement
67
+ requirements:
68
+ -
69
+ - ">"
70
+ - !ruby/object:Gem::Version
71
+ version: 0.0.0
72
+ version:
73
+ - !ruby/object:Gem::Dependency
74
+ name: rmail
75
+ version_requirement:
76
+ version_requirements: !ruby/object:Gem::Version::Requirement
77
+ requirements:
78
+ -
79
+ - ">"
80
+ - !ruby/object:Gem::Version
81
+ version: 0.0.0
82
+ version: