Class: Rscons::ConfigureOp

Inherits:
Object
  • Object
show all
Defined in:
lib/rscons/configure_op.rb

Overview

Class to manage a configure operation.

Instance Method Summary collapse

Constructor Details

#initialize(script) ⇒ ConfigureOp

Create a ConfigureOp.

Parameters:

  • script (Script)

    Build script.



13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# File 'lib/rscons/configure_op.rb', line 13

def initialize(script)
  @tested_compilers = {}
  @work_dir = "#{Rscons.application.build_dir}/_configure"
  FileUtils.mkdir_p(@work_dir)
  @log_file_name = "#{@work_dir}/config.log"
  @log_fh = File.open(@log_file_name, "wb")
  cache = Cache.instance
  cache["failed_commands"] = []
  cache["configuration_data"] = {}
  unless Rscons.application.silent_configure
    if project_name = script.project_name
      Ansi.write($stdout, "Configuring ", :cyan, project_name, :reset, "...\n")
    else
      $stdout.puts "Configuring project..."
    end
  end
  Task["configure"].params.each do |name, param|
    unless Rscons.application.silent_configure
      Ansi.write($stdout, "Setting #{name}... ", :green, param.value, :reset, "\n")
    end
  end
end

Instance Method Details

#check_c_compiler(*ccc) ⇒ void

This method returns an undefined value.

Check for a working C compiler.

Parameters:

  • ccc (Array<String>)

    C compiler(s) to check for.



57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/rscons/configure_op.rb', line 57

def check_c_compiler(*ccc)
  $stdout.write("Checking for C compiler... ")
  options = {}
  if ccc.last.is_a?(Hash)
    options = ccc.slice!(-1)
  end
  if ccc.empty?
    # Default C compiler search array.
    ccc = %w[gcc clang]
  end
  cc = ccc.find do |cc|
    test_c_compiler(cc, options)
  end
  if cc
    @tested_compilers["c"] ||= Set.new
    @tested_compilers["c"] << cc
  end
  complete(cc ? 0 : 1, options.merge(
    success_message: cc,
    fail_message: "not found (checked #{ccc.join(", ")})"))
end

#check_c_header(header_name, options = {}) ⇒ Object

Check for a C header.



158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
# File 'lib/rscons/configure_op.rb', line 158

def check_c_header(header_name, options = {})
  check_cpppath = [nil] + (options[:check_cpppath] || [])
  Ansi.write($stdout, "Checking for C header '", :cyan, header_name, :reset, "'... ")
  File.open("#{@work_dir}/cfgtest.c", "wb") do |fh|
    fh.puts <<-EOF
      #include "#{header_name}"
      int main(int argc, char * argv[]) {
        return 0;
      }
    EOF
  end
  vars = {
    "LD" => "${CC}",
    "_SOURCES" => "#{@work_dir}/cfgtest.c",
    "_TARGET" => "#{@work_dir}/cfgtest.o",
    "_DEPFILE" => "#{@work_dir}/cfgtest.mf",
  }
  status = 1
  check_cpppath.each do |cpppath|
    env = BasicEnvironment.new
    if cpppath
      env["CPPPATH"] += Array(cpppath)
    end
    command = env.build_command("${CCCMD}", vars)
    _, _, status = log_and_test_command(command)
    if status == 0
      if cpppath
        store_append({"CPPPATH" => Array(cpppath)}, options)
      end
      break
    end
  end
  complete(status, options)
end

#check_cfg(options = {}) ⇒ Object

Check for a package or configure program output.



136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
# File 'lib/rscons/configure_op.rb', line 136

def check_cfg(options = {})
  if package = options[:package]
    Ansi.write($stdout, "Checking for package '", :cyan, package, :reset, "'... ")
  elsif program = options[:program]
    Ansi.write($stdout, "Checking '", :cyan, program, :reset, "'... ")
  end
  unless program
    program = "pkg-config"
    unless Util.find_executable(program)
      raise RsconsError.new("Error: executable '#{program}' not found")
    end
  end
  args = options[:args] || %w[--cflags --libs]
  command = [program, *args, package].compact
  stdout, _, status = log_and_test_command(command)
  if status == 0
    store_parse(stdout, options)
  end
  complete(status, options)
