# ############################################################################### # 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)