-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Add 1st tools to build Eclipse extensions
- Loading branch information
1 parent
c0cd7f5
commit af6caa6
Showing
9 changed files
with
293 additions
and
38 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() |
Oops, something went wrong.