Add custom build command option.

Unfortunately I was not able to split out moving safe_make and
run_make_command with the actual implementation of this option
after-the-fact. Whoops.

This should implement support for a custom build command (and custom
install command, assuming the install can be performed by adding
'install' to the command line).

Hopefully that will be sufficient for 'ninja' or other buildsystems.
Note that this only changes the *build* command, the other parts of the
build system still needs to be supported in kdesrc-build (for now,
custom build systems probably wouldn't be too hard to implement if
necessary).

Assuming your proposed command is available in PATH and can be called
like make, doing something like this should suffice:

module (or module-set) foo
    custom-build-command ninja
end module (or module-set)

BUG:303692
wilder
Michael Pyne 14 years ago
parent bd875e6dd5
commit 9d0f34cefd
  1. 18
      doc/index.docbook
  2. 386
      kdesrc-build

@ -1665,6 +1665,24 @@ only works for qt.</emphasis></para>
</entry>
</row>
<row id="conf-custom-build-command">
<entry>custom-build-command</entry>
<entry>Module setting overrides global</entry>
<entry>
<para>This option can be set to run a different command (other than
<command>make</command>, for example) in order to perform the build
process. &kdesrc-build; should in general do the right thing, so you
should not need to set this option. However it can be useful to use
alternate build systems.
</para>
<para>The value of this option is used as the command line to run, modified
by the <link linkend="conf-make-options">make-options</link> option as
normal.
</para>
</entry>
</row>
<row id="conf-cxxflags">
<entry>cxxflags</entry>
<entry>Appends to global option</entry>

