Re: [Module::Build] M::B actions design
Status: Beta
Brought to you by:
kwilliams
|
From: Randy W. S. <Ra...@Th...> - 2003-10-18 09:15:17
|
On 10/17/2003 11:38 AM, Steve Purkis wrote:
> On Friday, October 17, 2003, at 03:57 am, Randy W. Sims wrote:
>
>> On 10/16/2003 6:50 AM, Steve Purkis wrote:
>>
>>> On Thursday, October 16, 2003, at 12:06 am, Randy W. Sims wrote:
>>>
>>>> <thinking out loud>
>>>>
>>>> Each module would contain one or more actions.
>>>
>>> I'd limit it to one a package. That way you can get the action name
>>> from the package name itself:
>>> 'build' => M::B::Action::Build
>>
>>
>> It would be more convenient to allow multiple related actions to share
>> a file so that they can share common functions/data. Also, it's best
>> if they do not share the name because the most appropriate name for an
>> action might be a perl keyword. The module should have a descriptive
>> name appropriate for the actions it contains. The actions
>> (packages/classes) themselves should probably follow the current
>> standard of prefacing them with 'ACTION_' to avoid conflicts.
>
>
> I see where you're coming from, but I still disagree. Here's why:
>
> 1. Simplicity.
> This is the key, really. A class that does one thing and does it well
> is easier to write, maintain, understand and sub-class. There's also
> less to go wrong, and it will make dispatching the actions a lot easier,
> seeing as each action would be dispatched in the same way:
>
> $action->dispatch(); # thank you Pipeline.pm :)
>
> 2. Common code can be inherited or called out.
> We talked about inheritance above; with dependencies between actions,
> common code can be easily called out. The M::B API for actions would
> also fit in here.
>
> 3. Highly configurable.
> If each action is implemented as a small unit of code, you could easily
> change the behaviour of the system by replacing one of the units.
>
> I don't think having an action that has the same name as a perl keyword
> (say 'if') would be a problem here.
>
>
Ok, I'm willing to conceed. I think my main objection was aesthetic; it
would mean a lot of subclasses (nearly two dozen for builtins).
>>>> M::B would search for plugin actions at startup, querying each
>>>> module for a list of supported actions.
>>>
>>> So M::B builds up a list of actions and their classes? And I assume
>>> it would search particular sub-classes, starting with
>>> Module::Build::Action? Maybe that's how you could do user level
>>> sub-classing: push on your own 'search' package:
>>> @M::B::SearchPackages = qw( My::Action Module::Build::Action );
>>
>>
>> This is where I still have uncertainties. One way that seems kind of
>> hackish to me is to scan for '*.pm' under M::B::Action. Then try to
>> execute a register function for each module found. If an author
>> provides an action that is intended to subclass/override another
>> action, then the register function for that action would have to
>> report that in some way. I think this kind of usage would be rare in
>> practice. ???
>
>
> I've done some work along these lines in the past - right off the bat
> I'll tell ya that looking for *.pm doesn't work as well as you might
> like :-/.
>
> Here are a couple of algorithms I've used in the past (I'm sure there's
> more ;)...
>
> 1. A type map & a register method.
> The idea is you have an hash like the 'ActionClassMap' I mentioned
> before (pls forgive the studley caps :). That hash is populated
> internally by the module author:
>
> package Module::Build::Action;
> our %ActionClassMap =
> (
> Build => 'My::Action::Build',
> ...
> );
>
> Externally, that hash is populated by a 'register' method like the one
> you had above:
>
> package My::Module::Action::Build;
> use base qw( Modle::Build::Action::Build );
> Module::Build::Action->register( build => __PACKAGE__ );
> # or alternatively:
> __PACKAGE__->register( 'build' );
>
> 2. Search packages.
> This option is potentially less work, but not as flexible in naming
> convention - the idea is you have a rule for naming action classes under
> a namespace, and an ordered list of packages to search. For example:
>
> # assuming a 'ucfirst' rule:
> $class = ucfirst( $action ); # 'build' => 'Build'
> @search_pkgs = qw( Module::Build::Action );
>
> foreach my $pkg ( @search_pkgs ) {
> $pkg .= "::$class";
> return $pkg if $pkg->can( 'new' );
> eval "require $pkg";
> return $pkg unless $@;
> }
>
> die "couldn't find action $action\n";
>
>
>
My ASSumptions here are that M::B would need to construct a list of
actions at startup in order to workout overrides by other action authors
(as opposed to module authors writing Build.PL scripts). I don't think
either of these designs allow for that, but OTOH that restriction could
come from a valid design decision.
>>>> my $top = new M::B::ACTION::Builtin( name => 'all' );
>>>> $top->add_dependency($action);
>>>
>>> I'd go with 'M::B::Action::All', it's more consistent.
>>
>>
>> Actually, right now I'm thinking more along the lines of
>>
>> my $top = new M::B::Action::find_action( name => 'all' );
>> $top->add_dependency($action);
>>
>> to get a reference to an existing action as it seems more flexible.
>
>
> Mmm.. so you'd like to be able to compile a list of dependencies
> external to the Actions themselves, eh? Well, it's not what I was
> thinking but it could work.
>
> Still, after a bit of thought I'd say keep it simple and sub-class if
> ever you want to change the dependency tree. Which is not as flexible
> when you're writing Build.PL's, but it does mean less work for the
> person implementing/maintaining M::B's action dispatching code.
>
> -Steve
M::B itself would manage all dependencies. Build.PL authors, action
authors would be responsible for registering the dependencies with M::B.
M::B would also maintain a dispatch table for all actions and be
responsible for finding and invoking the correct action. So, (third
times a charm) the above would actually be M::B::find_action(). That
gives M::B the flexibiliy to chose the correct action when there may be
overrides involved.
This is about as far as I can think through the design without resorting
to drawing up some diagrams and taking a close look at the existing
code, and Ken may already have something better in mind.
A couple a good design exercises might be 1) to write a sample Build.PL
script and a plugin that uses the as-yet-unimplimented interface, and 2)
draw a call graph that follows execution from the time the user invokes
'./Build action' and decide what methods get called, what class they
belong to, and what they do. (Standard stuff)
In any case, I would suggest that a good first phase might be to look at
all of the existing ACTION_* subs and modify them so that they don't
access M::B's innards directly and simultaneously build up an API.
Then it might be possible to play around with different plugin designs.
I think the key to this phase will be two routines. 1) something along
the lines of M::B::scan_plugins() that would magically locate plugins,
passing each to... 2) a M::B::register_plugins() routine that would
build the master dispatch table (resolving any overrides) and depenency
trees. It would also be responsible for creating virtual stub functions
in M::B::Base that simply forward the calls to the appropriate plugin.
These stubs will allow back-patability, and I think they will allow a
solution to Ken's two design conflicts mentioned in another message.
Regards,
Randy.
|