From 39cd6f1920d4c06b9574d5d112d23be414ea7450 Mon Sep 17 00:00:00 2001 From: Jamil RAICHOUNI Date: Sat, 19 Oct 2024 10:53:54 +0200 Subject: [PATCH] feat: Add tools to build and deploy Capella addons --- .devcontainer/Dockerfile | 71 --- .devcontainer/eclipse_plugin_builders.py | 327 ---------- .devcontainer/init.lua | 24 - .devcontainer/init.vim | 99 --- .devcontainer/java.lua | 152 ----- .devcontainer/nvim-cmp.lua | 98 --- .devcontainer/nvim-lspconfig.lua | 25 - .devcontainer/plugins.lua | 98 --- .github/CODEOWNERS | 4 + .github/workflows/build-addon.yml | 130 ++++ .gitignore | 2 + .pre-commit-config.yaml | 8 +- CONTRIBUTING.md | 87 +-- capella_addons/__init__.py | 11 + capella_addons/__main__.py | 751 +++++++++++++++++++++++ pyproject.toml | 167 +++++ rest-api/META-INF/MANIFEST.MF | 81 +++ rest-api/META-INF/MANIFEST.MF.license | 2 + 18 files changed, 1160 insertions(+), 977 deletions(-) delete mode 100644 .devcontainer/Dockerfile delete mode 100755 .devcontainer/eclipse_plugin_builders.py delete mode 100644 .devcontainer/init.lua delete mode 100644 .devcontainer/init.vim delete mode 100644 .devcontainer/java.lua delete mode 100644 .devcontainer/nvim-cmp.lua delete mode 100644 .devcontainer/nvim-lspconfig.lua delete mode 100644 .devcontainer/plugins.lua create mode 100644 .github/CODEOWNERS create mode 100644 .github/workflows/build-addon.yml create mode 100644 capella_addons/__init__.py create mode 100644 capella_addons/__main__.py create mode 100644 pyproject.toml create mode 100644 rest-api/META-INF/MANIFEST.MF create mode 100644 rest-api/META-INF/MANIFEST.MF.license diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile deleted file mode 100644 index 7328178..0000000 --- a/.devcontainer/Dockerfile +++ /dev/null @@ -1,71 +0,0 @@ -# Copyright DB InfraGO AG and contributors -# SPDX-License-Identifier: Apache-2.0 - -FROM fedora:38 -LABEL maintainer "Jamil André RAICHOUNI " - -# system pkg manager installs {{{ -RUN dnf --color yes --refresh -y install \ - # to clone bespoke eclipse-plugin-builders - gcc \ - git \ - # to develop Capella 5.x plugins - java-11-openjdk-devel \ - # to develop Capella 6.x plugins - java-17-openjdk-devel \ - maven \ - python3-pip \ - zsh -# }}} -# configure terminal {{{ -SHELL ["/bin/zsh", "-c"] -ENV SHELL=/bin/zsh -RUN usermod --shell /bin/zsh root -RUN echo -e "alias :q='exit'\nalias :qa='exit'\nalias ls='ls --color'\nalias l='ls -lh'\nalias ll='ls -lha'\nalias vi='nvim'" >> /root/.zshrc -# }}} -# {{{ Neovim setup -RUN dnf --color yes --refresh -y install \ - fd-find \ - # for TSInstall dockerfile - libstdc++-devel \ - neovim \ - ripgrep -# ensure minimum setup in $HOME/.local/share/nvim: -RUN nvim --headless --cmd q -RUN mkdir -p /root/.config/nvim/ftplugin && mkdir -p /root/.config/nvim/lua/config -COPY java.lua /root/.config/nvim/ftplugin -COPY init.vim /root/.config/nvim/init.vim -COPY init.lua /root/.config/nvim/lua -COPY plugins.lua /root/.config/nvim/lua -COPY nvim-cmp.lua /root/.config/nvim/lua/config -COPY nvim-lspconfig.lua /root/.config/nvim/lua/config -COPY eclipse_plugin_builders.py /opt/eclipse_plugin_builders.py -RUN python3 -m venv /root/.venv && /root/.venv/bin/pip install click lxml -# }}} -# {{{ java-debug -WORKDIR /opt -RUN curl -L -o java-debug.tar.gz "https://github.com/microsoft/java-debug/archive/refs/tags/0.52.0.tar.gz" && \ - tar xvzf java-debug.tar.gz && \ - rm java-debug.tar.gz && \ - mv java-debug-* java-debug -WORKDIR /opt/java-debug -RUN zsh -c "./mvnw clean install" && chown -R root:root . && \ - find . -name "com.microsoft.java.debug.plugin-*.jar" -exec mv {} /opt \; && \ - rm -rf /opt/java-debug -# }}} -# {{{ Eclipse JDT Language Server -RUN mkdir /opt/eclipse.jdt.ls -WORKDIR /opt/eclipse.jdt.ls -RUN curl -L -o jdt-language-server.tar.gz \ - "https://www.eclipse.org/downloads/download.php?file=/jdtls/milestones/1.34.0/jdt-language-server-1.34.0-202404031240.tar.gz" && \ - tar xvzf jdt-language-server.tar.gz && \ - rm jdt-language-server.tar.gz && \ - chown -R root:root . -ENV PATH=/opt/eclipse.jdt.ls/bin:$PATH -# }}} -ENV DISPLAY="host.docker.internal:0.0" -ENV JAVA_HOME=/usr/lib/jvm/java-17-openjdk -RUN mkdir -p /workspaces/RUNTIME && mkdir /root/dev -RUN echo -e "source /root/.venv/bin/activate" >> /root/.zshrc -WORKDIR /root/dev -ENTRYPOINT ["/bin/zsh"] diff --git a/.devcontainer/eclipse_plugin_builders.py b/.devcontainer/eclipse_plugin_builders.py deleted file mode 100755 index 6cf99a3..0000000 --- a/.devcontainer/eclipse_plugin_builders.py +++ /dev/null @@ -1,327 +0,0 @@ -# Copyright DB InfraGO AG and contributors -# SPDX-License-Identifier: Apache-2.0 -"""Tools to build, pack, deploy Eclipse plugins. - -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 plugin. - -`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 - -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() -def main() -> None: - """Console script for eclipse_plugin_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 sorted([pathlib.Path(p) for p in third_party_lib_paths]) - - -def compute_jar_name() -> str: - """Compute and return the name of the jar file to be built.""" - 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" - return f"{group_id}.{artifact_id}_{version}.jar" - - -def _output_and_jar_path() -> tuple[pathlib.Path, pathlib.Path]: - """Return paths to output dir and the jar file to be built.""" - 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) - jar_name = compute_jar_name() - 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: - dropins_jars = list(target_path.glob("dropins/**/*.jar")) - features_jars = list(target_path.glob("features/**/*.jar")) - jre_jars = list(target_path.glob("jre/**/*.jar")) - plugins_jars = list(target_path.glob("plugins/**/*.jar")) - libs = list( - set(dropins_jars + features_jars + jre_jars + plugins_jars) - sources - ) - libs = [l for l in libs if not l.name == compute_jar_name()] - 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="target/classes"), - 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 plugin. - - Parameters - ---------- - target_path : pathlib.Path - The installation directory of an Eclipse/ Capella application - where the plugin 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()}`.") - - -def _get_bundle_classpath(third_party_lib_paths: list[pathlib.Path]) -> str: - lib_paths = sorted([p.name for p in _third_party_lib_paths()]) - value = "." - if third_party_lib_paths: - value = ".,\n" - value += ",\n".join(f" lib/{p}" for p in lib_paths) - return f"Bundle-ClassPath: {value}" - - -def _update_bundle_classpath( - third_party_lib_paths: list[pathlib.Path], -) -> None: - manifest = MANIFEST_PATH.read_text(encoding="utf-8") - bundle_classpath = _get_bundle_classpath(third_party_lib_paths) - lines = manifest.splitlines() - manifest = "" - found_bundle_classpath = False - inside_bundle_classpath = False - for line in lines: - if line.startswith("Bundle-ClassPath:"): - found_bundle_classpath = True - manifest += bundle_classpath + "\n" - inside_bundle_classpath = True - continue - if inside_bundle_classpath: - if line.startswith(" "): - continue - else: - inside_bundle_classpath = False - manifest += line.rstrip() + "\n" - if bundle_classpath and not found_bundle_classpath: - if not manifest.endswith("\n"): - manifest += "\n" - manifest += bundle_classpath + "\n" - # ensure that the maximum line length is not exceeded - # max = 72 - # manifest = "\n".join( - # line[:max] + "\n" + line[max:] if len(line) > max else line - # for line in manifest.splitlines() - # ) - MANIFEST_PATH.write_text(manifest, encoding="utf-8") - - -@main.command() -def package() -> None: - """Package the eclipse plugin.""" - lib_dir = pathlib.Path("lib") - if lib_dir.is_dir(): - shutil.rmtree(lib_dir) - lib_dir.mkdir() - third_party_lib_paths = _third_party_lib_paths() - if third_party_lib_paths: - for path in third_party_lib_paths: - dest = lib_dir / path.name - dest.unlink(missing_ok=True) - shutil.copy(path, dest) - _update_bundle_classpath(third_party_lib_paths) - 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), - ] - potential_additional_dirs = ( - "lib", - "OSGI-INF", - ) - for dir_ in potential_additional_dirs: - if pathlib.Path(dir_).is_dir() and list(pathlib.Path(dir_).iterdir()): - jar_cmd.append(f"{dir_}/") - 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() diff --git a/.devcontainer/init.lua b/.devcontainer/init.lua deleted file mode 100644 index b19b375..0000000 --- a/.devcontainer/init.lua +++ /dev/null @@ -1,24 +0,0 @@ --- Copyright DB InfraGO AG and contributors --- SPDX-License-Identifier: Apache-2.0 --- Lazy {{{ -local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim" -if not vim.loop.fs_stat(lazypath) then - vim.fn.system({ - "git", - "clone", - "--filter=blob:none", - "https://github.com/folke/lazy.nvim.git", - "--branch=stable", -- latest stable release - lazypath, - }) -end -vim.opt.rtp:prepend(lazypath) -require("plugins") -require("lazy").setup("plugins", { - defaults = { lazy = true }, - ui = { - border = "rounded", - size = { width = 0.6, height = 0.9 }, - } -}) --- }}} diff --git a/.devcontainer/init.vim b/.devcontainer/init.vim deleted file mode 100644 index c2913b1..0000000 --- a/.devcontainer/init.vim +++ /dev/null @@ -1,99 +0,0 @@ -" Copyright DB InfraGO AG and contributors -" SPDX-License-Identifier: Apache-2.0 - -colorscheme slate -set shell=/bin/zsh -let g:mapleader="ö" -set colorcolumn=79 -set completeopt="menuone,noinsert,noselect,preview" -set cursorline -set cursorlineopt=number -set expandtab " blanks instead of tab -set foldexpr=nvim_treesitter#foldexpr() -" search: -set ignorecase " case insensitive search -set smartcase " but become case-sensitive if you type uppercase characters -" Highlight trailing whitespace -set list listchars=tab:→\ ,trail:·,extends:>,precedes:< -set mouse=a -set nobackup -set noswapfile -set nowrap -set nowritebackup -set number -set relativenumber -set shiftwidth=4 -set tabstop=4 " tab width -set termguicolors " needed by plugin feline -set wildmenu " display all matching files when we tab complete -lua require("init") -" LSP -nnoremap lc lua vim.lsp.buf.declaration() -nnoremap ld lua vim.lsp.buf.definition() z -nnoremap lf :lua vim.lsp.buf.format({timeout_ms = 5000}) -nnoremap lh lua vim.lsp.buf.hover() -nnoremap li lua vim.lsp.buf.implementation() -nnoremap lj lua vim.diagnostic.goto_next{wrap=false,popup_opts={border="single"}} -nnoremap lk lua vim.diagnostic.goto_prev{wrap=false,popup_opts={border="single"}} -nnoremap ln lua vim.lsp.buf.rename() -nnoremap lsd lua vim.lsp.buf.document_symbol()copen -nnoremap lsw lua vim.lsp.buf.workspace_symbol() -nnoremap lt lua vim.lsp.buf.type_definition() -nnoremap lS lua require('jdtls').super_implementation() -nnoremap lp lua vim.diagnostic.open_float(nil, {scope = "line", focus = true, focusable = true, focus_id = "1"}) -nnoremap lP lua vim.diagnostic.open_float(nil, {scope = "buffer", focus = true, focusable = true}) -nnoremap lr lua vim.lsp.buf.references() - -function! PackageAndDeployEclipsePlugin() - silent! !python3 -c "import time; time.sleep(1)" - !python3 /opt/eclipse_plugin_builders.py package - !python3 /opt/eclipse_plugin_builders.py deploy /opt/capella -endfunction -function! CompilePackageAndDeployEclipsePlugin() - JdtCompile full - silent! !python3 -c "import time; time.sleep(1)" - call PackageAndDeployEclipsePlugin() - silent! !python3 -c "import time; time.sleep(2)" -endfunction - -autocmd! BufWritePost *.java lua vim.defer_fn(function() vim.cmd('JdtUpdateHotcode') end, 100) -autocmd! BufWritePost MANIFEST.MF call PackageAndDeployEclipsePlugin() -autocmd! TermOpen * setlocal colorcolumn=0 nomodified nonumber ruler norelativenumber -" leave insert mode of terminal as it is in vim -tnoremap N - -" nvim-dap (see :h :dap.txt) -" dap mostly opening new windows -nnoremap Db lua require('dap').list_breakpoints()copen -nnoremap Df lua require('dap.ui.widgets').sidebar(require('dap.ui.widgets').frames).open() -nnoremap dh lua require('dap.ui.widgets').hover() -nnoremap Dr lua require('dap').repl.open() -nnoremap Ds lua require('dap.ui.widgets').sidebar(require('dap.ui.widgets').scopes).open() - -" fallbacks if function keys do not work: -nnoremap db lua require('dap').toggle_breakpoint() -nnoremap dr lua require('dap').restart() -" continue functions also as (re-)start -nnoremap dc lua require('dap').continue() -nnoremap dC lua require('dap').run_last() -nnoremap d lua require('dap').terminate() - -nnoremap do lua require('dap').step_out() -nnoremap dn lua require('dap').step_over() -nnoremap ds lua require('dap').step_into() -nnoremap dx lua require('dap').clear_breakpoints() -" java (eclipse.jdt.ls) -nnoremap jb lua require('jdtls').build_projects({select_mode='all', full_build=false}) -nnoremap jc lua require('jdtls').compile('full') -nnoremap jC call CompilePackageAndDeployEclipsePlugin() -nnoremap jh JdtUpdateHotcode -nnoremap jo lua require('jdtls').organize_imports() -nnoremap jp !python3 /opt/eclipse_plugin_builders.py build-classpath /opt/capella - -" Clear search highlighting -nnoremap ö nohlsearch - -" line numbers -noremap a setlocal norelativenumber number -noremap r setlocal relativenumber -noremap n setlocal norelativenumber nonumber diff --git a/.devcontainer/java.lua b/.devcontainer/java.lua deleted file mode 100644 index d4bd2e9..0000000 --- a/.devcontainer/java.lua +++ /dev/null @@ -1,152 +0,0 @@ --- Copyright DB InfraGO AG and contributors --- SPDX-License-Identifier: Apache-2.0 - --- https://github.com/mfussenegger/nvim-dap/wiki/Java -local dap = require('dap') - -local project_name = vim.fn.fnamemodify(vim.fn.getcwd(), ':p:h:t') -local workspaces_dir = '/workspaces/' -local workspace_dir = workspaces_dir .. project_name - -dap.configurations.java = { - { - name = "Eclipse Plugin", - request = "launch", - type = "java", - javaExec = "java", - args = - "-product org.polarsys.capella.rcp.product -launcher /opt/capella/capella -name Eclipse -data /workspaces/RUNTIME --add-modules=ALL-SYSTEM -os linux -ws gtk -arch aarch64 -nl en_US -clean -consoleLog -debug", - vmArgs = - "-XX:+ShowCodeDetailsInExceptionMessages -Dorg.eclipse.swt.graphics.Resource.reportNonDisposed=true -Declipse.pde.launch=true -Dfile.encoding=UTF-8", - classPaths = { - "/opt/capella/plugins/org.eclipse.equinox.launcher_1.6.200.v20210416-2027.jar", - }, - mainClass = "org.eclipse.equinox.launcher.Main", - env = { - -- MODEL_INBOX_DIRECTORIES = "/dev/github/capella-addons/data/models/empty_capella_project:/dev/github/capella-addons/data/models/test:/dev/github/capella-rest-api/data/models/automated-train", - -- SELECTED_ELEMENTS_SERVICE_TARGET_URL = "http://localhost:8080", - } - }, -} - - -local jdtls = require('jdtls') -local extendedClientCapabilities = jdtls.extendedClientCapabilities -extendedClientCapabilities.resolveAdditionalTextEditsSupport = true -local bundles = { - vim.fn.glob("/opt/com.microsoft.java.debug.plugin-*.jar", 1), -} -local config = { - cmd = { - 'java', - '-Declipse.application=org.eclipse.jdt.ls.core.id1', - '-Dosgi.bundles.defaultStartLevel=4', - '-Declipse.product=org.eclipse.jdt.ls.core.product', - '-Dlog.protocol=true', - '-Dlog.level=ALL', - '-Xmx1g', - '--add-modules=ALL-SYSTEM', - '--add-opens', 'java.base/java.util=ALL-UNNAMED', - '--add-opens', 'java.base/java.lang=ALL-UNNAMED', - '-jar', - '/opt/eclipse.jdt.ls/plugins/org.eclipse.equinox.launcher_1.6.800.v20240304-1850.jar', - '-configuration', '/opt/eclipse.jdt.ls/config_linux_arm', - '-data', workspace_dir, - }, - root_dir = vim.fs.dirname(vim.fs.find({ 'plugin.xml', 'pom.xml', '.project' }, { upward = true })[1]), - settings = { - java = { - configuration = { - -- See https://github.com/eclipse/eclipse.jdt.ls/wiki/Running-the-JAVA-LS-server-from-the-command-line#initialize-request - -- And search for `interface RuntimeOption` - -- The `name` is NOT arbitrary, but must match one of the elements from `enum ExecutionEnvironment` in the link above - runtimes = { - { - name = "JavaSE-11", - path = "/usr/lib/jvm/java-11-openjdk/", - }, - { - name = "JavaSE-17", - path = "/usr/lib/jvm/java-17-openjdk/", - } - } - }, - -- updateBuildConfiguration = "automatic", - signatureHelp = { enabled = true }, - } - }, - capabilities = require("cmp_nvim_lsp").default_capabilities(), - handlers = { - ['textDocument/documentSymbol'] = function(_, result, ctx, _) - local bufnr = - vim.uri_to_bufnr(vim.uri_from_fname(ctx.params.textDocument.uri)) - - local items = {} - local symbols = {} - for _, symbol in ipairs(result) do - -- print(symbol.kind) - print(vim.inspect(symbol)) - local range = symbol.location and symbol.location.range or symbol.range - local start = range.start - table.insert(symbols, symbol) - table.insert(items, { - bufnr = bufnr, - lnum = start.line + 1, - col = start.character + 1, - text = symbol.name, - type = 'I', -- I for Information - }) - end - items = vim.lsp.util.symbols_to_items(symbols) - - -- Update quickfix list with the items - -- see `:h setqflist` - vim.fn.setqflist({}, 'r', { - title = 'Document Symbols', - items = items, - quickfixtextfunc = function(info) - local custom_items = vim.fn.getqflist({ id = info.id, items = 1 }).items - local l = {} - local txt = '' - for idx = info.start_idx, info.end_idx do - -- table.insert(l, vim.fn.fnamemodify(vim.fn.bufname(custom_items[idx].bufnr), ':p:.')) - -- see `:h quickfix-window-function` - txt = custom_items[idx].text - -- see https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#symbolKind - txt = txt:gsub('%[Class%]', ' ') - txt = txt:gsub('%[Constant%]', ' ') - txt = txt:gsub('%[Constructor%]', ' ') - txt = txt:gsub('%[Enum%]', ' ') - txt = txt:gsub('%[EnumMember%]', ' ') - txt = txt:gsub('%[Event%]', ' ') - txt = txt:gsub('%[Field%]', ' ') - txt = txt:gsub('%[File%]', ' ') - txt = txt:gsub('%[Function%]', ' ') - txt = txt:gsub('%[Interface%]', ' ') - txt = txt:gsub('%[Key%]', ' ') - txt = txt:gsub('%[Method%]', ' ') - txt = txt:gsub('%[Module%]', ' ') - txt = txt:gsub('%[Namespace%]', ' ') - txt = txt:gsub('%[Number%]', ' ') - txt = txt:gsub('%[Object%]', ' ') - txt = txt:gsub('%[Operator%]', ' ') - txt = txt:gsub('%[Package%]', ' ') - txt = txt:gsub('%[Property%]', ' ') - txt = txt:gsub('%[String%]', ' ') - txt = txt:gsub('%[Struct%]', ' ') - txt = txt:gsub('%[TypeParameter%]', ' ') - txt = txt:gsub('%[Variable%]', ' ') - table.insert(l, txt) - end - return l - end - }) - end, - }, - init_options = { - bundles = { - vim.fn.glob("/opt/com.microsoft.java.debug.plugin-*.jar", 1) - }, - } -} -jdtls.start_or_attach(config) diff --git a/.devcontainer/nvim-cmp.lua b/.devcontainer/nvim-cmp.lua deleted file mode 100644 index 4ce9046..0000000 --- a/.devcontainer/nvim-cmp.lua +++ /dev/null @@ -1,98 +0,0 @@ --- Copyright DB InfraGO AG and contributors --- SPDX-License-Identifier: Apache-2.0 - -local cmp = require("cmp") - -local cmp_kinds = { - Class = " ", - Color = " ", - Constant = " ", - Constructor = " ", - Enum = " ", - EnumMember = " ", - Event = " ", - Field = " ", - File = " ", - Folder = " ", - Function = " ", - Interface = " ", - Keyword = " ", - Method = " ", - Module = " ", - Operator = " ", - Property = " ", - Reference = " ", - Snippet = " ", - Struct = " ", - Text = " ", - TypeParameter = " ", - Unit = " ", - Value = " ", - Variable = " ", -} -local has_words_before = function() - local line, col = unpack(vim.api.nvim_win_get_cursor(0)) - return col ~= 0 and vim.api.nvim_buf_get_lines(0, line - 1, line, true)[1]:sub(col, col):match("%s") == nil -end - -local feedkey = function(key, mode) - vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes(key, true, true, true), mode, true) -end - -cmp.setup({ - formatting = { - -- https://github.com/hrsh7th/nvim-cmp/wiki/Menu-Appearance#how-to-add-visual-studio-code-codicons-to-the-menu - format = function(_, vim_item) - vim_item.kind = (cmp_kinds[vim_item.kind] or "") .. vim_item.kind - return vim_item - end, - }, - -- window = { - -- completion = cmp.config.window.bordered(), - -- documentation = cmp.config.window.bordered(), - -- }, - sources = cmp.config.sources({ - { - name = "buffer", - option = { - keyword_pattern = [[\K\k*]], - }, - }, - { name = "nvim_lsp" }, - { name = "nvim_lsp_document_symbol" }, - { name = "nvim_lsp_signature_help" }, - { - name = "path", - option = { - trailing_slash = true - } - }, - }), - mapping = cmp.mapping.preset.insert({ - [""] = cmp.mapping(function(fallback) - if cmp.visible() then - cmp.select_next_item() - elseif vim.fn["vsnip#available"](1) == 1 then - feedkey("(vsnip-expand-or-jump)", "") - elseif has_words_before() then - cmp.complete() - else - fallback() -- The fallback function sends an already mapped key. In this case, it's probably ``. - end - -- end, { "i", "s", "t" }), - end, { "i", "s" }), - [""] = cmp.mapping(function() - if cmp.visible() then - cmp.select_prev_item() - elseif vim.fn["vsnip#jumpable"](-1) == 1 then - feedkey("(vsnip-jump-prev)", "") - end - -- end, { "i", "s", "t" }), - end, { "i", "s" }), - [''] = cmp.mapping.scroll_docs(-4), - [''] = cmp.mapping.scroll_docs(4), - [''] = cmp.mapping.complete(), - [''] = cmp.mapping.abort(), - [''] = cmp.mapping.confirm({ select = true }), -- Accept currently selected item. Set `select` to `false` to only confirm explicitly selected items. - }) -}) diff --git a/.devcontainer/nvim-lspconfig.lua b/.devcontainer/nvim-lspconfig.lua deleted file mode 100644 index 0c278e6..0000000 --- a/.devcontainer/nvim-lspconfig.lua +++ /dev/null @@ -1,25 +0,0 @@ --- Copyright DB InfraGO AG and contributors --- SPDX-License-Identifier: Apache-2.0 - -vim.lsp.set_log_level("warn") - -vim.diagnostic.config({ - signs = true, - underline = true, - update_in_insert = true, - float = { - focusable = true, - focus = true, - severity_sort = true, - source = "always" - }, - severity_sort = true, - source = true, - virtual_text = false -}) - -local signs = { Error = " ", Warn = " ", Hint = " ", Info = " " } -for type, icon in pairs(signs) do - local hl = "DiagnosticSign" .. type - vim.fn.sign_define(hl, { text = icon, texthl = hl, numhl = hl }) -end diff --git a/.devcontainer/plugins.lua b/.devcontainer/plugins.lua deleted file mode 100644 index 305973a..0000000 --- a/.devcontainer/plugins.lua +++ /dev/null @@ -1,98 +0,0 @@ --- Copyright DB InfraGO AG and contributors --- SPDX-License-Identifier: Apache-2.0 - -return { - { - -- https://github.com/tpope/vim-commentary - "tpope/vim-commentary", - event = "VeryLazy" - }, - { - -- https://github.com/tpope/vim-fugitive - "tpope/vim-fugitive", - cmd = { "G", "Gclog" }, - event = "VeryLazy" - }, - { - -- https://github.com/tpope/vim-repeat - "tpope/vim-repeat", - event = "VeryLazy" - }, - { - -- https://github.com/neovim/nvim-lspconfig - "neovim/nvim-lspconfig", - event = "VeryLazy", - config = function() require("config.nvim-lspconfig") end, - enabled = function() - return os.getenv("HOSTNAME") == "devcontainer" - end - }, - { - -- https://github.com/tpope/vim-surround - -- (e. g. cs"' to replace double by single quotes) - "tpope/vim-surround", - event = "VeryLazy" - }, - { - -- https://github.com/nvim-treesitter/nvim-treesitter - "nvim-treesitter/nvim-treesitter", - lazy = false, - build = ":TSUpdate", - config = function() - require "nvim-treesitter.configs".setup { - ensure_installed = "java", - highlight = { enable = true } - } - end - }, - { - -- https://github.com/mfussenegger/nvim-dap - "mfussenegger/nvim-dap", - ft = { "java" }, - config = function() - local dap = require('dap') - dap.defaults.fallback.switchbuf = 'usetab' - dap.defaults.fallback.terminal_win_cmd = 'belowright 15new | wincmd J' - vim.fn.sign_define('DapBreakpoint', - { text = '', texthl = 'DapBreakpointText', linehl = 'DapBreakpointLine', numhl = '' }) - vim.fn.sign_define('DapStopped', - { text = '', texthl = 'DapStoppedText', linehl = 'DapStoppedLine', numhl = '' }) - end - }, - { - -- https://github.com/mfussenegger/nvim-jdtls - "mfussenegger/nvim-jdtls", - -- https://github.com/mfussenegger/nvim-dap - dependencies = "mfussenegger/nvim-dap", - ft = "java" - }, - { - -- https://github.com/hrsh7th/nvim-cmp - "hrsh7th/nvim-cmp", -- ENGINE - event = "InsertEnter", - config = function() require("config.nvim-cmp") end, - dependencies = { - -- SOURCES/ PROVIDERS - -- (sources are the bridge between provider and nvim-cmp): - { - -- https://github.com/hrsh7th/cmp-buffer - "hrsh7th/cmp-buffer", - }, - { - -- https://github.com/hrsh7th/cmp-nvim-lsp - "hrsh7th/cmp-nvim-lsp", -- source - dependencies = { "neovim/nvim-lspconfig" } -- provider - }, - { - -- https://github.com/hrsh7th/cmp-nvim-lsp-signature-help - "hrsh7th/cmp-nvim-lsp-signature-help", -- source - dependencies = { "neovim/nvim-lspconfig" } -- provider - }, - { - -- https://github.com/hrsh7th/cmp-path - "hrsh7th/cmp-path", -- source - }, - { "kyazdani42/nvim-web-devicons" } - } - }, -} diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..21cbf38 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,4 @@ +# SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: CC0-1.0 + +* @jamilraichouni diff --git a/.github/workflows/build-addon.yml b/.github/workflows/build-addon.yml new file mode 100644 index 0000000..f4c9006 --- /dev/null +++ b/.github/workflows/build-addon.yml @@ -0,0 +1,130 @@ +# SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: CC0-1.0 +name: Build addons + +on: + push: + pull_request: + branches: ["main"] + +env: + registry: ghcr.io/dsd-dbs/capella-addons/ + addons: rest-api + JVM_DIR: /usr/lib/jvm + +jobs: + # test: + # name: Test + # runs-on: ubuntu-latest + # steps: + # - name: Test + # run: |- + quality: + name: Ensure code quality + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.12" + - name: Install pre-commit + run: |- + python -m pip install pre-commit + - name: Run pre-commit + run: |- + pre-commit run --all-files + setup-build-environment: + strategy: + matrix: + capella: + - version: "6.0.0" + jdk: + name: "jdk-17.0.6+10" + url: "https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.6%2B10/OpenJDK17U-jdk_aarch64_linux_hotspot_17.0.6_10.tar.gz" + # url: "https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.6%2B10/OpenJDK17U-jdk_x64_linux_hotspot_17.0.6_10.tar.gz" + # - version: "6.1.0" + # jdk: + # name: "jdk-17.0.6+10" + # url: "https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.6%2B10/OpenJDK17U-jdk_aarch64_linux_hotspot_17.0.6_10.tar.gz" + # - version: "7.0.0" + # jdk: + # name: "jdk-17.0.11+9" + # url: "https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.11%2B9/OpenJDK17U-jdk_x64_linux_hotspot_17.0.11_9.tar.gz" + name: Setup build environment for Capella ${{ matrix.capella.version }} + # needs: [quality] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.12" + - name: Install `capella-addons` CLI tool + run: |- + pip install . + pip show capella-addons + - name: Install Capella from capelladocker-images + run: |- + docker pull ghcr.io/dsd-dbs/capella-dockerimages/capella/remote:${{ matrix.capella.version }}-selected-dropins-main + if [ ! -d /tmp/capella_${{ matrix.capella.version }} ]; then + docker run --rm -it -v /tmp:/tmp --entrypoint="" --user=root \ + ghcr.io/dsd-dbs/capella-dockerimages/capella/remote:${{ matrix.capella.version }}-selected-dropins-main \ + bash -c "cp -r /opt/capella /tmp/capella_${{ matrix.capella.version }}" + fi + - name: Install JDKs + run: |- + JVM_DIR=/usr/lib/jvm + TMP_JDK=/tmp/jdk.tar.gz + mkdir -p $JVM_DIR + cd $JVM_DIR + URL="${{ matrix.capella.jdk.url }}" + [[ -f $TMP_JDK ]] && rm $TMP_JDK + curl -Lo $TMP_JDK $URL + JDK_DIR_NAME=$(tar tf $TMP_JDK | head -n 1) + [[ -d $JDK_DIR_NAME ]] && rm -rf $JDK_DIR_NAME + tar xzf $TMP_JDK + mv $JVM_DIR/jdk-* /usr/lib/jvm/jdk + rm $TMP_JDK + # done + - name: Install Eclipse JDT language server + run: |- + if [ ! -d /tmp/jdtls ]; then mkdir /tmp/jdtls; fi + cd /tmp/jdtls + if [ ! -f jdtls.tar.gz ]; then + curl -Lo jdtls.tar.gz \ + https://download.eclipse.org/jdtls/milestones/1.40.0/jdt-language-server-1.40.0-202409261450.tar.gz + fi + tar xzf jdtls.tar.gz + rm *.tar.gz + build-addons: + name: Build addons + needs: [setup-build-environment] + runs-on: ubuntu-latest + strategy: + matrix: + capella_version: + - '6.0.0' + # - '6.1.0' + # - '7.0.0' + addon: + - rest-api + steps: + - name: Build `.classpath` file + run: |- + cd ${{ matrix.addon }} + python -m capella_addons build-classpath \ + --java-execution-environment=JavaSE-17 \ + $(find . -type f -name "Main.java") \ + /tmp/capella_${{ matrix.capella_version }} + cat .classpath + - name: Build workspace + run: |- + cd ${{ matrix.addon }} + rm -rf target + python -m capella_addons -v \ + build-workspace \ + --java-execution-environment=JavaSE-17 \ + /usr/lib/jvm/jdk-17.0.6+10 \ + /tmp/jdtls diff --git a/.gitignore b/.gitignore index 6c68ce1..8afbc5a 100644 --- a/.gitignore +++ b/.gitignore @@ -2,10 +2,12 @@ # SPDX-License-Identifier: CC0-1.0 *.class +*.egg-info/ *.jar .classpath .env .mypy_cache/ +.ruff_cache/ .settings/ lib/ target/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d4aa14f..3cb7cca 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -8,11 +8,11 @@ default_language_version: minimum_pre_commit_version: 3.2.0 repos: - repo: https://github.com/gitleaks/gitleaks.git - rev: v8.21.0 + rev: v8.21.1 hooks: - id: gitleaks - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.5.0 + rev: v5.0.0 hooks: - id: check-added-large-files - id: check-ast @@ -126,10 +126,6 @@ repos: rev: v2.3.0 hooks: - id: codespell - - repo: https://github.com/rhysd/actionlint - rev: v1.7.3 - hooks: - - id: actionlint-docker - repo: https://github.com/pre-commit/mirrors-mypy rev: v1.12.0 hooks: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4093a8d..1573de0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -3,84 +3,17 @@ ~ SPDX-License-Identifier: Apache-2.0 --> - - # Contributing -## Development container with setup to develop Capella plugins - -### Build container - -This repository comes with a subdirectory `.devcontainer` that contains a -`Dockerfile`. This `Dockerfile` is based on a Fedora image and installs all -dependencies needed to develop Capella plugins. - -For now the `Dockerfile` comes with everything needed to develop Capella -plugins using Neovim. - -Build the container via the following command: - -```bash -docker build -t capella-plugins-dev .devcontainer -``` - -### Run container on macOS - -Precondition: - -- Download and install XQuartz from https://www.xquartz.org/ -- Optionally: Register `XQuartz` as login item (macOS' autostart) -- Open the `XQuartz.app` and open the settings (menu). Got to tab "Security" - and tick the checkbox "Allow connections from network clients" -- Restart `XQuartz` -- Run a container via - - ```bash - docker run (...) \ - -v /tmp/.X11-unix:/tmp/.X11-unix (...) - ``` - -Store some Capella development project locally at `/tmp/dev`. You may want to -command - -```bash -mkdir /tmp/dev -git clone --branch feat-models-from-directory-importer \ - git@github.com:DSD-DBS/capella-addons.git \ - /tmp/dev/capella-addons -``` - -Run the container via the following command where a Capella is mounted to -`/opt/capella`. - -We you just want to build and not debug a plugin you can mount a local macOS -Capella installation to `/opt/capella`. - -To be able to debug a plugin during development run the container via the -following command where a Linux Capella with an appropriate architecture -(x86_64 or aarch64 depending on the Docker host) is mounted to `/opt/capella`: - -```bash -docker run --rm -it --hostname=devcontainer \ - -v /tmp/.X11-unix:/tmp/.X11-unix \ - -v /tmp/dev:/root/dev \ - -v /path/on/host/to/a/capella[\.app]?:/opt/capella \ - capella-plugins-dev -# for example: -docker run --rm -it --hostname=devcontainer \ - -v /tmp/.X11-unix:/tmp/.X11-unix \ - -v /tmp/dev:/root/dev \ - -v /Users/jamilraichouni/Applications/Capella_6.0.0.app:/opt/capella \ - capella-plugins-dev -``` - -to build plugins for Capella v.x.y +Capella addons are developed on separated branches. To contribute, please +follow these steps: -## Workflow +1. Fork the repository +1. Create a new branch from the `main` branch and name it according to the + Capella addon (name as defined in the next step) you plan to contribute. +1. Change into the root directory for your local checkout of the present + repository. +1. Create the new addon project using the [project template] which has + according instructions. -1. Decide for which Capella version you want to develop the plugin for and - adapt the `docker run` command from above accordingly. -1. Start the container +[project template]: https://github.com/DSD-DBS/cookiecutter-dbs-eclipse-addon diff --git a/capella_addons/__init__.py b/capella_addons/__init__.py new file mode 100644 index 0000000..fdc1222 --- /dev/null +++ b/capella_addons/__init__.py @@ -0,0 +1,11 @@ +# Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 +"""The capella_addons package.""" + +from importlib import metadata + +try: + __version__ = metadata.version("capella_addons") +except metadata.PackageNotFoundError: # pragma: no cover + __version__ = "0.0.0+unknown" +del metadata diff --git a/capella_addons/__main__.py b/capella_addons/__main__.py new file mode 100644 index 0000000..265c16e --- /dev/null +++ b/capella_addons/__main__.py @@ -0,0 +1,751 @@ +# Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 +import contextlib +import itertools +import json +import logging +import os +import pathlib +import shutil +import subprocess +import sys +import tempfile +import threading +import typing as t +from enum import Enum + +import click +import lxml.builder +import lxml.etree + +import capella_addons + +logging.basicConfig(level=logging.INFO, format="%(levelname)s - %(message)s") +logger = logging.getLogger(__name__) +response_stdout_generator = itertools.count(1) +response_stderr_generator = itertools.count(1) + + +class BuildWorkspaceStatus(Enum): + """Enumeration of the build workspace status. + + see + https://github.com/eclipse-jdtls/eclipse.jdt.ls/blob/master/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/BuildWorkspaceStatus.java + """ + + FAILED = 0 + SUCCEED = 1 + WITH_ERROR = 2 + CANCELLED = 3 + UNKNOWN_ERROR = 4 + + +AWAITED_RESPONSE_ID = 0 + +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=capella_addons.__version__, + prog_name="eclipse-plugin-builders", + message="%(prog)s %(version)s", +) +@click.option( + "-v", + "--verbose", + is_flag=True, + default=False, + help="Enable verbose output.", + expose_value=False, + callback=lambda _, __, x: logging.getLogger().setLevel( + logging.DEBUG if x else logging.INFO + ), +) +def main() -> None: + """Console tools to develop, build, pack, and deploy Capella addons. + + Preconditions that must be fulfilled on the system: + + The command line tool `mvn` must be installed and accessible via the + user's `PATH` environment variable. + `mvn` is the Maven build tool and is used to analyse the + dependencies listed in the `pom.xml` file to build the `.classpath` + file for a Capella addon project. + + A Java Development Kit (JDK) must be installed. + An Eclipse JDT Language Server (JDTLS) must be installed. + + Common workflow: + + 1. Change to the directory of the Eclipse/ Capella addon project. + 1. Run the `build-classpath` command to generate the `.classpath` + file. + 1. Run the `build-workspace` command to build the workspace of the + Eclipse/ Capella addon project. + 1. Run the `package` command to package the Eclipse/ Capella addon. + This creates a JAR file in the `target` directory. + 1. Run the `deploy` command to deploy the Eclipse/ Capella addon to + the target platform. + """ + + +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 sorted([pathlib.Path(p) for p in third_party_lib_paths]) + + +def compute_jar_name() -> str: + """Compute and return the name of the jar file to be built.""" + 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" + return f"{group_id}.{artifact_id}_{version}.jar" + + +def _output_and_jar_path() -> tuple[pathlib.Path, pathlib.Path]: + """Return paths to output dir and the jar file to be built.""" + 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) + jar_name = compute_jar_name() + jar_path = pathlib.Path("target") / jar_name + return output_path, jar_path + + +def _read_xml_file(path: str) -> lxml.etree._ElementTree: + """Read the classpath file.""" + if not pathlib.Path(path).exists(): + click.echo(f"`File {path}` not found.") + sys.exit(1) + return lxml.etree.parse(path) + + +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: + dropins_jars = list(target_path.glob("dropins/**/*.jar")) + features_jars = list(target_path.glob("features/**/*.jar")) + jre_jars = list(target_path.glob("jre/**/*.jar")) + plugins_jars = list(target_path.glob("plugins/**/*.jar")) + libs = list( + set(dropins_jars + features_jars + jre_jars + plugins_jars) - sources + ) + libs = [lst for lst in libs if lst.name != compute_jar_name()] + 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_", "_") + with contextlib.suppress(ValueError): + libs.remove(lib) + 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.option( + "--java-execution-environment", + type=click.Choice( + [ + "JavaSE-17", + "JavaSE-18", + "JavaSE-19", + "JavaSE-20", + "JavaSE-21", + "JavaSE-22", + ] + ), + required=True, + help=( + "The Java execution environment to be used. The value must be an" + " exact match of the execution environment name as it appears in" + " the enumeration named `ExecutionEnvironment` as defined here:" + " https://github.com/eclipse-jdtls/eclipse.jdt.ls/wiki/" + "Running-the-JAVA-LS-server-from-the-command-line#initialize-request" + ), +) +@click.argument("filename", type=click.Path(exists=True, dir_okay=True)) +@click.argument( + "target_platform_path", type=click.Path(exists=True, dir_okay=True) +) +def build_classpath( + java_execution_environment: str, + filename: pathlib.Path, + target_platform_path: pathlib.Path, +) -> None: + """Build `.classpath` file. + + \b + Arguments + --------- + filename + Any Java project file. The classpath will be built for this + project. + target_platform_path + The installation directory of an Eclipse/ Capella application + that will be referenced as target platform to build the + classpath. + """ # noqa: D301 + target_path = pathlib.Path(target_platform_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="target/classes"), + E.classpathentry( + kind="con", + path=( + "org.eclipse.jdt.launching.JRE_CONTAINER/" + "org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/" + f"{java_execution_environment}" + ), + ), + ] + with tempfile.NamedTemporaryFile(mode="w", delete=False) as w: + mvn_cmd = [ + "mvn", + "-q", + "dependency:build-classpath", + f"-Dmdep.outputFile={w.name}", + ] + + def find_eclipse_jdtls_project_directory() -> pathlib.Path | None: + path = pathlib.Path(filename) + for parent in path.parents: + if (parent / ".project").is_file() and ( + parent / "pom.xml" + ).is_file(): + return parent + return None + + project_dir = find_eclipse_jdtls_project_directory() + if project_dir is None: + raise RuntimeError( + "Could not find a valid Eclipse JDTLS project directory." + " containing a `.project` and a `pom.xml` file." + ) + os.chdir(project_dir) + print( + f"Building classpath for project in `{project_dir.resolve()}`..." + ) + # Run command and wait: + result = subprocess.run( + mvn_cmd, + capture_output=True, + text=True, + cwd=project_dir, + check=False, + ) + if result.returncode != 0: + raise RuntimeError(result.stdout) + with open(w.name, 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) + print("Created `.classpath` file.") + + +class LSPClient: + id_generator = itertools.count(1) + + def __init__(self, process): + self.awaited_response_id = 0 + self.lock = threading.Lock() + self.process = process + self.request_processed_event = threading.Event() + self.responses: list[dict] = [] + + def send_request(self, request): + method, params, wait_for_response = ( + request["method"], + request["params"], + request["wait_for_response"], + ) + request_id = request.get("id") + logger.debug("Sending request with id %d", request_id) + sys.stdout.flush() + self.request_processed_event.clear() + with self.lock: # Use the lock when accessing awaited_response_id + if wait_for_response: + self.awaited_response_id = request_id + else: + self.awaited_response_id = 0 + + request = { + "jsonrpc": "2.0", + "id": request_id, + "method": method, + "params": params, + } + message = json.dumps(request) + content_length = len(message) + communication_string = ( + f"Content-Length: {content_length}\r\n\r\n{message}" + ) + self.process.stdin.write(communication_string.encode()) + self.process.stdin.flush() + + @staticmethod + def content_length(line: bytes) -> int | None: + if line.startswith(b"Content-Length: "): + _, value = line.split(b"Content-Length: ") + value = value.strip() + try: + return int(value) + except ValueError as err: + raise ValueError( + f"Invalid Content-Length header: {value.decode()}" + ) from err + return None + + def read_response(self): + while True: + sys.stdout.flush() + stdout = self.process.stdout.readline() + # TODO: Handle stderr + if not stdout: + break + try: + num_bytes = self.content_length(stdout) + except ValueError: + continue + if num_bytes is None: + continue + while stdout and stdout.strip(): + stdout = self.process.stdout.readline() + if not stdout: + break + body = self.process.stdout.read(num_bytes) + response = json.loads(body) + with self.lock: + self.responses.append(response) + received_response_id = response.get("id", 0) + with self.lock: + if received_response_id == self.awaited_response_id: + logger.debug( + "Received awaited response with id %d", + received_response_id, + ) + sys.stdout.flush() + self.request_processed_event.set() + + def response_by_id(self, request_id) -> dict[str, t.Any] | None: + with self.lock: + for response in self.responses: + if response.get("id") == request_id: + return response + return None + + +@main.command() +@click.option( + "--java-execution-environment", + type=click.Choice( + [ + "JavaSE-17", + "JavaSE-18", + "JavaSE-19", + "JavaSE-20", + "JavaSE-21", + "JavaSE-22", + ] + ), + required=True, + help=( + "The Java execution environment to be used. The value must be an" + " exact match of the execution environment name as it appears in" + " the enumeration named `ExecutionEnvironment` as defined here:" + " https://github.com/eclipse-jdtls/eclipse.jdt.ls/wiki/" + "Running-the-JAVA-LS-server-from-the-command-line#initialize-request" + ), +) +@click.argument("java_home", type=click.Path(exists=True, dir_okay=True)) +@click.argument("jdtls_home", type=click.Path(exists=True, dir_okay=True)) +def build_workspace( + java_execution_environment: str, + java_home: pathlib.Path, + jdtls_home: pathlib.Path, +) -> None: + """Build (headless) an Eclipse Java project's workspace. + + \b + Arguments + --------- + java_home : pathlib.Path + The path to the Java (JDK) home directory. + jdtls_home : pathlib.Path + The path to the Eclipse JDT Language Server (JDTLS) installation + directory. + """ # noqa: D301 + java_executable = pathlib.Path(java_home).resolve() / "bin" / "java" + jdtls_home = pathlib.Path(jdtls_home).resolve() + if not java_executable.is_file(): + click.echo(f"Java executable `{java_executable}` not found.") + sys.exit(1) + if not jdtls_home.is_dir(): + click.echo(f"JDTLS home directory `{jdtls_home}` not found.") + sys.exit(1) + cwd = pathlib.Path.cwd() + click.echo(f"Building workspace for project at `{cwd}`...") + pathlib.Path("jdtls_stdout.log").unlink(missing_ok=True) + pathlib.Path("jdtls_stderr.log").unlink(missing_ok=True) + for filename in ( + ".classpath", + ".project", + "build.properties", + "plugin.xml", + "pom.xml", + ): + file_path = cwd / filename + if not file_path.is_file(): + click.echo(f"File `{file_path}` not found.") + sys.exit(1) + try: + jdtls_jar = next( + pathlib.Path(f"{jdtls_home}/plugins").glob( + "org.eclipse.equinox.launcher_*.jar" + ) + ) + except StopIteration: + click.echo( + f"Cannot find JDTLS jar in `{jdtls_home}/plugins`" + f" Tried to find `org.eclipse.equinox.launcher_*.jar`." + ) + sys.exit(1) + # Start the language server as a subprocess + process: subprocess.Popen = subprocess.Popen( + [ + str(java_executable), + "-Declipse.application=org.eclipse.jdt.ls.core.id1", + "-Dosgi.bundles.defaultStartLevel=4", + "-Declipse.product=org.eclipse.jdt.ls.core.product", + "-Dlog.protocol=true", + "-Dlog.level=DEBUG", + "-Xmx1g", + "--add-modules=ALL-SYSTEM", + "--add-opens", + "java.base/java.util=ALL-UNNAMED", + "--add-opens", + "java.base/java.lang=ALL-UNNAMED", + "-jar", + str(jdtls_jar), + "-configuration", + f"{jdtls_home}/config_linux", + "-data", + "/tmp/ws", + ], + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) + lsp_client = LSPClient(process) + response_thread = threading.Thread( + target=lsp_client.read_response, + daemon=True, + ) + response_thread.start() + + # key: method, val: params + requests: list[dict[str, int | str | dict | bool]] = [ + { + # https://github.com/Microsoft/language-server-protocol/blob/main/versions/protocol-2-x.md#initialize + "id": next(LSPClient.id_generator), + "method": "initialize", + "params": { + "processId": os.getpid(), + "rootPath": str(cwd), + "rootUri": f"file://{cwd}", + "trace": "off", + "workDoneToken": "1", + "workspaceFolders": [ + { + "name": str(cwd), + "uri": f"file://{cwd}", + } + ], + }, + "wait_for_response": True, + }, + { + "id": next(LSPClient.id_generator), + "method": "initialized", + "params": {}, + "wait_for_response": False, + }, + { + "id": next(LSPClient.id_generator), + "method": "workspace/didChangeConfiguration", + "params": { + "settings": { + "java": { + "format": {"enabled": False}, + "home": str(java_home), + "configuration": { + "runtimes": [ + { + "name": java_execution_environment, + "path": f"{java_home}/", + } + ] + }, + "trace": {"server": "verbose"}, + } + } + }, + "wait_for_response": False, + }, + { + "id": next(LSPClient.id_generator), + "method": "java/buildWorkspace", + "params": True, + "wait_for_response": True, + }, + { + # https://github.com/Microsoft/language-server-protocol/blob/main/versions/protocol-2-x.md#shutdown-request + "id": next(LSPClient.id_generator), + "method": "shutdown", + "params": {}, + "wait_for_response": True, + }, + { + # https://github.com/Microsoft/language-server-protocol/blob/main/versions/protocol-2-x.md#exit-notification + "id": next(LSPClient.id_generator), + "method": "exit", + "params": {}, + "wait_for_response": False, + }, + ] + for request in requests: + lsp_client.send_request(request) + if request["wait_for_response"]: + lsp_client.request_processed_event.wait() + response = lsp_client.response_by_id(request["id"]) + logger.debug( + "Received response: %s", json.dumps(response, indent=2) + ) + else: + continue + if request["method"] == "java/buildWorkspace": + if response is None: + click.echo( + "Build of workspace failed with" + " an unknown error. No response received." + ) + sys.exit(1) + status = response.get("result", BuildWorkspaceStatus.FAILED.value) + if status == BuildWorkspaceStatus.SUCCEED.value: + click.echo("Build of workspace succeeded.") + elif status == BuildWorkspaceStatus.CANCELLED.value: + click.echo("Build of workspace cancelled.") + elif status == BuildWorkspaceStatus.WITH_ERROR.value: + click.echo("Build of workspace failed with error.") + else: + click.echo("Build of workspace failed with an unknown error.") + sys.exit(status) + process.wait() + response_thread.join() + + +@main.command() +@click.argument("target_path", type=click.Path(exists=True, dir_okay=True)) +def deploy(target_path: pathlib.Path) -> None: + """Deploy the eclipse plugin. + + \b + Arguments + --------- + target_path : pathlib.Path + The installation directory of an Eclipse/ Capella application + where the plugin will be deployed into the subdirectory `dropins`. + """ # noqa: D301 + 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()}`.") + + +def _get_bundle_classpath(third_party_lib_paths: list[pathlib.Path]) -> str: + lib_paths = sorted([p.name for p in _third_party_lib_paths()]) + value = "." + if third_party_lib_paths: + value = ".,\n" + value += ",\n".join(f" lib/{p}" for p in lib_paths) + return f"Bundle-ClassPath: {value}" + + +def _update_bundle_classpath( + third_party_lib_paths: list[pathlib.Path], +) -> None: + manifest = MANIFEST_PATH.read_text(encoding="utf-8") + bundle_classpath = _get_bundle_classpath(third_party_lib_paths) + lines = manifest.splitlines() + manifest = "" + found_bundle_classpath = False + inside_bundle_classpath = False + for line in lines: + if line.startswith("Bundle-ClassPath:"): + found_bundle_classpath = True + manifest += bundle_classpath + "\n" + inside_bundle_classpath = True + continue + if inside_bundle_classpath: + if line.startswith(" "): + continue + inside_bundle_classpath = False + manifest += line.rstrip() + "\n" + if bundle_classpath and not found_bundle_classpath: + if not manifest.endswith("\n"): + manifest += "\n" + manifest += bundle_classpath + "\n" + # TODO: ensure that the maximum line length (72) is not exceeded + MANIFEST_PATH.write_text(manifest, encoding="utf-8") + + +@main.command() +@click.argument("java_home", type=click.Path(exists=True, dir_okay=True)) +def package(java_home: pathlib.Path) -> None: + """Package the eclipse plugin. + + \b + Arguments + --------- + java_home : pathlib.Path + The path to the Java home directory. + """ # noqa: D301 + lib_dir = pathlib.Path("lib") + if lib_dir.is_dir(): + shutil.rmtree(lib_dir) + lib_dir.mkdir() + third_party_lib_paths = _third_party_lib_paths() + if third_party_lib_paths: + for path in third_party_lib_paths: + dest = lib_dir / path.name + dest.unlink(missing_ok=True) + shutil.copy(path, dest) + _update_bundle_classpath(third_party_lib_paths) + 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 = pathlib.Path(java_home) / "bin" / "jar" + jar_cmd = [ + str(jar), + "cfm", + str(jar_path), + str(MANIFEST_PATH), + "-C", + f"{output_path}/", + ".", + str(PLUGIN_XML_PATH), + ] + potential_additional_dirs = ( + "lib", + "OSGI-INF", + ) + for dir_ in potential_additional_dirs: + if pathlib.Path(dir_).is_dir() and list(pathlib.Path(dir_).iterdir()): + jar_cmd.append(f"{dir_}/") + 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() diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..3351b4c --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,167 @@ +# Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +# SPDX-FileCopyrightText: Copyright DB InfraGO AG +# SPDX-License-Identifier: Apache-2.0 + +[build-system] +requires = ["setuptools>=64", "setuptools_scm[toml]>=3.4", "wheel"] +build-backend = "setuptools.build_meta" + +[project] +dynamic = ["version"] + +name = "capella-addons" +description = "Tools to develop Capella addons" +readme = "README.md" +requires-python = ">=3.11, <3.13" +license = { text = "Apache-2.0" } +authors = [{ name = "DB InfraGO AG" }] +keywords = ["capella", "mbse", "model-based systems engineering", "addon"] +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Intended Audience :: Developers", + "License :: OSI Approved :: Apache Software License", + "Natural Language :: English", + "Operating System :: OS Independent", + "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Topic :: Other/Nonlisted Topic", + "Topic :: Scientific/Engineering", + "Topic :: Software Development :: Libraries :: Python Modules", + "Typing :: Typed", +] +dependencies = [ + "click", + "lxml" +] + +[project.urls] +Homepage = "https://github.com/DSD-DBS/capella-addons" +# Documentation = "" + +[tool.docformatter] +wrap-descriptions = 72 +wrap-summaries = 79 + +[tool.isort] +profile = 'black' +line_length = 79 + +[tool.mypy] +check_untyped_defs = true +no_implicit_optional = true +show_error_codes = true +warn_redundant_casts = true +warn_unreachable = true +warn_unused_ignores = true +python_version = "3.12" + +[[tool.mypy.overrides]] +module = ["tests.*"] +disallow_incomplete_defs = false +disallow_untyped_defs = false + +[[tool.mypy.overrides]] +# Untyped third party libraries +module = [ + "lxml.*", +] +ignore_missing_imports = true + +[tool.pytest.ini_options] +addopts = """ + --import-mode=importlib + --strict-config + --strict-markers + --tb=short +""" +testpaths = ["tests"] +xfail_strict = true + +[tool.ruff] +line-length = 79 + +[tool.ruff.lint] +extend-select = [ + "ARG", # flake8-unused-arguments + "B", # flake8-bugbear + "C4", # flake8-comprehensions + "C90", # mccabe + "D", # pydocstyle + "D212", # "Multi-line docstring summary should start at the first line" + "D402", # "First line should not be the function’s 'signature'" + "D417", # "Missing argument descriptions in the docstring" + "DTZ", # flake8-datetimez + "ERA", # eradicate + "FA", # flake8-future-annotations + "FBT", # flake8-boolean-trap + "FIX", # flake8-fixme + "FURB", # refurb + "G", # flake8-logging-format + "ICN", # flake8-import-conventions + "ISC001", # "Implicitly concatenated string literals on one line" + "ISC003", # "Explicitly concatenated string should be implicitly concatenated" + "LOG", # flake8-logging + "PIE", # flake8-pie + "PL", # pylint + "PT", # flake8-pytest-style + "RET", # flake8-return + "RUF", # ruff + "SIM", # flake8-simplify + "TCH005", # "Found empty type-checking block" + "T1", # flake8-debugger + "UP", # pyupgrade + "YTT", # flake8-2020 +] +extend-ignore = [ + "D1", # Missing docstring in _ + "D201", # No blank lines allowed before function docstring # auto-formatting + "D202", # No blank lines allowed after function docstring # auto-formatting + "D203", # 1 blank line required before class docstring # auto-formatting + "D204", # 1 blank line required after class docstring # auto-formatting + "D211", # No blank lines allowed before class docstring # auto-formatting + "D213", # Multi-line docstring summary should start at the second line + "DTZ001", # `tzinfo=None` passed to `datetime.datetime()` + "DTZ005", # `tz=None` passed to `datetime.datetime.now()` + "E402", # Module level import not at top of file + "F403", # `from _ import *` used; unable to detect undefined names + "F405", # `_` may be undefined, or defined from star imports + "PLC0414", # Import alias does not rename original package # used for explicit reexports + "PLR0904", # Too many public methods + "PLR0911", # Too many return statements + "PLR0912", # Too many branches + "PLR0913", # Too many arguments in function definition + "PLR0914", # Too many local variables + "PLR0915", # Too many statements + "PLR0916", # Too many Boolean expressions + "PLR0917", # Too many positional arguments + "SIM108", # Use ternary operator instead of `if`-`else`-block +] + +[tool.ruff.lint.extend-per-file-ignores] +"__init__.py" = [ + "PLE0604", # Invalid object in `__all__`, must contain only strings # false-positive when unpacking imported submodule __all__ +] +"tests/test_*.py" = [ + "F811", # Redefinition of unused `_` from line _ + "PLR2004", # Magic value used in comparison, consider replacing `_` with a constant variable +] + +[tool.ruff.lint.pydocstyle] +convention = "numpy" +ignore-decorators = ["typing.overload"] + +[tool.ruff.lint.mccabe] +max-complexity = 14 + +[tool.setuptools] +platforms = ["any"] +zip-safe = false + +[tool.setuptools.packages.find] +include = ["capella_addons", "capella_addons.*"] + +[tool.setuptools_scm] +# This section must exist for setuptools_scm to work diff --git a/rest-api/META-INF/MANIFEST.MF b/rest-api/META-INF/MANIFEST.MF new file mode 100644 index 0000000..81f1041 --- /dev/null +++ b/rest-api/META-INF/MANIFEST.MF @@ -0,0 +1,81 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: REST API +Bundle-SymbolicName: rest-api;singleton:=true +Bundle-Version: 0.0.1 +Automatic-Module-Name: com.deutschebahn +Bundle-Vendor: DB InfraGO AG +Eclipse-BuddyPolicy: global +Bundle-ClassPath: ., + lib/angus-activation-2.0.1.jar, + lib/aopalliance-repackaged-3.0.6.jar, + lib/checker-qual-3.37.0.jar, + lib/classgraph-4.8.154.jar, + lib/commons-lang3-3.14.0.jar, + lib/error_prone_annotations-2.21.1.jar, + lib/failureaccess-1.0.1.jar, + lib/grizzly-framework-4.0.2.jar, + lib/grizzly-http-4.0.2.jar, + lib/grizzly-http-server-4.0.2.jar, + lib/guava-32.1.3-android.jar, + lib/hamcrest-core-1.3.jar, + lib/hk2-api-3.0.6.jar, + lib/hk2-locator-3.0.6.jar, + lib/hk2-utils-3.0.6.jar, + lib/j2objc-annotations-2.8.jar, + lib/jackson-annotations-2.15.2.jar, + lib/jackson-core-2.15.2.jar, + lib/jackson-databind-2.15.1.jar, + lib/jackson-dataformat-yaml-2.17.0.jar, + lib/jackson-datatype-joda-2.15.2.jar, + lib/jackson-datatype-jsr310-2.15.2.jar, + lib/jackson-jakarta-rs-base-2.15.1.jar, + lib/jackson-jakarta-rs-json-provider-2.15.1.jar, + lib/jackson-jaxrs-base-2.15.2.jar, + lib/jackson-jaxrs-json-provider-2.15.2.jar, + lib/jackson-module-jakarta-xmlbind-annotations-2.15.1.jar, + lib/jackson-module-jaxb-annotations-2.15.2.jar, + lib/jakarta.activation-api-2.1.0.jar, + lib/jakarta.annotation-api-2.1.1.jar, + lib/jakarta.inject-api-2.0.1.jar, + lib/jakarta.servlet-api-5.0.0.jar, + lib/jakarta.validation-api-3.0.2.jar, + lib/jakarta.ws.rs-api-3.1.0.jar, + lib/jakarta.xml.bind-api-4.0.0.jar, + lib/javassist-3.29.2-GA.jar, + lib/jaxb-core-4.0.3.jar, + lib/jaxb-impl-4.0.3.jar, + lib/jersey-client-3.1.6.jar, + lib/jersey-common-3.1.6.jar, + lib/jersey-container-grizzly2-http-3.1.6.jar, + lib/jersey-container-servlet-core-3.1.6.jar, + lib/jersey-hk2-3.1.6.jar, + lib/jersey-media-multipart-3.1.6.jar, + lib/jersey-server-3.1.6.jar, + lib/joda-time-2.10.14.jar, + lib/jsr305-3.0.2.jar, + lib/jsr311-api-1.1.1.jar, + lib/junit-4.13.2.jar, + lib/listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar, + lib/logback-classic-1.4.14.jar, + lib/logback-core-1.4.14.jar, + lib/migbase64-2.2.jar, + lib/mimepull-1.9.15.jar, + lib/osgi-resource-locator-1.0.3.jar, + lib/reflections-0.10.2.jar, + lib/slf4j-api-2.0.7.jar, + lib/snakeyaml-2.0.jar, + lib/swagger-annotations-1.6.14.jar, + lib/swagger-annotations-jakarta-2.2.15.jar, + lib/swagger-core-1.6.14.jar, + lib/swagger-core-jakarta-2.2.15.jar, + lib/swagger-integration-jakarta-2.2.15.jar, + lib/swagger-jaxrs-1.6.14.jar, + lib/swagger-jaxrs2-jakarta-2.2.15.jar, + lib/swagger-jaxrs2-servlet-initializer-v2-jakarta-2.2.15.jar, + lib/swagger-models-1.6.14.jar, + lib/swagger-models-jakarta-2.2.15.jar, + lib/validation-api-1.1.0.Final.jar +Import-Package: org.eclipse.ui, + org.eclipse.ui.part, + org.eclipse.core.resources diff --git a/rest-api/META-INF/MANIFEST.MF.license b/rest-api/META-INF/MANIFEST.MF.license new file mode 100644 index 0000000..02c8c23 --- /dev/null +++ b/rest-api/META-INF/MANIFEST.MF.license @@ -0,0 +1,2 @@ +Copyright DB InfraGO AG and contributors +SPDX-License-Identifier: Apache-2.0