makeconf 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,108 @@
1
+ # An external project is typically a third-party library dependency
2
+ # that does not use makeconf for it's build system.
3
+ #
4
+ class ExternalProject < Buildable
5
+
6
+ require 'net/http'
7
+ require 'uri'
8
+
9
+ attr_accessor :uri, :patch
10
+
11
+ def initialize(options)
12
+ # KLUDGE - parent constructor will barf unless we delete our
13
+ # custom options
14
+ @uri = options[:uri]
15
+ options.delete :uri
16
+ @configure = options[:configure] # options passed to ./configure
17
+ options.delete :configure
18
+ @configure = './configure' if @configure.nil?
19
+ @patch = options[:patch]
20
+ options.delete :patch
21
+ @patch = [] if @patch.nil?
22
+
23
+ super(options)
24
+
25
+ @installable = false
26
+ @distributable = false
27
+ end
28
+
29
+ # Examine the operating environment and set configuration options
30
+ def configure
31
+ printf "checking for external project #{@id}... "
32
+ if File.exists?(@id)
33
+ puts "yes"
34
+ else
35
+ puts "no"
36
+ download
37
+
38
+ # Apply patches
39
+ @patch.each do |p|
40
+ system "cd #{@id} && patch -p0 -N < ../#{p}" \
41
+ or throw "failed to apply patch: ../#{p}"
42
+ end
43
+ end
44
+
45
+ # KLUDGE: passthrough certain options
46
+ passthru = []
47
+ Makeconf.original_argv.each do |x|
48
+ passthru.push x if x =~ /^--(host|with-ndk|with-sdk)/
49
+ end
50
+ @configure += ' ' + passthru.join(' ') unless passthru.empty?
51
+
52
+ # FIXME: this works, but autotools differ widely across host systems.
53
+ # make this an optional step that can be done
54
+ #
55
+ # Regenerate the Autotools files
56
+ #if File.exists?(@id + '/configure.ac')
57
+ # system "cd #{@id} && autoreconf -fvi" \
58
+ # or throw "autoreconf failed"
59
+ # end
60
+
61
+ # Run the autoconf-style ./configure script
62
+ puts "*** Configuring #{@id} using #{@configure}"
63
+ system "cd #{@id} && #{@configure}" \
64
+ or throw "Unable to configure #{@id}"
65
+ puts "*** Done"
66
+ end
67
+
68
+ def build
69
+ makefile = Makefile.new
70
+ return makefile unless @buildable
71
+ makefile.add_dependency('all', "#{@id}-build-stamp")
72
+ makefile.add_target("#{@id}-build-stamp", [],
73
+ [
74
+ "cd #{@id} && make",
75
+ "touch #{@id}-build-stamp",
76
+ ])
77
+ makefile.add_rule('check', [ "cd #{@id} && make check" ])
78
+ makefile.add_rule('clean', Platform.rm("#{@id}-build-stamp"))
79
+ makefile
80
+ end
81
+
82
+
83
+ private
84
+
85
+ # Download the project from an external source
86
+ def download
87
+
88
+ #example for tarball
89
+ # x = Net::HTTP.get(URI(uri))
90
+ # end
91
+ uri = URI(@uri)
92
+
93
+ case uri.scheme
94
+ when 'svn', 'svn+ssh'
95
+ puts "downloading #{@uri}.. "
96
+ system "svn co #{@uri} #{@id}" or throw "Unable to checkout working copy"
97
+ when 'file'
98
+ puts "unpacking #{uri.host}.. "
99
+ system "tar zxf #{uri.host}" or throw "Unable to unpack #{uri.host}"
100
+ else
101
+ throw "Unsupported URI scheme #{@uri}"
102
+ end
103
+ end
104
+
105
+ def log
106
+ Makeconf.logger
107
+ end
108
+ end
@@ -0,0 +1,239 @@
1
+ class Makeconf::GUI
2
+
3
+ def initialize(project)
4
+ require 'tk'
5
+
6
+ @project = project
7
+ @page = [ 'intro_page',
8
+ 'license_page',
9
+ # TODO:'install_prefix_page',
10
+ 'build_page',
11
+ 'outro_page',
12
+ ]
13
+ @pageIndex = 0
14
+
15
+ @mainTitle = TkVariable.new
16
+ @mainMessage = TkVariable.new
17
+
18
+ @root = TkRoot.new() {
19
+ title "Installation"
20
+ }
21
+
22
+ @mainTitleWidget = TkLabel.new(@root) {
23
+ pack('side' => 'top')
24
+ }
25
+ @mainTitleWidget.configure('textvariable', @mainTitle)
26
+
27
+ @mainFrame = TkFrame.new(@root) {
28
+ height 600
29
+ width 600
30
+ background 'white'
31
+ borderwidth 5
32
+ relief 'groove'
33
+ padx 10
34
+ pady 10
35
+ pack('side' => 'top')
36
+ }
37
+
38
+ @mainLabel = TkLabel.new(@mainFrame) {
39
+ background 'white'
40
+ }
41
+ @mainLabel.configure('textvariable', @mainMessage)
42
+
43
+ @mainText = TkText.new(@mainFrame) {
44
+ background 'white'
45
+ }
46
+
47
+ @cancelButton = TkButton.new(@root) {
48
+ text "Cancel"
49
+ command proc {
50
+ exit 1
51
+ }
52
+ pack('side' => 'left')
53
+ }
54
+
55
+ @nextButton = TkButton.new(@root) {
56
+ text "Next"
57
+ pack('side' => 'right')
58
+ }
59
+ @nextButton.configure('command', method(:next_page))
60
+ #nextButton.configure('command', proc { mainMessage.set_value 'You click it' })
61
+ @nextButtonEnable = true
62
+
63
+ @backButton = TkButton.new(@root) {
64
+ text "Back"
65
+ command proc { prev_page }
66
+ state 'disabled'
67
+ pack('side' => 'right')
68
+ }
69
+ @backButton.configure('command', method(:prev_page))
70
+ @backButtonEnable = true
71
+
72
+ update_buttons
73
+ end
74
+
75
+ def next_page
76
+ eval "#{@page[@pageIndex]}(false)"
77
+ @pageIndex = @pageIndex + 1
78
+ eval "#{@page[@pageIndex]}(true)"
79
+ update_buttons
80
+ end
81
+
82
+ def prev_page
83
+ eval "#{@page[@pageIndex]}(false)"
84
+ @pageIndex = @pageIndex - 1
85
+ eval "#{@page[@pageIndex]}(true)"
86
+ update_buttons
87
+ end
88
+
89
+ # Update the Back and Next buttons based on the position in the pagelist
90
+ def update_buttons
91
+ if @pageIndex == 0
92
+ @backButton.configure('state', 'disabled')
93
+ @nextButton.configure('state', 'normal')
94
+ @nextButton.configure('text', 'Next')
95
+ elsif @pageIndex == @page.length - 1
96
+ @nextButton.configure('text', 'Finish')
97
+ @nextButton.configure('command', proc { exit 0 })
98
+ @backButton.configure('state', 'disabled')
99
+ @cancelButton.configure('state', 'disabled')
100
+ else
101
+ @nextButton.configure('text', 'Next')
102
+ @nextButton.configure('state', @nextButtonEnable ? 'normal' : 'disabled')
103
+ @backButton.configure('state', @backButtonEnable ? 'normal' : 'disabled')
104
+ end
105
+ end
106
+
107
+ def main_loop
108
+ eval "#{@page[0]}(true)"
109
+ Tk.mainloop()
110
+ end
111
+
112
+ def intro_page(display)
113
+ if display
114
+ @mainTitle.set_value 'Welcome'
115
+ @mainLabel.place('relx'=>0.0, 'rely' => 0.0)
116
+ @mainMessage.set_value "This will install #{@project.id} on your computer"
117
+ else
118
+ TkPlace.forget(@mainLabel)
119
+ end
120
+ end
121
+
122
+ def license_page(display)
123
+ if display
124
+ @mainTitle.set_value 'License Agreement'
125
+ @mainText.insert('end', File.read(@project.license_file))
126
+ @mainText.place('relx'=>0.0, 'rely' => 0.0)
127
+ else
128
+ TkPlace.forget(@mainText)
129
+ @mainText.delete(1.0, 'end')
130
+ end
131
+ end
132
+
133
+ def install_prefix_page(display)
134
+ if display
135
+ @mainTitle.set_value 'Installation Path'
136
+ @mainText.insert('end', "choose an installation path")
137
+ @mainText.place('relx'=>0.0, 'rely' => 0.0)
138
+ Tk.getOpenFile
139
+ else
140
+ TkPlace.forget(@mainText)
141
+ @mainText.delete(1.0, 'end')
142
+ end
143
+ end
144
+
145
+ def build_page(display)
146
+ if display
147
+ @nextButtonEnable = false
148
+ update_buttons
149
+ @mainTitle.set_value 'Checking Configuration'
150
+ @mainText.delete(1.0, 'end')
151
+ @mainText.place('relx'=>0.0, 'rely' => 0.0)
152
+ Thread.new {
153
+ @mainText.insert('end', "Configuring.. ")
154
+ Makeconf.configure_project @project
155
+ @mainText.insert('end', "done\n")
156
+
157
+ make = Platform.is_windows? ? 'nmake' : 'make'
158
+
159
+ @mainText.insert('end', "Building.. ")
160
+ system "#{make}"
161
+ @mainText.insert('end', "done\n")
162
+
163
+ @mainText.insert('end', "Installing.. ")
164
+ system "#{make} install"
165
+ @mainText.insert('end', "done\n")
166
+
167
+ @mainText.insert('end', "\nAll tasks completed.")
168
+
169
+ @nextButtonEnable = true
170
+ update_buttons
171
+ }
172
+ else
173
+ TkPlace.forget(@mainText)
174
+ @mainText.delete(1.0, 'end')
175
+ end
176
+ end
177
+
178
+ def outro_page(display)
179
+ if display
180
+ @mainTitle.set_value 'Installation Complete'
181
+ @mainLabel.place('relx'=>0.0, 'rely' => 0.0)
182
+ @mainMessage.set_value "Installation is complete."
183
+ else
184
+ TkPlace.forget(@mainLabel)
185
+ end
186
+ end
187
+
188
+ end
189
+
190
+ __END__
191
+ # UNUSED: might use for showing error messages if "require 'tk'" fails
192
+ #
193
+ class Makeconf::GUI::Minimal
194
+
195
+ if Platform.is_windows?
196
+ require 'dl'
197
+ end
198
+
199
+ def initialize
200
+ end
201
+
202
+ # Display a graphical message box
203
+ def message_box(txt, title, buttons=0)
204
+ if Platform.is_windows?
205
+
206
+ # FIXME: add conditional
207
+
208
+ #Ruby 1.8:
209
+ # user32 = DL.dlopen('user32')
210
+ # msgbox = user32['MessageBoxA', 'ILSSI']
211
+ # r, rs = msgbox.call(0, txt, title, buttons)
212
+ # return r
213
+
214
+ #Ruby 1.9:
215
+ user32 = DL.dlopen('user32')
216
+ msgbox = DL::CFunc.new(user32['MessageBoxA'], DL::TYPE_LONG, 'MessageBox')
217
+ r, rs = msgbox.call([0, txt, title, buttons].pack('L!ppL!').unpack('L!*'))
218
+ return r
219
+ elsif Platform.is_linux?
220
+ #XXX-scrub txt to eliminate "'" character
221
+ cmd = "zenity --text='#{txt}' " + (buttons > 0 ? '--question' : '--info')
222
+ rv = system cmd
223
+ return rv == true ? 1 : 0
224
+ else
225
+ throw 'STUB'
226
+ end
227
+ end
228
+
229
+ # Display an informational message with a single 'OK' button
230
+ def notice(txt, title)
231
+ message_box(txt, title, 0)
232
+ end
233
+
234
+ # Display an confirmation message with an OK button and CANCEL button
235
+ def confirm(txt, title)
236
+ return (message_box(txt, title, 1) == 1) ? true : false
237
+ end
238
+
239
+ end
@@ -0,0 +1,37 @@
1
+ # A generic Header class that installs header files into $(PKGINCLUDEDIR)
2
+
3
+ class Header < Buildable
4
+
5
+ def initialize(options)
6
+ raise ArgumentError unless options.kind_of?(Hash)
7
+
8
+ # KLUDGE - parent constructor will barf unless we delete our
9
+ # custom options
10
+ @namespace = options[:namespace]
11
+ options.delete :namespace
12
+
13
+ super(options)
14
+ end
15
+
16
+ def build
17
+ mk = Makefile.new
18
+
19
+ mk.distribute(@sources)
20
+
21
+ dest = '$(INCLUDEDIR)'
22
+ dest += '/' + @namespace unless @namespace.nil?
23
+
24
+ @project.installer.install(
25
+ :sources => @sources,
26
+ :dest => dest,
27
+ :mode => '644'
28
+ )
29
+
30
+ return mk
31
+ end
32
+
33
+ def makedepends
34
+ []
35
+ end
36
+
37
+ end
@@ -0,0 +1,205 @@
1
+ # An installer copies files from the current directory to an OS-wide location
2
+ class Installer
3
+
4
+ attr_reader :dir
5
+ attr_accessor :package
6
+
7
+ def initialize
8
+ @items = [] # Items to be installed
9
+ @project = nil
10
+ @path = nil
11
+
12
+ # Set default installation paths
13
+ @dir = {
14
+ :prefix => Platform.is_superuser? ? '/usr' : '/usr/local',
15
+ :eprefix => '$(PREFIX)', # this is --exec-prefix
16
+ :bindir => '$(EPREFIX)/bin',
17
+ :datarootdir => '$(PREFIX)/share',
18
+ :datadir => '$(DATAROOTDIR)',
19
+ :docdir => '$(DATAROOTDIR)/doc/$(PACKAGE)',
20
+ :includedir => '$(PREFIX)/include',
21
+ :infodir => '$(DATAROOTDIR)/info',
22
+ :libdir => '$(EPREFIX)/lib',
23
+ :libexecdir => '$(EPREFIX)/libexec',
24
+ :localedir => '$(DATAROOTDIR)/locale',
25
+ :localstatedir => '$(PREFIX)/var',
26
+ :mandir => '$(DATAROOTDIR)/man',
27
+ :oldincludedir => '/usr/include',
28
+ :sbindir => '$(EPREFIX)/sbin',
29
+ :sysconfdir => '$(PREFIX)/etc',
30
+ :sharedstatedir => '$(PREFIX)/com',
31
+
32
+ # Package-specific directories
33
+ :pkgincludedir => '$(INCLUDEDIR)/$(PACKAGE)',
34
+ :pkgdatadir => '$(DATADIR)/$(PACKAGE)',
35
+ :pkglibdir => '$(LIBDIR)/$(PACKAGE)',
36
+
37
+ #TODO: document this
38
+ #DEPRECATED: htmldir, dvidir, pdfdir, psdir
39
+ }
40
+
41
+ @dir[:prefix] = ENV['SystemDrive'] + @dir[:prefix] if Platform.is_windows?
42
+ end
43
+
44
+ # Examine the operating environment and set configuration options
45
+ def configure(project)
46
+ @project = project
47
+ printf 'checking for a BSD-compatible install.. '
48
+ if Platform.is_windows?
49
+ puts 'not found'
50
+ else
51
+ @path = search() or throw 'No installer found'
52
+ printf @path + "\n"
53
+ end
54
+ end
55
+
56
+ # Parse command line options.
57
+ # Should only be called from Makeconf.parse_options()
58
+ def parse_options(opts)
59
+ opts.separator ""
60
+ opts.separator "Installation options:"
61
+
62
+ # Convert symbols to strings
63
+ tmp = {}
64
+ @dir.each { |k,v| tmp[k.to_s] = v }
65
+
66
+ tmp.sort.each do |k, v|
67
+ opts.on('--' + k + ' [DIRECTORY]', "TODO describe this [#{v}]") do |arg|
68
+ @dir[k.to_sym] = arg
69
+ end
70
+ end
71
+
72
+ end
73
+
74
+ # Register a file to be copied during the 'make install' phase.
75
+ def install(src)
76
+ buf = {
77
+ :sources => nil,
78
+ :dest => nil,
79
+ :rename => nil,
80
+ :directory? => false,
81
+ :group => nil,
82
+ :user => nil,
83
+ :mode => '0755',
84
+ }
85
+ #TODO: check for leading '/': raise ArgumentError, 'absolute path is required' unless src[:dest].index(0) == '/'
86
+ raise ArgumentError, ':dest is require' if src[:dest].nil?
87
+ raise ArgumentError, 'Cannot specify both directory and sources' \
88
+ if buf[:directory] == true and not buf[:sources].nil
89
+ @items.push buf.merge(src)
90
+ end
91
+
92
+ def to_make
93
+ mkdir_list = [] # all directories that have been created so far
94
+
95
+ m = Makefile.new
96
+ m.define_variable('INSTALL', '?=', @path) unless @path.nil?
97
+
98
+ # Add 'make install' rules
99
+ @items.each do |i|
100
+ # Automatically create the destination directory, if needed
101
+ destdir = expand_dir(i[:dest])
102
+ unless mkdir_list.include?(destdir)
103
+ m.add_rule('install', Platform.is_windows? ?
104
+ "dir $(DESTDIR)#{destdir} >NUL 2>NUL || mkdir $(DESTDIR)#{destdir}" :
105
+ "/usr/bin/test -e $(DESTDIR)#{destdir} || $(INSTALL) -d -m 755 $(DESTDIR)#{destdir}")
106
+ mkdir_list.push(destdir)
107
+ end
108
+
109
+ m.add_rule('install', install_command(i))
110
+ m.add_rule('uninstall', uninstall_command(i))
111
+ end
112
+
113
+ return m
114
+ end
115
+
116
+ private
117
+
118
+ # Expand makefile variables related to installation directories
119
+ def expand_dir(s)
120
+ buf = s
121
+
122
+ throw 'FIXME -- handle $(PACKAGE)' if buf =~ /\$\(PACKAGE\)/
123
+ while buf =~ /\$/
124
+ old = buf.dup
125
+ @dir.each do |k,v|
126
+ buf.gsub!(/\$\(#{k.to_s.upcase}\)/, v)
127
+ end
128
+ # Crude way of bailing out when there are undefined variables
129
+ # like $(DESTDIR)
130
+ break if old == buf and buf =~ /\$\(/
131
+ end
132
+
133
+ Platform.pathspec(buf)
134
+ end
135
+
136
+ # Translate an @item into the equivalent shell command(s)
137
+ def install_command(h)
138
+ res = []
139
+ h[:sources] = [ h[:sources] ] if h[:sources].kind_of? String
140
+
141
+ # TODO: more sanity checks (e.g. !h[:directory] && h[:sources])
142
+
143
+ if Platform.is_windows?
144
+ # XXX-this is not fully implemented, need mode/owner/group
145
+ if h[:directory]
146
+ res.push 'mkdir $(DESTDIR)' + expand_dir(h[:dest])
147
+ else
148
+ res.push "copy"
149
+ h[:sources].each do |src|
150
+ res.push Platform.pathspec(src)
151
+ end
152
+ res.push '$(DESTDIR)' + expand_dir(h[:dest])
153
+ end
154
+ else
155
+ res.push '$(INSTALL)'
156
+ res.push '-d' if h[:directory]
157
+ res.push('-m', h[:mode]) if h[:mode]
158
+ res.push('-o', h[:owner]) if h[:owner]
159
+ res.push('-g', h[:group]) if h[:group]
160
+ h[:sources].each do |src|
161
+ res.push Platform.pathspec(src)
162
+ end
163
+ dst = '$(DESTDIR)' + expand_dir(h[:dest])
164
+ dst += '/' + h[:rename] unless h[:rename].nil?
165
+ res.push dst
166
+ end
167
+
168
+ res.join(' ')
169
+ end
170
+
171
+ # Translate an @item into the equivalent uninstallation shell command(s)
172
+ def uninstall_command(h)
173
+ res = []
174
+
175
+ h[:sources] = [ h[:sources] ] if h[:sources].kind_of?(String)
176
+
177
+ # TODO: use Platform abstractions instead of duplicate logic
178
+ if Platform.is_windows?
179
+ unless h[:sources]
180
+ res.push 'del', Platform.pathspec('$(DESTDIR)' + h[:dest])
181
+ else
182
+ h[:sources].each do |src|
183
+ res.push 'del', Platform.pathspec('$(DESTDIR)' + h[:dest] + '/' + File.basename(src) )
184
+ end
185
+ end
186
+ else
187
+ unless h[:sources]
188
+ res.push 'rmdir', '$(DESTDIR)' + h[:dest]
189
+ else
190
+ res.push 'rm', '-f', h[:sources].map { |x| '$(DESTDIR)' + h[:dest] + '/' + x }
191
+ end
192
+ end
193
+
194
+ res.join(' ')
195
+ end
196
+
197
+ def search()
198
+ [ ENV['INSTALL'], '/usr/ucb/install', '/usr/bin/install' ].each do |x|
199
+ if !x.nil? and File.exists?(x)
200
+ return x
201
+ end
202
+ end
203
+ end
204
+
205
+ end
@@ -0,0 +1,72 @@
1
+ # A generic Library class that builds both shared and static
2
+
3
+ class Library < Buildable
4
+
5
+ attr_reader :buildable
6
+
7
+ def initialize(options)
8
+ raise ArgumentError unless options.kind_of?(Hash)
9
+ @buildable = [SharedLibrary.new(options), StaticLibrary.new(options)]
10
+ end
11
+
12
+ end
13
+
14
+ class SharedLibrary < Buildable
15
+
16
+ def initialize(options)
17
+ raise ArgumentError unless options.kind_of?(Hash)
18
+ id = options[:id]
19
+
20
+ super(options)
21
+ @abi_major = 0
22
+ @abi_minor = 0
23
+ @output = id + Platform.shared_library_extension
24
+ @output_type = 'shared library'
25
+ #FIXME: @cc.ld.flags.push('-export-dynamic') unless Platform.is_solaris?
26
+ end
27
+
28
+ end
29
+
30
+ class StaticLibrary < Buildable
31
+
32
+ def initialize(options)
33
+ raise ArgumentError unless options.kind_of?(Hash)
34
+ id = options[:id]
35
+ super(options)
36
+ @output = id + Platform.static_library_extension
37
+ @output_type = 'static library'
38
+
39
+ # FIXME: clashes with shared objects
40
+ # src = d.sub(/-static#{Platform.object_extension}$/, '.c')
41
+ end
42
+
43
+ end
44
+
45
+ #
46
+ # UnionLibrary - combine multiple static libraries into a single library.
47
+ #
48
+ # The :sources for this library should be an array of Library objects
49
+ #
50
+ class UnionLibrary < Library
51
+
52
+ def initialize(options)
53
+ raise ArgumentError unless options.kind_of?(Hash)
54
+ @buildable = []
55
+ options[:sources].each do |x|
56
+ x.buildable.each do |y|
57
+ @buildable.push y if y.kind_of?(StaticLibrary)
58
+ end
59
+ end
60
+ @buildable.flatten!
61
+
62
+ # Build a list of all source files within each component library
63
+ sources = []
64
+ @buildable.each { |x| sources.push x.sources }
65
+ sources.flatten!
66
+
67
+ @buildable.push StaticLibrary.new(
68
+ :id => options[:id],
69
+ :sources => sources
70
+ )
71
+ end
72
+ end