end

#check_cxx_compiler(*ccc) ⇒ void

This method returns an undefined value.

Check for a working C++ compiler.

Parameters:

  • ccc (Array<String>)

    C++ compiler(s) to check for.



85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
# File 'lib/rscons/configure_op.rb', line 85

def check_cxx_compiler(*ccc)
  $stdout.write("Checking for C++ compiler... ")
  options = {}
  if ccc.last.is_a?(Hash)
    options = ccc.slice!(-1)
  end
  if ccc.empty?
    # Default C++ compiler search array.
    ccc = %w[g++ clang++]
  end
  cc = ccc.find do |cc|
    test_cxx_compiler(cc, options)
  end
  if cc
    @tested_compilers["cxx"] ||= Set.new
    @tested_compilers["cxx"] << cc
  end
  complete(cc ? 0 : 1, options.merge(
    success_message: cc,
    fail_message: "not found (checked #{ccc.join(", ")})"))
end

#check_cxx_header(header_name, options = {}) ⇒ Object

Check for a C++ header.



194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
# File 'lib/rscons/configure_op.rb', line 194

def check_cxx_header(header_name, options = {})
  check_cpppath = [nil] + (options[:check_cpppath] || [])
  Ansi.write($stdout, "Checking for C++ header '", :cyan, header_name, :reset, "'... ")
  File.open("#{@work_dir}/cfgtest.cxx", "wb") do |fh|
    fh.puts <<-EOF
      #include "#{header_name}"
      int main(int argc, char * argv[]) {
        return 0;
      }
    EOF
  end
  vars = {
    "LD" => "${CXX}",
    "_SOURCES" => "#{@work_dir}/cfgtest.cxx",
    "_TARGET" => "#{@work_dir}/cfgtest.o",
    "_DEPFILE" => "#{@work_dir}/cfgtest.mf",
  }
  status = 1
  check_cpppath.each do |cpppath|
    env = BasicEnvironment.new
    if cpppath
      env["CPPPATH"] += Array(cpppath)
    end
    command = env.build_command("${CXXCMD}", vars)
    _, _, status = log_and_test_command(command)
    if status == 0
      if cpppath
        store_append({"CPPPATH" => Array(cpppath)}, options)
      end
      break
    end
  end
  complete(status, options)
end

#check_d_compiler(*cdc) ⇒ void

This method returns an undefined value.

Check for a working D compiler.

Parameters:

  • cdc (Array<String>)

    D compiler(s) to check for.



113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
# File 'lib/rscons/configure_op.rb', line 113

def check_d_compiler(*cdc)
  $stdout.write("Checking for D compiler... ")
  options = {}
  if cdc.last.is_a?(Hash)
    options = cdc.slice!(-1)
  end
  if cdc.empty?
    # Default D compiler search array.
    cdc = %w[gdc ldc2 ldc]
  end
  dc = cdc.find do |dc|
    test_d_compiler(dc, options)
  end
  if dc
    @tested_compilers["d"] ||= Set.new
    @tested_compilers["d"] << dc
  end
  complete(dc ? 0 : 1, options.merge(
    success_message: dc,
    fail_message: "not found (checked #{cdc.join(", ")})"))
end

#check_d_import(d_import, options = {}) ⇒ Object

Check for a D import.



230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
# File 'lib/rscons/configure_op.rb', line 230

