Skip to content

Commit

Permalink
feat: Add 1st tools to build Eclipse extensions
Browse files Browse the repository at this point in the history
  • Loading branch information
jamilraichouni committed Mar 19, 2024
1 parent c0cd7f5 commit af6caa6
Show file tree
Hide file tree
Showing 9 changed files with 293 additions and 38 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/commit-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ jobs:
env:
TEXT: |-
The pull request does not conform to the conventional commit specification. Please ensure that your commit messages follow the spec: <https://www.conventionalcommits.org/>.
We also strongly recommend that you set up your development environment with pre-commit, as described in our [CONTRIBUTING guidelines](https://github.com/DSD-DBS/eclipse-extension-builders/blob/master/CONTRIBUTING.md). This will run all the important checks right before you commit your changes, and avoids lengthy CI wait time and round trips.
We also strongly recommend that you set up your development environment with pre-commit, as described in our [CONTRIBUTING guidelines](https://github.com/DSD-DBS/eclipse-plugin-builders/blob/master/CONTRIBUTING.md). This will run all the important checks right before you commit your changes, and avoids lengthy CI wait time and round trips.
This is the commit validation log:
```
Expand Down
4 changes: 2 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ reduce the burden on our maintainers, please make sure that your code follows
our style guidelines outlined below.

<!-- prettier-ignore -->
[open an issue]: https://github.com/DSD-DBS/eclipse-extension-builders/issues
[open a pull request]: https://github.com/DSD-DBS/eclipse-extension-builders/pulls
[open an issue]: https://github.com/DSD-DBS/eclipse-plugin-builders/issues
[open a pull request]: https://github.com/DSD-DBS/eclipse-plugin-builders/pulls

## Developing

Expand Down
14 changes: 7 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,31 +3,31 @@
~ SPDX-License-Identifier: Apache-2.0
-->

# eclipse-extension-builders
# eclipse-plugin-builders

![image](https://github.com/DSD-DBS/eclipse-extension-builders/actions/workflows/build-test-publish.yml/badge.svg)
![image](https://github.com/DSD-DBS/eclipse-extension-builders/actions/workflows/lint.yml/badge.svg)
![image](https://github.com/DSD-DBS/eclipse-plugin-builders/actions/workflows/build-test-publish.yml/badge.svg)
![image](https://github.com/DSD-DBS/eclipse-plugin-builders/actions/workflows/lint.yml/badge.svg)

Tools to build Eclipse extensions without the Eclipse IDE frontend

# Documentation

Read the [full documentation on Github pages](https://dsd-dbs.github.io/eclipse-extension-builders).
Read the [full documentation on Github pages](https://dsd-dbs.github.io/eclipse-plugin-builders).

# Installation

You can install the latest released version directly from PyPI.

```sh
pip install eclipse-extension-builders
pip install eclipse-plugin-builders
```

To set up a development environment, clone the project and install it into a
virtual environment.

```sh
git clone https://github.com/DSD-DBS/eclipse-extension-builders
cd eclipse-extension-builders
git clone https://github.com/DSD-DBS/eclipse-plugin-builders
cd eclipse-plugin-builders
python -m venv .venv

source .venv/bin/activate.sh # for Linux / Mac
Expand Down
4 changes: 2 additions & 2 deletions docs/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
with open("../../pyproject.toml", "rb") as f:
_metadata = tomllib.load(f)["project"]

project = "eclipse-extension-builders"
project = "eclipse-plugin-builders"
author = _metadata["authors"][0]["name"]
copyright = f"{author} and the {_metadata['name']} contributors"

Expand Down Expand Up @@ -97,7 +97,7 @@
"footer_icons": [
{
"name": "GitHub",
"url": "https://github.com/DSD-DBS/eclipse-extension-builders",
"url": "https://github.com/DSD-DBS/eclipse-plugin-builders",
"html": '<img src="/_static/img/github-logo.svg"/>',
"class": "",
},
Expand Down
2 changes: 1 addition & 1 deletion docs/source/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
Copyright DB InfraGO AG and contributors
SPDX-License-Identifier: Apache-2.0
Welcome to eclipse-extension-builders's documentation!
Welcome to eclipse-plugin-builders's documentation!
======================================================

.. toctree::
Expand Down
21 changes: 0 additions & 21 deletions eclipse_extension_builders/__main__.py

This file was deleted.

File renamed without changes.
271 changes: 271 additions & 0 deletions eclipse_plugin_builders/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,271 @@
# Copyright DB InfraGO AG and contributors
# SPDX-License-Identifier: Apache-2.0
"""Tools to build, pack, deploy Eclipse extensions.
The present CLI needs the command line tools `jar` and `mvn` to be installed
and accessible via the user's PATH.
`jar` is part of the Java Development Kit (JDK) and is used to create the jar
file of the Eclipse extension.
`mvn` is the Maven build tool and is used to analyse the dependencies listed
in the `pom.xml` file to build the `.classpath` file.
"""

import pathlib
import shutil
import subprocess
import sys
import tempfile

import click
import lxml.builder
import lxml.etree

import eclipse_extension_builders

E = lxml.builder.ElementMaker()
MANIFEST_PATH = pathlib.Path("META-INF/MANIFEST.MF")
PLUGIN_XML_PATH = pathlib.Path("plugin.xml")
PATH_BLACKLIST = (
".pde.",
"/jre/",
"/org.eclipse.equinox.p2.repository/",
"ant",
"artifacts.jar",
"content.jar",
"ease",
"egit",
"jdt.debug",
"jgit",
"pydev",
)


@click.group()
@click.version_option(
version=eclipse_extension_builders.__version__,
prog_name="eclipse-plugin-builders",
message="%(prog)s %(version)s",
)
def main() -> None:
"""Console script for eclipse_extension_builders."""


def _third_party_lib_paths() -> list[pathlib.Path]:
"""Return the paths to the third-party libraries."""
classpath_root = _read_xml_file(".classpath")
third_party_lib_paths = classpath_root.xpath(
'classpathentry[@kind="lib" and '
'not(starts-with(@path, "/opt/capella_6.0.0"))]/@path'
)
return [pathlib.Path(p) for p in third_party_lib_paths]


def _output_and_jar_path() -> tuple[pathlib.Path, pathlib.Path]:
"""Return the path to the jar file."""
classpath_root = _read_xml_file(".classpath")
output = classpath_root.xpath('//classpathentry[@kind="output"]')
if not output:
click.echo(
"Output directory not found. Missing `classpathentry` with kind "
"`output` in `.classpath` file."
)
sys.exit(1)
output_path = pathlib.Path(output[0].get("path"))
if not list(output_path.iterdir()):
click.echo(f"Output directory `{output_path}` is empty.")
sys.exit(1)
pom = _read_xml_file("pom.xml")
# get the namespace from the root element
ns = {"m": "http://maven.apache.org/POM/4.0.0"} # Register the namespace
group_id = pom.xpath("//m:groupId", namespaces=ns)
artifact_id = pom.xpath("//m:artifactId", namespaces=ns)
version = pom.xpath("//m:version", namespaces=ns)
group_id = group_id[0].text if group_id else "unknown"
artifact_id = artifact_id[0].text if artifact_id else "unknown"
version = version[0].text if version else "unknown"
jar_name = f"{group_id}.{artifact_id}_{version}.jar"
jar_path = pathlib.Path("target") / jar_name
return output_path, jar_path


def _read_xml_file(path: str) -> lxml.etree.Element:
"""Read the classpath file."""
if not pathlib.Path(path).exists():
click.echo(f"`File {path}` not found.")
sys.exit(1)
tree = lxml.etree.parse(path)
return tree


def _collect_target_platform_plugins(
target_path: pathlib.Path,
) -> list[lxml.etree.Element]:
"""Add the target platform plugins to the classpath."""
# Recursively find all src JARs:
sources: set[pathlib.Path] = set(target_path.glob("**/*.source_*.jar"))
# Recursively find all lib JARs:
libs = list(set(target_path.glob("**/*.jar")) - sources)
srcs = list(sources)
target_classpaths = []
for src in srcs:
skip = False
for pattern in PATH_BLACKLIST:
skip = pattern in str(src)
if skip:
break
if skip:
continue
# get parent dir
parent = src.parent
# get base name
base = src.name
lib = parent / base.replace(".source_", "_")
try:
libs.remove(lib)
except ValueError:
pass
if lib.is_file() and src.is_file():
target_classpaths.append(
E.classpathentry(
kind="lib", path=str(lib), sourcepath=str(src)
)
)
for lib in libs:
skip = False
for pattern in PATH_BLACKLIST:
skip = pattern in str(lib)
if skip:
break
if skip:
continue
if lib.is_file():
target_classpaths.append(
E.classpathentry(kind="lib", path=str(lib))
)
target_classpaths.sort(key=lambda x: x.get("path"))
return target_classpaths


@main.command()
@click.argument("target_path", type=click.Path(exists=True, dir_okay=True))
def build_classpath(target_path: pathlib.Path) -> None:
"""Build `.classpath` file.
Parameters
----------
target_path : pathlib.Path
The installation directory of an Eclipse/ Capella application
that will be references as target platform to build the classpath.
"""
target_path = pathlib.Path(target_path)
if not target_path.is_dir():
click.echo(
f"Target platform installation dir `{target_path}` not found."
)
sys.exit(1)
classpaths = [
E.classpathentry(kind="src", path="src", including="**/*.java"),
E.classpathentry(kind="output", path="bin"),
E.classpathentry(
kind="con",
path=(
"org.eclipse.jdt.launching.JRE_CONTAINER/"
"org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/"
"JavaSE-17"
),
),
]
with tempfile.NamedTemporaryFile(mode="w", delete=False) as w:
mvn_cmd = [
"mvn",
"-q",
"dependency:build-classpath",
f"-Dmdep.outputFile={w.name}",
]
# Run command and wait:
subprocess.run(mvn_cmd, check=True)
with open(w.name, "r", encoding="utf-8") as tmp:
# Replace all colons with newlines and sort the lines:
classpath_3rdparty = tmp.read().replace(":", "\n").splitlines()
classpath_3rdparty.sort()
for path in classpath_3rdparty:
classpaths.append(E.classpathentry(kind="lib", path=path))
target_classpaths = _collect_target_platform_plugins(target_path)
classpath = E.classpath(*(classpaths + target_classpaths))
tree = lxml.etree.ElementTree(classpath)
xml_string = lxml.etree.tostring(
tree, xml_declaration=True, encoding="utf-8", pretty_print=True
)
pathlib.Path(".classpath").write_bytes(xml_string)


@main.command()
@click.argument("target_path", type=click.Path(exists=True, dir_okay=True))
def deploy(target_path: pathlib.Path) -> None:
"""Deploy the eclipse extension.
Parameters
----------
target_path : pathlib.Path
The installation directory of an Eclipse/ Capella application
where the extension will be deployed into the subdirectory `dropins`.
"""
target_path = pathlib.Path(target_path) / "dropins"
if not target_path.is_dir():
click.echo(f"Target directory `{target_path}` not found.")
sys.exit(1)
_, jar_path = _output_and_jar_path()
dest = target_path / jar_path.name
dest.unlink(missing_ok=True)
shutil.copy(jar_path, dest)
if dest.is_file():
click.echo(f"Deployed `{dest.resolve()}`.")


@main.command()
def package() -> None:
"""Package the eclipse extension."""
third_party_lib_paths = _third_party_lib_paths()
if third_party_lib_paths:
lib_dir = pathlib.Path("lib")
lib_dir.mkdir(exist_ok=True)
for path in third_party_lib_paths:
dest = lib_dir / path.name
dest.unlink(missing_ok=True)
shutil.copy(path, dest)
for path in (MANIFEST_PATH, PLUGIN_XML_PATH):
if not path.is_file():
click.echo(f"`{path}` file not found.")
sys.exit(1)
output_path, jar_path = _output_and_jar_path()
jar_path.unlink(missing_ok=True)
jar_cmd = [
"jar",
"cfm",
str(jar_path),
str(MANIFEST_PATH),
"-C",
f"{output_path}/",
".",
str(PLUGIN_XML_PATH),
]
if pathlib.Path("lib").is_dir() and list(pathlib.Path("lib").iterdir()):
jar_cmd.append("lib/")
jar_path.parent.mkdir(parents=True, exist_ok=True)
click.echo(f"Running command: {' '.join(jar_cmd)}")
subprocess.run(jar_cmd, check=True)
if jar_path.is_file():
click.echo(f"Created `{jar_path.resolve()}`.")


# Define another subcommand
@main.command()
def clean() -> None:
"""Clean the build artifacts."""
click.echo("Cleaning build artifacts...")


if __name__ == "__main__":
main()
Loading

0 comments on commit af6caa6

Please sign in to comment.