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,30 @@
1
+ # An executable binary file
2
+ class Binary < Buildable
3
+
4
+ def initialize(options)
5
+ raise ArgumentError unless options.kind_of?(Hash)
6
+ super(options)
7
+ @output = @id + Platform.executable_extension
8
+ @output_type = 'binary'
9
+ end
10
+
11
+ def DEADWOOD_build
12
+ binfile = @id + Platform.executable_extension
13
+ cc = @compiler.clone
14
+ cc.is_library = false
15
+ cc.sources = @sources
16
+ cc.output = binfile
17
+
18
+ #XXX-BROKEN cc.add_targets(@makefile)
19
+
20
+ @makefile.merge!(cc.to_make(binfile))
21
+
22
+ @makefile.clean(cc.objs)
23
+ @makefile.install(binfile, '$(BINDIR)', { 'mode' => '755' }) \
24
+ if @installable
25
+ @output.push binfile
26
+ super()
27
+ end
28
+
29
+ end
30
+
@@ -0,0 +1,270 @@
1
+ # A buildable object like a library or executable
2
+ class Buildable
3
+
4
+ attr_accessor :id, :project,
5
+ :buildable, :installable, :distributable,
6
+ :localdep, :sysdep, :enable,
7
+ :output, :output_type, :sources, :cflags, :ldadd, :rpath,
8
+ :topdir
9
+
10
+ def initialize(options)
11
+ raise ArgumentError unless options.kind_of?(Hash)
12
+ default = {
13
+ :id => options[:id],
14
+ :enable => true,
15
+ :buildable => true,
16
+ :distributable => true,
17
+ :installable => true,
18
+ :extension => '',
19
+ :cflags => [],
20
+ :ldflags => [],
21
+ :ldadd => [],
22
+ :rpath => '',
23
+ :topdir => '',
24
+ :depends => [],
25
+ }
26
+ default.each do |k,v|
27
+ instance_variable_set('@' + k.to_s, v)
28
+ end
29
+ @output = id
30
+ @output_type = nil # filled in by the derived class
31
+
32
+ # Local and system header dependencies for each @sources file
33
+ # These are filled in by Compiler.makedepends()
34
+ @localdep = {}
35
+ @sysdep = {}
36
+
37
+ # Filled in by sources=()
38
+ @sources = []
39
+ @source_code = {}
40
+
41
+ # Parse options
42
+
43
+ # FIXME- consider adding support for:
44
+ #%w{name enable distributable installable extension
45
+ # topdir rpath}
46
+
47
+ log.debug "Buildable options: " + options.pretty_inspect
48
+
49
+ options.each do |k,v|
50
+ log.debug "k=#{k} v=#{v.to_s}"
51
+ case k
52
+ when :id
53
+ @id = v
54
+ when :cc
55
+ @cc = v.clone
56
+ when :cflags
57
+ @cflags = v
58
+ @cflags = [ @cflags ] if @cflags.kind_of?(String)
59
+ when :ldflags
60
+ @ldflags = v
61
+ when :ldadd
62
+ @ldadd.push(v).flatten!
63
+ when :project
64
+ @project = v
65
+ when :buildable
66
+ @buildable = v
67
+ when :sources
68
+ v = [ v ] if v.kind_of?(String)
69
+ @sources = v
70
+ else
71
+ throw "Unrecognized option -- #{k}: #{v}"
72
+ end
73
+ end
74
+ log.debug "Buildable parsed as: " + self.pretty_inspect
75
+
76
+ #FIXME: move some of these to the switch statement
77
+ # # Parse simple textual child elements
78
+ # %w{cflags ldflags ldadd depends sources}.each do |k|
79
+ # instance_variable_set('@' + k, yaml[k]) if yaml.has_key? k
80
+ # end
81
+
82
+ end
83
+
84
+ def expand_sources(x)
85
+ log.info "expanding [#{x.to_s}] to source file list"
86
+ raise ArgumentError('Wrong type') unless x.is_a? Array
87
+
88
+ # Use glob(3) to expand the list of sources
89
+ buf = []
90
+ x.each do |src|
91
+ if src =~ /\*/
92
+ buf << Dir.glob(src)
93
+ else
94
+ buf.push src
95
+ end
96
+ end
97
+ buf.flatten
98
+
99
+ # TODO: elsewhere
100
+ # Ensure that all source files exist
101
+ #@sources.each do |src|
102
+ # throw ArgumentError("#{src} does not exist") unless File.exist? src
103
+ # end
104
+
105
+ #XXX-lame
106
+ # # Read all source code into a single array
107
+ # @source_code = {}
108
+ # @sources.each { |x| @source_code[x] = File.readlines(x) }
109
+
110
+ end
111
+
112
+ # Return the list of intermediate object files
113
+ def objects
114
+ expand_sources(@sources).map { |x| x.gsub(/\.c$/, '.o') }
115
+ end
116
+
117
+ def library?
118
+ @output_type == 'shared library' or @output_type == 'static library'
119
+ end
120
+
121
+ def library_type
122
+ case @output_type
123
+ when 'shared library'
124
+ return :shared
125
+ when 'static library'
126
+ return :static
127
+ else
128
+ throw 'Not a library'
129
+ end
130
+ end
131
+
132
+ def binary?
133
+ @output_type =~ /binary/
134
+ end
135
+
136
+ def finalize
137
+ end
138
+
139
+ # Return a hash containing Makefile rules and targets
140
+ # needed to build the object.
141
+ #
142
+ def build
143
+ makefile = Makefile.new
144
+ objs = []
145
+ sources = expand_sources(@sources)
146
+
147
+ # Don't do anything if we aren't going to be built
148
+ return makefile unless @buildable
149
+
150
+ # Allow ndk-build to create the object, for Android
151
+ return makefile if SystemType.host =~ /-androideabi$/
152
+
153
+ log.debug 'buildable = ' + self.pretty_inspect
154
+
155
+ if sources.empty?
156
+ pp self
157
+ raise 'One or more source files are required' if sources.empty?
158
+ end
159
+
160
+ # Generate the targets and rules for each translation unit
161
+ sources.each do |src|
162
+ object_suffix = ''
163
+ if library?
164
+ if library_type == :shared
165
+ #DEADWOOD:cflags.push '-fpic'
166
+ else
167
+ object_suffix = '-static'
168
+ end
169
+ end
170
+ obj = src.sub(/.c$/, object_suffix + Platform.object_extension)
171
+ cc = @project.cc.clone
172
+ cc.shared_library = library? and library_type == :shared
173
+ cc.flags = @cflags
174
+ cc.output = obj
175
+ cc.sources = src
176
+ #TODO: cc.topdir = @topdir
177
+
178
+ ld = cc.ld
179
+ ld.flags = @ldflags
180
+ @ldadd = [ @ldadd ] if @ldadd.kind_of?(String)
181
+ @ldadd.each { |lib| ld.library lib }
182
+
183
+ makefile.add_target(obj, [src, localdep[src]].flatten, cc.rule)
184
+ makefile.clean(obj)
185
+ objs.push obj
186
+ end
187
+
188
+ # Generate the targets and rules for the link stage
189
+ if library? and library_type == :static
190
+ cmd = Platform.archiver(output, objs)
191
+ else
192
+ cc = @project.cc.clone
193
+ cc.shared_library = library? and library_type == :shared
194
+ cc.flags = @cflags
195
+ cc.sources = sources
196
+ cc.ld.flags = @ldflags
197
+ @ldadd = [ @ldadd ] if @ldadd.kind_of?(String)
198
+ @ldadd.each { |lib| cc.ld.library lib }
199
+ cc.ld.output = @output
200
+ cmd = cc.ld.rule
201
+ end
202
+ makefile.add_target(output, objs, cmd)
203
+ makefile.add_dependency('all', output)
204
+ makefile.clean(output)
205
+ makefile.distribute(sources) if distributable
206
+
207
+ return makefile
208
+ end
209
+
210
+ # Return a hash containing Makefile dependencies
211
+ def makedepends
212
+ res = []
213
+
214
+ if @sources.nil?
215
+ log.error self.to_s
216
+ raise 'Missing sources'
217
+ end
218
+
219
+ # Generate the targets and rules for each translation unit
220
+ expand_sources(@sources).each do |src|
221
+ next if src =~ /\.o$/
222
+ cc = @project.cc.clone
223
+ cc.flags = [ @cflags, '-E' ]
224
+ cc.output = '-'
225
+ cc.sources = src
226
+ #TODO: topdir
227
+ cmd = cc.command + Platform.dev_null_stderr
228
+
229
+ # TODO: do @sysdep also
230
+ @localdep[src] = []
231
+ IO.popen(cmd).each do |x|
232
+ if x =~ /^# \d+ "([^\/<].*\.h)"/
233
+ @localdep[src].push $1
234
+ end
235
+ end
236
+ @localdep[src].sort!.uniq!
237
+ res.concat @localdep[src]
238
+
239
+ # Generate a list of system header dependencies
240
+ # FIXME: does a lot of duplicate work reading files in..
241
+ buf = []
242
+ @sysdep[src] = []
243
+ buf.concat File.readlines(src)
244
+ @localdep[src].each do |x|
245
+ if File.exist? x
246
+ buf.concat File.readlines(x)
247
+ end
248
+ end
249
+ buf.each do |x|
250
+ begin
251
+ if x =~ /^#\s*include\s+<(.*?)>/
252
+ @sysdep[src].push $1
253
+ end
254
+ rescue ArgumentError
255
+ # FIXME: should give more info about which file and line
256
+ warn "** WARNING: invalid multibyte sequence encountered in one of the source code files:"
257
+ end
258
+ end
259
+ @sysdep[src].sort!.uniq!
260
+
261
+ end
262
+ res
263
+ end
264
+
265
+ private
266
+
267
+ def log
268
+ Makeconf.logger
269
+ end
270
+ end
@@ -0,0 +1,332 @@
1
+ # Processes source code files to produce intermediate object files.
2
+ #
3
+ class Compiler
4
+
5
+ require 'tempfile'
6
+
7
+ attr_accessor :sysroot
8
+ attr_reader :ld
9
+
10
+ def initialize(language, extension)
11
+ @language = language
12
+ @extension = extension
13
+ @ld = Linker.new()
14
+ windows_init if Platform.is_windows?
15
+ @flags = []
16
+ @sources = [] # List of input files
17
+ @output = nil
18
+ @sysroot = nil
19
+ @quiet = false # If true, output will be suppressed
20
+
21
+ # TODO:
22
+ # If true, all source files will be passed to the compiler at one time.
23
+ # This will also combine the link and compilation phases.
24
+ # See: the -combine option in GCC
25
+ #@combine = false
26
+
27
+ end
28
+
29
+ def sources=(a)
30
+ a = [ a ] if a.kind_of?(String)
31
+ throw 'Array input required' unless a.kind_of?(Array)
32
+ @sources = a
33
+ @ld.objects = a.map { |x| x.sub(/\.c$/, '.o') } #FIXME: hardcoded to C
34
+ end
35
+
36
+ def cflags
37
+ @flags
38
+ end
39
+
40
+ def clone
41
+ Marshal.load(Marshal.dump(self))
42
+ end
43
+
44
+ # Return the intermediate object files for each source file
45
+ def object_files(sources)
46
+ res = []
47
+ sources.sort.each do |s|
48
+ res.push s.sub(/.c$/, Platform.object_extension)
49
+ end
50
+ res
51
+ end
52
+
53
+ def quiet=(b)
54
+ ld.quiet = b
55
+ @quiet = b
56
+ end
57
+
58
+ def output=(s)
59
+ @output = s
60
+ ld.output = s
61
+ end
62
+
63
+ def makefile
64
+ m = Makefile.new
65
+ m.define_variable('CC', ':=', @path)
66
+ m.define_variable('LD', ':=', @ld.path)
67
+ return m
68
+ end
69
+
70
+ # Return the command formatted as a Makefile rule
71
+ def rule
72
+ [ '$(CC)', '-c', flags, '$(CFLAGS)', @sources ].flatten.join(' ')
73
+ end
74
+
75
+ # Return the complete command line to compile an object
76
+ def command
77
+ log.debug self.pretty_inspect
78
+
79
+ throw 'Invalid linker' unless @ld.is_a?(Linker)
80
+ throw 'One or more source files are required' unless @sources.length > 0
81
+ # cflags = default_flags
82
+ # cflags.concat @flags
83
+ # end
84
+ # throw cflags
85
+
86
+ # topdir = h[:topdir] || ''
87
+ # ld = @ld.clone
88
+ # ldadd = h[:ldadd]
89
+ # ld.flags = h[:ldflags]
90
+ # ld.output = Platform.pathspec(h[:output])
91
+ # ld.rpath = h[:rpath] if h[:rpath].length > 0
92
+
93
+ # inputs = h[:sources]
94
+ # inputs = [ inputs ] if inputs.is_a? String
95
+ # inputs = inputs.map { |x| Platform.pathspec(topdir + x) }
96
+ # throw 'One or more sources are required' unless inputs.count
97
+
98
+ #TODO:if @combine
99
+ # return [ @path, cflags, '-combine', ldflags, inputs, ldadd ].flatten.join(' ')
100
+ #
101
+
102
+ cmd = [ @path, '-c', flags, @sources ].flatten.join(' ')
103
+
104
+ cmd += Platform.dev_null if @quiet
105
+
106
+ log.debug "Compiler command: #{cmd}"
107
+
108
+ cmd
109
+ end
110
+
111
+ def flags
112
+ tok = @flags
113
+
114
+ # KLUDGE: remove things that CL.EXE doesn't understand
115
+ # if @path.match(/cl.exe$/i)
116
+ # cflags += ' '
117
+ # cflags.gsub!(/ -Wall /, ' ') # /Wall generates too much noise
118
+ # cflags.gsub!(/ -Werror /, ' ') # Could use /WX here
119
+ # cflags.gsub!(/ -W /, ' ')
120
+ # cflags.gsub!(/ -Wno-.*? /, ' ')
121
+ # cflags.gsub!(/ -Wextra /, ' ')
122
+ # cflags.gsub!(/ -fpic /, ' ')
123
+ # cflags.gsub!(/ -std=.*? /, ' ')
124
+ # cflags.gsub!(/ -pedantic /, ' ')
125
+ # end
126
+
127
+ # Set the output path
128
+ unless @output.nil?
129
+ outfile = Platform.pathspec(@output)
130
+ if vendor == 'Microsoft'
131
+ tok.push '"-IC:\Program Files\Microsoft Visual Studio 10.0\VC\include"' # XXX-HARDCODED
132
+ tok.push '/Fo' + outfile
133
+ tok.push '/MD'
134
+ else
135
+ tok.push '-o', outfile
136
+ end
137
+ end
138
+
139
+ if @ld.shared_library
140
+ if Platform.is_windows?
141
+ throw 'FIXME'
142
+ else
143
+ tok.push '-fpic' unless is_mingw?
144
+ end
145
+ end
146
+
147
+ tok.push '--sysroot=' + @sysroot unless @sysroot.nil?
148
+
149
+ tok.join(' ')
150
+ end
151
+
152
+ def flags=(s)
153
+ @flags = s
154
+ @flags = @flags.split(' ') if @flags.kind_of?(String)
155
+ end
156
+
157
+ # Enable compiler and linker options to create a shared library
158
+ def shared_library=(b)
159
+ case b
160
+ when true
161
+ if Platform.is_windows?
162
+ # noop
163
+ else
164
+ @flags.push '-fpic'
165
+ @ld.shared_library = true
166
+ end
167
+ when false
168
+ # noop
169
+ else
170
+ throw 'Invalid value'
171
+ end
172
+ end
173
+
174
+ # Test if the compiler supports a command line option
175
+ def has_option(opt)
176
+
177
+ # Create a simple test file
178
+ f = Tempfile.new(['test_has_option', @extension]);
179
+ f.puts 'int main() { }'
180
+ f.flush
181
+
182
+ #FIXME: /dev/null not portable
183
+ cmd = [ @path, opt, '-o /dev/null', '-c', f.path ].join(' ') + Platform.dev_null
184
+ Platform.execute cmd
185
+ end
186
+
187
+ # Check if a header is available
188
+ def check_header(path)
189
+ test_compile("#include <" + path + ">")
190
+ end
191
+
192
+ # Compile and link a test program
193
+ def test_link(code)
194
+ test_compile(code, :combined)
195
+ end
196
+
197
+ # Run the compilation command
198
+ def compile
199
+ cmd = self.command
200
+ log.debug "Invoking the compiler"
201
+ rc = Platform.execute cmd
202
+ log.debug "Compilation complete; rc=#{rc.to_s}"
203
+ end
204
+
205
+ # Compile a test program
206
+ def test_compile(code, stage = :compile)
207
+
208
+ # Write the code to a temporary source file
209
+ f = Tempfile.new(['test_compile', @extension]);
210
+ f.print code
211
+ f.flush
212
+ ###objfile = f.path + '.out'
213
+
214
+ # Run the compiler
215
+ cc = self.clone
216
+ cc.sources = f.path
217
+ ### cc.output = objfile
218
+ cc.flags += '-o /dev/null' #FIXME: /dev/null not portable
219
+ cc.quiet = true
220
+ rc = cc.compile
221
+
222
+ # Delete the object file
223
+ objfile = File.basename( f.path.sub(@extension, '.o') )
224
+ File.unlink(objfile) if File.exist? objfile
225
+
226
+ return rc
227
+ end
228
+
229
+
230
+ # Try to determine a usable default set of compiler flags
231
+ def default_flags
232
+ cflags = []
233
+
234
+ # GCC on Solaris 10 produces 32-bit code by default, so add -m64
235
+ # when running in 64-bit mode.
236
+ if Platform.is_solaris? and Platform.word_size == 64
237
+ cflags.push '-m64'
238
+ end
239
+
240
+ cflags
241
+ end
242
+
243
+ private
244
+
245
+ # Special initialization for MS Windows
246
+ def windows_init
247
+ # FIXME: hardcoded to VS10 on C: drive, should pull the information from vcvars.bat
248
+ ENV['PATH'] = 'C:\Program Files\Microsoft Visual Studio 10.0\Common7\IDE\;C:\Program Files\Microsoft Visual Studio 10.0\VC\BIN;C:\Program Files\Microsoft Visual Studio 10.0\Common7\Tools' + ENV['PATH']
249
+ ENV['INCLUDE'] = 'INCLUDE=C:\Program Files\Microsoft Visual Studio 10.0\VC\INCLUDE;C:\Program Files\Microsoft SDKs\Windows\v7.0A\include;'
250
+ ENV['LIB'] = 'C:\Program Files\Microsoft Visual Studio 10.0\VC\LIB;C:\Program Files\Microsoft SDKs\Windows\v7.0A\lib;'
251
+ ENV['LIBPATH'] = 'C:\WINDOWS\Microsoft.NET\Framework\v4.0.30319;C:\WINDOWS\Microsoft.NET\Framework\v3.5;C:\Program Files\Microsoft Visual Studio 10.0\VC\LIB;'
252
+ ENV['VCINSTALLDIR'] = "C:\Program Files\\Microsoft Visual Studio 10.0\\VC\\"
253
+ ENV['VS100COMNTOOLS'] = "C:\\Program Files\\Microsoft Visual Studio 10.0\\Common7\\Tools\\"
254
+ ENV['VSINSTALLDIR'] = "C:\\Program Files\\Microsoft Visual Studio 10.0\\"
255
+ ENV['WindowsSdkDir'] = "C:\\Program Files\\Microsoft SDKs\\Windows\\v7.0A\\"
256
+ end
257
+
258
+ # Search for a suitable compiler
259
+ def search(compilers)
260
+ res = nil
261
+ if ENV['CC']
262
+ res = ENV['CC']
263
+ else
264
+ compilers.each do |command|
265
+ if (command =~ /^\// and File.exists?(command)) or Platform.which(command)
266
+ res = command
267
+ break
268
+ end
269
+ end
270
+ end
271
+
272
+ # FIXME: kludge for Windows, breaks mingw
273
+ if Platform.is_windows?
274
+ res = 'cl.exe'
275
+ end
276
+
277
+ throw 'No suitable compiler found' if res.nil? || res == ''
278
+
279
+ if Platform.is_windows? && res.match(/cl.exe/i)
280
+ help = ' /? <NUL'
281
+ else
282
+ help = ' --help'
283
+ end
284
+
285
+ # Verify the command can be executed
286
+ cmd = res + help + Platform.dev_null
287
+ unless Platform.execute(cmd)
288
+ puts "not found"
289
+ print " -- tried: " + cmd
290
+ raise
291
+ end
292
+
293
+ puts res
294
+ res
295
+ end
296
+
297
+ # Return the name of the compiler vendor
298
+ def vendor
299
+ if @path.match(/cl.exe$/i)
300
+ 'Microsoft'
301
+ else
302
+ 'Unknown'
303
+ end
304
+ end
305
+ end
306
+
307
+ class CCompiler < Compiler
308
+
309
+ attr_accessor :output_type
310
+ attr_reader :path
311
+
312
+ def initialize(options = {})
313
+ @search_list = options.has_key?(:search) ? options[:search] : [ 'cc', 'gcc', 'clang', 'cl.exe']
314
+ @search_list = [ @search_list ] unless @search_list.kind_of?(Array)
315
+ @output_type = nil
316
+ super('C', '.c')
317
+ printf "checking for a C compiler.. "
318
+ @path = search(@search_list)
319
+ end
320
+
321
+ # Returns true if the compiler is MinGW
322
+ def is_mingw?
323
+ @path =~ /mingw/ # Kludge
324
+ end
325
+
326
+ private
327
+
328
+ def log
329
+ Makeconf.logger
330
+ end
331
+
332
+ end