# ###############################################################################
# Copyright (c) 2014 CEA LIST.
#
# All rights reserved. This program and the accompanying materials
# are made available under the terms of the Eclipse Public License 2.0
# which accompanies this distribution, and is available at
# https://www.eclipse.org/legal/epl-2.0/
#
# SPDX-License-Identifier: EPL-2.0
#
# Contributors:
# Laurent Wouters laurent.wouters@cea.fr - Initial API and implementation
#
# ###############################################################################
"""
This script provides an API to load and manipulate
Eclipse plugins and features in a code repository
"""
import os # File handling
import os.path # File path handling
import re # Regular expressions
import xml.dom.minidom # Minimal XML
class Bundle:
"""
Represents an Eclipse bundle
"""
def __init__(self, location):
"""
Initializes this bundle
:param location: The bundle's location
:return: The bundle
"""
# The name as the last element of the path (folder name)
self.name = os.path.basename(location)
# The folder
self.location = location
class Plugin(Bundle):
"""
Represents an Eclipse plugin
"""
def __init__(self, location):
"""
Initializes this plugin
:param location: The plugin's location
:return: The plugin
"""
Bundle.__init__(self, location)
# Initializes the list of dependencies
self.dependencies = []
# Initializes the manifest data
self.properties = {}
# Load the data from manifest
manifest = open(os.path.join(os.path.join(location, "META-INF"), "MANIFEST.MF"), "r")
on_dependency = False
for line in manifest:
if line.startswith("Require-Bundle:") or on_dependency:
m = re.search("[a-zA-Z_][a-zA-Z_0-9]+(\\.[a-zA-Z_][a-zA-Z_0-9]+)+", line)
dep = m.group(0)
self.dependencies.append(dep)
on_dependency = line.endswith(",")
elif line.startswith("Bundle-"):
m = re.match("Bundle-(\\w+): (.*)", line)
self.properties[m.group(1)] = m.group(2)
manifest.close()
class Feature(Bundle):
"""
Represents an Eclipse feature
"""
def __init__(self, location):
"""
Represents an Eclipse feature
:param location: The feature's location
:return: The feature
"""
Bundle.__init__(self, location)
# Initializes the list of the included features
self.included = []
# Initializes the list of the plugins
self.plugins = []
# Load the content
doc = xml.dom.minidom.parse(os.path.join(location, "feature.xml"))
for node in doc.getElementsByTagName("plugin"):
identifier = node.getAttribute("id")
self.plugins.append(identifier)
for node in doc.getElementsByTagName("includes"):
identifier = node.getAttribute("id")
self.included.append(identifier)
class Repository:
"""
Represents a repository of Eclipse plugins and features
"""
def __init__(self):
"""
Initializes this repository
:return: The repository
"""
# Initializes a dictionary of plugins indexed by name
self.plugins = {}
# Initializes a dictionary of features indexed by name
self.features = {}
def load(self, directory):
"""
Recursively load plugins and features in the given directory
:param directory: The directory to load from
:return: None
"""
subs = os.listdir(directory)
if "META-INF" in subs:
# this is a plugin
plugin = Plugin(directory)
self.plugins[plugin.name] = plugin
return
if "feature.xml" in subs:
# this is a feature
feature = Feature(directory)
self.features[feature.name] = feature
return
for name in subs:
sub = os.path.join(directory, name)
if os.path.isdir(sub):
self.load(sub)
def check(self, include_pattern, exclude_pattern):
"""
Checks the consistency of the repository to check whether
all required features and plugins are present
The given pattern is used to determine whether a feature or
plugin is required ; matching means required
This method returns the list of the missing features and plugins
:param include_pattern: A pattern matching the bundles to include
:param exclude_pattern: A pattern matching the bundles to exclude
:return: The missing bundles
"""
result = []
for name in self.features:
if match(name, exclude_pattern):
continue
feature = self.features[name]
for included in feature.included:
if match(included, exclude_pattern):
continue
if not included in self.features and match(included, include_pattern):
result.append(included)
for plugin in feature.plugins:
if match(plugin, exclude_pattern):
continue
if not plugin in self.plugins and match(plugin, include_pattern):
result.append(plugin)
for name in self.plugins:
if match(name, exclude_pattern):
continue
plugin = self.plugins[name]
for dep in plugin.dependencies:
if match(dep, exclude_pattern):
continue
if not dep in self.plugins and match(dep, include_pattern):
result.append(dep)
return result
def match(value, pattern):
"""
Determines whether the specified value matches the given pattern
:param value: A value
:param pattern: A pattern
:return: True if the value matches the pattern
"""
m = re.match(pattern, value)
return m is not None