@ -1528,6 +1528,7 @@ EOF
"cmake-options" => "",
"configure-flags" => "",
"colorful-output" => 1, # Use color by default.
"custom-build-command" => '',
"cxxflags" => "-pipe",
"debug" => "",
"debug-level" => ksb::Debug::INFO,
@ -3866,6 +3867,8 @@ EOF
ksb::Debug->import();
ksb::Util->import();
use List::Util qw(first);
sub new
{
my ($class, $module) = @_;
@ -3948,12 +3951,21 @@ EOF
return 'generic';
}
# Returns a list of possible build commands to run, any one of which should
# be supported by the build system.
sub buildCommands
{
# Non Linux systems can sometimes fail to build when GNU Make would work,
# so prefer GNU Make if present, otherwise try regular make.
return 'gmake', 'make';
}
# Return value style: boolean
sub buildInternal
{
my $self = shift;
return main::safe_make($self->module(), {
return $self->safe_make({
target => undef,
message => 'Compiling...',
'make-options' => [
@ -4005,7 +4017,7 @@ EOF
my $module = $self->module();
my @cmdPrefix = @_;
return main::safe_make ($module, {
return $self->safe_make ({
target => 'install',
message => "Installing g[$module]",
'prefix-options' => [@cmdPrefix],
@ -4022,7 +4034,7 @@ EOF
my $module = $self->module();
my @cmdPrefix = @_;
return main::safe_make ($module, {
return $self->safe_make ({
target => 'uninstall',
message => "Uninstalling g[$module]",
'prefix-options' => [@cmdPrefix],
@ -4089,6 +4101,198 @@ EOF
return 1;
}
# Subroutine to run the build command with the arguments given by the
# passed hash.
#
# In addition to finding the proper executable, this function handles the
# step of running the build command for individual subdirectories (as
# specified by the checkout-only option to the module). Due to the various
# ways the build command is called by this script, it is required to pass
# customization options in a hash:
# {
# target => undef, or a valid build target e.g. 'install',
# message => 'Compiling.../Installing.../etc.'
# make-options => [ list of command line arguments to pass to make. See
# make-options ],
# prefix-options => [ list of command line arguments to prefix *before* the
# make command, used for make-install-prefix support for
# e.g. sudo ],
# logbase => 'base-log-filename',
# subdirs => [ list of subdirectories of the module to build,
# relative to the module's own build directory. ]
# }
#
# target and message are required. logbase is required if target is left
# undefined, but otherwise defaults to the same value as target.
#
# Note that the make command is based on the results of the 'buildCommands'
# subroutine which should be overridden if necessary by subclasses. Each
# command should be the command name (i.e. no path). The user may override
# the command used (for build only) by using the 'custom-build-command'
# option.
#
# The first command name found which resolves to an executable on the
# system will be used, if no command this function will fail.
#
# The first argument should be the Module object to be made.
# The second argument should be the reference to the hash described above.
#
# Returns 0 on success, non-zero on failure (shell script style)
sub safe_make (@)
{
my ($self, $optsRef) = @_;
assert_isa($self, 'GenericBuildSystem');
my $module = $self->module();
# Convert the path to an absolute path since I've encountered a sudo
# that is apparently unable to guess. Maybe it's better that it
# doesn't guess anyways from a security point-of-view.
my $buildCommand = first { absPathToExecutable($_) } $self->buildCommands();
my @buildCommandLine = $buildCommand;
# Check for custom user command. We support command line options being
# passed to the command as well.
my $userCommand = $module->getOption('custom-build-command');
if ($userCommand) {
@buildCommandLine = split_quoted_on_whitespace($userCommand);
$buildCommand = absPathToExecutable($buildCommandLine[0]);
}
if (!$buildCommand) {
$buildCommand = $userCommand || $self->buildCommands();
error (" r[b[*] Unable to find the g[$buildCommand] executable!");
return 1;
}
# Make it prettier if pretending (Remove leading directories).
$buildCommand =~ s{^/.*/}{} if pretending();
shift @buildCommandLine; # $buildCommand is already the first entry.
# Simplify code by forcing lists to exist.
$optsRef->{'prefix-options'} //= [ ];
$optsRef->{'make-options'} //= [ ];
$optsRef->{'subdirs'} //= [ ];
my @prefixOpts = @{$optsRef->{'prefix-options'}};
# If using sudo ensure that it doesn't wait on tty, but tries to read from
# stdin (which should fail as we redirect that from /dev/null)
if (@prefixOpts && $prefixOpts[0] eq 'sudo' && !grep { /^-S$/ } @prefixOpts)
{
splice (@prefixOpts, 1, 0, '-S'); # Add -S right after 'sudo'
}
# Assemble arguments
my @args = (@prefixOpts, $buildCommand, @buildCommandLine);
push @args, $optsRef->{target} if $optsRef->{target};
push @args, @{$optsRef->{'make-options'}};
info ("\t", $optsRef->{message});
# Here we're attempting to ensure that we either run the build command
# in each subdirectory, *or* for the whole module, but not both.
my @dirs = @{$optsRef->{subdirs}};
push (@dirs, "") if scalar @dirs == 0;
for my $subdir (@dirs)
{
# Some subdirectories shouldn't have the build command run within
# them.
next unless $self->isSubdirBuildable($subdir);
my $logname = $optsRef->{logbase} // $optsRef->{target};
if ($subdir ne '')
{
$logname = $logname . "-$subdir";
# Remove slashes in favor of something else.
$logname =~ tr{/}{-};
# Mention subdirectory that we're working on, move ellipsis
# if present.
my $subdirMessage = $optsRef->{message};
if ($subdirMessage =~ /\.\.\.$/) {
$subdirMessage =~ s/(\.\.\.)?$/ subdirectory g[$subdir]$1/;
}
info ("\t$subdirMessage");
}
my $builddir = $module->fullpath('build') . "/$subdir";
$builddir =~ s/\/*$//; # Remove trailing /
p_chdir ($builddir);
my $result = $self->runBuildCommand($logname, \@args);
return $result if $result;
};
return 0;
}
# Subroutine to run make and process the build process output in order to
# provide completion updates. This procedure takes the same arguments as
# log_command() (described here as well), except that the callback argument
# is not used.
#
# First parameter is the name of the log file to use (relative to the log
# directory).
# Second parameter is a reference to an array with the command and its
# arguments. i.e. ['command', 'arg1', 'arg2']
# The return value is the shell return code, so 0 is success, and non-zero
# is failure.
sub runBuildCommand
{
my ($self, $filename, $argRef) = @_;
assert_isa($self, 'GenericBuildSystem');
my $module = $self->module();
# There are situations when we don't want (or can't get) progress output:
# 1. Not using CMake (i.e. Qt)
# 2. If we're not printing to a terminal.
# 3. When we're debugging (we'd interfere with debugging output).
if (!$self->isProgressOutputSupported() || ! -t STDERR || debugging())
{
return log_command($module, $filename, $argRef);
}
# Setup callback function for use by log_command.
my $last = -1;
# w00t. Check out the closure! Maks would be so proud.
my $log_command_callback = sub {
my ($input) = shift;
if (not defined $input)
{
# End of input, cleanup.
print STDERR "\r\e[K";
}
else
{
chomp($input);
my $percentage = '';
if ($input =~ /^\[\s*([0-9]+)%]/)
{
$percentage = $1;
}
# Update terminal (\e[K clears to the end of line) if the
# percentage changed.
if ($percentage and $percentage ne $last)
{
print STDERR "\r$percentage% \e[K";
}
$last = $percentage;
}
};
return log_command($module, $filename, $argRef, { callback => $log_command_callback });
}
1;
}
# }}}
@ -4261,7 +4465,7 @@ EOF
my @langs = $self->languages();
my $result = 0;
$result = (main::safe_make($self->module(), {
$result = ($self->safe_make({
target => undef,
message => "Building localization for language...",
logbase => "build",
@ -4297,7 +4501,7 @@ EOF
my $builddir = $self->module()->fullpath('build');
my @langs = $self->languages();
return (main::safe_make($self->module(), {
return ($self->safe_make({
target => 'install',
message => "Installing language...",
logbase => "install",
@ -5905,71 +6109,6 @@ sub download_file
return 0;
}
# Subroutine to run make and process the build process output in order to
# provide completion updates. This procedure takes the same arguments as
# log_command() (described here as well), except that the callback argument is
# not used.
#
# First parameter is the Module being built (for logging purposes and such).
# Second parameter is the name of the log file to use (relative to the log
# directory).
# Third parameter is a reference to an array with the command and its
# arguments. i.e. ['command', 'arg1', 'arg2']
# The return value is the shell return code, so 0 is success, and non-zero is
# failure.
sub run_make_command
{
my ($module, $filename, $argRef) = @_;
assert_isa($module, 'Module');
debug ("run_make_command: $module, ", join(', ', @{$argRef}));
# There are situations when we don't want (or can't get) progress output:
# 1. Not using CMake (i.e. Qt)
# 2. If we're not printing to a terminal.
# 3. When we're debugging (we'd interfere with debugging output).
if (!$module->buildSystem()->isProgressOutputSupported() || ! -t STDERR || debugging())
{
return log_command($module, $filename, $argRef);
}
# Setup callback function for use by log_command.
my $last = -1;
# w00t. Check out the closure! Maks would be so proud.
my $log_command_callback = sub {
my ($input) = shift;
if (not defined $input)
{
# End of input, cleanup.
print STDERR "\r\e[K";
}
else
{
chomp($input);
my $percentage = '';
if ($input =~ /^\[\s*([0-9]+)%]/)
{
$percentage = $1;
}
# Update terminal (\e[K clears to the end of line) if the
# percentage changed.
if ($percentage and $percentage ne $last)
{
print STDERR "\r$percentage% \e[K";
}
$last = $percentage;
}
};
return log_command($module, $filename, $argRef, { callback => $log_command_callback });
}
# Subroutine to delete a directory and all files and subdirectories within.
# Does nothing in pretend mode. An analogue to "rm -rf" from Linux.
# Requires File::Find module.
@ -6030,113 +6169,6 @@ sub safe_rmtree
return 1;
}
# Subroutine to run the make command with the arguments given by the passed
# hash. In addition to finding the proper make executable, this function
# handles the step of running make for individual subdirectories (as specified
# by the checkout-only option to the module). Due to the various ways make is
# used by this script, it is required to pass customization options in a hash:
# {
# target => undef, or a valid make target e.g. 'install',
# message => 'Compiling.../Installing.../etc.'
# make-options => [ list of command line arguments to pass to make. See
# make-options ],
# prefix-options => [ list of command line arguments to prefix *before* the
# make command, used for make-install-prefix support for
# e.g. sudo ],
# logbase => 'base-log-filename',
# subdirs => [ list of subdirectories of the module to build,
# relative to the module's own build directory. ]
# }
#
# target and message are required. logbase is required if target is left
# undefined, but otherwise defaults to the same value as target.
#
# The first argument should be the Module object to be made.
# The second argument should be the reference to the hash described above.
#
# Returns 0 on success, non-zero on failure (shell script style)
sub safe_make (@)
{
my ($module, $optsRef) = @_;
assert_isa($module, 'Module');
# Non Linux systems can sometimes fail to build when GNU Make would work,
# so prefer GNU Make if present, otherwise try regular make. Also, convert
# the path to an absolute path since I've encountered a sudo that is
# apparently unable to guess. Maybe it's better that it doesn't guess
# anyways from a security point-of-view.
my $make;
if(!($make = absPathToExecutable('gmake') || absPathToExecutable('make'))) {
# Weird, we can't find make, you'd think configure would have
# noticed...
error (" r[b[*] Unable to find the g[make] executable!");
return 1;
}
# Make it prettier if pretending (Remove leading directories).
$make =~ s{^/.*/}{} if pretending();
# Simplify code by forcing lists to exist.
$optsRef->{'prefix-options'} //= [ ];
$optsRef->{'make-options'} //= [ ];
$optsRef->{'subdirs'} //= [ ];
my @prefixOpts = @{$optsRef->{'prefix-options'}};
# If using sudo ensure that it doesn't wait on tty, but tries to read from
# stdin (which should fail as we redirect that from /dev/null)
if (@prefixOpts && $prefixOpts[0] eq 'sudo' && !grep { /^-S$/ } @prefixOpts)
{
splice (@prefixOpts, 1, 0, '-S'); # Add -S right after 'sudo'
}
# Assemble arguments
my @args = (@prefixOpts, $make);
push @args, $optsRef->{target} if $optsRef->{target};
push @args, @{$optsRef->{'make-options'}};
info ("\t", $optsRef->{message});
# Here we're attempting to ensure that we either run make in each
# subdirectory, *or* for the whole module, but not both.
my @dirs = @{$optsRef->{subdirs}};
push (@dirs, "") if scalar @dirs == 0;
for my $subdir (@dirs)
{
# Some subdirectories shouldn't have make run within them.
next unless $module->buildSystem()->isSubdirBuildable($subdir);
my $logname = $optsRef->{logbase} // $optsRef->{target};
if ($subdir ne '')
{
$logname = $logname . "-$subdir";
# Remove slashes in favor of something else.
$logname =~ tr{/}{-};
# Mention subdirectory that we're working on, move ellipsis
# if present.
my $subdirMessage = $optsRef->{message};
if ($subdirMessage =~ /\.\.\.$/) {
$subdirMessage =~ s/(\.\.\.)?$/ subdirectory g[$subdir]$1/;
}
info ("\t$subdirMessage");
}
my $builddir = $module->fullpath('build') . "/$subdir";
$builddir =~ s/\/*$//; # Remove trailing /
p_chdir ($builddir);
my $result = run_make_command ($module, $logname, \@args);
return $result if $result;
};
return 0;
}
# Reads a "line" from a file. This line is stripped of comments and extraneous
# whitespace. Also, backslash-continued multiple lines are merged into a single
# line.

Loading…
Cancel
Save