Commit 823e5969 authored by mkanat%bugzilla.org's avatar mkanat%bugzilla.org

Bug 430013: Make extensions load their modules like…

Bug 430013: Make extensions load their modules like Bugzilla::Extension::Foo::Bar, where Bar.pm is in extensions/Foo/lib/. Patch by Max Kanat-Alexander <mkanat@bugzilla.org> (module owner) a=mkanat
parent b04aed85
...@@ -26,7 +26,8 @@ use Bugzilla::Constants; ...@@ -26,7 +26,8 @@ use Bugzilla::Constants;
use Bugzilla::Error; use Bugzilla::Error;
use Bugzilla::Install::Util qw(extension_code_files); use Bugzilla::Install::Util qw(extension_code_files);
use File::Basename qw(basename); use File::Basename;
use File::Spec;
#################### ####################
# Subclass Methods # # Subclass Methods #
...@@ -45,18 +46,42 @@ sub new { ...@@ -45,18 +46,42 @@ sub new {
sub load { sub load {
my ($class, $extension_file, $config_file) = @_; my ($class, $extension_file, $config_file) = @_;
require $config_file if $config_file;
my $package; my $package;
# This is needed during checksetup.pl, because Extension packages can # This is needed during checksetup.pl, because Extension packages can
# only be loaded once (they return "1" the second time they're loaded, # only be loaded once (they return "1" the second time they're loaded,
# instead of their name). If an extension has only an Extension.pm, # instead of their name). During checksetup.pl, extensions are loaded
# and no Config.pm, the Extension.pm gets loaded by # once by Bugzilla::Install::Requirements, and then later again via
# Bugzilla::Install::Requirements before this load() method is ever # Bugzilla->extensions (because of hooks).
# called.
my $map = Bugzilla->request_cache->{extension_requirement_package_map}; my $map = Bugzilla->request_cache->{extension_requirement_package_map};
if ($config_file) {
if ($map and defined $map->{$config_file}) {
$package = $map->{$config_file};
}
else {
my $name = require $config_file;
if ($name =~ /^\d+$/) {
ThrowCodeError('extension_must_return_name',
{ extension => $config_file,
returned => $name });
}
$package = "${class}::$name";
}
# This allows people to override modify_inc in Config.pm, if they
# want to.
if ($package->can('modify_inc')) {
$package->modify_inc($config_file);
}
else {
modify_inc($package, $config_file);
}
}
if ($map and defined $map->{$extension_file}) { if ($map and defined $map->{$extension_file}) {
$package = $map->{$extension_file}; $package = $map->{$extension_file};
$package->modify_inc($extension_file) if !$config_file;
} }
else { else {
my $name = require $extension_file; my $name = require $extension_file;
...@@ -65,6 +90,7 @@ sub load { ...@@ -65,6 +90,7 @@ sub load {
{ extension => $extension_file, returned => $name }); { extension => $extension_file, returned => $name });
} }
$package = "${class}::$name"; $package = "${class}::$name";
$package->modify_inc($extension_file) if !$config_file;
} }
if (!eval { $package->NAME }) { if (!eval { $package->NAME }) {
...@@ -94,6 +120,45 @@ sub load_all { ...@@ -94,6 +120,45 @@ sub load_all {
return \@packages; return \@packages;
} }
# Modifies @INC so that extensions can use modules like
# "use Bugzilla::Extension::Foo::Bar", when Bar.pm is in the lib/
# directory of the extension.
sub modify_inc {
my ($class, $file) = @_;
my $lib_dir = File::Spec->catdir(dirname($file), 'lib');
# Allow Config.pm to override my_inc, if it wants to.
if ($class->can('my_inc')) {
unshift(@INC, sub { $class->my_inc($lib_dir, @_); });
}
else {
unshift(@INC, sub { my_inc($class, $lib_dir, @_); });
}
}
# This is what gets put into @INC by modify_inc.
sub my_inc {
my ($class, $lib_dir, undef, $file) = @_;
my @class_parts = split('::', $class);
my ($vol, $dir, $file_name) = File::Spec->splitpath($file);
my @dir_parts = File::Spec->splitdir($dir);
# Validate that this is a sub-package of Bugzilla::Extension::Foo ($class).
for (my $i = 0; $i < scalar(@class_parts); $i++) {
return if !@dir_parts;
if (File::Spec->case_tolerant) {
return if lc($class_parts[$i]) ne lc($dir_parts[0]);
}
else {
return if $class_parts[$i] ne $dir_parts[0];
}
shift(@dir_parts);
}
# For Bugzilla::Extension::Foo::Bar, this would look something like
# extensions/Example/lib/Bar.pm
my $resolved_path = File::Spec->catfile($lib_dir, @dir_parts, $file_name);
open(my $fh, '<', $resolved_path);
return $fh;
}
#################### ####################
# Instance Methods # # Instance Methods #
#################### ####################
...@@ -298,6 +363,30 @@ your extension is a single file named C<Foo.pm>. ...@@ -298,6 +363,30 @@ your extension is a single file named C<Foo.pm>.
If any of this is confusing, just look at the code of the Example extension. If any of this is confusing, just look at the code of the Example extension.
It uses this method to specify requirements. It uses this method to specify requirements.
=head2 Libraries
Extensions often want to have their own Perl modules. Your extension
can load any Perl module in its F<lib/> directory. (So, if your extension is
F<extensions/Foo/>, then your Perl modules go into F<extensions/Foo/lib/>.)
However, the C<package> name of your libraries will not work quite
like normal Perl modules do. F<extensions/Foo/lib/Bar.pm> is
loaded as C<Bugzilla::Extension::Foo::Bar>. Or, to say it another way,
C<use Bugzilla::Extension::Foo::Bar;> loads F<extensions/Foo/lib/Bar.pm>,
which should have C<package Bugzilla::Extension::Foo::Bar;> as its package
name.
This allows any place in Bugzilla to load your modules, which is important
for some hooks. It even allows other extensions to load your modules. It
even allows you to install your modules into the global Perl install
as F<Bugzilla/Extension/Foo/Bar.pm>, if you'd like, which helps allow CPAN
distribution of Bugzilla extensions.
B<Note:> If you want to C<use> or C<require> a module that's in
F<extensions/Foo/lib/> at the top level of your F<Extension.pm>,
you must have a F<Config.pm> (see above) with at least the C<NAME>
constant defined in it.
=head2 Disabling Your Extension =head2 Disabling Your Extension
If you want your extension to be totally ignored by Bugzilla (it will If you want your extension to be totally ignored by Bugzilla (it will
......
...@@ -22,15 +22,10 @@ ...@@ -22,15 +22,10 @@
package Bugzilla::Hook; package Bugzilla::Hook;
use strict; use strict;
use Bugzilla::Constants;
sub process { sub process {
my ($name, $args) = @_; my ($name, $args) = @_;
foreach my $extension (@{ Bugzilla->extensions }) { foreach my $extension (@{ Bugzilla->extensions }) {
local @INC = @INC;
my $ext_dir = bz_locations()->{'extensionsdir'};
my $ext_name = $extension->NAME;
unshift(@INC, "$ext_dir/$ext_name/lib");
if ($extension->can($name)) { if ($extension->can($name)) {
$extension->$name($args); $extension->$name($args);
} }
......
...@@ -24,10 +24,11 @@ package Bugzilla::Extension::Example; ...@@ -24,10 +24,11 @@ package Bugzilla::Extension::Example;
use strict; use strict;
use base qw(Bugzilla::Extension); use base qw(Bugzilla::Extension);
use Bugzilla::Util qw( use Bugzilla::Util qw(diff_arrays html_quote);
diff_arrays
html_quote # This is extensions/Example/lib/Util.pm. I can load this here in my
); # Extension.pm only because I have a Config.pm.
use Bugzilla::Extension::Example::Util;
use Data::Dumper; use Data::Dumper;
...@@ -57,7 +58,7 @@ sub auth_login_methods { ...@@ -57,7 +58,7 @@ sub auth_login_methods {
my ($self, $params) = @_; my ($self, $params) = @_;
my $modules = $params->{modules}; my $modules = $params->{modules};
if (exists $modules->{Example}) { if (exists $modules->{Example}) {
$modules->{Example} = 'extensions/Example/lib/AuthLogin.pm'; $modules->{Example} = 'Bugzilla/Extension/Example/Auth/Login.pm';
} }
} }
...@@ -65,7 +66,7 @@ sub auth_verify_methods { ...@@ -65,7 +66,7 @@ sub auth_verify_methods {
my ($self, $params) = @_; my ($self, $params) = @_;
my $modules = $params->{modules}; my $modules = $params->{modules};
if (exists $modules->{Example}) { if (exists $modules->{Example}) {
$modules->{Example} = 'extensions/Example/lib/AuthVerify.pm'; $modules->{Example} = 'Bugzilla/Extension/Example/Auth/Verify.pm';
} }
} }
...@@ -195,14 +196,14 @@ sub config { ...@@ -195,14 +196,14 @@ sub config {
my ($self, $params) = @_; my ($self, $params) = @_;
my $config = $params->{config}; my $config = $params->{config};
$config->{Example} = "extensions::Example::lib::ConfigExample"; $config->{Example} = "Bugzilla::Extension::Example::Config";
} }
sub config_add_panels { sub config_add_panels {
my ($self, $params) = @_; my ($self, $params) = @_;
my $modules = $params->{panel_modules}; my $modules = $params->{panel_modules};
$modules->{Example} = "extensions::Example::lib::ConfigExample"; $modules->{Example} = "Bugzilla::Extension::Example::Config";
} }
sub config_modify_panels { sub config_modify_panels {
...@@ -417,7 +418,7 @@ sub webservice { ...@@ -417,7 +418,7 @@ sub webservice {
my ($self, $params) = @_; my ($self, $params) = @_;
my $dispatch = $params->{dispatch}; my $dispatch = $params->{dispatch};
$dispatch->{Example} = "extensions::Example::lib::WSExample"; $dispatch->{Example} = "Bugzilla::Extension::Example::WebService";
} }
sub webservice_error_codes { sub webservice_error_codes {
......
# -*- Mode: perl; indent-tabs-mode: nil -*-
#
# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
# The Original Code is the Bugzilla Example Plugin.
#
# The Initial Developer of the Original Code is Canonical Ltd.
# Portions created by Canonical are Copyright (C) 2008 Canonical Ltd.
# All Rights Reserved.
#
# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
package Bugzilla::Extension::Example::Auth::Login;
use strict;
use base qw(Bugzilla::Auth::Login);
use constant user_can_create_account => 0;
use Bugzilla::Constants;
# Always returns no data.
sub get_login_info {
return { failure => AUTH_NODATA };
}
1;
# -*- Mode: perl; indent-tabs-mode: nil -*-
#
# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
# The Original Code is the Bugzilla Example Plugin.
#
# The Initial Developer of the Original Code is Canonical Ltd.
# Portions created by Canonical are Copyright (C) 2008 Canonical Ltd.
# All Rights Reserved.
#
# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
package Bugzilla::Extension::Example::Auth::Verify;
use strict;
use base qw(Bugzilla::Auth::Verify);
use Bugzilla::Constants;
# A verifier that always fails.
sub check_credentials {
return { failure => AUTH_NO_SUCH_USER };
}
1;
# -*- Mode: perl; indent-tabs-mode: nil -*-
#
# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
# The Original Code is the Bugzilla Example Plugin.
#
# The Initial Developer of the Original Code is Canonical Ltd.
# Portions created by Canonical Ltd. are Copyright (C) 2008
# Canonical Ltd. All Rights Reserved.
#
# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
# Bradley Baetz <bbaetz@acm.org>
package Bugzilla::Extension::Example::Config;
use strict;
use warnings;
use Bugzilla::Config::Common;
sub get_param_list {
my ($class) = @_;
my @param_list = (
{
name => 'example_string',
type => 't',
default => 'EXAMPLE',
},
);
return @param_list;
}
1;
# -*- Mode: perl; indent-tabs-mode: nil -*-
#
# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
# The Original Code is the Bugzilla Bug Tracking System.
#
# The Initial Developer of the Original Code is Everything Solved, Inc.
# Portions created by Everything Solved, Inc. are Copyright (C) 2009
# Everything Solved, Inc. All Rights Reserved.
#
# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
package Bugzilla::Extension::Example::Util;
use strict;
use warnings;
# This file exists only to demonstrate how to use and name your
# modules in an extension.
1;
# -*- Mode: perl; indent-tabs-mode: nil -*-
#
# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
# The Original Code is the Bugzilla Bug Tracking System.
#
# The Initial Developer of the Original Code is Everything Solved, Inc.
# Portions created by Everything Solved, Inc. are Copyright (C) 2007
# Everything Solved, Inc. All Rights Reserved.
#
# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
package Bugzilla::Extension::Example::WebService;
use strict;
use warnings;
use base qw(Bugzilla::WebService);
use Bugzilla::Error;
# This can be called as Example.hello() from the WebService.
sub hello { return 'Hello!'; }
sub throw_an_error { ThrowUserError('example_my_error') }
1;
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment