You may find yourself in the situation where it has become
necessary to extend the LCFG release tool, lcfg-reltool
,
to add new commands. For example, this might be to support building
binary packages for different platforms or maybe a test
framework. Whatever the need, it is very easy to extend by adding a
fairly simple Perl module.
The first stage is to choose the name of your command as this
affects the name of the Perl module you will have to create. Following
the standard didactic example of "Hello World" the example command
here will be called "hello". The module must be created in the
LCFG::Build::Tool
namespace and must extend the
LCFG::Build::Tool
Perl module. The module which will be
created should then be called LCFG::Build::Tool::Hello
.
It does not matter about any capitalisation in the final part of the
module name, the command will be the lower-cased version of that
string.
The LCFG build tools use a Perl object-oriented programming framework called Moose. It is not necessary to understand how the whole system works to just add a new command, just follow this example. You should also consider looking at the Tool modules shipped as part of the LCFG-Build-Tools package, those are more complex and you might find them quite instructive.
Here is a basic module:
package LCFG::Build::Tool::Hello; use strict; use warnings; use Moose; extends 'LCFG::Build::Tool'; sub run { my ( $self, $opt, $args ) = @_; print "hello world\n"; return; } no Moose; 1;
The run()
method is essential, it is called when the
command is actually called via lcfg-reltool
. Add this
module into somewhere in your Perl module include path. After that you
can run the release tool without any arguments and in the command list
you will see a new entry which looks something like:
hello: (unknown)
Running the command does what we wanted:
$ lcfg-reltool hello hello world
You may have noticed the "(unknown)" string in the help output
above. This is because we have not added any documentation for the
command module. This is achieved by simply adding an
abstract
subroutine which returns the documentation
string:
sub abstract { return 'Says hello to the world'; }
The help output now contains the following line for the "hello" command:
hello: Says hello to the world
Inside the run method you can access all the information stored in
the LCFG build metadata file. To do this you will either have
to specify the --dir
command line option or run the
release tool within the project directory.
Access to this information is gained through two attributes. The
first is named "spec" which holds a reference to the relevant
LCFG::Build::PkgSpec object. The second is named "vcs" which holds a
reference to the associated LCFG::Build::CVS
module. You
should read the specific perl documentation for the full capabilities
of each of those modules. Here's a simple example which exports the
tagged release for a project:
sub run { my ( $self, $opt, $args ) = @_; my ( $spec, $vcs ) = ( $self->spec, $self->vcs ); my $version = $spec->version; my $srcdir = $vcs->export( $version, $resultsdir ); return; }
You do not normally need to consider the second and third parameters to the run method. Everything is also encapsulated within the object-reference which is passed in as the first parameter.
If you look at the help page for the "hello" command you might be
surprised to see there are already some options defined. These have
been inherited from the LCFG::Build::Tool
parent
class.
$ lcfg-reltool help hello lcfg-reltoollcfg-reltool hello [long options...] --quiet Less output to the screen --resultsdir Base directory for storing results --dryrun Simulate operation --dir Project working directory
These options become object attributes so are very easy to access
from the run()
method. Here is a simple extension of the
previous example to demonstrate the point:
sub run { my ( $self, $opt, $args ) = @_; if ( $self->quiet ) { print "hello world\n"; } else { print "HELLO WORLD!\n"; } return; }
Running the command now gives:
$ lcfg-reltool hello --quiet hello world $ lcfg-reltool hello HELLO WORLD!
This now leads into adding extra options which are specific to this command. Again this is fairly simple and uses special syntax which is part of the Moose framework. Consider that we want to add an option to take the name of a user.
has 'name' => ( is => 'rw', isa => 'Str', default => sub { (getpwuid $< )[6] }, documentation => 'Your name', );
You do not particularly need to worry about the "is" clause, it can be either "ro" or "rw", it only affects whether you can modify the attribute within your code.
The "isa" clause is important, it states what type of option you
want for your module. Typically you will either want Str
for passing in a string or Bool
for a boolean option. The
interesting thing with the boolean options is that you can then use
them from the command line like --foo
and
--no-foo
.
You do not need to provide a default value but it is often a good idea. It can be either a single scalar value or, as in this case, a reference to an anonymous subroutine which does some work to discover the default. In this case it is pulling the name from the GECOS field.
The final part, which you should always supply, is the "documentation" clause. This short string appears in the help page for the command alongside the option and briefly explains its purpose.
There are quite a few more complex ways to control the options. You
will need to read the manual pages for the Moose
,
Moose::Getopt
and Moose::App::Cmd
Perl
modules for complete coverage.
Occasionally you might want to hide an option which is inherited from the parent class. You may wish to not provide support for that option at all or you might wish to hardwire the setting so it is always the same. This is quite easy to achieve with some like this:
has '+resultsdir' => ( traits => [ 'NoGetopt' ], );
Notice the +
symbol in front of the name of the
attribute you wish to hide. This is the way to use Moose to override
specific bits of an attribute. In this case a "NoGetopt" trait has
been set and the option will be ignored. Note though that the
attribute still exists for your objects, it just cannot be set from
the command line and is not listed in the help page. Running help
gives:
$ lcfg-reltool help hello lcfg-reltoollcfg-reltool hello [long options...] --quiet Less output to the screen --name Your name --dryrun Simulate operation --dir Project working directory
You might need to override a default value for an attribute which is inherited from the parent class. This is done in a similar way to the previous example:
has '+quiet' => ( default => 1, );
With that change the module will default to being quiet rather than noisy. To get the noisy output you would do:
$ lcfg-reltool hello --no-quiet --name 'Fred Bloggs' HELLO Fred Bloggs!
The code for the completed example is available. To try it out you will need a local perl directory which is in the Perl include path. If you do not have this configured then the easiest approach is this:
mkdir -p ~/perllibs/LCFG/Build/Tool export PERL5LIB=~/perllibs cp Hello.pm.txt ~/perllibs/LCFG/Build/Tool/Hello.pm
Occassionally you will want to inherit from other command modules to reuse code and attribute specifications. One particular case is when creating commands for building packages (e.g. "devrpm" or "rpm"). In this situation you should inherit from the "devpack" or "pack" modules. For example:
package LCFG::Build::Tool::DevRPM; use Moose; extends 'LCFG::Build::Tool::DevPack'; override 'abstract' => sub { return q{Build binary RPMS from the development source tree}; }; override 'run' => sub { my ($self) = @_; super; # do everything else necessary to build an RPM... }; no Moose; 1;
There is quite a lot going on in this example. Firstly, to inherit
from the "devpack" command it is necessary to use the Moose
extends
function. Secondly, it is necessary to override
the abstract
and run
methods, again this is
done with a Moose function. One possible gotcha here is that the
closing brace of the override subroutine block MUST
be followed with a semi-colon. The clever bit is the usage of the
Moose super
function to invoke the run method in the
"devpack" command module. The module can rely on this to do
everything necessary to package the development source tree. It is
then only necessary to do the extra RPM building and packaging
work. The same concepts would be use for the "rpm" command
or a "macosx" command.
Inheriting from another module like this provides access to all the
attributes declared in that module and consequently also the
associated command-line options. Adding further attributes works as
expected, see the full code for the
LCFG::Build::Tool::DevRPM
and
LCFG::Build::Tool::RPM
Perl modules for a complete
example.