autobuild 0.1

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