Commit c60cfc3e authored by tstoeckler's avatar tstoeckler

Issue #962214 by tstoeckler, sun: Add support for library dependencies.

parent 49de0084
Libraries 7.x-2.x, xxxx-xx-xx
-----------------------------
#962214 by tstoeckler, sun: Add support for library dependencies.
#1224838 by sun, mjpa: Fix library path not being prepended to JS/CSS files.
#1023258 by tstoeckler: Make 'files' consistently keyed by filename.
#958162 by sun, tstoeckler: Add pre-detect callback group.
......
......@@ -63,6 +63,27 @@
* - css: A list of CSS files to load, using the same syntax as Drupal
* core's hook_library().
* - php: A list of PHP files to load.
* - dependencies: An array of libraries this library depends on. Similar to
* declaring module dependencies, the dependency declaration may contain
* information on the supported version. Examples of supported declarations:
* @code
* $libraries['dependencies'] = array(
* // Load the 'example' library, regardless of the version available:
* 'example',
* // Only load the 'example' library, if version 1.2 is available:
* 'example (1.2)',
* // Only load a version later than 1.3-beta2 of the 'example' library:
* 'example (>1.3-beta2)'
* // Only load a version equal to or later than 1.3-beta3:
* 'example (>=1.3-beta3)',
* // Only load a version earlier than 1.5:
* 'example (<1.5)',
* // Only load a version equal to or earlier than 1.4:
* 'example (<=1.4)',
* // Combinations of the above are allowed as well:
* 'example (>=1.3-beta2, <1.5)',
* );
* @endcode
* - variants: (optional) An associative array of available library variants.
* For example, the top-level 'files' property may refer to a default
* variant that is compressed. If the library also ships with a minified and
......
......@@ -261,6 +261,55 @@ function libraries_prepare_files(&$library, $version = NULL, $variant = NULL) {
}
}
/**
* Library post-detect callback to process and detect dependencies.
*
* It checks whether each of the dependencies of a library are installed and
* available in a compatible version.
*
* @param $library
* An associative array of library information or a part of it, passed by
* reference.
* @param $version
* If the library information belongs to a specific version, the version
* string. NULL otherwise.
* @param $variant
* If the library information belongs to a specific variant, the variant name.
* NULL otherwise.
*
* @see libraries_info()
* @see libraries_invoke()
*/
function libraries_detect_dependencies(&$library, $version = NULL, $variant = NULL) {
if (isset($library['dependencies'])) {
foreach ($library['dependencies'] as &$dependency_string) {
$dependency_info = drupal_parse_dependency($dependency_string);
$dependency = libraries_detect($dependency_info['name']);
if (!$dependency['installed']) {
$library['installed'] = FALSE;
$library['error'] = 'missing dependency';
$library['error message'] = t('The %dependency library, which the %library library depends on, is not installed.', array(
'%dependency' => $dependency['name'],
'%library' => $library['name'],
));
}
elseif (drupal_check_incompatibility($dependency_info, $dependency['version'])) {
$library['installed'] = FALSE;
$library['error'] = 'incompatible dependency';
$library['error message'] = t('The version %dependency_version of the %dependency library is not compatible with the %library library.', array(
'%dependency_version' => $dependency['version'],
'%dependency' => $dependency['name'],
'%library' => $library['name'],
));
}
// Remove the version string from the dependency, so libraries_load() can
// load the libraries directly.
$dependency_string = $dependency_info['name'];
}
}
}
/**
* Returns information about registered libraries.
*
......@@ -339,6 +388,7 @@ function libraries_info_defaults(&$library, $name) {
'version callback' => 'libraries_get_version',
'version arguments' => array(),
'files' => array(),
'dependencies' => array(),
'variants' => array(),
'versions' => array(),
'integration files' => array(),
......@@ -352,7 +402,8 @@ function libraries_info_defaults(&$library, $name) {
);
// Add our own callbacks before any others.
$library['callbacks']['info'] = array_merge(array('libraries_prepare_files'), $library['callbacks']['info']);
array_unshift($library['callbacks']['info'], 'libraries_prepare_files');
array_unshift($library['callbacks']['post-detect'], 'libraries_detect_dependencies');
return $library;
}
......@@ -538,6 +589,13 @@ function libraries_load($name, $variant = NULL) {
// If the library (variant) is installed, load it.
$library['loaded'] = FALSE;
if ($library['installed']) {
// Load library dependencies.
if (isset($library['dependencies'])) {
foreach ($library['dependencies'] as $dependency) {
libraries_load($dependency);
}
}
// Invoke callbacks in the 'load' group.
libraries_invoke('load', $library);
......
......@@ -50,6 +50,78 @@ class LibrariesTestCase extends DrupalWebTestCase {
libraries_prepare_files($library, NULL, NULL);
$this->assertEqual($expected, $library, 'libraries_prepare_files() works correctly.');
// Test libraries_detect_dependencies().
$library = array(
'name' => 'Example',
'dependencies' => array('example_missing'),
);
libraries_detect_dependencies($library);
$this->assertEqual($library['error'], 'missing dependency', 'libraries_detect_dependencies() detects missing dependency');
$error_message = t('The %dependency library, which the %library library depends on, is not installed.', array(
'%dependency' => 'Example missing',
'%library' => $library['name'],
));
$this->verbose("Expected:<br>$error_message");
$this->verbose('Actual:<br>' . $library['error message']);
$this->assertEqual($library['error message'], $error_message, 'Correct error message for a missing dependency');
$version = '1.1';
$compatible = array(
'1.1',
'<=1.1',
'>=1.1',
'<1.2',
'<2.0',
'>1.0',
'>1.0-rc1',
'>1.0-beta2',
'>1.0-alpha3',
'>0.1',
'<1.2, >1.0',
'>0.1, <=1.1',
);
$incompatible = array(
'1.2',
'2.0',
'<1.1',
'>1.1',
'<=1.0',
'<=1.0-rc1',
'<=1.0-beta2',
'<=1.0-alpha3',
'>=1.2',
'<1.1, >0.9',
'>=0.1, <1.1',
);
$library = array(
'name' => 'Example',
);
foreach ($compatible as $version_string) {
$library['dependencies'][0] = "example_dependency ($version_string)";
// libraries_detect_dependencies() is a post-detect callback, so
// 'installed' is already set, when it is called. It sets the value to
// FALSE for missing or incompatible dependencies.
$library['installed'] = TRUE;
libraries_detect_dependencies($library);
$this->assertTrue($library['installed'], "libraries_detect_dependencies() detects compatible version string: '$version_string' is compatible with '$version'");
}
foreach ($incompatible as $version_string) {
$library['dependencies'][0] = "example_dependency ($version_string)";
$library['installed'] = TRUE;
unset($library['error'], $library['error message']);
libraries_detect_dependencies($library);
$this->assertEqual($library['error'], 'incompatible dependency', "libraries_detect_dependencies() detects incompatible version strings: '$version_string' is incompatible with '$version'");
}
// Instead of repeating this assertion for each version string, we just
// re-use the $library variable from the foreach loop.
$error_message = t('The version %dependency_version of the %dependency library is not compatible with the %library library.', array(
'%dependency_version' => $version,
'%dependency' => 'Example dependency',
'%library' => $library['name'],
));
$this->verbose("Expected:<br>$error_message");
$this->verbose('Actual:<br>' . $library['error message']);
$this->assertEqual($library['error message'], $error_message, 'Correct error message for an incompatible dependency');
// Test that library information is found correctly.
$expected = array(
'name' => 'Example files',
......@@ -157,6 +229,20 @@ class LibrariesTestCase extends DrupalWebTestCase {
$this->verbose('<pre>' . var_export($library, TRUE) . '</pre>');
$this->assertEqual($library['variants']['example_variant']['installed'], TRUE, 'Existing variant found.');
// Test dependencies.
$library = libraries_load('example_dependency_missing');
$this->verbose('<pre>' . var_export($library, TRUE) . '</pre>');
$this->assertFalse($library['loaded'], 'Library with missing dependency cannot be loaded');
$library = libraries_load('example_dependency_incompatible');
$this->verbose('<pre>' . var_export($library, TRUE) . '</pre>');
$this->assertFalse($library['loaded'], 'Library with incompatible dependency cannot be loaded');
$library = libraries_load('example_dependency_compatible');
$this->verbose('<pre>' . var_export($library, TRUE) . '</pre>');
$this->assertEqual($library['loaded'], 1, 'Library with compatible dependency is loaded');
$loaded = &drupal_static('libraries_load');
$this->verbose('<pre>' . var_export($loaded, TRUE) . '</pre>');
$this->assertEqual($loaded['example_dependency']['loaded'], 1, 'Dependency library is also loaded');
// Test the applying of callbacks.
$expected = array(
'name' => 'Example callback',
......
......@@ -200,6 +200,38 @@ function libraries_test_libraries_info() {
),
);
// Test dependency loading.
// We add one file to each library to be able to verify if it was loaded with
// libraries_load().
// This library acts as a dependency for the libraries below.
$libraries['example_dependency'] = array(
'name' => 'Example dependency',
'library path' => drupal_get_path('module', 'libraries') . '/tests/example',
'version' => '1.1',
'files' => array('js' => array('example_1.js')),
);
$libraries['example_dependency_missing'] = array(
'name' => 'Example dependency missing',
'library path' => drupal_get_path('module', 'libraries') . '/tests/example',
'version' => '1',
'dependencies' => array('example_missing'),
'files' => array('js' => array('example_1.js')),
);
$libraries['example_dependency_incompatible'] = array(
'name' => 'Example dependency incompatible',
'library path' => drupal_get_path('module', 'libraries') . '/tests/example',
'version' => '1',
'dependencies' => array('example_dependency (>1.1)'),
'files' => array('js' => array('example_1.js')),
);
$libraries['example_dependency_compatible'] = array(
'name' => 'Example dependency compatible',
'library path' => drupal_get_path('module', 'libraries') . '/tests/example',
'version' => '1',
'dependencies' => array('example_dependency (>=1.1)'),
'files' => array('js' => array('example_1.js')),
);
// Test the applying of callbacks.
$libraries['example_callback'] = array(
'name' => 'Example callback',
......
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