def check_d_import(d_import, options = {})
  check_d_import_path = [nil] + (options[:check_d_import_path] || [])
  Ansi.write($stdout, "Checking for D import '", :cyan, d_import, :reset, "'... ")
  File.open("#{@work_dir}/cfgtest.d", "wb") do |fh|
    fh.puts <<-EOF
      import #{d_import};
      int main() {
        return 0;
      }
    EOF
  end
  vars = {
    "LD" => "${DC}",
    "_SOURCES" => "#{@work_dir}/cfgtest.d",
    "_TARGET" => "#{@work_dir}/cfgtest.o",
    "_DEPFILE" => "#{@work_dir}/cfgtest.mf",
  }
  status = 1
  check_d_import_path.each do |d_import_path|
    env = BasicEnvironment.new
    if d_import_path
      env["D_IMPORT_PATH"] += Array(d_import_path)
    end
    command = env.build_command("${DCCMD}", vars)
    _, _, status = log_and_test_command(command)
    if status == 0
      if d_import_path
        store_append({"D_IMPORT_PATH" => Array(d_import_path)}, options)
      end
      break
    end
  end
  complete(status, options)
end

#check_lib(lib, options = {}) ⇒ Object

Check for a library.



266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
# File 'lib/rscons/configure_op.rb', line 266

def check_lib(lib, options = {})
  check_libpath = [nil] + (options[:check_libpath] || [])
  Ansi.write($stdout, "Checking for library '", :cyan, lib, :reset, "'... ")
  if @tested_compilers["d"]
    source_file = "#{@work_dir}/cfgtest.d"
    File.open(source_file, "wb") do |fh|
      fh.puts <<-EOF
        int main() {
          return 0;
        }
      EOF
    end
  else
    source_file = "#{@work_dir}/cfgtest.c"
    File.open(source_file, "wb") do |fh|
      fh.puts <<-EOF
        int main(int argc, char * argv[]) {
          return 0;
        }
      EOF
    end
  end
  ld = "${CC}"
  %w[d cxx c].each do |language|
    if @tested_compilers[language]
      ld = @tested_compilers[language].first
      break
    end
  end
  vars = {
    "LD" => ld,
    "LIBS" => [lib],
    "_SOURCES" => source_file,
    "_TARGET" => "#{@work_dir}/cfgtest.exe",
  }
  status = 1
  check_libpath.each do |libpath|
    env = BasicEnvironment.new
    if libpath
      env["LIBPATH"] += Array(libpath)
    end
    command = env.build_command("${LDCMD}", vars)
    _, _, status = log_and_test_command(command)
    if status == 0
      if libpath
        store_append({"LIBPATH" => Array(libpath)}, options)
      end
      break
    end
  end
  if status == 0
    store_append({"LIBS" => [lib]}, options)
  end
  complete(status, options)
end

#check_program(program, options = {}) ⇒ Object

Check for a executable program.



323
324
325
326
327
# File 'lib/rscons/configure_op.rb', line 323

def check_program(program, options = {})
  Ansi.write($stdout, "Checking for program '", :cyan, program, :reset, "'... ")
  path = Util.find_executable(program)
  complete(path ? 0 : 1, options.merge(success_message: path))
end

#close(success) ⇒ void

This method returns an undefined value.

Close the log file handle.

Parameters:

  • success (Boolean)

    Whether all configure operations were successful.



42
43
44
45
46
47
48
49
# File 'lib/rscons/configure_op.rb', line 42

def close(success)
  @log_fh.close
  @log_fh = nil
  cache = Cache.instance
  cache["configuration_data"]["configured"] = success
  cache["configuration_data"]["params"] = Task["configure"].param_values
  cache.write
end

#complete(status, options) ⇒ Object

Perform processing common to several configure checks.

Parameters:

  • status (Process::Status, Integer)

    Process exit code. 0 for success, non-zero for error.

  • options (Hash)

    Common check options.

Options Hash (options):

  • :fail (Boolean)

    Whether to fail configuration if the requested item is not found. This defaults to true if the :set_define option is not specified, otherwise defaults to false if :set_define option is specified.

  • :set_define (String)

    A define to set (in CPPDEFINES) if the requested item is found.

  • :success_message (String)

    Message to print on success (default “found”).

  • :fail_message (String)

    Message to print on failure (default “not found”).



