350 lines
10 KiB
Ruby
350 lines
10 KiB
Ruby
module Rscons
|
|
|
|
# The Script class encapsulates the state of a build script.
|
|
class Script
|
|
|
|
# Global DSL methods.
|
|
class GlobalDsl
|
|
# Create a GlobalDsl.
|
|
def initialize(script)
|
|
@script = script
|
|
end
|
|
|
|
# Return a list of paths matching the specified pattern(s).
|
|
#
|
|
# A pattern can contain a "/**" component to recurse through directories.
|
|
# If the pattern ends with "/**" then only the recursive list of
|
|
# directories will be returned.
|
|
#
|
|
# Examples:
|
|
# - "src/**": return all directories under "src", recursively (including
|
|
# "src" itself).
|
|
# - "src/**/*": return all files and directories recursively under the src
|
|
# directory.
|
|
# - "src/**/*.c": return all .c files recursively under the src directory.
|
|
# - "dir/*/": return all directories in dir, but no files.
|
|
#
|
|
# @return [Array<String>] Paths matching the specified pattern(s).
|
|
def glob(*patterns)
|
|
require "pathname"
|
|
patterns.reduce([]) do |result, pattern|
|
|
if pattern.end_with?("/**")
|
|
pattern += "/"
|
|
end
|
|
result += Dir.glob(pattern).map do |path|
|
|
Pathname.new(path.gsub("\\", "/")).cleanpath.to_s
|
|
end
|
|
end.sort
|
|
end
|
|
|
|
# Construct a task parameter.
|
|
#
|
|
# @param name [String]
|
|
# Param name.
|
|
# @param value [String, nil]
|
|
# Param value.
|
|
# @param takes_arg [String]
|
|
# Whether the parameter takes an argument.
|
|
# @param description [String]
|
|
# Param description.
|
|
def param(name, value, takes_arg, description)
|
|
Task::Param.new(name, value, takes_arg, description)
|
|
end
|
|
|
|
# Return path components from the PATH variable.
|
|
#
|
|
# @return [Array<String>]
|
|
# Path components from the PATH variable.
|
|
def path_components
|
|
ENV["PATH"].split(File::PATH_SEPARATOR)
|
|
end
|
|
|
|
# Prepend a path component to the PATH variable.
|
|
#
|
|
# @param path [String]
|
|
# Path to prepend.
|
|
#
|
|
# @return [void]
|
|
def path_prepend(path)
|
|
path_set([File.expand_path(path)] + path_components)
|
|
end
|
|
|
|
# Append a path component to the PATH variable.
|
|
#
|
|
# @param path [String]
|
|
# Path to append.
|
|
#
|
|
# @return [void]
|
|
def path_append(path)
|
|
path_set(path_components + [File.expand_path(path)])
|
|
end
|
|
|
|
# Set the PATH variable.
|
|
#
|
|
# @param new_path [String, Array<String>]
|
|
# New PATH variable value as an array or string.
|
|
#
|
|
# @return [void]
|
|
def path_set(new_path)
|
|
if new_path.is_a?(Array)
|
|
new_path = new_path.join(File::PATH_SEPARATOR)
|
|
end
|
|
ENV["PATH"] = new_path
|
|
end
|
|
|
|
# Invoke rscons in a subprocess for a subsidiary Rsconscript file.
|
|
#
|
|
# @param path [String]
|
|
# Path to subsidiary Rsconscript to execute, or path to subsidiary
|
|
# directory to run rscons in.
|
|
# @param args[Array<String>]
|
|
# Arguments to pass to rscons subprocess.
|
|
def rscons(path, *args)
|
|
rscons_path = File.expand_path($0)
|
|
path = File.expand_path(path)
|
|
if File.directory?(path)
|
|
command = [*args]
|
|
dir = path
|
|
else
|
|
command = ["-f", path, *args]
|
|
dir = File.dirname(path)
|
|
end
|
|
if File.exist?("#{dir}/rscons")
|
|
rscons_path = "#{dir}/rscons"
|
|
end
|
|
command = [rscons_path] + command
|
|
print_dir = dir != "." && dir != File.expand_path(Dir.pwd)
|
|
if ENV["specs"] and not ENV["dist_specs"] # specs
|
|
command = ["ruby", $LOAD_PATH.map {|p| ["-I", p]}, command].flatten # specs
|
|
end # specs
|
|
puts "rscons: Entering directory '#{dir}'" if print_dir
|
|
result = system(*command, chdir: dir)
|
|
puts "rscons: Leaving directory '#{dir}'" if print_dir
|
|
unless result
|
|
raise RsconsError.new("Failed command: " + command.join(" "))
|
|
end
|
|
end
|
|
|
|
# Execute a shell command, exiting on failure.
|
|
# The behavior to exit on failure is suppressed if the +:continue+
|
|
# option is given.
|
|
#
|
|
# @overload sh(command, options = {})
|
|
# @param command [String, Array<String>]
|
|
# Command to execute. The command is executed and interpreted by the
|
|
# system shell when given as a single string. It is not passed to the
|
|
# system shell if the array size is greater than 1.
|
|
# @param options [Hash]
|
|
# Options.
|
|
# @option options [Boolean] :continue
|
|
# If set to +true+, rscons will continue executing afterward, even if
|
|
# the command fails.
|
|
#
|
|
# @overload sh(*command, options = {})
|
|
# @param command [String, Array<String>]
|
|
# Command to execute. The command is executed and interpreted by the
|
|
# system shell when given as a single string. It is not passed to the
|
|
# system shell if the array size is greater than 1.
|
|
# @param options [Hash]
|
|
# Options.
|
|
# @option options [Boolean] :continue
|
|
# If set to +true+, rscons will continue executing afterward, even if
|
|
# the command fails.
|
|
def sh(*command)
|
|
options = {}
|
|
if command.last.is_a?(Hash)
|
|
options = command.slice!(-1)
|
|
end
|
|
if command.size == 1 && command[0].is_a?(Array)
|
|
command = command[0]
|
|
end
|
|
if Rscons.application.verbose
|
|
if command.size > 1
|
|
puts Util.command_to_s(command)
|
|
else
|
|
puts command[0]
|
|
end
|
|
end
|
|
begin
|
|
system(*command, exception: true)
|
|
rescue StandardError => e
|
|
message = "#{e.backtrace[2]}: #{e.message}"
|
|
if options[:continue]
|
|
Ansi.write($stderr, :red, message, :reset, "\n")
|
|
else
|
|
raise RsconsError.new(message)
|
|
end
|
|
end
|
|
end
|
|
|
|
# Create or modify a task.
|
|
def task(name, options = {}, &block)
|
|
if task = Task.tasks[name]
|
|
task.modify(options, &block)
|
|
else
|
|
task = Task.new(name, options, &block)
|
|
end
|
|
task
|
|
end
|
|
|
|
[
|
|
:cd,
|
|
:chmod,
|
|
:chmod_R,
|
|
:chown,
|
|
:chown_R,
|
|
:cp,
|
|
:cp_lr,
|
|
:cp_r,
|
|
:install,
|
|
:ln,
|
|
:ln_s,
|
|
:ln_sf,
|
|
:mkdir,
|
|
:mkdir_p,
|
|
:mv,
|
|
:pwd,
|
|
:rm,
|
|
:rm_f,
|
|
:rm_r,
|
|
:rm_rf,
|
|
:rmdir,
|
|
:touch,
|
|
].each do |method|
|
|
define_method(method) do |*args, **kwargs, &block|
|
|
FileUtils.__send__(method, *args, **kwargs, &block)
|
|
end
|
|
end
|
|
|
|
end
|
|
|
|
# Top-level DSL available to the Rsconscript.
|
|
class TopLevelDsl < GlobalDsl
|
|
# Set the project name.
|
|
def project_name(project_name)
|
|
@script.project_name = project_name
|
|
end
|
|
|
|
# Whether to automatically configure (default true).
|
|
def autoconf(autoconf)
|
|
@script.autoconf = autoconf
|
|
end
|
|
|
|
# Shortcut methods to create task blocks for special tasks.
|
|
[
|
|
:clean,
|
|
:distclean,
|
|
:configure,
|
|
:default,
|
|
:install,
|
|
:uninstall,
|
|
].each do |method_name|
|
|
define_method(method_name) do |*args, &block|
|
|
task(method_name.to_s, *args, &block)
|
|
end
|
|
end
|
|
end
|
|
|
|
# DSL available to the 'configure' block.
|
|
class ConfigureDsl < GlobalDsl
|
|
# Create a ConfigureDsl.
|
|
#
|
|
# @param script [Script]
|
|
# The Script being evaluated.
|
|
# @param configure_op [ConfigureOp]
|
|
# The configure operation object.
|
|
def initialize(script, configure_op)
|
|
super(script)
|
|
@configure_op = configure_op
|
|
end
|
|
|
|
[
|
|
:check_c_compiler,
|
|
:check_cxx_compiler,
|
|
:check_d_compiler,
|
|
:check_cfg,
|
|
:check_c_header,
|
|
:check_cxx_header,
|
|
:check_d_import,
|
|
:check_lib,
|
|
:check_program,
|
|
].each do |method_name|
|
|
define_method(method_name) do |*args|
|
|
@configure_op.__send__(method_name, *args)
|
|
end
|
|
end
|
|
|
|
# Perform a custom configuration check.
|
|
#
|
|
# @param message [String]
|
|
# Custom configuration check message (e.g. "Checking for foo").
|
|
# rscons will add "... " to the end of the message.
|
|
# @yieldparam configure_op [ConfigureOp]
|
|
# {ConfigureOp} object.
|
|
# @return [void]
|
|
def custom_check(message, &block)
|
|
$stdout.write(message + "... ")
|
|
block[@configure_op]
|
|
end
|
|
end
|
|
|
|
# @return [String, nil]
|
|
# Project name.
|
|
attr_accessor :project_name
|
|
|
|
# @return [Boolean]
|
|
# Whether to autoconfigure if the user does not explicitly configure
|
|
# before calling a normal task (default: true).
|
|
attr_accessor :autoconf
|
|
|
|
# Construct a Script.
|
|
def initialize
|
|
@autoconf = true
|
|
TopLevelDsl.new(self).instance_eval do
|
|
task("clean",
|
|
desc: "Remove build artifacts (but not configuration)",
|
|
autoconf: false) do
|
|
Rscons.application.clean
|
|
end
|
|
task("configure",
|
|
desc: "Configure the project",
|
|
autoconf: false,
|
|
params: [param("prefix", "/usr/local", true, "Set installation prefix (default: /usr/local)")])
|
|
task("distclean",
|
|
desc: "Remove build directory and configuration",
|
|
autoconf: false) do
|
|
Rscons.application.distclean
|
|
end
|
|
task("install",
|
|
desc: "Install project to configured installation prefix")
|
|
task("uninstall",
|
|
desc: "Uninstall project",
|
|
autoconf: false) do
|
|
Rscons.application.uninstall
|
|
end
|
|
end
|
|
end
|
|
|
|
# Load a script from the specified file.
|
|
#
|
|
# @param path [String]
|
|
# File name of the rscons script to load.
|
|
#
|
|
# @return [void]
|
|
def load(path)
|
|
script_contents = File.read(path, mode: "rb")
|
|
TopLevelDsl.new(self).instance_eval(script_contents, path, 1)
|
|
end
|
|
|
|
# Perform configure action.
|
|
def configure(configure_op)
|
|
cdsl = ConfigureDsl.new(self, configure_op)
|
|
Task["configure"].actions.each do |action|
|
|
cdsl.instance_eval(&action)
|
|
end
|
|
end
|
|
|
|
end
|
|
|
|
end
|