# ###############################################################################
# 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 v1.0
# which accompanies this distribution, and is available at
# http://www.eclipse.org/legal/epl-v10.html
#
# Contributors:
# Laurent Wouters laurent.wouters@cea.fr - Initial API and implementation
#
# ###############################################################################
"""
This script provides an API to generate the Tycho
configuration files (pom.xml) for an Eclipse code repository
"""
import os # File handling
import os.path # File path handling
import sys # System
import xml.dom.minidom # Minimal XML
import console # Console pretty printing
import eclipse # Eclipse API
import xmlutils # XML utilities
class Target:
"""
Represents a build target
"""
def __init__(self, name, pom, site, feature):
"""
Initializes this build target
:param name: The target's name
:param pom: Path to the target top pom.xml
:param site: Path to the target's site
:param feature: Identifier of the target's top Eclipse feature
:return: The build target
"""
self.name = name
self.pom = pom
self.site = site
self.feature = feature
# General constants
MAVEN_MODEL_VERSION = "4.0.0"
# Product constants
PRODUCT_VERSION = "1.0.0"
PRODUCT_CATEGORY_ID = "org.eclipse.papyrus.category"
PRODUCT_CATEGORY_LABEL = "Papyrus Category"
PRODUCT_CATEGORY_DESC = PRODUCT_CATEGORY_LABEL
PRODUCT_GROUP = "org.eclipse.papyrus"
# Generator targets configuration
TARGETS = [Target("main",
"releng/top-pom-main.xml",
"releng/main",
"org.eclipse.papyrus.sdk.feature"),
Target("extras",
"releng/top-pom-extras.xml",
"releng/extras",
"org.eclipse.papyrus.extra.feature"),
Target("dev",
"releng/top-pom-dev.xml",
"releng/dev",
"org.eclipse.papyrus.dev.feature")]
# Generator inputs configuration
INPUTS = [
"plugins",
"extraplugins",
"features/papyrus-main-features",
"features/papyrus-extra-features",
"features/papyrus-dev-features"
]
# Pattern to recognize required plugin to include in the build
PATTERN_INCLUDE = "org\\.eclipse\\.papyrus\\..*"
# Pattern to recognize required plugin to exclude from the build
PATTERN_EXCLUDE = "(.*\\.source.feature)|(.*\\.tests)"
def print_usage():
"""
Print how to use this script
:return: None
"""
print("Usage:")
print(" python tycho-generator.py [-h | --help] [--color]")
print("Options:")
print(" -h, --help: print this screen")
print(" --color: activate console color")
def generate(inputs, targets, include_pattern, exclude_pattern):
"""
Generate the Tycho data and files
:param inputs: Array of input directories to load Eclipse plugins and features from
:param targets: Array of build targets
:param include_pattern: Pattern matching Eclipse bundle to include into a build target
:param exclude_pattern: Pattern matching Eclipse bundle to exclude from any build target
:return: The error code, or 0 if all went well
"""
# Build repo
console.log("== Preparing the repository ...")
repo = __build_repository(inputs, include_pattern, exclude_pattern)
if repo is None:
return 1
# Setup the bundles' target data
for target in targets:
__add_target(repo, target.feature, target)
# Generate all bundles POM
console.log("== Generating POM for features ...")
for name in iter(sorted(repo.features)):
if not __generate_bundle_pom(repo.features[name], "eclipse-feature"):
return 2
console.log("== Generating POM for plugins ...")
for name in iter(sorted(repo.plugins)):
if not __generate_bundle_pom(repo.plugins[name], "eclipse-plugin"):
return 2
# Update the targets' top pom.xml
console.log("== Updating the module references in top POMs ...")
for target in targets:
__update_modules(repo, target)
return 0
def __build_repository(inputs, include_pattern, exclude_pattern):
"""
Gets an initialized repository of features and plugins
:param inputs: Array of input directories to load Eclipse plugins and features from
:param include_pattern: Pattern matching Eclipse bundle to include into a build target
:param exclude_pattern: Pattern matching Eclipse bundle to exclude from any build target
:return: The corresponding repository of Eclipse plugins and features
"""
# Build the repo
repository = eclipse.Repository()
for directory_input in inputs:
repository.load(directory_input)
# Check for missing bundles
missing = repository.check(include_pattern, exclude_pattern)
for m in missing:
console.log("Missing bundle " + m, "ERROR")
if len(missing) > 0:
return None
# Initializes the targets
for name in repository.plugins:
repository.plugins[name].targets = []
for name in repository.features:
repository.features[name].targets = []
return repository
def __add_target(repository, feature_identifier, target):
"""
Recursively add a build target to a feature, its included features and its plugins
:param repository: The Eclipse repository to work on
:param feature_identifier: The identifier of the Eclipse feature to add a build target for
:param target: The build target to add
:return: None
"""
# If the feature is missing
if not feature_identifier in repository.features:
return
feature = repository.features[feature_identifier]
# Add the target is required
if not target in feature.targets:
feature.targets.append(target)
# Traverse all sub-features
for included in feature.included:
__add_target(repository, included, target)
# Traverse all plugins
for name in feature.plugins:
if name in repository.plugins:
plugin = repository.plugins[name]
if not target in plugin.targets:
plugin.targets.append(target)
def __generate_bundle_pom(bundle, packaging):
"""
Generate the pom.xml file for the given bundle and given packaging
:param bundle: The bundle to generate the pom for
:param packaging: The type of packaging (feature or plugin)
:return: True if the operation succeeded, False otherwise
"""
if len(bundle.targets) == 0:
console.log("Bundle " + bundle.name + " has no target => skipped", "WARNING")
return True
if len(bundle.targets) >= 2:
console.log("Bundle " + bundle.name + " has more than one target:", "ERROR")
for target in bundle.targets:
console.log("\t" + target, "ERROR")
return False
if os.path.isfile(os.path.join(bundle.location, "pom.xml")):
console.log("Bundle " + bundle.name + " already has pom.xml => skipped")
return True
relative = os.path.relpath(".", bundle.location)
relative = os.path.join(relative, bundle.targets[0].pom)
impl = xml.dom.minidom.getDOMImplementation()
doc = impl.createDocument(None, "project", None)
project = doc.documentElement
__xml_append_text(doc, project, "modelVersion", MAVEN_MODEL_VERSION)
parent = doc.createElement("parent")
project.appendChild(parent)
__xml_append_tycho_ref(doc, parent, PRODUCT_GROUP)
__xml_append_text(doc, parent, "relativePath", relative)
__xml_append_tycho_ref(doc, project, bundle.name)
__xml_append_text(doc, project, "packaging", packaging)
xmlutils.output(doc, os.path.join(bundle.location, "pom.xml"))
console.log("Bundle " + bundle.name + " POM generated for target " + bundle.targets[0].name)
return True
def __xml_append_text(doc, parent, tag, content):
"""
Append an element node with the given tag and content
:param doc: The parent document
:param parent: The parent XML element node
:param tag: The element tag to create
:param content: The content of the element to create
:return: None
"""
child = doc.createElement(tag)
parent.appendChild(child)
child.appendChild(doc.createTextNode(content))
def __xml_append_tycho_ref(doc, parent, tycho_identifier):
"""
Append a reference to a Tycho module
:param doc: The parent document
:param parent: The parent XML element node
:param tycho_identifier: The Tycho module identifier
:return:
"""
__xml_append_text(doc, parent, "artifactId", tycho_identifier)
__xml_append_text(doc, parent, "groupId", PRODUCT_GROUP)
__xml_append_text(doc, parent, "version", PRODUCT_VERSION + "-SNAPSHOT")
def __update_modules(repository, target):
"""
Updates the modules for the given target
:param repository: The Eclipse repository to work on
:param target: The build target to update
:return: None
"""
doc = xml.dom.minidom.parse(target.pom)
modules = doc.getElementsByTagName("modules")[0]
for module in modules.getElementsByTagName("module"):
modules.removeChild(module)
for name in iter(sorted(repository.features)):
feature = repository.features[name]
if target in feature.targets:
modules.appendChild(__get_module_node(feature, doc))
for name in iter(sorted(repository.plugins)):
plugin = repository.plugins[name]
if target in plugin.targets:
modules.appendChild(__get_module_node(plugin, doc))
repo_node = doc.createElement("module")
repo_node.appendChild(doc.createTextNode(target.name))
modules.appendChild(repo_node)
xmlutils.output(doc, target.pom)
console.log("Updated top POM for target " + target.name)
def __get_module_node(bundle, doc):
"""
Get the path to the specified bundle relatively to the given target's top POM
:param bundle: An Eclipse bundle
:param doc: The parent XML document
:return: The XML node containing the relative path
"""
child = doc.createElement("module")
child.appendChild(doc.createTextNode(os.path.join("..", bundle.location)))
return child
# Main script
if __name__ == "__main__":
# Checks the arguments
for arg in sys.argv[1:]:
if arg == "-h" or arg == "--help":
print_usage()
sys.exit(0)
elif arg == console.CLI_COLOR:
console.USE_COLOR = True
# Execute the generation
code = generate(INPUTS, TARGETS, PATTERN_INCLUDE, PATTERN_EXCLUDE)
sys.exit(code)