425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
# File 'lib/rscons/configure_op.rb', line 425

def complete(status, options)
  success_message = options[:success_message] || "found"
  fail_message = options[:fail_message] || "not found"
  if status == 0
    Ansi.write($stdout, :green, "#{success_message}\n")
    if options[:set_define]
      store_append("CPPDEFINES" => [options[:set_define]])
    end
  else
    should_fail =
      if options.has_key?(:fail)
        options[:fail]
      else
        !options[:set_define]
      end
    color = should_fail ? :red : :yellow
    Ansi.write($stdout, color, "#{fail_message}\n")
    if options[:on_fail].is_a?(String)
      $stdout.puts(options[:on_fail])
    elsif options[:on_fail].is_a?(Proc)
      options[:on_fail].call
    end
    if should_fail
      raise RsconsError.new("Configuration failed; log file written to #{@log_file_name}")
    end
  end
end

#log_and_test_command(command, options = {}) ⇒ String, Process::Status

Execute a test command and log the result.

Parameters:

  • command (Array<String>)

    Command to execute.

  • options (Hash) (defaults to: {})

    Optional arguments.

Options Hash (options):

  • :stdin (String)

    Data to send to standard input stream of the executed command.

Returns:

  • (String, String, Process::Status)

    stdout, stderr, status



340
341
342
343
344
345
346
347
348
349
350
351
# File 'lib/rscons/configure_op.rb', line 340

def log_and_test_command(command, options = {})
  begin
    @log_fh.puts("Command: #{command.join(" ")}")
    stdout, stderr, status = Open3.capture3(*command, stdin_data: options[:stdin])
    @log_fh.puts("Exit status: #{status.to_i}")
    @log_fh.write(stdout)
    @log_fh.write(stderr)
    [stdout, stderr, status]
  rescue Errno::ENOENT
    ["", "", 127]
  end
end

#store_append(vars, options = {}) ⇒ Object

Store construction variables for appending into the Cache.

Parameters:

  • vars (Hash)

    Hash containing the variables to append.

  • options (Hash) (defaults to: {})

    Options.

Options Hash (options):

  • :use (String)

    A ‘use’ name. If specified, the construction variables are only applied to an Environment if the Environment is constructed with a matching ‘:use` value.



381
382
383
384
385
386
387
388
389
390
391
# File 'lib/rscons/configure_op.rb', line 381

def store_append(vars, options = {})
  store_vars = store_common(options)
  store_vars["append"] ||= {}
  vars.each_pair do |key, value|
    if store_vars["append"][key].is_a?(Array) and value.is_a?(Array)
      store_vars["append"][key] += value
    else
      store_vars["append"][key] = value
    end
  end
end

#store_merge(vars, options = {}) ⇒ Object

Store construction variables for merging into the Cache.

Parameters:

  • vars (Hash)

    Hash containing the variables to merge.

  • options (Hash) (defaults to: {})

    Options.

Options Hash (options):

  • :use (String)

    A ‘use’ name. If specified, the construction variables are only applied to an Environment if the Environment is constructed with a matching ‘:use` value.



363
364
365
366
367
368
369
# File 'lib/rscons/configure_op.rb', line 363

def store_merge(vars, options = {})
  store_vars = store_common(options)
  store_vars["merge"] ||= {}
  vars.each_pair do |key, value|
    store_vars["merge"][key] = value
  end
end

#store_parse(flags, options = {}) ⇒ Object

Store flags to be parsed into the Cache.

Parameters:

  • flags (String)

    String containing the flags to parse.

  • options (Hash) (defaults to: {})

    Options.

Options Hash (options):

  • :use (String)

    A ‘use’ name. If specified, the construction variables are only applied to an Environment if the Environment is constructed with a matching ‘:use` value.



403
404
405
406
407
# File 'lib/rscons/configure_op.rb', line 403

def store_parse(flags, options = {})
  store_vars = store_common(options)
  store_vars["parse"] ||= []
  store_vars["parse"] << flags
end