From 439807ad8d39c02b35cb12699aebe86dfff41886 Mon Sep 17 00:00:00 2001 From: Fabio Niephaus Date: Tue, 1 Oct 2024 10:22:36 +0200 Subject: [PATCH] Add GraalPy guides. Co-authored-by: Tim Felgentreff Co-authored-by: stepan --- .github/scripts/check-snippets.py | 67 +++ .github/scripts/requirements.txt | 1 + .github/workflows/graalpy-check-snippets.yml | 27 + .../workflows/graalpy-custom-venv-guide.yml | 43 ++ .../graalpy-freeze-dependencies-guide.yml | 33 ++ .github/workflows/graalpy-javase-guide.yml | 32 ++ .github/workflows/graalpy-jython-guide.yml | 32 ++ .github/workflows/graalpy-micronaut-guide.yml | 47 ++ .../graalpy-native-extensions-guide.yml | 42 ++ .github/workflows/graalpy-openai-starter.yml | 1 - .../workflows/graalpy-spring-boot-guide.yml | 47 ++ .github/workflows/graalpy-starter.yml | 1 - .../.mvn/wrapper/maven-wrapper.properties | 19 + graalpy/graalpy-custom-venv-guide/README.md | 195 +++++++ graalpy/graalpy-custom-venv-guide/mvnw | 259 +++++++++ graalpy/graalpy-custom-venv-guide/mvnw.cmd | 149 ++++++ graalpy/graalpy-custom-venv-guide/pom.xml | 30 ++ .../src/main/java/org/example/App.java | 37 ++ .../.mvn/wrapper/maven-wrapper.properties | 19 + .../README.md | 168 ++++++ .../graalpy-freeze-dependencies-guide/mvnw | 259 +++++++++ .../mvnw.cmd | 149 ++++++ .../graalpy-freeze-dependencies-guide/pom.xml | 100 ++++ .../src/main/java/org/example/App.java | 20 + .../.mvn/wrapper/maven-wrapper.properties | 19 + graalpy/graalpy-javase-guide/README.md | 267 ++++++++++ graalpy/graalpy-javase-guide/mvnw | 259 +++++++++ graalpy/graalpy-javase-guide/mvnw.cmd | 149 ++++++ graalpy/graalpy-javase-guide/pom.xml | 63 +++ graalpy/graalpy-javase-guide/screenshot.png | Bin 0 -> 4085 bytes .../src/main/java/org/example/App.java | 34 ++ .../src/main/java/org/example/GraalPy.java | 29 ++ .../src/main/java/org/example/IO.java | 17 + .../src/main/java/org/example/QRCode.java | 15 + .../.mvn/wrapper/maven-wrapper.properties | 19 + graalpy/graalpy-jython-guide/README.md | 232 +++++++++ graalpy/graalpy-jython-guide/mvnw | 259 +++++++++ graalpy/graalpy-jython-guide/mvnw.cmd | 149 ++++++ graalpy/graalpy-jython-guide/pom.xml | 36 ++ graalpy/graalpy-jython-guide/screenshot.png | Bin 0 -> 9143 bytes .../src/main/java/org/example/App.java | 19 + .../java/org/example/EchoInputCallback.java | 22 + .../org/example/GraalPyInputCallback.java | 43 ++ .../main/java/org/example/InputCallback.java | 15 + .../java/org/example/JythonInputCallback.java | 46 ++ .../src/main/java/org/example/Workspace.java | 72 +++ graalpy/graalpy-micronaut-guide/.gitignore | 15 + .../.mvn/wrapper/maven-wrapper.properties | 19 + graalpy/graalpy-micronaut-guide/README.md | 491 ++++++++++++++++++ .../aot-jar.properties | 37 ++ .../graalpy-micronaut-guide/micronaut-cli.yml | 6 + graalpy/graalpy-micronaut-guide/mvnw | 259 +++++++++ graalpy/graalpy-micronaut-guide/mvnw.cmd | 149 ++++++ graalpy/graalpy-micronaut-guide/pom.xml | 154 ++++++ .../graalpy-micronaut-guide/screenshot.png | Bin 0 -> 236583 bytes .../main/java/org/example/Application.java | 15 + .../main/java/org/example/GraalPyContext.java | 37 ++ .../java/org/example/SentimentAnalysis.java | 30 ++ .../example/SentimentAnalysisController.java | 35 ++ .../example/SentimentIntensityAnalyzer.java | 13 + .../META-INF/native-image/proxy-config.json | 3 + .../src/main/resources/application.properties | 2 + .../src/main/resources/logback.xml | 14 + .../src/main/resources/views/index.html | 87 ++++ .../SentimentAnalysisControllerTest.java | 25 + .../.mvn/wrapper/maven-wrapper.properties | 19 + .../graalpy-native-extensions-guide/README.md | 353 +++++++++++++ graalpy/graalpy-native-extensions-guide/mvnw | 259 +++++++++ .../graalpy-native-extensions-guide/mvnw.cmd | 149 ++++++ .../graalpy-native-extensions-guide/pom.xml | 54 ++ .../src/main/java/org/example/App.java | 49 ++ .../src/main/java/org/example/AppLogging.java | 52 ++ .../java/org/example/MultiContextApp.java | 51 ++ .../.mvn/wrapper/maven-wrapper.properties | 19 + graalpy/graalpy-spring-boot-guide/README.md | 485 +++++++++++++++++ graalpy/graalpy-spring-boot-guide/mvnw | 259 +++++++++ graalpy/graalpy-spring-boot-guide/mvnw.cmd | 149 ++++++ graalpy/graalpy-spring-boot-guide/pom.xml | 78 +++ .../graalpy-spring-boot-guide/screenshot.png | Bin 0 -> 236583 bytes .../com/example/demo/DemoApplication.java | 17 + .../java/com/example/demo/DemoController.java | 32 ++ .../java/com/example/demo/GraalPyContext.java | 34 ++ .../demo/SentimentAnalysisService.java | 28 + .../demo/SentimentIntensityAnalyzer.java | 13 + .../META-INF/native-image/proxy-config.json | 3 + .../src/main/resources/application.properties | 1 + .../src/main/resources/templates/index.html | 87 ++++ .../example/demo/DemoApplicationTests.java | 44 ++ 88 files changed, 7165 insertions(+), 2 deletions(-) create mode 100755 .github/scripts/check-snippets.py create mode 100644 .github/scripts/requirements.txt create mode 100644 .github/workflows/graalpy-check-snippets.yml create mode 100644 .github/workflows/graalpy-custom-venv-guide.yml create mode 100644 .github/workflows/graalpy-freeze-dependencies-guide.yml create mode 100644 .github/workflows/graalpy-javase-guide.yml create mode 100644 .github/workflows/graalpy-jython-guide.yml create mode 100644 .github/workflows/graalpy-micronaut-guide.yml create mode 100644 .github/workflows/graalpy-native-extensions-guide.yml create mode 100644 .github/workflows/graalpy-spring-boot-guide.yml create mode 100644 graalpy/graalpy-custom-venv-guide/.mvn/wrapper/maven-wrapper.properties create mode 100644 graalpy/graalpy-custom-venv-guide/README.md create mode 100755 graalpy/graalpy-custom-venv-guide/mvnw create mode 100644 graalpy/graalpy-custom-venv-guide/mvnw.cmd create mode 100644 graalpy/graalpy-custom-venv-guide/pom.xml create mode 100644 graalpy/graalpy-custom-venv-guide/src/main/java/org/example/App.java create mode 100644 graalpy/graalpy-freeze-dependencies-guide/.mvn/wrapper/maven-wrapper.properties create mode 100644 graalpy/graalpy-freeze-dependencies-guide/README.md create mode 100755 graalpy/graalpy-freeze-dependencies-guide/mvnw create mode 100644 graalpy/graalpy-freeze-dependencies-guide/mvnw.cmd create mode 100644 graalpy/graalpy-freeze-dependencies-guide/pom.xml create mode 100644 graalpy/graalpy-freeze-dependencies-guide/src/main/java/org/example/App.java create mode 100644 graalpy/graalpy-javase-guide/.mvn/wrapper/maven-wrapper.properties create mode 100644 graalpy/graalpy-javase-guide/README.md create mode 100755 graalpy/graalpy-javase-guide/mvnw create mode 100644 graalpy/graalpy-javase-guide/mvnw.cmd create mode 100644 graalpy/graalpy-javase-guide/pom.xml create mode 100755 graalpy/graalpy-javase-guide/screenshot.png create mode 100644 graalpy/graalpy-javase-guide/src/main/java/org/example/App.java create mode 100644 graalpy/graalpy-javase-guide/src/main/java/org/example/GraalPy.java create mode 100644 graalpy/graalpy-javase-guide/src/main/java/org/example/IO.java create mode 100644 graalpy/graalpy-javase-guide/src/main/java/org/example/QRCode.java create mode 100644 graalpy/graalpy-jython-guide/.mvn/wrapper/maven-wrapper.properties create mode 100644 graalpy/graalpy-jython-guide/README.md create mode 100755 graalpy/graalpy-jython-guide/mvnw create mode 100644 graalpy/graalpy-jython-guide/mvnw.cmd create mode 100644 graalpy/graalpy-jython-guide/pom.xml create mode 100755 graalpy/graalpy-jython-guide/screenshot.png create mode 100644 graalpy/graalpy-jython-guide/src/main/java/org/example/App.java create mode 100644 graalpy/graalpy-jython-guide/src/main/java/org/example/EchoInputCallback.java create mode 100644 graalpy/graalpy-jython-guide/src/main/java/org/example/GraalPyInputCallback.java create mode 100644 graalpy/graalpy-jython-guide/src/main/java/org/example/InputCallback.java create mode 100644 graalpy/graalpy-jython-guide/src/main/java/org/example/JythonInputCallback.java create mode 100644 graalpy/graalpy-jython-guide/src/main/java/org/example/Workspace.java create mode 100644 graalpy/graalpy-micronaut-guide/.gitignore create mode 100644 graalpy/graalpy-micronaut-guide/.mvn/wrapper/maven-wrapper.properties create mode 100644 graalpy/graalpy-micronaut-guide/README.md create mode 100644 graalpy/graalpy-micronaut-guide/aot-jar.properties create mode 100644 graalpy/graalpy-micronaut-guide/micronaut-cli.yml create mode 100755 graalpy/graalpy-micronaut-guide/mvnw create mode 100644 graalpy/graalpy-micronaut-guide/mvnw.cmd create mode 100644 graalpy/graalpy-micronaut-guide/pom.xml create mode 100644 graalpy/graalpy-micronaut-guide/screenshot.png create mode 100644 graalpy/graalpy-micronaut-guide/src/main/java/org/example/Application.java create mode 100644 graalpy/graalpy-micronaut-guide/src/main/java/org/example/GraalPyContext.java create mode 100644 graalpy/graalpy-micronaut-guide/src/main/java/org/example/SentimentAnalysis.java create mode 100644 graalpy/graalpy-micronaut-guide/src/main/java/org/example/SentimentAnalysisController.java create mode 100644 graalpy/graalpy-micronaut-guide/src/main/java/org/example/SentimentIntensityAnalyzer.java create mode 100644 graalpy/graalpy-micronaut-guide/src/main/resources/META-INF/native-image/proxy-config.json create mode 100644 graalpy/graalpy-micronaut-guide/src/main/resources/application.properties create mode 100644 graalpy/graalpy-micronaut-guide/src/main/resources/logback.xml create mode 100644 graalpy/graalpy-micronaut-guide/src/main/resources/views/index.html create mode 100644 graalpy/graalpy-micronaut-guide/src/test/java/org/example/SentimentAnalysisControllerTest.java create mode 100644 graalpy/graalpy-native-extensions-guide/.mvn/wrapper/maven-wrapper.properties create mode 100644 graalpy/graalpy-native-extensions-guide/README.md create mode 100755 graalpy/graalpy-native-extensions-guide/mvnw create mode 100644 graalpy/graalpy-native-extensions-guide/mvnw.cmd create mode 100644 graalpy/graalpy-native-extensions-guide/pom.xml create mode 100644 graalpy/graalpy-native-extensions-guide/src/main/java/org/example/App.java create mode 100644 graalpy/graalpy-native-extensions-guide/src/main/java/org/example/AppLogging.java create mode 100644 graalpy/graalpy-native-extensions-guide/src/main/java/org/example/MultiContextApp.java create mode 100644 graalpy/graalpy-spring-boot-guide/.mvn/wrapper/maven-wrapper.properties create mode 100644 graalpy/graalpy-spring-boot-guide/README.md create mode 100755 graalpy/graalpy-spring-boot-guide/mvnw create mode 100644 graalpy/graalpy-spring-boot-guide/mvnw.cmd create mode 100644 graalpy/graalpy-spring-boot-guide/pom.xml create mode 100644 graalpy/graalpy-spring-boot-guide/screenshot.png create mode 100644 graalpy/graalpy-spring-boot-guide/src/main/java/com/example/demo/DemoApplication.java create mode 100644 graalpy/graalpy-spring-boot-guide/src/main/java/com/example/demo/DemoController.java create mode 100644 graalpy/graalpy-spring-boot-guide/src/main/java/com/example/demo/GraalPyContext.java create mode 100644 graalpy/graalpy-spring-boot-guide/src/main/java/com/example/demo/SentimentAnalysisService.java create mode 100644 graalpy/graalpy-spring-boot-guide/src/main/java/com/example/demo/SentimentIntensityAnalyzer.java create mode 100644 graalpy/graalpy-spring-boot-guide/src/main/resources/META-INF/native-image/proxy-config.json create mode 100644 graalpy/graalpy-spring-boot-guide/src/main/resources/application.properties create mode 100644 graalpy/graalpy-spring-boot-guide/src/main/resources/templates/index.html create mode 100644 graalpy/graalpy-spring-boot-guide/src/test/java/com/example/demo/DemoApplicationTests.java diff --git a/.github/scripts/check-snippets.py b/.github/scripts/check-snippets.py new file mode 100755 index 0000000..b7d8cf6 --- /dev/null +++ b/.github/scripts/check-snippets.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python +from glob import glob +from os import PathLike +from os.path import dirname, join +from typing import cast + +import re + + +STRIP_WHITESPACE_RE = re.compile(r'\s*') +INDENT_RE = re.compile(r'^', re.MULTILINE) + + +def get_file_snippets(path: str | PathLike[str]): + from marko.block import FencedCode + from marko.inline import CodeSpan, RawText + from marko.ext.gfm import gfm + from marko.ext.gfm.elements import Paragraph + + with open(path) as f: + doc = gfm.parse(f.read()) + codeblocks: list[tuple[str, str]] = [] + for idx, child in enumerate(doc.children): + if idx > 0: + if child.get_type() == "FencedCode": + if (p := doc.children[idx - 1]).get_type() == "Paragraph": + children = cast(Paragraph, p).children + if len(children) == 1 and (filename := children[0]).get_type() == "CodeSpan": + code = cast(RawText, cast(FencedCode, child).children[0]).children + codeblocks.append((cast(str, cast(CodeSpan, filename).children), code)) + return codeblocks + + +def match_snippets(snippets, rootdir): + unmatched_snippets: list[tuple[list[str], str]] = [] + for filename, snippet in snippets: + stripped_snippet = STRIP_WHITESPACE_RE.sub("", snippet) + files = glob(join(rootdir, "**", filename), recursive=True) + for filepath in files: + with open(filepath) as f: + contents = f.read() + contents = STRIP_WHITESPACE_RE.sub("", contents) + if stripped_snippet in contents: + break + else: + unmatched_snippets.append((files, snippet)) + return unmatched_snippets + + +if __name__ == "__main__": + import sys + from argparse import ArgumentParser + + parser = ArgumentParser(__file__) + parser.add_argument("filename", nargs='+') + args = parser.parse_args() + + failed = False + for filename in args.filename: + snippets = get_file_snippets(filename) + unmatched_snippets = match_snippets(snippets, dirname(filename)) + print(f"Testing {filename}") + for files, snippet in unmatched_snippets: + failed = True + print("Failed to match the following snippet:", INDENT_RE.sub("\t", snippet), f"Tried these files: {files}", sep="\n") + + sys.exit(failed) diff --git a/.github/scripts/requirements.txt b/.github/scripts/requirements.txt new file mode 100644 index 0000000..07bef3a --- /dev/null +++ b/.github/scripts/requirements.txt @@ -0,0 +1 @@ +marko diff --git a/.github/workflows/graalpy-check-snippets.yml b/.github/workflows/graalpy-check-snippets.yml new file mode 100644 index 0000000..a71f0e1 --- /dev/null +++ b/.github/workflows/graalpy-check-snippets.yml @@ -0,0 +1,27 @@ +name: Check Snippets in GraalPy Guides +on: + push: + paths: + - 'graalpy/**' + - '.github/workflows/graalpy-check-snippets.yml' + pull_request: + paths: + - 'graalpy/**' + - '.github/workflows/graalpy-check-snippets.yml' +permissions: + contents: read + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: graalpy-24.1 + - name: Install dependencies + run: | + python -m pip install -r .github/scripts/requirements.txt + - name: Check README snippets + run: find graalpy -name "README.md" -exec python .github/scripts/check-snippets.py "{}" "+" diff --git a/.github/workflows/graalpy-custom-venv-guide.yml b/.github/workflows/graalpy-custom-venv-guide.yml new file mode 100644 index 0000000..8192f4c --- /dev/null +++ b/.github/workflows/graalpy-custom-venv-guide.yml @@ -0,0 +1,43 @@ +name: Test GraalPy Custom Venv Guide +on: + push: + paths: + - 'graalpy/graalpy-custom-venv-guide/**' + - '.github/workflows/graalpy-custom-venv-guide.yml' + pull_request: + paths: + - 'graalpy/graalpy-custom-venv-guide/**' + - '.github/workflows/graalpy-custom-venv-guide.yml' + workflow_dispatch: +permissions: + contents: read +jobs: + run: + name: 'graalpy-custom-venv-guide' + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - uses: actions/checkout@v4 + - uses: graalvm/setup-graalvm@v1 + with: + java-version: '23.0.0' + distribution: 'graalvm' + github-token: ${{ secrets.GITHUB_TOKEN }} + cache: 'maven' + - uses: actions/setup-python@v5 + with: + python-version: 'graalpy-24.1' + - name: Build, test, and run 'graalpy-custom-venv-guide' using Maven + shell: bash + run: | + cd graalpy/graalpy-custom-venv-guide + ./mvnw --no-transfer-progress compile + graalpy -m venv venv + ./venv/bin/graalpy -m pip install art==6.3 + ./mvnw --no-transfer-progress exec:java -Dexec.mainClass=org.example.App -Dvenv=venv | tee /tmp/output + grep -F " ____ _ ____" /tmp/output + grep -F " / ___| _ __ __ _ __ _ | || _ \ _ _" /tmp/output + # | | _ | '__| / _` | / _` || || |_) || | | | escaping is too cumbersome for this line + grep -F "| |_| || | | (_| || (_| || || __/ | |_| |" /tmp/output + grep -F " \____||_| \__,_| \__,_||_||_| \__, |" /tmp/output + grep -F " |___/" /tmp/output \ No newline at end of file diff --git a/.github/workflows/graalpy-freeze-dependencies-guide.yml b/.github/workflows/graalpy-freeze-dependencies-guide.yml new file mode 100644 index 0000000..3a58158 --- /dev/null +++ b/.github/workflows/graalpy-freeze-dependencies-guide.yml @@ -0,0 +1,33 @@ +name: Test GraalPy Freeze Dependencies Guide +on: + push: + paths: + - 'graalpy/graalpy-freeze-dependencies-guide/**' + - '.github/workflows/graalpy-freeze-dependencies-guide.yml' + pull_request: + paths: + - 'graalpy/graalpy-freeze-dependencies-guide/**' + - '.github/workflows/graalpy-freeze-dependencies-guide.yml' + workflow_dispatch: +permissions: + contents: read +jobs: + run: + name: 'graalpy-freeze-dependencies-guide' + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - uses: actions/checkout@v4 + - uses: graalvm/setup-graalvm@v1 + with: + java-version: '23.0.0' + distribution: 'graalvm' + github-token: ${{ secrets.GITHUB_TOKEN }} + cache: 'maven' + - name: Build, test, and run 'graalpy-freeze-dependencies-guide' using Maven + shell: bash + run: | + cd graalpy/graalpy-freeze-dependencies-guide + ./mvnw --no-transfer-progress compile + ./mvnw --no-transfer-progress exec:java -Dexec.mainClass=org.example.App | tee /tmp/output + grep darent /tmp/output \ No newline at end of file diff --git a/.github/workflows/graalpy-javase-guide.yml b/.github/workflows/graalpy-javase-guide.yml new file mode 100644 index 0000000..e8466a4 --- /dev/null +++ b/.github/workflows/graalpy-javase-guide.yml @@ -0,0 +1,32 @@ +name: Test GraalPy Java SE Guide +on: + push: + paths: + - 'graalpy/graalpy-javase-guide/**' + - '.github/workflows/graalpy-javase-guide.yml' + pull_request: + paths: + - 'graalpy/graalpy-javase-guide/**' + - '.github/workflows/graalpy-javase-guide.yml' + workflow_dispatch: +permissions: + contents: read +jobs: + run: + name: 'graalpy-javase-guide' + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - uses: actions/checkout@v4 + - uses: graalvm/setup-graalvm@v1 + with: + java-version: '23.0.0' + distribution: 'graalvm' + github-token: ${{ secrets.GITHUB_TOKEN }} + cache: 'maven' + - name: Build, test, and run 'graalpy-javase-guide' using Maven + run: | + cd graalpy/graalpy-javase-guide + ./mvnw --no-transfer-progress compile + xvfb-run ./mvnw --no-transfer-progress exec:java -Dexec.mainClass=org.example.App -Dgraalpy.resources=./python-resources & + sleep 20 diff --git a/.github/workflows/graalpy-jython-guide.yml b/.github/workflows/graalpy-jython-guide.yml new file mode 100644 index 0000000..af2e107 --- /dev/null +++ b/.github/workflows/graalpy-jython-guide.yml @@ -0,0 +1,32 @@ +name: Test GraalPy Jython Guide +on: + push: + paths: + - 'graalpy/graalpy-jython-guide/**' + - '.github/workflows/graalpy-jython-guide.yml' + pull_request: + paths: + - 'graalpy/graalpy-jython-guide/**' + - '.github/workflows/graalpy-jython-guide.yml' + workflow_dispatch: +permissions: + contents: read +jobs: + run: + name: 'graalpy-jython-guide' + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - uses: actions/checkout@v4 + - uses: graalvm/setup-graalvm@v1 + with: + java-version: '23.0.0' + distribution: 'graalvm' + github-token: ${{ secrets.GITHUB_TOKEN }} + cache: 'maven' + - name: Build, test, and run 'graalpy-jython-guide' using Maven + run: | + cd graalpy/graalpy-jython-guide + ./mvnw --no-transfer-progress compile + xvfb-run ./mvnw --no-transfer-progress exec:java -Dexec.mainClass=org.example.App & + sleep 20 diff --git a/.github/workflows/graalpy-micronaut-guide.yml b/.github/workflows/graalpy-micronaut-guide.yml new file mode 100644 index 0000000..36af5dd --- /dev/null +++ b/.github/workflows/graalpy-micronaut-guide.yml @@ -0,0 +1,47 @@ +name: Test GraalPy Micronaut Guide +on: + push: + paths: + - 'graalpy/graalpy-micronaut-guide/**' + - '.github/workflows/graalpy-micronaut-guide.yml' + pull_request: + paths: + - 'graalpy/graalpy-micronaut-guide/**' + - '.github/workflows/graalpy-micronaut-guide.yml' + workflow_dispatch: +permissions: + contents: read +env: + NATIVE_IMAGE_OPTIONS: '-J-Xmx16g' +jobs: + run: + name: 'graalpy-micronaut-guide' + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - uses: actions/checkout@v4 + - uses: graalvm/setup-graalvm@v1 + with: + java-version: '23.0.0' + distribution: 'graalvm' + github-token: ${{ secrets.GITHUB_TOKEN }} + cache: 'maven' + native-image-job-reports: 'true' + - name: Build, test, and run 'graalpy-micronaut-guide' using Maven + run: | + cd graalpy/graalpy-micronaut-guide + ./mvnw --no-transfer-progress clean test -Dmicronaut.http.client.read-timeout=1m + ./mvnw --no-transfer-progress mn:run & + mnpid="$!" + sleep 30 + curl -s -D - -o /dev/null http://localhost:8080/ + kill $mnpid + - name: Build and run native 'graalpy-micronaut-guide' using Maven + run: | + cd graalpy/graalpy-micronaut-guide + ./mvnw --no-transfer-progress clean package -DskipTests -Dpackaging=native-image + ./target/graalpy-micronaut & + mnpid="$!" + sleep 20 + curl -s -D - -o /dev/null http://localhost:8080/ + kill $mnpid diff --git a/.github/workflows/graalpy-native-extensions-guide.yml b/.github/workflows/graalpy-native-extensions-guide.yml new file mode 100644 index 0000000..fd42af0 --- /dev/null +++ b/.github/workflows/graalpy-native-extensions-guide.yml @@ -0,0 +1,42 @@ +name: Test GraalPy Native Extensions Guide +on: + push: + paths: + - 'graalpy/graalpy-native-extensions-guide/**' + - '.github/workflows/graalpy-native-extensions-guide.yml' + pull_request: + paths: + - 'graalpy/graalpy-native-extensions-guide/**' + - '.github/workflows/graalpy-native-extensions-guide.yml' + workflow_dispatch: +permissions: + contents: read +jobs: + run: + name: 'graalpy-native-extensions-guide' + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - uses: actions/checkout@v4 + - uses: graalvm/setup-graalvm@v1 + with: + java-version: '23.0.0' + distribution: 'graalvm' + github-token: ${{ secrets.GITHUB_TOKEN }} + cache: 'maven' + - name: Build, test, and run 'graalpy-native-extensions-guide' using Maven + shell: bash + run: | + cd graalpy/graalpy-native-extensions-guide + ./mvnw --no-transfer-progress compile + ./mvnw --no-transfer-progress exec:java -Dexec.mainClass=org.example.App -Dexec.args="appkle pear anana tomato" | tee /tmp/output + grep "did you mean 'apple'" /tmp/output + ./mvnw --no-transfer-progress exec:java -Dexec.mainClass=org.example.AppLogging -Dexec.args="appkle pear anana tomato" 2>&1 | tee /tmp/output2 + grep "Loading C extension module polyleven" /tmp/output2 + grep "did you mean 'apple'" /tmp/output2 + if mvn package exec:java -Dexec.mainClass=org.example.MultiContextApp -Dexec.args="appkle pear anana strawberry tomato" 2>&1 | tee /tmp/output3; then + echo "the command was supposed to fail" + exit 1 + fi + grep "did you mean 'apple'" /tmp/output3 + grep "SystemError" /tmp/output3 \ No newline at end of file diff --git a/.github/workflows/graalpy-openai-starter.yml b/.github/workflows/graalpy-openai-starter.yml index 87f2f53..4c62143 100644 --- a/.github/workflows/graalpy-openai-starter.yml +++ b/.github/workflows/graalpy-openai-starter.yml @@ -24,7 +24,6 @@ jobs: distribution: 'graalvm' github-token: ${{ secrets.GITHUB_TOKEN }} cache: 'maven' - native-image-job-reports: 'true' - name: Build, test, and run 'graalpy-openai-starter' using Maven run: | cd graalpy/graalpy-openai-starter diff --git a/.github/workflows/graalpy-spring-boot-guide.yml b/.github/workflows/graalpy-spring-boot-guide.yml new file mode 100644 index 0000000..08006c2 --- /dev/null +++ b/.github/workflows/graalpy-spring-boot-guide.yml @@ -0,0 +1,47 @@ +name: Test GraalPy Spring Boot Guide +on: + push: + paths: + - 'graalpy/graalpy-spring-boot-guide/**' + - '.github/workflows/graalpy-spring-boot-guide.yml' + pull_request: + paths: + - 'graalpy/graalpy-spring-boot-guide/**' + - '.github/workflows/graalpy-spring-boot-guide.yml' + workflow_dispatch: +permissions: + contents: read +env: + NATIVE_IMAGE_OPTIONS: '-J-Xmx16g' +jobs: + run: + name: 'graalpy-spring-boot-guide' + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - uses: actions/checkout@v4 + - uses: graalvm/setup-graalvm@v1 + with: + java-version: '23.0.0' + distribution: 'graalvm' + github-token: ${{ secrets.GITHUB_TOKEN }} + cache: 'maven' + native-image-job-reports: 'true' + - name: Build, test, and run 'graalpy-spring-boot-guide' using Maven + run: | + cd graalpy/graalpy-spring-boot-guide + ./mvnw --no-transfer-progress clean test -Dspring.mvc.async.request-timeout=60000 + ./mvnw --no-transfer-progress spring-boot:run & + sbpid="$!" + sleep 30 + curl -s -D - -o /dev/null http://localhost:8080/ + kill $sbpid + - name: Build and run native 'graalpy-spring-boot-guide' using Maven + run: | + cd graalpy/graalpy-spring-boot-guide + ./mvnw --no-transfer-progress clean -DskipTests -Pnative native:compile + ./target/graalpy-springboot & + sbpid="$!" + sleep 20 + curl -s -D - -o /dev/null http://localhost:8080/ + kill $sbpid diff --git a/.github/workflows/graalpy-starter.yml b/.github/workflows/graalpy-starter.yml index 73c9168..5ac0d41 100644 --- a/.github/workflows/graalpy-starter.yml +++ b/.github/workflows/graalpy-starter.yml @@ -24,7 +24,6 @@ jobs: distribution: 'graalvm' github-token: ${{ secrets.GITHUB_TOKEN }} cache: 'maven' - native-image-job-reports: 'true' - name: Build, test, and run 'graalpy-starter' using Maven run: | cd graalpy/graalpy-starter diff --git a/graalpy/graalpy-custom-venv-guide/.mvn/wrapper/maven-wrapper.properties b/graalpy/graalpy-custom-venv-guide/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..d58dfb7 --- /dev/null +++ b/graalpy/graalpy-custom-venv-guide/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,19 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +wrapperVersion=3.3.2 +distributionType=only-script +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip diff --git a/graalpy/graalpy-custom-venv-guide/README.md b/graalpy/graalpy-custom-venv-guide/README.md new file mode 100644 index 0000000..f22a110 --- /dev/null +++ b/graalpy/graalpy-custom-venv-guide/README.md @@ -0,0 +1,195 @@ +# Using Custom GraalPy Virtual Environment in a Java Application + +While GraalPy provides a [python-embedding](https://central.sonatype.com/artifact/org.graalvm.python/python-embedding) package and GraalPy [Maven plugin](https://www.graalvm.org/latest/reference-manual/python/Embedding-Build-Tools/) simplify the required setup to ship Python packages +as Java resources or in separate folders, sometimes users may want to install the Python packages manually +using the GraalPy standalone distribution and then use the manually installed packages in Java. + +This approach requires more effort, but may be useful in the following scenarios: + +- User wants to have full control over the installation, including installation options, and the embedding `Context` setup. +- The required packages cannot be installed through the Maven integration due to compatibility issues. Some packages +make assumptions about the installation environment that are difficult or impossible to replicate in the Maven setup. + +## 1. Getting Started + +In this guide, we will add a small Python library to [generate ASCII art](https://pypi.org/project/art) +and use it from Java. + +## 2. What you will need + +To complete this guide, you will need the following: + +* Some time on your hands +* A decent text editor or IDE +* A supported JDK[^1], preferably the latest [GraalVM JDK](https://graalvm.org/downloads/) + +[^1]: Oracle JDK 17 and OpenJDK 17 are supported with interpreter only. +GraalVM JDK 21, Oracle JDK 21, OpenJDK 21 and newer with [JIT compilation](https://www.graalvm.org/latest/reference-manual/embed-languages/#runtime-optimization-support). +Note: GraalVM for JDK 17 is **not supported**. + +## 3. Solution + +We recommend that you follow the instructions in the next sections and create the application step by step. +However, you can go right to the [completed example](./). + +## 4. Writing the application + +You can start with any Maven application that runs on JDK 17 or newer. +We will use a default Maven application [generated](https://maven.apache.org/archetypes/maven-archetype-quickstart/) from an archetype. + +```shell +mvn archetype:generate -DarchetypeGroupId=org.apache.maven.archetypes \ + -DarchetypeArtifactId=maven-archetype-quickstart -DarchetypeVersion=1.5 \ + -DgroupId=example -DartifactId=example -Dpackage=org.example \ + -Dversion=1.0-SNAPSHOT -DinteractiveMode=false +``` + +## 4.1 Dependency configuration + +Add the required dependencies for GraalPy in the `` section of the POM. + +`pom.xml` +```xml + + + org.graalvm.polyglot + python + 24.1.0 + pom + + + + org.graalvm.polyglot + polyglot + 24.1.0 + + +``` + +❶ The `python` dependency is a meta-package that transitively depends on all resources and libraries to run GraalPy. + +❷ Note that the `python` package is not a JAR - it is simply a `pom` that declares more dependencies. + +❸ The `polyglot` dependency provides the APIs to call Python from Java. + +## 4.2 Custom Virtual Environment + +In the Python ecosystem, it is common to use [virtual environments](https://docs.python.org/3/library/venv.html) to manage dependencies. +One can think of a virtual environment as a separate isolated Python installation, while under the hood, the environment shares +some parts with the Python installation that created it. + +To create our virtual environment, [download GraalPy standalone](https://github.com/oracle/graalpython/releases/) distribution for your system. +Make sure to download exactly the same version as the version of the Maven dependencies we added above. +Extract the distribution on your system and invoke the `graalpy` command from it as follows: + +```shell +> {path/to/graalpy-standalone}/bin/graalpy -m venv myvenv +``` + +This will create a new virtual environment in a directory called "myenv". Now we can install a Python +package into the virtual environment: + +```shell +> . {path/to/graalpy-standalone}/bin/activate +> python --version +> pip install art==6.3 +``` + +We can try using the new package from GraalPy REPL to see if it works as expected. +Continuing in the shell where we activated our virtual environment: + +``` +> python +Python 3.11.7 (Thu Sep 05 15:19:24 UTC 2024) +[Graal, Oracle GraalVM, Java 23 (amd64)] on linux +Type "help", "copyright", "credits" or "license" for more information. +>>> import art +>>> print(art.text2art("GraalPy")) + ____ _ ____ + / ___| _ __ __ _ __ _ | || _ \ _ _ +| | _ | '__| / _` | / _` || || |_) || | | | +| |_| || | | (_| || (_| || || __/ | |_| | + \____||_| \__,_| \__,_||_||_| \__, | + |___/ +``` + +## 4.3 Creating a Python context + +Now we switch back to Java. We will configure our embedding `Context` so that can load +packages from our virtual environment: + +`App.java` +```java +package org.example; + +import org.graalvm.polyglot.*; +import org.graalvm.polyglot.io.*; + +import java.nio.file.*; + +public class App { + private static final boolean IS_WINDOWS = System.getProperty("os.name").toLowerCase().contains("windows"); + + public static void main(String[] args) { + if (System.getProperty("venv") == null) { + System.err.println("Provide 'venv' system property."); + System.exit(1); + } + Path executable; + if (IS_WINDOWS) { // ① + executable = Paths.get(System.getProperty("venv"), "Scripts", "python.exe"); + } else { + executable = Paths.get(System.getProperty("venv"), "bin", "python"); + } + try (Context context = Context.newBuilder() // ② + .option("python.Executable", executable.toAbsolutePath().toString()) // ③ + .option("python.ForceImportSite", "true") // ④ + .allowIO(IOAccess.newBuilder().allowHostFileAccess(true).build()) // ⑤ + .build()) { + Value asciiArt = context.eval("python", "import art; art.text2art('GraalPy')"); // ⑥ + System.out.println(asciiArt.asString()); // ⑦ + } + } +} +``` + +❶ We need a path to the python executable within the virtual environment. The path is different on Windows systems. + +❷ Create a new `Context` builder + +❸ Set option `python.Executable`, which will activate the virtual environment for our `Context` + +❹ Set option `python.ForceImportSite`, which activates the `site.py` module, +which adds the packages installed in the virtual environment to the Python packages search path + +❺ Allow host filesystem access, so that the Python code can actually load the packages from disk. +Alternatively, one can use `allowAllAccess(true)` to allow access to all resources. If you run +into errors, you can try running your code with `allowAllAccess(true)` to rule out a permission issue. + +❻ Use the installed package to create a Python string with ASCII art text + +❼ Print the Python string using Java `System.out` + +## 5. Running the application + +If you followed along with the example, you can now compile and run your application from the commandline: + +```shell +./mvnw compile +./mvnw exec:java -Dexec.mainClass=org.example.App -Dvenv=/path/to/myvenv +``` + +Make sure to replace the `/path/to/myvenv` with the actual path where you created +the virtual filesystem on your system. + +## 6. Next steps + +- Use GraalPy with popular Java frameworks, such as [Spring Boot](../graalpy-spring-boot-guide/README.md) or [Micronaut](../graalpy-micronaut-guide/README.md) +- Install and use Python packages that rely on [native code](../graalpy-native-extensions-guide/README.md), e.g. for data science and machine learning +- [Migrate from Jython](../graalpy-jython-guide/README.md) to GraalPy +- [Freeze](../graalpy-freeze-dependencies-guide/README.md) transitive Python dependencies for reproducible builds + + +- Learn more about the GraalPy [Maven plugin](https://www.graalvm.org/latest/reference-manual/python/Embedding-Build-Tools/) +- Learn more about the Polyglot API for [embedding languages](https://www.graalvm.org/latest/reference-manual/embed-languages/) +- Explore in depth with GraalPy [reference manual](https://www.graalvm.org/latest/reference-manual/python/) diff --git a/graalpy/graalpy-custom-venv-guide/mvnw b/graalpy/graalpy-custom-venv-guide/mvnw new file mode 100755 index 0000000..19529dd --- /dev/null +++ b/graalpy/graalpy-custom-venv-guide/mvnw @@ -0,0 +1,259 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Apache Maven Wrapper startup batch script, version 3.3.2 +# +# Optional ENV vars +# ----------------- +# JAVA_HOME - location of a JDK home dir, required when download maven via java source +# MVNW_REPOURL - repo url base for downloading maven distribution +# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output +# ---------------------------------------------------------------------------- + +set -euf +[ "${MVNW_VERBOSE-}" != debug ] || set -x + +# OS specific support. +native_path() { printf %s\\n "$1"; } +case "$(uname)" in +CYGWIN* | MINGW*) + [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")" + native_path() { cygpath --path --windows "$1"; } + ;; +esac + +# set JAVACMD and JAVACCMD +set_java_home() { + # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched + if [ -n "${JAVA_HOME-}" ]; then + if [ -x "$JAVA_HOME/jre/sh/java" ]; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACCMD="$JAVA_HOME/jre/sh/javac" + else + JAVACMD="$JAVA_HOME/bin/java" + JAVACCMD="$JAVA_HOME/bin/javac" + + if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then + echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2 + echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2 + return 1 + fi + fi + else + JAVACMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v java + )" || : + JAVACCMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v javac + )" || : + + if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then + echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2 + return 1 + fi + fi +} + +# hash string like Java String::hashCode +hash_string() { + str="${1:-}" h=0 + while [ -n "$str" ]; do + char="${str%"${str#?}"}" + h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296)) + str="${str#?}" + done + printf %x\\n $h +} + +verbose() { :; } +[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; } + +die() { + printf %s\\n "$1" >&2 + exit 1 +} + +trim() { + # MWRAPPER-139: + # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds. + # Needed for removing poorly interpreted newline sequences when running in more + # exotic environments such as mingw bash on Windows. + printf "%s" "${1}" | tr -d '[:space:]' +} + +# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties +while IFS="=" read -r key value; do + case "${key-}" in + distributionUrl) distributionUrl=$(trim "${value-}") ;; + distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;; + esac +done <"${0%/*}/.mvn/wrapper/maven-wrapper.properties" +[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties" + +case "${distributionUrl##*/}" in +maven-mvnd-*bin.*) + MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ + case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in + *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;; + :Darwin*x86_64) distributionPlatform=darwin-amd64 ;; + :Darwin*arm64) distributionPlatform=darwin-aarch64 ;; + :Linux*x86_64*) distributionPlatform=linux-amd64 ;; + *) + echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2 + distributionPlatform=linux-amd64 + ;; + esac + distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip" + ;; +maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;; +*) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;; +esac + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}" +distributionUrlName="${distributionUrl##*/}" +distributionUrlNameMain="${distributionUrlName%.*}" +distributionUrlNameMain="${distributionUrlNameMain%-bin}" +MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}" +MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")" + +exec_maven() { + unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || : + exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD" +} + +if [ -d "$MAVEN_HOME" ]; then + verbose "found existing MAVEN_HOME at $MAVEN_HOME" + exec_maven "$@" +fi + +case "${distributionUrl-}" in +*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;; +*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;; +esac + +# prepare tmp dir +if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then + clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; } + trap clean HUP INT TERM EXIT +else + die "cannot create temp dir" +fi + +mkdir -p -- "${MAVEN_HOME%/*}" + +# Download and Install Apache Maven +verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +verbose "Downloading from: $distributionUrl" +verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +# select .zip or .tar.gz +if ! command -v unzip >/dev/null; then + distributionUrl="${distributionUrl%.zip}.tar.gz" + distributionUrlName="${distributionUrl##*/}" +fi + +# verbose opt +__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR='' +[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v + +# normalize http auth +case "${MVNW_PASSWORD:+has-password}" in +'') MVNW_USERNAME='' MVNW_PASSWORD='' ;; +has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;; +esac + +if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then + verbose "Found wget ... using wget" + wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl" +elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then + verbose "Found curl ... using curl" + curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl" +elif set_java_home; then + verbose "Falling back to use Java to download" + javaSource="$TMP_DOWNLOAD_DIR/Downloader.java" + targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName" + cat >"$javaSource" <<-END + public class Downloader extends java.net.Authenticator + { + protected java.net.PasswordAuthentication getPasswordAuthentication() + { + return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() ); + } + public static void main( String[] args ) throws Exception + { + setDefault( new Downloader() ); + java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() ); + } + } + END + # For Cygwin/MinGW, switch paths to Windows format before running javac and java + verbose " - Compiling Downloader.java ..." + "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java" + verbose " - Running Downloader.java ..." + "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")" +fi + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +if [ -n "${distributionSha256Sum-}" ]; then + distributionSha256Result=false + if [ "$MVN_CMD" = mvnd.sh ]; then + echo "Checksum validation is not supported for maven-mvnd." >&2 + echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + elif command -v sha256sum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c >/dev/null 2>&1; then + distributionSha256Result=true + fi + elif command -v shasum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then + distributionSha256Result=true + fi + else + echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2 + echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + fi + if [ $distributionSha256Result = false ]; then + echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2 + echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2 + exit 1 + fi +fi + +# unzip and move +if command -v unzip >/dev/null; then + unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip" +else + tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar" +fi +printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url" +mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME" + +clean || : +exec_maven "$@" diff --git a/graalpy/graalpy-custom-venv-guide/mvnw.cmd b/graalpy/graalpy-custom-venv-guide/mvnw.cmd new file mode 100644 index 0000000..b150b91 --- /dev/null +++ b/graalpy/graalpy-custom-venv-guide/mvnw.cmd @@ -0,0 +1,149 @@ +<# : batch portion +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Apache Maven Wrapper startup batch script, version 3.3.2 +@REM +@REM Optional ENV vars +@REM MVNW_REPOURL - repo url base for downloading maven distribution +@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output +@REM ---------------------------------------------------------------------------- + +@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0) +@SET __MVNW_CMD__= +@SET __MVNW_ERROR__= +@SET __MVNW_PSMODULEP_SAVE=%PSModulePath% +@SET PSModulePath= +@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @( + IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B) +) +@SET PSModulePath=%__MVNW_PSMODULEP_SAVE% +@SET __MVNW_PSMODULEP_SAVE= +@SET __MVNW_ARG0_NAME__= +@SET MVNW_USERNAME= +@SET MVNW_PASSWORD= +@IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*) +@echo Cannot start maven from wrapper >&2 && exit /b 1 +@GOTO :EOF +: end batch / begin powershell #> + +$ErrorActionPreference = "Stop" +if ($env:MVNW_VERBOSE -eq "true") { + $VerbosePreference = "Continue" +} + +# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties +$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl +if (!$distributionUrl) { + Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" +} + +switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) { + "maven-mvnd-*" { + $USE_MVND = $true + $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip" + $MVN_CMD = "mvnd.cmd" + break + } + default { + $USE_MVND = $false + $MVN_CMD = $script -replace '^mvnw','mvn' + break + } +} + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +if ($env:MVNW_REPOURL) { + $MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" } + $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')" +} +$distributionUrlName = $distributionUrl -replace '^.*/','' +$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$','' +$MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain" +if ($env:MAVEN_USER_HOME) { + $MAVEN_HOME_PARENT = "$env:MAVEN_USER_HOME/wrapper/dists/$distributionUrlNameMain" +} +$MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' +$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME" + +if (Test-Path -Path "$MAVEN_HOME" -PathType Container) { + Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME" + Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" + exit $? +} + +if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) { + Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl" +} + +# prepare tmp dir +$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile +$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir" +$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null +trap { + if ($TMP_DOWNLOAD_DIR.Exists) { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } + } +} + +New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null + +# Download and Install Apache Maven +Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +Write-Verbose "Downloading from: $distributionUrl" +Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +$webclient = New-Object System.Net.WebClient +if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) { + $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD) +} +[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 +$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum +if ($distributionSha256Sum) { + if ($USE_MVND) { + Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." + } + Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash + if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) { + Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property." + } +} + +# unzip and move +Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null +Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null +try { + Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null +} catch { + if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) { + Write-Error "fail to move MAVEN_HOME" + } +} finally { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } +} + +Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" diff --git a/graalpy/graalpy-custom-venv-guide/pom.xml b/graalpy/graalpy-custom-venv-guide/pom.xml new file mode 100644 index 0000000..1e8b59b --- /dev/null +++ b/graalpy/graalpy-custom-venv-guide/pom.xml @@ -0,0 +1,30 @@ + + + 4.0.0 + + org.example + custom-venv + 1.0-SNAPSHOT + customvenv + + + UTF-8 + 17 + + + + + org.graalvm.polyglot + python + 24.1.0 + pom + + + + org.graalvm.polyglot + polyglot + 24.1.0 + + + diff --git a/graalpy/graalpy-custom-venv-guide/src/main/java/org/example/App.java b/graalpy/graalpy-custom-venv-guide/src/main/java/org/example/App.java new file mode 100644 index 0000000..0a6adb1 --- /dev/null +++ b/graalpy/graalpy-custom-venv-guide/src/main/java/org/example/App.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at https://opensource.org/license/UPL. + */ + +package org.example; + +import org.graalvm.polyglot.*; +import org.graalvm.polyglot.io.*; + +import java.nio.file.*; + +public class App { + private static final boolean IS_WINDOWS = System.getProperty("os.name").toLowerCase().contains("windows"); + + public static void main(String[] args) { + if (System.getProperty("venv") == null) { + System.err.println("Provide 'venv' system property."); + System.exit(1); + } + Path executable; + if (IS_WINDOWS) { // ① + executable = Paths.get(System.getProperty("venv"), "Scripts", "python.exe"); + } else { + executable = Paths.get(System.getProperty("venv"), "bin", "python"); + } + try (Context context = Context.newBuilder() // ② + .option("python.Executable", executable.toAbsolutePath().toString()) // ③ + .option("python.ForceImportSite", "true") // ④ + .allowIO(IOAccess.newBuilder().allowHostFileAccess(true).build()) // ⑤ + .build()) { + Value asciiArt = context.eval("python", "import art; art.text2art('GraalPy')"); // ⑥ + System.out.println(asciiArt.asString()); // ⑦ + } + } +} diff --git a/graalpy/graalpy-freeze-dependencies-guide/.mvn/wrapper/maven-wrapper.properties b/graalpy/graalpy-freeze-dependencies-guide/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..93a0d79 --- /dev/null +++ b/graalpy/graalpy-freeze-dependencies-guide/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,19 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +wrapperVersion=3.3.2 +distributionType=only-script +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.4/apache-maven-3.9.4-bin.zip diff --git a/graalpy/graalpy-freeze-dependencies-guide/README.md b/graalpy/graalpy-freeze-dependencies-guide/README.md new file mode 100644 index 0000000..3ccffa8 --- /dev/null +++ b/graalpy/graalpy-freeze-dependencies-guide/README.md @@ -0,0 +1,168 @@ +# Pinning Python Dependencies + +Python libraries can be used in and shipped with plain Java applications. + +The [GraalPy Maven artifacts](https://central.sonatype.com/artifact/org.graalvm.polyglot/python) and [GraalVM Polyglot APIs](https://www.graalvm.org/latest/reference-manual/embed-languages/) allow flexible integration with different project setups. + +Using Python packages in Java projects often requires a bit more setup, due to the nature of the Python packaging ecosystem. +GraalPy provides a [python-embedding](https://central.sonatype.com/artifact/org.graalvm.python/python-embedding) package that simplifies the required setup to ship Python packages as Java resources or in separate folders. +The important entry points to do so are the [VirtualFileSystem](https://github.com/oracle/graalpython/blob/master/docs/user/Embedding-Build-Tools.md#virtual-filesystem) and the [GraalPyResources](https://github.com/oracle/graalpython/blob/master/docs/user/Embedding-Build-Tools.md#deployment) classes. + +Unlike with Java libraries, Python packages frequently specify their dependencies as a range +rather than one specific version. This can create issues during development and testing, because +transitive set of dependencies may change when some package publishes a new release. + +We recommend pinning all transitive dependencies to single version and upgrade them +manually in a controlled fashion. This guide shows how this can be done with the +GraalPy Maven plugin. We will install package `vaderSentiment`, discover all its +transitive dependencies and then pin them in the Maven. + +## 1. What you will need + +To complete this guide, you will need the following: + +* Some time on your hands +* A decent text editor or IDE +* A supported JDK[^1], preferably the latest [GraalVM JDK](https://graalvm.org/downloads/) + +[^1]: Oracle JDK 17 and OpenJDK 17 are supported with interpreter only. +GraalVM JDK 21, Oracle JDK 21, OpenJDK 21 and newer with [JIT compilation](https://www.graalvm.org/latest/reference-manual/embed-languages/#runtime-optimization-support). +Note: GraalVM for JDK 17 is **not supported**. + +## 2. Solution + +We recommend that you follow the instructions in the next sections and create the application step by step. +However, you can go right to the [completed example](https://github.com/graalvm/graalpy-demos/tree/master/nativext). + +## 3. Writing the application + +You can start with any Maven application that runs on JDK 17 or newer. +We will use a default Maven application [generated](https://maven.apache.org/archetypes/maven-archetype-quickstart/) from an archetype. + +```shell +mvn archetype:generate -DarchetypeGroupId=org.apache.maven.archetypes \ + -DarchetypeArtifactId=maven-archetype-quickstart -DarchetypeVersion=1.5 \ + -DgroupId=example -DartifactId=example -Dpackage=org.example \ + -Dversion=1.0-SNAPSHOT -DinteractiveMode=false +``` + +## 4. Adding packages + +Most Python packages are hosted on [PyPI](https://pypi.org) and can be installed via the `pip` tool. +The Python ecosystem has conventions about the filesystem layout of installed packages that need to be kept in mind when embedding into Java. +You can use the GraalPy plugin to manage Python packages for you. + +`pom.xml` +```xml + + + + org.graalvm.python + graalpy-maven-plugin + 24.1.0 + + + + + vaderSentiment==3.3.2 + + + + process-graalpy-resources + + + + + + +``` + +❶ The `packages` section lists all Python packages optionally with [requirement specifiers](https://pip.pypa.io/en/stable/reference/requirement-specifiers/). +In this case, we install the `vaderSentiment` package and pin it to version `3.3.2`. Because we are not +specifying `` the Maven plugin will embed the packages into the +resulting JAR as a standard Java resource. + +## 5. Determining and Pinning Transitive Dependencies + +Package the application using Maven, this should install all the transitive dependencies +in a newly created [virtual environment](https://docs.python.org/3/library/venv.html): + +```shell +mvn package +``` + +If the compilation is successful, one can run the following command to get +versions of all the installed Python packages: + +```shell +./target/classes/org.graalvm.python.vfs/venv/bin/pip3 freeze -l +certifi==2024.8.30 +charset-normalizer==3.1.0 +idna==3.8 +requests==2.32.3 +urllib3==2.2.2 +vaderSentiment==3.3.2 +``` + +Copy and paste the package names and versions to the `pom.xml`, and wrap +them in `` xml tag: + +`pom.xml` +```xml + + + + org.graalvm.python + graalpy-maven-plugin + 24.1.0 + + + + + vaderSentiment==3.3.2 + certifi==2024.8.30 + charset-normalizer==3.1.0 + idna==3.8 + requests==2.32.3 + urllib3==2.2.2 + + + + process-graalpy-resources + + + + + + +``` + +Note: one can use other Python tools, such as `pipdeptree` to generate the following +dependency tree, where we can also see the version ranges. + +``` +vaderSentiment==3.3.2 +└── requests [required: Any, installed: 2.32.3] + ├── certifi [required: >=2017.4.17, installed: 2024.8.30] + ├── charset-normalizer [required: >=2,<4, installed: 3.1.0] + ├── idna [required: >=2.5,<4, installed: 3.8] + └── urllib3 [required: >=1.21.1,<3, installed: 2.2.2] +``` + +*Warning: +Is it not recommended to manually alter the virtual environment. +Any changes will be overridden by the GraalPy Maven plugin.* + +## 8. Next steps + +- Use GraalPy in a [Java SE application](../graalpy-javase-guide/README.md) +- Use GraalPy and `vaderSentiment` with popular Java frameworks, such as [Spring Boot](../graalpy-spring-boot-guide/README.md) or [Micronaut](../graalpy-micronaut-guide/README.md) +- Install and use Python packages that rely on [native code](../graalpy-native-extensions-guide/README.md), e.g. for data science and machine learning +- Follow along how you can manually [install Python packages and files](../graalpy-custom-venv-guide/README.md) if the Maven plugin gives not enough control +- [Freeze](../graalpy-freeze-dependencies-guide/README.md) transitive Python dependencies for reproducible builds +- [Migrate from Jython](../graalpy-jython-guide/README.md) to GraalPy + + +- Learn more about the GraalPy [Maven plugin](https://www.graalvm.org/latest/reference-manual/python/Embedding-Build-Tools/) +- Learn more about the Polyglot API for [embedding languages](https://www.graalvm.org/latest/reference-manual/embed-languages/) +- Explore in depth with GraalPy [reference manual](https://www.graalvm.org/latest/reference-manual/python/) \ No newline at end of file diff --git a/graalpy/graalpy-freeze-dependencies-guide/mvnw b/graalpy/graalpy-freeze-dependencies-guide/mvnw new file mode 100755 index 0000000..19529dd --- /dev/null +++ b/graalpy/graalpy-freeze-dependencies-guide/mvnw @@ -0,0 +1,259 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Apache Maven Wrapper startup batch script, version 3.3.2 +# +# Optional ENV vars +# ----------------- +# JAVA_HOME - location of a JDK home dir, required when download maven via java source +# MVNW_REPOURL - repo url base for downloading maven distribution +# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output +# ---------------------------------------------------------------------------- + +set -euf +[ "${MVNW_VERBOSE-}" != debug ] || set -x + +# OS specific support. +native_path() { printf %s\\n "$1"; } +case "$(uname)" in +CYGWIN* | MINGW*) + [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")" + native_path() { cygpath --path --windows "$1"; } + ;; +esac + +# set JAVACMD and JAVACCMD +set_java_home() { + # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched + if [ -n "${JAVA_HOME-}" ]; then + if [ -x "$JAVA_HOME/jre/sh/java" ]; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACCMD="$JAVA_HOME/jre/sh/javac" + else + JAVACMD="$JAVA_HOME/bin/java" + JAVACCMD="$JAVA_HOME/bin/javac" + + if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then + echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2 + echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2 + return 1 + fi + fi + else + JAVACMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v java + )" || : + JAVACCMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v javac + )" || : + + if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then + echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2 + return 1 + fi + fi +} + +# hash string like Java String::hashCode +hash_string() { + str="${1:-}" h=0 + while [ -n "$str" ]; do + char="${str%"${str#?}"}" + h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296)) + str="${str#?}" + done + printf %x\\n $h +} + +verbose() { :; } +[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; } + +die() { + printf %s\\n "$1" >&2 + exit 1 +} + +trim() { + # MWRAPPER-139: + # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds. + # Needed for removing poorly interpreted newline sequences when running in more + # exotic environments such as mingw bash on Windows. + printf "%s" "${1}" | tr -d '[:space:]' +} + +# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties +while IFS="=" read -r key value; do + case "${key-}" in + distributionUrl) distributionUrl=$(trim "${value-}") ;; + distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;; + esac +done <"${0%/*}/.mvn/wrapper/maven-wrapper.properties" +[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties" + +case "${distributionUrl##*/}" in +maven-mvnd-*bin.*) + MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ + case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in + *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;; + :Darwin*x86_64) distributionPlatform=darwin-amd64 ;; + :Darwin*arm64) distributionPlatform=darwin-aarch64 ;; + :Linux*x86_64*) distributionPlatform=linux-amd64 ;; + *) + echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2 + distributionPlatform=linux-amd64 + ;; + esac + distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip" + ;; +maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;; +*) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;; +esac + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}" +distributionUrlName="${distributionUrl##*/}" +distributionUrlNameMain="${distributionUrlName%.*}" +distributionUrlNameMain="${distributionUrlNameMain%-bin}" +MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}" +MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")" + +exec_maven() { + unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || : + exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD" +} + +if [ -d "$MAVEN_HOME" ]; then + verbose "found existing MAVEN_HOME at $MAVEN_HOME" + exec_maven "$@" +fi + +case "${distributionUrl-}" in +*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;; +*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;; +esac + +# prepare tmp dir +if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then + clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; } + trap clean HUP INT TERM EXIT +else + die "cannot create temp dir" +fi + +mkdir -p -- "${MAVEN_HOME%/*}" + +# Download and Install Apache Maven +verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +verbose "Downloading from: $distributionUrl" +verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +# select .zip or .tar.gz +if ! command -v unzip >/dev/null; then + distributionUrl="${distributionUrl%.zip}.tar.gz" + distributionUrlName="${distributionUrl##*/}" +fi + +# verbose opt +__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR='' +[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v + +# normalize http auth +case "${MVNW_PASSWORD:+has-password}" in +'') MVNW_USERNAME='' MVNW_PASSWORD='' ;; +has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;; +esac + +if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then + verbose "Found wget ... using wget" + wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl" +elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then + verbose "Found curl ... using curl" + curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl" +elif set_java_home; then + verbose "Falling back to use Java to download" + javaSource="$TMP_DOWNLOAD_DIR/Downloader.java" + targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName" + cat >"$javaSource" <<-END + public class Downloader extends java.net.Authenticator + { + protected java.net.PasswordAuthentication getPasswordAuthentication() + { + return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() ); + } + public static void main( String[] args ) throws Exception + { + setDefault( new Downloader() ); + java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() ); + } + } + END + # For Cygwin/MinGW, switch paths to Windows format before running javac and java + verbose " - Compiling Downloader.java ..." + "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java" + verbose " - Running Downloader.java ..." + "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")" +fi + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +if [ -n "${distributionSha256Sum-}" ]; then + distributionSha256Result=false + if [ "$MVN_CMD" = mvnd.sh ]; then + echo "Checksum validation is not supported for maven-mvnd." >&2 + echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + elif command -v sha256sum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c >/dev/null 2>&1; then + distributionSha256Result=true + fi + elif command -v shasum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then + distributionSha256Result=true + fi + else + echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2 + echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + fi + if [ $distributionSha256Result = false ]; then + echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2 + echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2 + exit 1 + fi +fi + +# unzip and move +if command -v unzip >/dev/null; then + unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip" +else + tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar" +fi +printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url" +mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME" + +clean || : +exec_maven "$@" diff --git a/graalpy/graalpy-freeze-dependencies-guide/mvnw.cmd b/graalpy/graalpy-freeze-dependencies-guide/mvnw.cmd new file mode 100644 index 0000000..b150b91 --- /dev/null +++ b/graalpy/graalpy-freeze-dependencies-guide/mvnw.cmd @@ -0,0 +1,149 @@ +<# : batch portion +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Apache Maven Wrapper startup batch script, version 3.3.2 +@REM +@REM Optional ENV vars +@REM MVNW_REPOURL - repo url base for downloading maven distribution +@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output +@REM ---------------------------------------------------------------------------- + +@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0) +@SET __MVNW_CMD__= +@SET __MVNW_ERROR__= +@SET __MVNW_PSMODULEP_SAVE=%PSModulePath% +@SET PSModulePath= +@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @( + IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B) +) +@SET PSModulePath=%__MVNW_PSMODULEP_SAVE% +@SET __MVNW_PSMODULEP_SAVE= +@SET __MVNW_ARG0_NAME__= +@SET MVNW_USERNAME= +@SET MVNW_PASSWORD= +@IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*) +@echo Cannot start maven from wrapper >&2 && exit /b 1 +@GOTO :EOF +: end batch / begin powershell #> + +$ErrorActionPreference = "Stop" +if ($env:MVNW_VERBOSE -eq "true") { + $VerbosePreference = "Continue" +} + +# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties +$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl +if (!$distributionUrl) { + Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" +} + +switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) { + "maven-mvnd-*" { + $USE_MVND = $true + $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip" + $MVN_CMD = "mvnd.cmd" + break + } + default { + $USE_MVND = $false + $MVN_CMD = $script -replace '^mvnw','mvn' + break + } +} + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +if ($env:MVNW_REPOURL) { + $MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" } + $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')" +} +$distributionUrlName = $distributionUrl -replace '^.*/','' +$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$','' +$MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain" +if ($env:MAVEN_USER_HOME) { + $MAVEN_HOME_PARENT = "$env:MAVEN_USER_HOME/wrapper/dists/$distributionUrlNameMain" +} +$MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' +$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME" + +if (Test-Path -Path "$MAVEN_HOME" -PathType Container) { + Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME" + Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" + exit $? +} + +if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) { + Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl" +} + +# prepare tmp dir +$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile +$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir" +$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null +trap { + if ($TMP_DOWNLOAD_DIR.Exists) { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } + } +} + +New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null + +# Download and Install Apache Maven +Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +Write-Verbose "Downloading from: $distributionUrl" +Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +$webclient = New-Object System.Net.WebClient +if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) { + $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD) +} +[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 +$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum +if ($distributionSha256Sum) { + if ($USE_MVND) { + Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." + } + Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash + if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) { + Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property." + } +} + +# unzip and move +Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null +Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null +try { + Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null +} catch { + if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) { + Write-Error "fail to move MAVEN_HOME" + } +} finally { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } +} + +Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" diff --git a/graalpy/graalpy-freeze-dependencies-guide/pom.xml b/graalpy/graalpy-freeze-dependencies-guide/pom.xml new file mode 100644 index 0000000..e01cc24 --- /dev/null +++ b/graalpy/graalpy-freeze-dependencies-guide/pom.xml @@ -0,0 +1,100 @@ + + + 4.0.0 + + org.example + freeze-deps + 1.0-SNAPSHOT + jar + freezedeps + + + 17 + 17 + UTF-8 + + + + + org.graalvm.polyglot + python + 24.1.0 + pom + + + + org.graalvm.python + python-embedding + 24.1.0 + + + + + + + default-build + + true + + + + + org.graalvm.python + graalpy-maven-plugin + 24.1.0 + + + + + vaderSentiment==3.3.2 + certifi==2024.8.30 + charset-normalizer==3.1.0 + idna==3.8 + requests==2.32.3 + urllib3==2.2.2 + + + + process-graalpy-resources + + + + + + + + + + no-transitive-deps + + + no.transitive.dependencies + true + + + + + + org.graalvm.python + graalpy-maven-plugin + 24.1.0 + + + + + vaderSentiment==3.3.2 + + + + process-graalpy-resources + + + + + + + + + diff --git a/graalpy/graalpy-freeze-dependencies-guide/src/main/java/org/example/App.java b/graalpy/graalpy-freeze-dependencies-guide/src/main/java/org/example/App.java new file mode 100644 index 0000000..e9b163f --- /dev/null +++ b/graalpy/graalpy-freeze-dependencies-guide/src/main/java/org/example/App.java @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at https://opensource.org/license/UPL. + */ +package org.example; + +import org.graalvm.polyglot.Context; +import org.graalvm.python.embedding.utils.GraalPyResources; + +public class App { + public static void main(String[] args) { + try (Context context = GraalPyResources.createContext()) { + context.eval("python", """ + from vaderSentiment.vaderSentiment import NEGATE; + print(NEGATE) + """); + } + } +} \ No newline at end of file diff --git a/graalpy/graalpy-javase-guide/.mvn/wrapper/maven-wrapper.properties b/graalpy/graalpy-javase-guide/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..d58dfb7 --- /dev/null +++ b/graalpy/graalpy-javase-guide/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,19 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +wrapperVersion=3.3.2 +distributionType=only-script +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip diff --git a/graalpy/graalpy-javase-guide/README.md b/graalpy/graalpy-javase-guide/README.md new file mode 100644 index 0000000..1b056cd --- /dev/null +++ b/graalpy/graalpy-javase-guide/README.md @@ -0,0 +1,267 @@ +# Using Python Packages in a Java SE Application + +Python libraries can be used in and shipped with plain Java applications. +The [GraalPy Maven artifacts](https://central.sonatype.com/artifact/org.graalvm.polyglot/python) and [GraalVM Polyglot APIs](https://www.graalvm.org/latest/reference-manual/embed-languages/) allow flexible integration with different project setups. + +Using Python packages in Java projects often requires a bit more setup, due to the nature of the Python packaging ecosystem. +GraalPy provides a [python-embedding](https://central.sonatype.com/artifact/org.graalvm.python/python-embedding) package that simplifies the required setup to ship Python packages as Java resources or in separate folders. +The important entry points to do so are the [VirtualFileSystem](https://github.com/oracle/graalpython/blob/master/docs/user/Embedding-Build-Tools.md#virtual-filesystem) and the [GraalPyResources](https://github.com/oracle/graalpython/blob/master/docs/user/Embedding-Build-Tools.md#deployment) classes. + +## 1. Getting Started + +In this guide, we will add a small Python library to [generate QR codes](https://pypi.org/project/qrcode) to a Java GUI application: +![Screenshot of the app](screenshot.png) + +## 2. What you will need + +To complete this guide, you will need the following: + + * Some time on your hands + * A decent text editor or IDE + * A supported JDK[^1], preferably the latest [GraalVM JDK](https://graalvm.org/downloads/) + + [^1]: Oracle JDK 17 and OpenJDK 17 are supported with interpreter only. + GraalVM JDK 21, Oracle JDK 21, OpenJDK 21 and newer with [JIT compilation](https://www.graalvm.org/latest/reference-manual/embed-languages/#runtime-optimization-support). + Note: GraalVM for JDK 17 is **not supported**. + +## 3. Solution + +We recommend that you follow the instructions in the next sections and create the application step by step. +However, you can go right to the [completed example](./). + +## 4. Writing the application + +You can start with any Maven application that runs on JDK 17 or newer. +We will use a default Maven application [generated](https://maven.apache.org/archetypes/maven-archetype-quickstart/) from an archetype. + +```shell +mvn archetype:generate -DarchetypeGroupId=org.apache.maven.archetypes \ + -DarchetypeArtifactId=maven-archetype-quickstart -DarchetypeVersion=1.5 \ + -DgroupId=example -DartifactId=example -Dpackage=org.example \ + -Dversion=1.0-SNAPSHOT -DinteractiveMode=false +``` + +## 4.1 Dependency configuration + +Add the required dependencies for GraalPy in the `` section of the POM. + +`pom.xml` +```xml + + + org.graalvm.polyglot + python + 24.1.0 + pom + + + + org.graalvm.python + python-embedding + 24.1.0 + + +``` + +❶ The `python` dependency is a meta-package that transitively depends on all resources and libraries to run GraalPy. + +❷ Note that the `python` package is not a JAR - it is simply a `pom` that declares more dependencies. + +❸ The `python-embedding` dependency provides the APIs to manage and use GraalPy from Java. + +## 4.2 Adding packages + +Most Python packages are hosted on [PyPI](https://pypi.org) and can be installed via the `pip` tool. +The Python ecosystem has conventions about the filesystem layout of installed packages that need to be kept in mind when embedding into Java. +You can use the GraalPy plugin to manage Python packages for you. + +`pom.xml` +```xml + + + + org.graalvm.python + graalpy-maven-plugin + 24.1.0 + + + + + qrcode==7.4.2 + + + + + + .* + + + + ${project.basedir}/python-resources + + + + process-graalpy-resources + + + + + + +``` + +❶ The `packages` section lists all Python packages optionally with [requirement specifiers](https://pip.pypa.io/en/stable/reference/requirement-specifiers/). +In this case, we install the `qrcode` package and pin it to version `7.4.2`. + + +❷ The GraalPy plugin can copy the Python standard library resources. +This is mainly useful when creating a [GraalVM Native Image](https://www.graalvm.org/latest/reference-manual/native-image/), a use-case that we are not going to cover right now. +We disable this by specifying that we want to exclude all standard library files matching the regular expression `.*`, i.e., all of them, from the included Python home. + + +❸ We can specify where the plugin should place Python files for packages and the standard library that the application will use. +Omit this section if you want to include the Python packages into the Java resources (and, for example, ship them in the Jar). +[Later in the Java code](#external-or-embedded-python-code-java) we can configure the GraalPy runtime to load the package from the filesystem or from resources. + +## 4.3 Creating a Python context + +GraalPy provides APIs to make setting up a context to load Python packages from Java as easy as possible. + +`GraalPy.java` +```java +package org.example; + +import java.nio.file.Path; + +import org.graalvm.polyglot.Context; +import org.graalvm.python.embedding.utils.*; + +public class GraalPy { + static VirtualFileSystem vfs; + + public static Context createPythonContext(String pythonResourcesDirectory) { // ① + return GraalPyResources.contextBuilder(Path.of(pythonResourcesDirectory)) + .option("python.PythonHome", "") // ② + .build(); + } + + public static Context createPythonContextFromResources() { + if (vfs == null) { // ③ + vfs = VirtualFileSystem.newBuilder().allowHostIO(VirtualFileSystem.HostIO.READ).build(); + } + return GraalPyResources.contextBuilder(vfs).option("python.PythonHome", "").build(); + } +} +``` + +❶ [If we set the `pythonResourcesDirectory` property](#external-or-embedded-python-code-pom) in our pom, we use this factory method to tell GraalPy where that folder is at runtime. + +❷ We [excluded](#external-or-embedded-stdlib-pom) all of the Python standard library from the resources in our pom. +The GraalPy VirtualFileSystem is set up to ship even the standard library in the resources. +Since we did not include any standard library, we set the `"python.PythonHome"` option to an empty string. + +❸ [If we do not set the `pythonResourcesDirectory` property](#external-or-embedded-python-code-pom), the GraalPy Maven plugin will place the packages inside the Java resources. +Because Python libraries assume they are running from a filesystem, not a resource location, GraalPy provides the `VirtualFileSystem`, and API to make Java resource locations available to Python code as if it were in the real filesystem. +VirtualFileSystem instances can be configured to allow different levels of through-access to the underlying host filesystem. +In this demo we use the same VirtualFileSystem instance in multiple Python contexts. + +## 4.3 Using a Python library from Java + +After reading the [qrcode](https://pypi.org/project/qrcode/) docs, we can write Java interfaces that match the Python types we want to use and methods we want to call on them. +GraalPy makes it easy to access Python objects via these interfaces. +Java method names are mapped directly to Python method names. +Return values are mapped according to a set of [generic rules](https://www.graalvm.org/latest/reference-manual/python/Modern-Python-on-JVM/#java-to-python-types-automatic-conversion). +The names of the interfaces can be chosen freely, but it makes sense to base them on the Python types, as we do below. + +`QRCode.java` +```java +package org.example; + +interface QRCode { + PyPNGImage make(String data); + + interface PyPNGImage { + void save(IO.BytesIO bio); + } +} +``` + +`IO.java` +```java +package org.example; + +import org.graalvm.polyglot.io.ByteSequence; + +interface IO { + BytesIO BytesIO(); + + interface BytesIO { + ByteSequence getvalue(); + } +} +``` + +Using these interfaces and the `GraalPy` class, we can now create QR-codes and show them in, for example, a [JLabel](https://docs.oracle.com/en/java/javase/21/docs/api/java.desktop/javax/swing/JLabel.html). + +`App.java` +```java +package org.example; + +import java.io.*; +import javax.imageio.ImageIO; +import javax.swing.*; + +public class App { + public static void main(String[] args) throws IOException { + if (System.getProperty("graalpy.resources") == null) { + System.err.println("Please provide 'graalpy.resources' system property."); + System.exit(1); + } + try (var context = GraalPy.createPythonContext(System.getProperty("graalpy.resources"))) { // ① + QRCode qrCode = context.eval("python", "import qrcode; qrcode").as(QRCode.class); // ② + IO io = context.eval("python", "import io; io").as(IO.class); + + IO.BytesIO bytesIO = io.BytesIO(); // ③ + qrCode.make("Hello from GraalPy on JDK " + System.getProperty("java.version")).save(bytesIO); + + var qrImage = ImageIO.read(new ByteArrayInputStream(bytesIO.getvalue().toByteArray())); // ④ + JFrame frame = new JFrame("QR Code"); + frame.getContentPane().add(new JLabel(new ImageIcon(qrImage))); + frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); + frame.setSize(400, 400); + frame.setVisible(true); + } + } +} +``` + +❶ If we do not want to ship the directory with the Python package separately and pass the location at runtime, we can [embed them](#external-or-embedded-python-code-pom) and use the virtual filesystem [constructor](#external-or-embedded-python-code-java). + +❷ Python objects are returned using a generic [Value](https://docs.oracle.com/en/graalvm/enterprise/20/sdk/org/graalvm/polyglot/Value.html) type. +We cast the `io` and `qrcode` packages to our declared interfaces so we can use Java typing and IDE completion features. + +❸ Method calls on our interfaces are transparently forwarded to the Python objects, arguments and return values are coerced automatically. + +❹ Python code returns the generated PNG as an array of unsigned bytes, which we can process on the Java side. + +## 5. Running the application + +If you followed along with the example, you can now compile and run your application from the commandline: + +```shell +./mvnw compile +./mvnw exec:java -Dexec.mainClass=org.example.App -Dgraalpy.resources=./python-resources +``` + +## 6. Next steps + +- Use GraalPy with popular Java frameworks, such as [Spring Boot](../graalpy-spring-boot-guide/README.md) or [Micronaut](../graalpy-micronaut-guide/README.md) +- Install and use Python packages that rely on [native code](../graalpy-native-extensions-guide/README.md), e.g. for data science and machine learning +- Follow along how you can manually [install Python packages and files](../graalpy-custom-venv-guide/README.md) if the Maven plugin gives not enough control +- [Freeze](../graalpy-freeze-dependencies-guide/README.md) transitive Python dependencies for reproducible builds +- [Migrate from Jython](../graalpy-jython-guide/README.md) to GraalPy + + +- Learn more about the GraalPy [Maven plugin](https://www.graalvm.org/latest/reference-manual/python/Embedding-Build-Tools/) +- Learn more about the Polyglot API for [embedding languages](https://www.graalvm.org/latest/reference-manual/embed-languages/) +- Explore in depth with GraalPy [reference manual](https://www.graalvm.org/latest/reference-manual/python/) diff --git a/graalpy/graalpy-javase-guide/mvnw b/graalpy/graalpy-javase-guide/mvnw new file mode 100755 index 0000000..19529dd --- /dev/null +++ b/graalpy/graalpy-javase-guide/mvnw @@ -0,0 +1,259 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Apache Maven Wrapper startup batch script, version 3.3.2 +# +# Optional ENV vars +# ----------------- +# JAVA_HOME - location of a JDK home dir, required when download maven via java source +# MVNW_REPOURL - repo url base for downloading maven distribution +# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output +# ---------------------------------------------------------------------------- + +set -euf +[ "${MVNW_VERBOSE-}" != debug ] || set -x + +# OS specific support. +native_path() { printf %s\\n "$1"; } +case "$(uname)" in +CYGWIN* | MINGW*) + [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")" + native_path() { cygpath --path --windows "$1"; } + ;; +esac + +# set JAVACMD and JAVACCMD +set_java_home() { + # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched + if [ -n "${JAVA_HOME-}" ]; then + if [ -x "$JAVA_HOME/jre/sh/java" ]; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACCMD="$JAVA_HOME/jre/sh/javac" + else + JAVACMD="$JAVA_HOME/bin/java" + JAVACCMD="$JAVA_HOME/bin/javac" + + if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then + echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2 + echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2 + return 1 + fi + fi + else + JAVACMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v java + )" || : + JAVACCMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v javac + )" || : + + if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then + echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2 + return 1 + fi + fi +} + +# hash string like Java String::hashCode +hash_string() { + str="${1:-}" h=0 + while [ -n "$str" ]; do + char="${str%"${str#?}"}" + h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296)) + str="${str#?}" + done + printf %x\\n $h +} + +verbose() { :; } +[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; } + +die() { + printf %s\\n "$1" >&2 + exit 1 +} + +trim() { + # MWRAPPER-139: + # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds. + # Needed for removing poorly interpreted newline sequences when running in more + # exotic environments such as mingw bash on Windows. + printf "%s" "${1}" | tr -d '[:space:]' +} + +# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties +while IFS="=" read -r key value; do + case "${key-}" in + distributionUrl) distributionUrl=$(trim "${value-}") ;; + distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;; + esac +done <"${0%/*}/.mvn/wrapper/maven-wrapper.properties" +[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties" + +case "${distributionUrl##*/}" in +maven-mvnd-*bin.*) + MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ + case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in + *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;; + :Darwin*x86_64) distributionPlatform=darwin-amd64 ;; + :Darwin*arm64) distributionPlatform=darwin-aarch64 ;; + :Linux*x86_64*) distributionPlatform=linux-amd64 ;; + *) + echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2 + distributionPlatform=linux-amd64 + ;; + esac + distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip" + ;; +maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;; +*) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;; +esac + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}" +distributionUrlName="${distributionUrl##*/}" +distributionUrlNameMain="${distributionUrlName%.*}" +distributionUrlNameMain="${distributionUrlNameMain%-bin}" +MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}" +MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")" + +exec_maven() { + unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || : + exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD" +} + +if [ -d "$MAVEN_HOME" ]; then + verbose "found existing MAVEN_HOME at $MAVEN_HOME" + exec_maven "$@" +fi + +case "${distributionUrl-}" in +*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;; +*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;; +esac + +# prepare tmp dir +if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then + clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; } + trap clean HUP INT TERM EXIT +else + die "cannot create temp dir" +fi + +mkdir -p -- "${MAVEN_HOME%/*}" + +# Download and Install Apache Maven +verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +verbose "Downloading from: $distributionUrl" +verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +# select .zip or .tar.gz +if ! command -v unzip >/dev/null; then + distributionUrl="${distributionUrl%.zip}.tar.gz" + distributionUrlName="${distributionUrl##*/}" +fi + +# verbose opt +__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR='' +[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v + +# normalize http auth +case "${MVNW_PASSWORD:+has-password}" in +'') MVNW_USERNAME='' MVNW_PASSWORD='' ;; +has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;; +esac + +if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then + verbose "Found wget ... using wget" + wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl" +elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then + verbose "Found curl ... using curl" + curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl" +elif set_java_home; then + verbose "Falling back to use Java to download" + javaSource="$TMP_DOWNLOAD_DIR/Downloader.java" + targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName" + cat >"$javaSource" <<-END + public class Downloader extends java.net.Authenticator + { + protected java.net.PasswordAuthentication getPasswordAuthentication() + { + return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() ); + } + public static void main( String[] args ) throws Exception + { + setDefault( new Downloader() ); + java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() ); + } + } + END + # For Cygwin/MinGW, switch paths to Windows format before running javac and java + verbose " - Compiling Downloader.java ..." + "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java" + verbose " - Running Downloader.java ..." + "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")" +fi + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +if [ -n "${distributionSha256Sum-}" ]; then + distributionSha256Result=false + if [ "$MVN_CMD" = mvnd.sh ]; then + echo "Checksum validation is not supported for maven-mvnd." >&2 + echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + elif command -v sha256sum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c >/dev/null 2>&1; then + distributionSha256Result=true + fi + elif command -v shasum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then + distributionSha256Result=true + fi + else + echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2 + echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + fi + if [ $distributionSha256Result = false ]; then + echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2 + echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2 + exit 1 + fi +fi + +# unzip and move +if command -v unzip >/dev/null; then + unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip" +else + tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar" +fi +printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url" +mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME" + +clean || : +exec_maven "$@" diff --git a/graalpy/graalpy-javase-guide/mvnw.cmd b/graalpy/graalpy-javase-guide/mvnw.cmd new file mode 100644 index 0000000..b150b91 --- /dev/null +++ b/graalpy/graalpy-javase-guide/mvnw.cmd @@ -0,0 +1,149 @@ +<# : batch portion +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Apache Maven Wrapper startup batch script, version 3.3.2 +@REM +@REM Optional ENV vars +@REM MVNW_REPOURL - repo url base for downloading maven distribution +@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output +@REM ---------------------------------------------------------------------------- + +@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0) +@SET __MVNW_CMD__= +@SET __MVNW_ERROR__= +@SET __MVNW_PSMODULEP_SAVE=%PSModulePath% +@SET PSModulePath= +@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @( + IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B) +) +@SET PSModulePath=%__MVNW_PSMODULEP_SAVE% +@SET __MVNW_PSMODULEP_SAVE= +@SET __MVNW_ARG0_NAME__= +@SET MVNW_USERNAME= +@SET MVNW_PASSWORD= +@IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*) +@echo Cannot start maven from wrapper >&2 && exit /b 1 +@GOTO :EOF +: end batch / begin powershell #> + +$ErrorActionPreference = "Stop" +if ($env:MVNW_VERBOSE -eq "true") { + $VerbosePreference = "Continue" +} + +# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties +$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl +if (!$distributionUrl) { + Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" +} + +switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) { + "maven-mvnd-*" { + $USE_MVND = $true + $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip" + $MVN_CMD = "mvnd.cmd" + break + } + default { + $USE_MVND = $false + $MVN_CMD = $script -replace '^mvnw','mvn' + break + } +} + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +if ($env:MVNW_REPOURL) { + $MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" } + $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')" +} +$distributionUrlName = $distributionUrl -replace '^.*/','' +$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$','' +$MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain" +if ($env:MAVEN_USER_HOME) { + $MAVEN_HOME_PARENT = "$env:MAVEN_USER_HOME/wrapper/dists/$distributionUrlNameMain" +} +$MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' +$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME" + +if (Test-Path -Path "$MAVEN_HOME" -PathType Container) { + Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME" + Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" + exit $? +} + +if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) { + Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl" +} + +# prepare tmp dir +$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile +$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir" +$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null +trap { + if ($TMP_DOWNLOAD_DIR.Exists) { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } + } +} + +New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null + +# Download and Install Apache Maven +Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +Write-Verbose "Downloading from: $distributionUrl" +Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +$webclient = New-Object System.Net.WebClient +if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) { + $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD) +} +[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 +$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum +if ($distributionSha256Sum) { + if ($USE_MVND) { + Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." + } + Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash + if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) { + Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property." + } +} + +# unzip and move +Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null +Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null +try { + Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null +} catch { + if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) { + Write-Error "fail to move MAVEN_HOME" + } +} finally { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } +} + +Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" diff --git a/graalpy/graalpy-javase-guide/pom.xml b/graalpy/graalpy-javase-guide/pom.xml new file mode 100644 index 0000000..f4d98c7 --- /dev/null +++ b/graalpy/graalpy-javase-guide/pom.xml @@ -0,0 +1,63 @@ + + + 4.0.0 + + org.example + javase + 1.0-SNAPSHOT + jar + javase + + + 17 + 17 + UTF-8 + + + + + org.graalvm.polyglot + python + 24.1.0 + pom + + + + org.graalvm.python + python-embedding + 24.1.0 + + + + + + + org.graalvm.python + graalpy-maven-plugin + 24.1.0 + + + + + qrcode==7.4.2 + + + + + + .* + + + + ${project.basedir}/python-resources + + + + process-graalpy-resources + + + + + + + diff --git a/graalpy/graalpy-javase-guide/screenshot.png b/graalpy/graalpy-javase-guide/screenshot.png new file mode 100755 index 0000000000000000000000000000000000000000..5f49c9c948942f76f02bf22eac38938f0c9b6a7a GIT binary patch literal 4085 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 L$)@t$e`5a&tChhY literal 0 HcmV?d00001 diff --git a/graalpy/graalpy-javase-guide/src/main/java/org/example/App.java b/graalpy/graalpy-javase-guide/src/main/java/org/example/App.java new file mode 100644 index 0000000..d50f5ba --- /dev/null +++ b/graalpy/graalpy-javase-guide/src/main/java/org/example/App.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at https://opensource.org/license/UPL. + */ + +package org.example; + +import java.io.*; +import javax.imageio.ImageIO; +import javax.swing.*; + +public class App { + public static void main(String[] args) throws IOException { + if (System.getProperty("graalpy.resources") == null) { + System.err.println("Please provide 'graalpy.resources' system property."); + System.exit(1); + } + try (var context = GraalPy.createPythonContext(System.getProperty("graalpy.resources"))) { // ① + QRCode qrCode = context.eval("python", "import qrcode; qrcode").as(QRCode.class); // ② + IO io = context.eval("python", "import io; io").as(IO.class); + + IO.BytesIO bytesIO = io.BytesIO(); // ③ + qrCode.make("Hello from GraalPy on JDK " + System.getProperty("java.version")).save(bytesIO); + + var qrImage = ImageIO.read(new ByteArrayInputStream(bytesIO.getvalue().toByteArray())); // ④ + JFrame frame = new JFrame("QR Code"); + frame.getContentPane().add(new JLabel(new ImageIcon(qrImage))); + frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); + frame.setSize(400, 400); + frame.setVisible(true); + } + } +} diff --git a/graalpy/graalpy-javase-guide/src/main/java/org/example/GraalPy.java b/graalpy/graalpy-javase-guide/src/main/java/org/example/GraalPy.java new file mode 100644 index 0000000..c4e59fb --- /dev/null +++ b/graalpy/graalpy-javase-guide/src/main/java/org/example/GraalPy.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at https://opensource.org/license/UPL. + */ + +package org.example; + +import java.nio.file.Path; + +import org.graalvm.polyglot.Context; +import org.graalvm.python.embedding.utils.*; + +public class GraalPy { + static VirtualFileSystem vfs; + + public static Context createPythonContext(String pythonResourcesDirectory) { // ① + return GraalPyResources.contextBuilder(Path.of(pythonResourcesDirectory)) + .option("python.PythonHome", "") // ② + .build(); + } + + public static Context createPythonContextFromResources() { + if (vfs == null) { // ③ + vfs = VirtualFileSystem.newBuilder().allowHostIO(VirtualFileSystem.HostIO.READ).build(); + } + return GraalPyResources.contextBuilder(vfs).option("python.PythonHome", "").build(); + } +} diff --git a/graalpy/graalpy-javase-guide/src/main/java/org/example/IO.java b/graalpy/graalpy-javase-guide/src/main/java/org/example/IO.java new file mode 100644 index 0000000..674a3f6 --- /dev/null +++ b/graalpy/graalpy-javase-guide/src/main/java/org/example/IO.java @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at https://opensource.org/license/UPL. + */ + +package org.example; + +import org.graalvm.polyglot.io.ByteSequence; + +interface IO { + BytesIO BytesIO(); + + interface BytesIO { + ByteSequence getvalue(); + } +} diff --git a/graalpy/graalpy-javase-guide/src/main/java/org/example/QRCode.java b/graalpy/graalpy-javase-guide/src/main/java/org/example/QRCode.java new file mode 100644 index 0000000..13bb3ef --- /dev/null +++ b/graalpy/graalpy-javase-guide/src/main/java/org/example/QRCode.java @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at https://opensource.org/license/UPL. + */ + +package org.example; + +interface QRCode { + PyPNGImage make(String data); + + interface PyPNGImage { + void save(IO.BytesIO bio); + } +} diff --git a/graalpy/graalpy-jython-guide/.mvn/wrapper/maven-wrapper.properties b/graalpy/graalpy-jython-guide/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..d58dfb7 --- /dev/null +++ b/graalpy/graalpy-jython-guide/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,19 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +wrapperVersion=3.3.2 +distributionType=only-script +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip diff --git a/graalpy/graalpy-jython-guide/README.md b/graalpy/graalpy-jython-guide/README.md new file mode 100644 index 0000000..8aaff49 --- /dev/null +++ b/graalpy/graalpy-jython-guide/README.md @@ -0,0 +1,232 @@ +# Migrating from Jython to GraalPy for Java Integrations + +Jython can be embedded into Java applications to, for example, provide scripting capabilities to Java applications or to extend applications with Python modules. +"Scripting for the Java Platform", also known as JSR 223, provides a generic mechanism for such integrations. +This mechanism has some limitations, but is supported by both Jython and [Truffle](https://www.graalvm.org/latest/reference-manual/embed-languages/#compatibility-with-jsr-223-scriptengine). +However, Jython also provides an API to interact with the Python interpreter directly and migrating such integrations to GraalPy requires more work to match the appropriate APIs. + +## 1. Getting Started + +In this guide, we will show how to migrate from Jython to GraalPy on a Java application that uses Swing for interactive scripting. +![Screenshot of the app](screenshot.png) + +## 2. What you will need + +To complete this guide, you will need the following: + + * Some time on your hands + * A decent text editor or IDE + * A supported JDK[^1], preferably the latest [GraalVM JDK](https://graalvm.org/downloads/) + + [^1]: Oracle JDK 17 and OpenJDK 17 are supported with interpreter only. + GraalVM JDK 21, Oracle JDK 21, OpenJDK 21 and newer with [JIT compilation](https://www.graalvm.org/latest/reference-manual/embed-languages/#runtime-optimization-support). + Note: GraalVM for JDK 17 is **not supported**. + +## 3. The original application + +The demo application we use provides a Swing UI with text input, a label, and a drop-down menu. +Each element of the drop-down menu defines how to interpret text inputs, as well as setup and teardown code for the interpreter. +The simplest interpreter is "Echo", which needs no setup or teardown and simply returns the input string back: + +`EchoInputCallback.java` +```java +package org.example; + +final class EchoInputCallback implements InputCallback { + @Override + public void setUp(Workspace workspace) { + } + + @Override + public String interpret(String code) { + return code; + } + + @Override + public void tearDown() { + } +} +``` + +The Jython entry uses Jython to evaluate the code and return the result. + +`JythonInputCallback.java` +```java +package org.example; + +import org.python.core.*; +import org.python.util.PythonInterpreter; + +final class JythonInputCallback implements InputCallback { + static PyCode JYTHON_CODE = new PythonInterpreter().compile("__import__('sys').version"); // ① + private PythonInterpreter python; + + @Override + public void setUp(Workspace workspace) { + var globals = new PyDictionary(); + this.python = new PythonInterpreter(globals); // ② + globals.put(new PyString("this"), workspace); + } + + @Override + public String interpret(String code) { + try { + PyObject result; + if (code.isBlank()) { + result = python.eval(JYTHON_CODE); + } else { + result = python.eval(code); + } + if (result instanceof PyString strResult) { // ③ + return code + "\n... " + strResult.asString(); + } + return code + "\n...(repr) " + result.__repr__(); + } catch (PySyntaxError e) { + python.exec(code); // ④ + return ""; + } + } + + @Override + public void tearDown() { + python.close(); + } +} +``` + +❶ Jython compiles Python code snippets to PyCode objects, which hold bytecode and can be stored and re-used, serialized, and run in different Jython executions without re-parsing. + +❷ The PythonInterpreter class is the official API to instantiate Jython. A custom `PyDictionary` object can be passed that can be used to share global bindings with Java. + +❸ Jython returns evaluation results directly as the implementation types of the Python runtime. + +❹ The Jython interpreter exposes the Python-specific difference between `eval` of expressions and `exec` of statements by raising a syntax error. + +## 4. Migrating the application + +We recommend to check out the [completed example](./) and follow along with the steps in the next sections that were used to migrate the application step by step. + +## 4.1 Dependency configuration + +Add the required dependencies for GraalPy in the `` section of the POM. + +`pom.xml` +```xml + + org.graalvm.polyglot + python + 24.1.0 + pom + + + + org.graalvm.polyglot + polyglot + 24.1.0 + +``` + +❶ The `python` dependency is a meta-package that transitively depends on all resources and libraries to run GraalPy. + +❷ Note that the `python` package is not a JAR - it is simply a `pom` that declares more dependencies. + +❸ The `polyglot` dependency provides the APIs to manage and use GraalPy and other Graal languages from Java. + +## 4.2 Migrating the setup code + +`GraalPyInputCallback.java` +```java +package org.example; + +import org.graalvm.polyglot.*; + +final class GraalPyInputCallback implements InputCallback { + static Source GRAALPY_CODE = Source.create("python", "__import__('sys').version"); + static Engine engine = Engine.create("python"); + private Context python; + + @Override + public void setUp(Workspace workspace) { + this.python = Context.newBuilder("python") // ① + .engine(engine) // ② + .allowAllAccess(true).option("python.EmulateJython", "true") // ③ + .build(); + python.getBindings("python").putMember("this", workspace); // ④ + } +``` + +❶ The [Polyglot API](https://www.graalvm.org/sdk/javadoc/org/graalvm/polyglot/Context.html) is the generic way to create interpreters of Graal languages, including GraalPy. + +❷ Where Jython allows sharing code by sharing `PyCode` objects, Graal languages share code by holding on to [`Source`](https://www.graalvm.org/sdk/javadoc/org/graalvm/polyglot/Source.html) objects. +JIT compiled code related to a given `Source` is shared when running on a Graal JDK if multiple Contexts are configured to share an [`Engine`](https://www.graalvm.org/sdk/javadoc/org/graalvm/polyglot/Engine.html). + +❸ Jython does not impose access restrictions on the Python code. +GraalPy, on the other hand, is locked down by default. +Jython also provides some conveniences such as redirecting Python field access to Java object getter methods. +For migration it makes sense to remove access restrictions and enable additional Jython heuristics also on GraalPy. +After migration, we should try to remove as many permissions and heuristics as possible. + +❹ Instead of accessing the Python `globals` dictionary, which is a detail of the Python language, [bindings](https://www.graalvm.org/sdk/javadoc/org/graalvm/polyglot/Context.html#getBindings(java.lang.String)) offer a language-agnostic way to interact with the global scope. + +## 4.3 Migrating the interpretation + +`GraalPyInputCallback.java` +```java +@Override +public String interpret(String code) { + Value result; + if (code.isBlank()) { + result = python.eval(GRAALPY_CODE); + } else { + result = python.eval("python", code); // ① + } + if (result.isString()) { // ② + return code + "\n... " + result.asString(); + } + return code + "\n...(repr) " + result.toString(); +} +``` + +❶ Evaluation of code is very similar to how it is done for Jython. +Note, however, that the distinction in the Python language between `exec` and `eval` is not exposed, so both are represented with the same API call on GraalPy. + +❷ GraalPy implementation types are hidden behind the [`Value`](https://www.graalvm.org/sdk/javadoc/org/graalvm/polyglot/Value.html) type, so instead of Java typechecks there are APIs to determine what kind of object we are dealing with and to convert it to basic Java types like `String`. + +## 4.4 Migrating the teardown + +`GraalPyInputCallback.java` +```java +@Override +public void tearDown() { + python.close(true); // ① +} +``` + +❶ Closing the Jython interpreter cancels all executions that may be running on other threads, whereas GraalPy tries to wait for executions to finish. +To get the same behavior as Jython, we pass `true` to the `close` call to cancel executions immediately. + +## 5. Running the application + +If you downloaded the [example](https://github.com/graalvm/graalpy-demos/tree/master/jython), you can now compile and run your application from the commandline: + +```shell +./mvnw compile +./mvnw exec:java -Dexec.mainClass=org.example.App +``` + +You can switch between Jython and GraalPy and compare the output. +Also try to access the `this` object that is injected into the top Python scope. +Try to access the `this.frame` and you will observe that the `getFrame()` method is selected to get the result heuristically. + +## 6. Next steps + +- Use GraalPy in a [clean-slate Java SE application](../graalpy-javase-guide/README.md) +- Use GraalPy with popular Java frameworks, such as [Spring Boot](../graalpy-spring-boot-guide/README.md) or [Micronaut](../graalpy-micronaut-guide/README.md) +- Install and use Python packages that rely on [native code](../graalpy-native-extensions-guide/README.md), e.g. for data science and machine learning +- Follow along how you can manually [install Python packages and files](../graalpy-custom-venv-guide/README.md) if the Maven plugin gives not enough control +- [Freeze](../graalpy-freeze-dependencies-guide/README.md) transitive Python dependencies for reproducible builds + + +- Learn more about the GraalPy [Maven plugin](https://www.graalvm.org/latest/reference-manual/python/Embedding-Build-Tools/) +- Learn more about the Polyglot API for [embedding languages](https://www.graalvm.org/latest/reference-manual/embed-languages/) +- Explore in depth with GraalPy [reference manual](https://www.graalvm.org/latest/reference-manual/python/) diff --git a/graalpy/graalpy-jython-guide/mvnw b/graalpy/graalpy-jython-guide/mvnw new file mode 100755 index 0000000..19529dd --- /dev/null +++ b/graalpy/graalpy-jython-guide/mvnw @@ -0,0 +1,259 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Apache Maven Wrapper startup batch script, version 3.3.2 +# +# Optional ENV vars +# ----------------- +# JAVA_HOME - location of a JDK home dir, required when download maven via java source +# MVNW_REPOURL - repo url base for downloading maven distribution +# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output +# ---------------------------------------------------------------------------- + +set -euf +[ "${MVNW_VERBOSE-}" != debug ] || set -x + +# OS specific support. +native_path() { printf %s\\n "$1"; } +case "$(uname)" in +CYGWIN* | MINGW*) + [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")" + native_path() { cygpath --path --windows "$1"; } + ;; +esac + +# set JAVACMD and JAVACCMD +set_java_home() { + # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched + if [ -n "${JAVA_HOME-}" ]; then + if [ -x "$JAVA_HOME/jre/sh/java" ]; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACCMD="$JAVA_HOME/jre/sh/javac" + else + JAVACMD="$JAVA_HOME/bin/java" + JAVACCMD="$JAVA_HOME/bin/javac" + + if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then + echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2 + echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2 + return 1 + fi + fi + else + JAVACMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v java + )" || : + JAVACCMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v javac + )" || : + + if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then + echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2 + return 1 + fi + fi +} + +# hash string like Java String::hashCode +hash_string() { + str="${1:-}" h=0 + while [ -n "$str" ]; do + char="${str%"${str#?}"}" + h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296)) + str="${str#?}" + done + printf %x\\n $h +} + +verbose() { :; } +[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; } + +die() { + printf %s\\n "$1" >&2 + exit 1 +} + +trim() { + # MWRAPPER-139: + # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds. + # Needed for removing poorly interpreted newline sequences when running in more + # exotic environments such as mingw bash on Windows. + printf "%s" "${1}" | tr -d '[:space:]' +} + +# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties +while IFS="=" read -r key value; do + case "${key-}" in + distributionUrl) distributionUrl=$(trim "${value-}") ;; + distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;; + esac +done <"${0%/*}/.mvn/wrapper/maven-wrapper.properties" +[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties" + +case "${distributionUrl##*/}" in +maven-mvnd-*bin.*) + MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ + case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in + *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;; + :Darwin*x86_64) distributionPlatform=darwin-amd64 ;; + :Darwin*arm64) distributionPlatform=darwin-aarch64 ;; + :Linux*x86_64*) distributionPlatform=linux-amd64 ;; + *) + echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2 + distributionPlatform=linux-amd64 + ;; + esac + distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip" + ;; +maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;; +*) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;; +esac + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}" +distributionUrlName="${distributionUrl##*/}" +distributionUrlNameMain="${distributionUrlName%.*}" +distributionUrlNameMain="${distributionUrlNameMain%-bin}" +MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}" +MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")" + +exec_maven() { + unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || : + exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD" +} + +if [ -d "$MAVEN_HOME" ]; then + verbose "found existing MAVEN_HOME at $MAVEN_HOME" + exec_maven "$@" +fi + +case "${distributionUrl-}" in +*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;; +*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;; +esac + +# prepare tmp dir +if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then + clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; } + trap clean HUP INT TERM EXIT +else + die "cannot create temp dir" +fi + +mkdir -p -- "${MAVEN_HOME%/*}" + +# Download and Install Apache Maven +verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +verbose "Downloading from: $distributionUrl" +verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +# select .zip or .tar.gz +if ! command -v unzip >/dev/null; then + distributionUrl="${distributionUrl%.zip}.tar.gz" + distributionUrlName="${distributionUrl##*/}" +fi + +# verbose opt +__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR='' +[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v + +# normalize http auth +case "${MVNW_PASSWORD:+has-password}" in +'') MVNW_USERNAME='' MVNW_PASSWORD='' ;; +has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;; +esac + +if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then + verbose "Found wget ... using wget" + wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl" +elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then + verbose "Found curl ... using curl" + curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl" +elif set_java_home; then + verbose "Falling back to use Java to download" + javaSource="$TMP_DOWNLOAD_DIR/Downloader.java" + targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName" + cat >"$javaSource" <<-END + public class Downloader extends java.net.Authenticator + { + protected java.net.PasswordAuthentication getPasswordAuthentication() + { + return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() ); + } + public static void main( String[] args ) throws Exception + { + setDefault( new Downloader() ); + java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() ); + } + } + END + # For Cygwin/MinGW, switch paths to Windows format before running javac and java + verbose " - Compiling Downloader.java ..." + "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java" + verbose " - Running Downloader.java ..." + "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")" +fi + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +if [ -n "${distributionSha256Sum-}" ]; then + distributionSha256Result=false + if [ "$MVN_CMD" = mvnd.sh ]; then + echo "Checksum validation is not supported for maven-mvnd." >&2 + echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + elif command -v sha256sum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c >/dev/null 2>&1; then + distributionSha256Result=true + fi + elif command -v shasum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then + distributionSha256Result=true + fi + else + echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2 + echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + fi + if [ $distributionSha256Result = false ]; then + echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2 + echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2 + exit 1 + fi +fi + +# unzip and move +if command -v unzip >/dev/null; then + unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip" +else + tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar" +fi +printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url" +mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME" + +clean || : +exec_maven "$@" diff --git a/graalpy/graalpy-jython-guide/mvnw.cmd b/graalpy/graalpy-jython-guide/mvnw.cmd new file mode 100644 index 0000000..b150b91 --- /dev/null +++ b/graalpy/graalpy-jython-guide/mvnw.cmd @@ -0,0 +1,149 @@ +<# : batch portion +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Apache Maven Wrapper startup batch script, version 3.3.2 +@REM +@REM Optional ENV vars +@REM MVNW_REPOURL - repo url base for downloading maven distribution +@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output +@REM ---------------------------------------------------------------------------- + +@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0) +@SET __MVNW_CMD__= +@SET __MVNW_ERROR__= +@SET __MVNW_PSMODULEP_SAVE=%PSModulePath% +@SET PSModulePath= +@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @( + IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B) +) +@SET PSModulePath=%__MVNW_PSMODULEP_SAVE% +@SET __MVNW_PSMODULEP_SAVE= +@SET __MVNW_ARG0_NAME__= +@SET MVNW_USERNAME= +@SET MVNW_PASSWORD= +@IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*) +@echo Cannot start maven from wrapper >&2 && exit /b 1 +@GOTO :EOF +: end batch / begin powershell #> + +$ErrorActionPreference = "Stop" +if ($env:MVNW_VERBOSE -eq "true") { + $VerbosePreference = "Continue" +} + +# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties +$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl +if (!$distributionUrl) { + Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" +} + +switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) { + "maven-mvnd-*" { + $USE_MVND = $true + $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip" + $MVN_CMD = "mvnd.cmd" + break + } + default { + $USE_MVND = $false + $MVN_CMD = $script -replace '^mvnw','mvn' + break + } +} + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +if ($env:MVNW_REPOURL) { + $MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" } + $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')" +} +$distributionUrlName = $distributionUrl -replace '^.*/','' +$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$','' +$MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain" +if ($env:MAVEN_USER_HOME) { + $MAVEN_HOME_PARENT = "$env:MAVEN_USER_HOME/wrapper/dists/$distributionUrlNameMain" +} +$MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' +$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME" + +if (Test-Path -Path "$MAVEN_HOME" -PathType Container) { + Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME" + Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" + exit $? +} + +if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) { + Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl" +} + +# prepare tmp dir +$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile +$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir" +$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null +trap { + if ($TMP_DOWNLOAD_DIR.Exists) { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } + } +} + +New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null + +# Download and Install Apache Maven +Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +Write-Verbose "Downloading from: $distributionUrl" +Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +$webclient = New-Object System.Net.WebClient +if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) { + $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD) +} +[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 +$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum +if ($distributionSha256Sum) { + if ($USE_MVND) { + Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." + } + Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash + if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) { + Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property." + } +} + +# unzip and move +Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null +Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null +try { + Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null +} catch { + if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) { + Write-Error "fail to move MAVEN_HOME" + } +} finally { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } +} + +Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" diff --git a/graalpy/graalpy-jython-guide/pom.xml b/graalpy/graalpy-jython-guide/pom.xml new file mode 100644 index 0000000..5a5a236 --- /dev/null +++ b/graalpy/graalpy-jython-guide/pom.xml @@ -0,0 +1,36 @@ + + + 4.0.0 + + org.example + jython + 1.0-SNAPSHOT + + jython + http://www.example.com + + + UTF-8 + 17 + + + + + org.python + jython-standalone + 2.7.4 + + + org.graalvm.polyglot + python + 24.1.0 + pom + + + org.graalvm.polyglot + polyglot + 24.1.0 + + + diff --git a/graalpy/graalpy-jython-guide/screenshot.png b/graalpy/graalpy-jython-guide/screenshot.png new file mode 100755 index 0000000000000000000000000000000000000000..430bb9b2731c2c8d6ff674b6593b8024b26cef00 GIT binary patch literal 9143 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 literal 0 HcmV?d00001 diff --git a/graalpy/graalpy-jython-guide/src/main/java/org/example/App.java b/graalpy/graalpy-jython-guide/src/main/java/org/example/App.java new file mode 100644 index 0000000..eb45142 --- /dev/null +++ b/graalpy/graalpy-jython-guide/src/main/java/org/example/App.java @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at https://opensource.org/license/UPL. + */ + +package org.example; + +import java.util.LinkedHashMap; + +public class App { + public static void main(String[] args) { + var interpreters = new LinkedHashMap(); + interpreters.put("Echo", new EchoInputCallback()); + interpreters.put("Jython", new JythonInputCallback()); + interpreters.put("GraalPy", new GraalPyInputCallback()); + Workspace.open(interpreters); + } +} diff --git a/graalpy/graalpy-jython-guide/src/main/java/org/example/EchoInputCallback.java b/graalpy/graalpy-jython-guide/src/main/java/org/example/EchoInputCallback.java new file mode 100644 index 0000000..7e01f62 --- /dev/null +++ b/graalpy/graalpy-jython-guide/src/main/java/org/example/EchoInputCallback.java @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at https://opensource.org/license/UPL. + */ + +package org.example; + +final class EchoInputCallback implements InputCallback { + @Override + public void setUp(Workspace workspace) { + } + + @Override + public String interpret(String code) { + return code; + } + + @Override + public void tearDown() { + } +} diff --git a/graalpy/graalpy-jython-guide/src/main/java/org/example/GraalPyInputCallback.java b/graalpy/graalpy-jython-guide/src/main/java/org/example/GraalPyInputCallback.java new file mode 100644 index 0000000..022cae0 --- /dev/null +++ b/graalpy/graalpy-jython-guide/src/main/java/org/example/GraalPyInputCallback.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at https://opensource.org/license/UPL. + */ + +package org.example; + +import org.graalvm.polyglot.*; + +final class GraalPyInputCallback implements InputCallback { + static Source GRAALPY_CODE = Source.create("python", "__import__('sys').version"); + static Engine engine = Engine.create("python"); + private Context python; + + @Override + public void setUp(Workspace workspace) { + this.python = Context.newBuilder("python") // ① + .engine(engine) // ② + .allowAllAccess(true).option("python.EmulateJython", "true") // ③ + .build(); + python.getBindings("python").putMember("this", workspace); // ④ + } + + @Override + public String interpret(String code) { + Value result; + if (code.isBlank()) { + result = python.eval(GRAALPY_CODE); + } else { + result = python.eval("python", code); // ① + } + if (result.isString()) { // ② + return code + "\n... " + result.asString(); + } + return code + "\n...(repr) " + result.toString(); + } + + @Override + public void tearDown() { + python.close(true); // ① + } +} diff --git a/graalpy/graalpy-jython-guide/src/main/java/org/example/InputCallback.java b/graalpy/graalpy-jython-guide/src/main/java/org/example/InputCallback.java new file mode 100644 index 0000000..e0ee46c --- /dev/null +++ b/graalpy/graalpy-jython-guide/src/main/java/org/example/InputCallback.java @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at https://opensource.org/license/UPL. + */ + +package org.example; + +interface InputCallback { + void setUp(Workspace workspace); + + String interpret(String code); + + void tearDown(); +} diff --git a/graalpy/graalpy-jython-guide/src/main/java/org/example/JythonInputCallback.java b/graalpy/graalpy-jython-guide/src/main/java/org/example/JythonInputCallback.java new file mode 100644 index 0000000..417807b --- /dev/null +++ b/graalpy/graalpy-jython-guide/src/main/java/org/example/JythonInputCallback.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at https://opensource.org/license/UPL. + */ + +package org.example; + +import org.python.core.*; +import org.python.util.PythonInterpreter; + +final class JythonInputCallback implements InputCallback { + static PyCode JYTHON_CODE = new PythonInterpreter().compile("__import__('sys').version"); // ① + private PythonInterpreter python; + + @Override + public void setUp(Workspace workspace) { + var globals = new PyDictionary(); + this.python = new PythonInterpreter(globals); // ② + globals.put(new PyString("this"), workspace); + } + + @Override + public String interpret(String code) { + try { + PyObject result; + if (code.isBlank()) { + result = python.eval(JYTHON_CODE); + } else { + result = python.eval(code); + } + if (result instanceof PyString strResult) { // ③ + return code + "\n... " + strResult.asString(); + } + return code + "\n...(repr) " + result.__repr__(); + } catch (PySyntaxError e) { + python.exec(code); // ④ + return ""; + } + } + + @Override + public void tearDown() { + python.close(); + } +} diff --git a/graalpy/graalpy-jython-guide/src/main/java/org/example/Workspace.java b/graalpy/graalpy-jython-guide/src/main/java/org/example/Workspace.java new file mode 100644 index 0000000..b07892e --- /dev/null +++ b/graalpy/graalpy-jython-guide/src/main/java/org/example/Workspace.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at https://opensource.org/license/UPL. + */ + +package org.example; + +import java.awt.BorderLayout; +import java.awt.TextField; +import java.awt.event.ItemEvent; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.util.Map; + +import javax.swing.JComboBox; +import javax.swing.JFrame; +import javax.swing.JLabel; + +public class Workspace { + private final JFrame privFrame; + + public JFrame getFrame() { + return privFrame; + } + + public static Workspace open(Map cb) { + var frame = new JFrame(); + var ws = new Workspace(frame); + + var combobox = new JComboBox<>(cb.keySet().toArray()); + combobox.addItemListener((e) -> { + var item = e.getItem(); + switch (e.getStateChange()) { + case ItemEvent.SELECTED: + cb.get(item).setUp(ws); + break; + case ItemEvent.DESELECTED: + cb.get(item).tearDown(); + } + }); + frame.add(combobox, BorderLayout.NORTH); + + var label = new JLabel(""); + frame.add(label, BorderLayout.CENTER); + + var input = new TextField(20); + input.addActionListener((e) -> { + var result = cb.get(combobox.getSelectedItem()).interpret(input.getText()).replace("\n", "
"); + input.setText(""); + label.setText(label.getText() + "
" + combobox.getSelectedItem() + ">>> " + result); + }); + frame.add(input, BorderLayout.SOUTH); + + frame.addWindowListener(new WindowAdapter() { + @Override + public void windowClosing(WindowEvent e) { + cb.get(combobox.getSelectedItem()).tearDown(); + System.exit(0); + } + }); + + frame.setSize(640, 480); + frame.setVisible(true); + cb.get(combobox.getSelectedItem()).setUp(ws); + return ws; + } + + private Workspace(JFrame frame) { + this.privFrame = frame; + } +} diff --git a/graalpy/graalpy-micronaut-guide/.gitignore b/graalpy/graalpy-micronaut-guide/.gitignore new file mode 100644 index 0000000..5a03bc3 --- /dev/null +++ b/graalpy/graalpy-micronaut-guide/.gitignore @@ -0,0 +1,15 @@ +Thumbs.db +.DS_Store +.gradle +build/ +target/ +out/ +.micronaut/ +.idea +*.iml +*.ipr +*.iws +.project +.settings +.classpath +.factorypath diff --git a/graalpy/graalpy-micronaut-guide/.mvn/wrapper/maven-wrapper.properties b/graalpy/graalpy-micronaut-guide/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..d58dfb7 --- /dev/null +++ b/graalpy/graalpy-micronaut-guide/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,19 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +wrapperVersion=3.3.2 +distributionType=only-script +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip diff --git a/graalpy/graalpy-micronaut-guide/README.md b/graalpy/graalpy-micronaut-guide/README.md new file mode 100644 index 0000000..ef8ffef --- /dev/null +++ b/graalpy/graalpy-micronaut-guide/README.md @@ -0,0 +1,491 @@ +## GraalPy Micronaut Guide + +## 1. Getting Started + +In this guide, we will use the Python library [vaderSentiment](https://github.com/cjhutto/vaderSentiment) from a Micronaut application written in Java. +![Screenshot of the app](screenshot.png) + +## 2. What you will need + +To complete this guide, you will need the following: + +* Some time on your hands +* A decent text editor or IDE +* A supported JDK[^1], preferably the latest [GraalVM JDK](https://graalvm.org/downloads/) + + [^1]: Oracle JDK 17 and OpenJDK 17 are supported with interpreter only. + GraalVM JDK 21, Oracle JDK 21, OpenJDK 21 and newer with [JIT compilation](https://www.graalvm.org/latest/reference-manual/embed-languages/#runtime-optimization-support). + Note: GraalVM for JDK 17 is **not supported**. + +## 3. Solution + +We recommend that you follow the instructions in the next sections and create the application step by step. +However, you can go right to the [completed example](./). + +## 4. Writing the application + +Create an application using the [Micronaut Command Line Interface](https://docs.micronaut.io/latest/guide/#cli) or with [Micronaut Launch](https://micronaut.io/launch/). +To make copying of the code snippets in this guide as smooth as possible, the application should have the base package `org.example`. +We also recommend to use Micronaut version 4.6.2 or newer. + +```bash +mn create-app org.example.demo \ +--build=maven \ +--lang=java \ +--test=junit +``` + +### 4.1. Application + +The generated Micronaut application will already contain the file _Application.java_, which is used when running the application via Maven or via deployment. +You can also run the main class directly within your IDE if it is configured correctly. + +`src/main/java/org/example/Application.java` +```java +package org.example; +import io.micronaut.runtime.Micronaut; + +public class Application { + + public static void main(String[] args) { + Micronaut.run(Application.class, args); + } +} +``` + +### 4.2 Dependency configuration + +Add the required dependencies for GraalPy in the dependency section of the POM. + +`pom.xml` +```xml + + org.graalvm.python + python + 24.1.0 + pom + + + org.graalvm.python + python-embedding + 24.1.0 + + + io.micronaut.views + micronaut-views-thymeleaf + +``` + +❶ The `python` dependency is a meta-package that transitively depends on all resources and libraries to run GraalPy. + +❷ Note that the `python` package is not a JAR - it is simply a `pom` that declares more dependencies. + +❸ The `python-embedding` dependency provides the APIs to manage and use GraalPy from Java. + +❹ The `thymeleaf` dependency is used to render the index page. + +### 4.3 Adding packages - graalpy-maven-plugin configuration + +Most Python packages are hosted on [PyPI](https://pypi.org) and can be installed via the `pip` tool. +The Python ecosystem has conventions about the filesystem layout of installed packages that need to be kept in mind when embedding into Java. +You can use the GraalPy plugin to manage Python packages for you. + +Also add the `graalpy-maven-plugin` configuration into the plugins section of the POM. + +`pom.xml` +```xml + + org.graalvm.python + graalpy-maven-plugin + 24.1.0 + + + + + vader-sentiment==3.2.1.1 + requests + + + + process-graalpy-resources + + + + +``` + +❶ The `packages` section lists all Python packages optionally with [requirement specifiers](https://pip.pypa.io/en/stable/reference/requirement-specifiers/). + +❷ Python packages and their versions can be specified as if used with pip. +Install and pin the `vader-sentiment` package to version `3.2.1.1`. + +❸ The `vader_sentiment` package does not declare `requests` as a dependency so it has to done so manually at this place. + +### 4.4 Creating a Python context + +GraalPy provides APIs to make setting up a context to load Python packages from Java as easy as possible. + +Create a Java class which will serve as a wrapper bean for the GraalPy context: + +`src/main/java/org/example/GraalPyContext.java` +```java +package org.example; + +import io.micronaut.context.annotation.Context; +import jakarta.annotation.PreDestroy; +import org.graalvm.python.embedding.utils.GraalPyResources; + +@Context // ① +public final class GraalPyContext { + + static final String PYTHON = "python"; + + private final org.graalvm.polyglot.Context context; + + public GraalPyContext() { + context = GraalPyResources.createContext(); // ② + context.initialize(PYTHON); // ③ + } + + org.graalvm.polyglot.Context get() { + return context; // ④ + } + + @PreDestroy + void close() { + try { + context.close(true); // ⑤ + } catch (Exception e) { + // ignore + } + } +} +``` + +❶ Eagerly initialize as a singleton bean. + +❷ The created GraalPy context will serve as a single access point to GraalPy for the whole application. + +❸ Initializing a GraalPy context isn't cheap, so we do so already at creation time to avoid delayed response time. + +❹ Return the GraalPy context. + +❺ Close the GraalPy context. + +### 4.5 Using a Python library from Java + +After reading the [vaderSentiment](https://github.com/cjhutto/vaderSentiment) docs, you can now write the Java interface that matches the Python type and function you want to call. + +GraalPy makes it easy to access Python objects via these interfaces. +Java method names are mapped directly to Python method names. +Return values and arguments are mapped according to a set of [generic rules](https://www.graalvm.org/latest/reference-manual/python/Modern-Python-on-JVM/#java-to-python-types-automatic-conversion) as well as the [Target type mapping](https://www.graalvm.org/truffle/javadoc/org/graalvm/polyglot/Value.html#target-type-mapping-heading). +The names of the interfaces can be chosen freely, but it makes sense to base them on the Python types, as we do below. + +Your application will call the Python function `polarity_scores(text)` from the `vader_sentiment.SentimentIntensityAnalyzer` Python class. + +In oder to do so, create a Java interface with a method matching that function: + +`src/main/java/org/example/SentimentIntensityAnalyzer.java` +```java +package org.example; + +import java.util.Map; + +public interface SentimentIntensityAnalyzer { + Map polarity_scores(String text); // ① +} +``` + +❶ The Java method to call into `SentimentIntensityAnalyzer.polarity_scores(text)`. +The Python return value is a `dict` and can be directly converted to a Java Map on the fly. +The same applies to the argument, which is a Python String and therefore also a String on Java side. + +Using this Java interface and the GraalPy context, you can now construct a bean which calls the `SentimentIntensityAnalyzer.polarity_scores(text)` Python function: + +`src/main/java/org/example/SentimentAnalysis.java` +```java +package org.example; + +import io.micronaut.context.annotation.Bean; +import org.graalvm.polyglot.Value; +import java.util.Map; +import static org.example.GraalPyContext.PYTHON; + +@Bean +public class SentimentAnalysis { + + private final SentimentIntensityAnalyzer sentimentIntensityAnalyzer; + + public SentimentAnalysis(GraalPyContext context) { + Value value = context.get().eval(PYTHON, """ + from vader_sentiment.vader_sentiment import SentimentIntensityAnalyzer + SentimentIntensityAnalyzer() # ① + """); + sentimentIntensityAnalyzer = value.as(SentimentIntensityAnalyzer.class); // ② + } + + public Map getPolarityScores(String text) { + return sentimentIntensityAnalyzer.polarity_scores(text); // ③ + } +} +``` + +❶ The executed Python snippet imports the `vader_sentiment.SentimentIntensityAnalyzer` Python class into the GraalPy context and returns a new instance of it. +Note that the GraalPy context preserves its state and an eventual subsequent `eval` call accessing `SentimentIntensityAnalyzer` would not require an import anymore. + +❷ Map the obtained `org.graalvm.polyglot.Value` to the `SentimentIntensityAnalyzer` type. + +❸ Return the `sentimentIntensityAnalyzer` object. + +### 4.6 Index page + +The application will have a simple chat-like view, which takes text as input and return its sentiment value in form of an emoticon. + +Create a html file, which will be later on rendered with the help of the `thymeleaf` library: + +`resources/views/index.html` +```html + + + + Demo App + + + + + + + +
+
+
+

Sentiment analysis demo

+

Input messages and the application will visualize the sentiment.

+
+
+
+
+
+ + +
+
+
+
+
+
+
+ + +``` + +### 4.7 Controller + +To create a microservice that provides a simple sentiment analysis, you also need a controller: + +`src/main/java/org/example/SentimentAnalysisController.java` +```java +package org.example; + +import io.micronaut.http.annotation.*; +import io.micronaut.scheduling.TaskExecutors; +import io.micronaut.scheduling.annotation.ExecuteOn; +import io.micronaut.views.View; +import java.util.Map; + +@Controller // ① +public class SentimentAnalysisController { + + private final SentimentAnalysis sentimentAnalysis; + + SentimentAnalysisController(SentimentAnalysis sentimentAnalysis) { // ② + this.sentimentAnalysis = sentimentAnalysis; + } + + @Get // ③ + @View("index") // ④ + public void index() { + + } + + @Get(value = "/analyze") // ⑤ + @ExecuteOn(TaskExecutors.BLOCKING) // ⑥ + public Map answer(String text) { + return sentimentAnalysis.getPolarityScores(text); // ⑦ + } +} +``` + +❶ The class is defined as a controller with the [@Controller](https://docs.micronaut.io/latest/api/io/micronaut/http/annotation/Controller.html) annotation. + +❷ Use constructor injection to inject a bean of the type `SentimentAnalysis`. + +❸ The [@Get](https://docs.micronaut.io/latest/api/io/micronaut/http/annotation/Get.html) annotation maps the `index` method to an HTTP GET request on "/". + +❹ The `thymeleaf` [@View](https://micronaut-projects.github.io/micronaut-views/latest/api/io/micronaut/views/View.html) annotation indicates that _resources/views/index.html_ should be rendered on a HTTP GET request. + +❺ The [@Get](https://docs.micronaut.io/latest/api/io/micronaut/http/annotation/Get.html) annotation maps the path `/analyze` to a HTTP GET request. + +❻ Since the computation is somewhat CPU intensive, we offload it from the Netty event loop using `@ExecuteOn(TaskExecutors.BLOCKING)`. + +❼ Use the `SentimentAnalysis` bean to call the `SentimentIntensityAnalyzer.polarity_scores(text)` Python function. + +### 4.8 Test + +Create a test to verify that when you make a GET request to `/analyze` you get the expected sentiment score response: + +`src/test/java/org/example/SentimentAnalysisControllerTest.java` +```java +package org.example; + +import io.micronaut.http.client.HttpClient; +import io.micronaut.http.client.annotation.Client; +import io.micronaut.test.extensions.junit5.annotation.MicronautTest; +import org.junit.jupiter.api.Test; +import java.util.Map; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@MicronautTest // ① +class SentimentAnalysisControllerTest { + @Test + void testAnalyzeResponse(@Client("/") HttpClient client) { // ② + Map response = client.toBlocking().retrieve("/analyze?text=happy", Map.class); // ③ + assertTrue(response.get("compound") > 0.1); + response = client.toBlocking().retrieve("/analyze?text=sad", Map.class); + assertTrue(response.get("compound") < -0.1); + } +} +``` + +❶ Annotate the class with `@MicronautTest` so the Micronaut framework will initialize the application context and the embedded server. +[More info](https://micronaut-projects.github.io/micronaut-test/latest/guide/). + +❷ Inject the `HttpClient` bean and point it to the embedded server. + +❸ Creating HTTP Requests is easy thanks to the Micronaut framework fluid API. + +## 5. Testing the Application + +To run the tests: + +```bash +./mvnw test +``` + +## 6. Running the Application + +To run the application: + +```bash +./mvnw mn:run +``` + +This will start the application on port 8080. + +## 7. GraalVM Native Executable + +### 7.1. Native Executable metadata + +The [GraalVM](https://www.graalvm.org/) Native Image compilation requires metadata to properly run code that uses [dynamic proxies](https://www.graalvm.org/latest/reference-manual/native-image/metadata/#dynamic-proxy). + +For the case that also a native executable has to be generated, create a proxy configuration file: + +`src/main/resources/META-INF/native-image/proxy-config.json` +```json +[ + ["org.example.SentimentIntensityAnalyzer"] +] +``` + +### 7.2. Generate a Micronaut Application Native Executable with GraalVM + +We will use GraalVM, the polyglot embeddable virtual machine, to generate a native executable of our Micronaut application. + +Compiling native executables ahead of time with GraalVM improves startup time and reduces the memory footprint of JVM-based applications. + +Make sure that the `JAVA_HOME` environmental variable is set to the location of a GraalVM installation. +We recommend using a GraalVM 24.1, which works fine with Micronaut 4.6.0. + +To generate a native executable using Maven, run: + +```bash +./mvnw package -Dpackaging=native-image +``` + +The native executable is created in the `target` directory and can be run with: + +```bash +./target/demo +``` + +## 8. Next steps + +- [Micronaut GraalPy plugin guide](https://guides.micronaut.io/latest/micronaut-graalpy.html) +to read on the Micronaut GraalPy plugin that can remove some of the boilerplate code. +- Use GraalPy in a [Java SE application](../graalpy-javase-guide/README.md) +- Use GraalPy with [Spring Boot](../graalpy-spring-boot-guide/README.md) +- Install and use Python packages that rely on [native code](../graalpy-native-extensions-guide/README.md), e.g. for data science and machine learning +- Follow along how you can manually [install Python packages and files](../graalpy-custom-venv-guide/README.md) if the Maven plugin gives not enough control +- [Freeze](../graalpy-freeze-dependencies-guide/README.md) transitive Python dependencies for reproducible builds +- [Migrate from Jython](../graalpy-jython-guide/README.md) to GraalPy + + +- Learn more about GraalPy [Micronaut plugin](https://guides.micronaut.io/latest/micronaut-graalpy.html) +- Learn more about the GraalPy [Maven plugin](https://www.graalvm.org/latest/reference-manual/python/Embedding-Build-Tools/) +- Learn more about the Polyglot API for [embedding languages](https://www.graalvm.org/latest/reference-manual/embed-languages/) +- Explore in depth with GraalPy [reference manual](https://www.graalvm.org/latest/reference-manual/python/) \ No newline at end of file diff --git a/graalpy/graalpy-micronaut-guide/aot-jar.properties b/graalpy/graalpy-micronaut-guide/aot-jar.properties new file mode 100644 index 0000000..3820d55 --- /dev/null +++ b/graalpy/graalpy-micronaut-guide/aot-jar.properties @@ -0,0 +1,37 @@ +# AOT configuration properties for jar packaging +# Please review carefully the optimizations enabled below +# Check https://micronaut-projects.github.io/micronaut-aot/latest/guide/ for more details + +# Caches environment property values: environment properties will be deemed immutable after application startup. +cached.environment.enabled=true + +# Precomputes Micronaut configuration property keys from the current environment variables +precompute.environment.properties.enabled=true + +# Replaces logback.xml with a pure Java configuration +logback.xml.to.java.enabled=true + +# Converts YAML configuration files to Java configuration +yaml.to.java.config.enabled=true + +# Scans for service types ahead-of-time, avoiding classpath scanning at startup +serviceloading.jit.enabled=true + +# Scans reactive types at build time instead of runtime +scan.reactive.types.enabled=true + +# Deduces the environment at build time instead of runtime +deduce.environment.enabled=true + +# Checks for the existence of some types at build time instead of runtime +known.missing.types.enabled=true + +# Precomputes property sources at build time +sealed.property.source.enabled=true + +# The list of service types to be scanned (comma separated) +service.types=io.micronaut.context.env.PropertySourceLoader,io.micronaut.inject.BeanConfiguration,io.micronaut.inject.BeanDefinitionReference,io.micronaut.http.HttpRequestFactory,io.micronaut.http.HttpResponseFactory,io.micronaut.core.beans.BeanIntrospectionReference,io.micronaut.core.convert.TypeConverterRegistrar,io.micronaut.context.env.PropertyExpressionResolver + +# A list of types that the AOT analyzer needs to check for existence (comma separated) +known.missing.types.list=io.reactivex.Observable,reactor.core.publisher.Flux,kotlinx.coroutines.flow.Flow,io.reactivex.rxjava3.core.Flowable,io.reactivex.rxjava3.core.Observable,io.reactivex.Single,reactor.core.publisher.Mono,io.reactivex.Maybe,io.reactivex.rxjava3.core.Single,io.reactivex.rxjava3.core.Maybe,io.reactivex.Completable,io.reactivex.rxjava3.core.Completable,io.methvin.watchservice.MacOSXListeningWatchService,io.micronaut.core.async.publisher.CompletableFuturePublisher,io.micronaut.core.async.publisher.Publishers.JustPublisher,io.micronaut.core.async.subscriber.Completable + diff --git a/graalpy/graalpy-micronaut-guide/micronaut-cli.yml b/graalpy/graalpy-micronaut-guide/micronaut-cli.yml new file mode 100644 index 0000000..edf1bc0 --- /dev/null +++ b/graalpy/graalpy-micronaut-guide/micronaut-cli.yml @@ -0,0 +1,6 @@ +applicationType: default +defaultPackage: org.example +testFramework: junit +sourceLanguage: java +buildTool: maven +features: [app-name, http-client-test, java, java-application, junit, logback, maven, maven-enforcer-plugin, micronaut-aot, micronaut-http-validation, netty-server, properties, readme, serialization-jackson, shade, static-resources] diff --git a/graalpy/graalpy-micronaut-guide/mvnw b/graalpy/graalpy-micronaut-guide/mvnw new file mode 100755 index 0000000..19529dd --- /dev/null +++ b/graalpy/graalpy-micronaut-guide/mvnw @@ -0,0 +1,259 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Apache Maven Wrapper startup batch script, version 3.3.2 +# +# Optional ENV vars +# ----------------- +# JAVA_HOME - location of a JDK home dir, required when download maven via java source +# MVNW_REPOURL - repo url base for downloading maven distribution +# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output +# ---------------------------------------------------------------------------- + +set -euf +[ "${MVNW_VERBOSE-}" != debug ] || set -x + +# OS specific support. +native_path() { printf %s\\n "$1"; } +case "$(uname)" in +CYGWIN* | MINGW*) + [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")" + native_path() { cygpath --path --windows "$1"; } + ;; +esac + +# set JAVACMD and JAVACCMD +set_java_home() { + # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched + if [ -n "${JAVA_HOME-}" ]; then + if [ -x "$JAVA_HOME/jre/sh/java" ]; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACCMD="$JAVA_HOME/jre/sh/javac" + else + JAVACMD="$JAVA_HOME/bin/java" + JAVACCMD="$JAVA_HOME/bin/javac" + + if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then + echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2 + echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2 + return 1 + fi + fi + else + JAVACMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v java + )" || : + JAVACCMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v javac + )" || : + + if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then + echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2 + return 1 + fi + fi +} + +# hash string like Java String::hashCode +hash_string() { + str="${1:-}" h=0 + while [ -n "$str" ]; do + char="${str%"${str#?}"}" + h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296)) + str="${str#?}" + done + printf %x\\n $h +} + +verbose() { :; } +[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; } + +die() { + printf %s\\n "$1" >&2 + exit 1 +} + +trim() { + # MWRAPPER-139: + # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds. + # Needed for removing poorly interpreted newline sequences when running in more + # exotic environments such as mingw bash on Windows. + printf "%s" "${1}" | tr -d '[:space:]' +} + +# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties +while IFS="=" read -r key value; do + case "${key-}" in + distributionUrl) distributionUrl=$(trim "${value-}") ;; + distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;; + esac +done <"${0%/*}/.mvn/wrapper/maven-wrapper.properties" +[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties" + +case "${distributionUrl##*/}" in +maven-mvnd-*bin.*) + MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ + case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in + *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;; + :Darwin*x86_64) distributionPlatform=darwin-amd64 ;; + :Darwin*arm64) distributionPlatform=darwin-aarch64 ;; + :Linux*x86_64*) distributionPlatform=linux-amd64 ;; + *) + echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2 + distributionPlatform=linux-amd64 + ;; + esac + distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip" + ;; +maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;; +*) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;; +esac + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}" +distributionUrlName="${distributionUrl##*/}" +distributionUrlNameMain="${distributionUrlName%.*}" +distributionUrlNameMain="${distributionUrlNameMain%-bin}" +MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}" +MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")" + +exec_maven() { + unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || : + exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD" +} + +if [ -d "$MAVEN_HOME" ]; then + verbose "found existing MAVEN_HOME at $MAVEN_HOME" + exec_maven "$@" +fi + +case "${distributionUrl-}" in +*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;; +*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;; +esac + +# prepare tmp dir +if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then + clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; } + trap clean HUP INT TERM EXIT +else + die "cannot create temp dir" +fi + +mkdir -p -- "${MAVEN_HOME%/*}" + +# Download and Install Apache Maven +verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +verbose "Downloading from: $distributionUrl" +verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +# select .zip or .tar.gz +if ! command -v unzip >/dev/null; then + distributionUrl="${distributionUrl%.zip}.tar.gz" + distributionUrlName="${distributionUrl##*/}" +fi + +# verbose opt +__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR='' +[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v + +# normalize http auth +case "${MVNW_PASSWORD:+has-password}" in +'') MVNW_USERNAME='' MVNW_PASSWORD='' ;; +has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;; +esac + +if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then + verbose "Found wget ... using wget" + wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl" +elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then + verbose "Found curl ... using curl" + curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl" +elif set_java_home; then + verbose "Falling back to use Java to download" + javaSource="$TMP_DOWNLOAD_DIR/Downloader.java" + targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName" + cat >"$javaSource" <<-END + public class Downloader extends java.net.Authenticator + { + protected java.net.PasswordAuthentication getPasswordAuthentication() + { + return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() ); + } + public static void main( String[] args ) throws Exception + { + setDefault( new Downloader() ); + java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() ); + } + } + END + # For Cygwin/MinGW, switch paths to Windows format before running javac and java + verbose " - Compiling Downloader.java ..." + "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java" + verbose " - Running Downloader.java ..." + "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")" +fi + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +if [ -n "${distributionSha256Sum-}" ]; then + distributionSha256Result=false + if [ "$MVN_CMD" = mvnd.sh ]; then + echo "Checksum validation is not supported for maven-mvnd." >&2 + echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + elif command -v sha256sum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c >/dev/null 2>&1; then + distributionSha256Result=true + fi + elif command -v shasum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then + distributionSha256Result=true + fi + else + echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2 + echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + fi + if [ $distributionSha256Result = false ]; then + echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2 + echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2 + exit 1 + fi +fi + +# unzip and move +if command -v unzip >/dev/null; then + unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip" +else + tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar" +fi +printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url" +mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME" + +clean || : +exec_maven "$@" diff --git a/graalpy/graalpy-micronaut-guide/mvnw.cmd b/graalpy/graalpy-micronaut-guide/mvnw.cmd new file mode 100644 index 0000000..b150b91 --- /dev/null +++ b/graalpy/graalpy-micronaut-guide/mvnw.cmd @@ -0,0 +1,149 @@ +<# : batch portion +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Apache Maven Wrapper startup batch script, version 3.3.2 +@REM +@REM Optional ENV vars +@REM MVNW_REPOURL - repo url base for downloading maven distribution +@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output +@REM ---------------------------------------------------------------------------- + +@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0) +@SET __MVNW_CMD__= +@SET __MVNW_ERROR__= +@SET __MVNW_PSMODULEP_SAVE=%PSModulePath% +@SET PSModulePath= +@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @( + IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B) +) +@SET PSModulePath=%__MVNW_PSMODULEP_SAVE% +@SET __MVNW_PSMODULEP_SAVE= +@SET __MVNW_ARG0_NAME__= +@SET MVNW_USERNAME= +@SET MVNW_PASSWORD= +@IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*) +@echo Cannot start maven from wrapper >&2 && exit /b 1 +@GOTO :EOF +: end batch / begin powershell #> + +$ErrorActionPreference = "Stop" +if ($env:MVNW_VERBOSE -eq "true") { + $VerbosePreference = "Continue" +} + +# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties +$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl +if (!$distributionUrl) { + Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" +} + +switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) { + "maven-mvnd-*" { + $USE_MVND = $true + $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip" + $MVN_CMD = "mvnd.cmd" + break + } + default { + $USE_MVND = $false + $MVN_CMD = $script -replace '^mvnw','mvn' + break + } +} + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +if ($env:MVNW_REPOURL) { + $MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" } + $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')" +} +$distributionUrlName = $distributionUrl -replace '^.*/','' +$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$','' +$MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain" +if ($env:MAVEN_USER_HOME) { + $MAVEN_HOME_PARENT = "$env:MAVEN_USER_HOME/wrapper/dists/$distributionUrlNameMain" +} +$MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' +$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME" + +if (Test-Path -Path "$MAVEN_HOME" -PathType Container) { + Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME" + Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" + exit $? +} + +if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) { + Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl" +} + +# prepare tmp dir +$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile +$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir" +$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null +trap { + if ($TMP_DOWNLOAD_DIR.Exists) { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } + } +} + +New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null + +# Download and Install Apache Maven +Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +Write-Verbose "Downloading from: $distributionUrl" +Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +$webclient = New-Object System.Net.WebClient +if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) { + $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD) +} +[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 +$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum +if ($distributionSha256Sum) { + if ($USE_MVND) { + Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." + } + Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash + if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) { + Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property." + } +} + +# unzip and move +Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null +Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null +try { + Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null +} catch { + if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) { + Write-Error "fail to move MAVEN_HOME" + } +} finally { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } +} + +Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" diff --git a/graalpy/graalpy-micronaut-guide/pom.xml b/graalpy/graalpy-micronaut-guide/pom.xml new file mode 100644 index 0000000..b973a99 --- /dev/null +++ b/graalpy/graalpy-micronaut-guide/pom.xml @@ -0,0 +1,154 @@ + + + 4.0.0 + org.example + graalpy-micronaut + 0.1 + ${packaging} + + + io.micronaut.platform + micronaut-parent + 4.6.0 + + + jar + 17 + 17 + 4.6.0 + netty + false + org.example.aot.generated + org.example.Application + + + + + central + https://repo.maven.apache.org/maven2 + + + + + + + org.graalvm.python + python + 24.1.0 + pom + + + org.graalvm.python + python-embedding + 24.1.0 + + + io.micronaut.views + micronaut-views-thymeleaf + + + + io.micronaut + micronaut-http-server-netty + compile + + + io.micronaut.serde + micronaut-serde-jackson + compile + + + ch.qos.logback + logback-classic + runtime + + + io.micronaut + micronaut-http-client + test + + + io.micronaut.test + micronaut-test-junit5 + test + + + org.junit.jupiter + junit-jupiter-api + test + + + org.junit.jupiter + junit-jupiter-engine + test + + + + + + + org.graalvm.python + graalpy-maven-plugin + 24.1.0 + + + + + vader-sentiment==3.2.1.1 + requests + + + + process-graalpy-resources + + + + + + + io.micronaut.maven + micronaut-maven-plugin + + aot-${packaging}.properties + + + + org.apache.maven.plugins + maven-enforcer-plugin + + + org.apache.maven.plugins + maven-compiler-plugin + + + + + + + io.micronaut + micronaut-http-validation + ${micronaut.core.version} + + + io.micronaut.serde + micronaut-serde-processor + ${micronaut.serialization.version} + + + io.micronaut + micronaut-inject + + + + + + -Amicronaut.processing.group=org.example + -Amicronaut.processing.module=demo + + + + + + + diff --git a/graalpy/graalpy-micronaut-guide/screenshot.png b/graalpy/graalpy-micronaut-guide/screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..ecf95e96734dc846ed0265097748fa5cdfdbec4c GIT binary patch literal 236583 zcmaHSbzEG%)-GDC#hv2CiVRTPrMSDhOR?fMP~07gyE_zjcPQ@eI@sV0mvip<&b|M9 z@BU>bne6QBWM$>avsN-;it-YlkO`5YprAfUNq$#`f`Si+g8G1q_~BnU1({!#|l|O!xaQ>z(LbN7X%_^lt(_fh2wBTE`SK7KY$a40!=3EQBM~{K^Kf;8D`{%4v61j;I4|_ z`N+LNUmq4@CcO5+FCICBauP-e%({0hw#HDByMk7mA4`J{lOrCoL?Z~qMB8x=t;~%= zG%gjO+(pQ&=&~~ZXye4C)u$L(fmNm6H6mM#8>@TVpushuR;_CohEG{{bBV>4u^Y z$6LIJEc6qLF8PtFFgY)Yv0fWdpP?El9FK zkW*U$o!ppGT#F$ZVOjsBcF9MEsb32A%6JsdPDdH_Q=!X2`?<@fjs3B+p0 z9Q+p~3{vcmF*hmTYhjGNF4X$#a{l2&Fz$%>-mhZx!vN-31Q1o7Qm>JH*cOs0+}q`( ze-g^>M1b1L8|abzj<+vRw)t1wM~{N?Pg@K0!Qb=?iV;A!=J>`^;1LKOz%&J>RW%D-tYP)V*d|Raz7|D^ zOEjRUf_mBr%#ikw{t)20Qv{-V%u8599aa;Ub->eazQ<>ZQt2LCbhn86^j!1{hZJZYnGhco@7+V;f z9{C!CGlZxuL@uOBzlbf?X4H$``PonUe!iEe-s0$ifr=P}c_H|exS_;5k<93%U(k^x zjN`Ds5RZrihQg)#k4uckCP~8A!Iui(kxvXu?eWcot%;LRbPU_@Ey1-8)y~UT z`Y`&v@2|TvUs{m7c*HdPk+L(fcGQJ9(cgq4v0I*%piUtKWotcH$|Gh=`b%n_aBZHu$_^cOVoRb+BG0JR zDE}ytnVFf28QMO+ng8|YeV0+Mq$xTq6^*6P)=JYg2X<_M#QI2IB^&23YHqxu?02 zljIWym*^)3ur$~loPGjJ$o7dQiZ;qSii)s}ZPMn}GM z4L3?AU13$6SMGFy!dlC!w!!Jo=$|`F7fY~>jdhzPjkUsj)to@tdSSq^=1GmqwR7_E z*sl3o5`R_wMqZJ^@4`KRX<0+Y6H1LyyN+)xwFzY z5`z62^;8Ug7PpSNB^)8v;&FN#=KWCOUnyMC;~Y@=;rs(^uy}A!Fhw6YxcK99q;up; zq*J6i5%ISZ_9B93e3VEHB2rFY`Ng7d#~!yXw;Y~bVg6E~Vg05NQXwN2D)h5(vmQm@ zqWcM2^N;ni_4DSH=C22X2N(z6#%(4*nz4-dxXZXUjFZ~!O@HcS>vS!bEtgFSZ3Y^@ zHkLKCTF5orHDuZ9{qUIxw(P4JX;?P=xs2Q3(lF6j`KQA)WTj;p>5OB^C7L4g8lUR3 z8vjXAM>zj%=Wyo&BsU;hw7HKjL_8!Ve?p=-TA$_V?8rX4D%vA@^)raaiYJI?;-|Hv z=eh0axJ#9DCr2<3wdMF3i=2sm%Tmjw9!Kk%Q(=pEGn4ZVm)Dbn3u@Q$pQkRIZVGPI zPF*8^jlX|OW}fsw=X>Ed_xgPGaCCg2b=0zX0|0nc>sIUO^P5x-UJuz3Juj`!rSEnO zw2g&aBwW-l7mqFWr|jySKMzlh#~e1Tg9v7WQ~TB?ypKUgfIGpxx2*k;mwh@xbN!S>;!P2Vj)_}Gu}8fcJ7Kn1^DMe9Y7yKRR2n!C zq!!4J-1G@Ar0IJOMlMD<#f6ymcVtSUd?9RJYqhyo%kra<=@64pmT8tG76p?`V~hT* zsQdxB$W@$srVT+~t(SGUQ#dIRn>=k}ZDYBe=K{ZiBq_a-`~(YX1KDW>i~_d8cx5b= zref4cl+R7(x^8JU+EOb~SL(aP(Sm})K#`5We5`qDCu$N&ngW_akn~$`*pBMY53EZB zY6P#kxNeO1@KZ5OX^Ug`2i1FmW4DvKsmlrbnGIjdIafK`_}Ub@F;x-u_XGEAM?JEM zlG-TUm;rRv0tR$~{9RflbmKUUa;#}=F3QEj@~RXkX=mIX6MMdnzumo{m6A&u49m8ME#xxJn+Hjvt&aZWjosza31vb0u*9 z9Ahw5AFNndHm}DUzsSPkvpCcpU0ZAEn);d10e|k{l{y z$8@XRY=zM&SyN(i^?TqH4@keCq)+>kfgbQ&7udGfTP@#J_3YwcbubfcW;e#cM8kx~ z)K%N-HZYk)NFSjgppR2qX2kB}a(vQcLtVvfaG~63#b{P(`eU*6(naocVi92QR!Gsd z<5>ta^GOn(-zmE%awn>?ycK`Pq+Vpv@xUjLrbqSsky=OboNKs>Lg_F zq0+n!a#we5;D+etd>*;JtiG$SQa@(6Sf~9*U3s;Q_s9J{KeSic$=PY+-057(IlLE@ zS2jpy)6E391X+NMgmpn+L{%rwBmVkBh_HzLmeZWmmA#P7g(HNcsnNaRGtZfW`w&I~ zWs*cg3=^+7cjMO5*454~EX{YCCwcQs?=_xU@I~pK%HoRg3Xgn9rhpsu(RgY7c>U0n zM>en1#*Oz4{9w>vgdHByiry;P>&4S^E^1SBu*^kD8k3i<-SOvDkF#)4Qi29-RaVPj zr|XsH9(a-niPLUfwr$xZ!ma$!b>m#aE&Rf}llPEhTmRSx{Jf9$kwo>)=tjaPMQ3q3 z1R8CXbb!D0`S_ILxb~FO2Q`)D!>US&0Ru}w_{1HIcn?Ak z3l`=5i}wGwls_xKdnFxNmYV$)=wj*|~336!YGeWIw@L(*4 zTWU2p3;`=#MJs9AJU>iyx~DL|P(N&FE6i{7sh^m_9|S7^1Vx8`NetgJ98MXfF@6iMadcB#y| zxVT7Bg$qSLO&5AdCbJgha@JQt1_Y8p-LIkVhJ>6Q`Yn6Zz#T_cp0qd1_Hf)m9j*8opvwCjrjd!2WvI)1u1!3~3u0R&i~c zh{4)x_Nal`m-dz8M%;lbXCF}~k? zwdowXTXw-ngnoD84c?6OWfCsL&*)e_1Y(rVz``uD&=81t_A&X~^k0|ft6U6EJ2pj=n3m-KJ*8wZ`>cBbi$9(&W&y1v-EQ%73nxa06B+XintwAP6`)(`u zyy~3`SgSqPSHu!Nt%=#{YV}b`_Jtn%0VSDlty2cRwte&l&z&&mO*{c*zFiG`)u6bh zthd^0CE!cn{r3pbkp9udTTgzGw)XapR?W|Hq;Hd!{5>#cma=0+i}BL0kX^+1UG2aA zHX%N5f0;l}yFPCwX4Zt2RUb?(4cP(J-g9U_?w+7o+HWwgx<@)ujx)F$LKzFq)V{p)Zpo~ zdPY&6J0&~sb)f&;35Hbx|~uKkXxtC9vYn`@YLC`Q$~yL%k@gIP`FIsKw?!1G#T>2$Y@dPkZ+q z(4ilvZn?A3y^9x?1RA)y0Ja|zdjnqX4TfnPr$T-%?596L01GxHt>tJZMMm2$4vUjcTLy<_*2QE)&vJrRTLDsIsQ13 zd)p<4p5oiWW7y0DZq$FAEX4;cJ%S3ut|Y)<_r}dG_s3){r)LD`$1BJX7;i=H>;>ig z4dyZ?B^&Zgdbp|Znmq#E)O%ZdJgDlj<8_2s03l||zMx59CDtvVBYUB$$wpBGc$tue0D4397x_>`zyAuOyDOJJq|}fc-8bUaYV^C@iNCh*NK!vawhL2Ttc47Vc z1REm;KI8r|2V^hQu&ebqT zRhHV4kID7r=p4EvRk9K2V@&QJSM}Tf><`61j>R1{j~Yy24F{B3aE$SZ-_ zLPZMd6m)-eWQmunHh}(n)a>6@{p#@Hq^F1TnE)Dc%08514-Yir3cobYsL0!>G|B$ zRwM7$Ibi%A&8(nI0W^Bw$;96vsP`BxVRt@d|G2`d{Rz}cI5@C+*ae~5d0xe^wRczr z!{5;q%j|`iAo?=g&i!Xg1UWHiUSjt4yR{eoFJt{QgSvOfJrS!8Jw$%s{zl-+p|@(zdK6vEKWh zQ?%-?m)C6&03_)CHt?*l0layHRafwaINe8hL-uZ(yrTO}V8ICXmLI`*Ckk_$hFg0d zd!E#Rwl?+&cdp>0oDb?YIyz>VCyk6YX=<~2^yP#aqt;;TTJv>60yg*9xrGJE^;pl+ zs;ZeTYpe_M4N=wsMBBN~${eyahBb!b>0KR{DbuLyN}SE|Y3l6d><;PFB{kc9W69OA zW-=+G2&5CqK`6Lmmr9g-78b9nu8*Tkb;siAJM(^8zV1O}IsmRkj0YtbyqP6(CP_cI ze+YG7w&1M41fPQ~5}_-Y(@^GKW!S{Ir-RH`njrdysx z+H5xtXicX3miOB2%|rk~8_N|oUDa|L5M79;=_V@6M5^`az7+#$Nhh)x15QuPPV51( zKCYV@!3$`@dUK#W?b0&1bJnKJ7cywfe+WlEVVDmZJmUYDR#e++)5Wwu#I~~>vX@qs zC--~`D3oT5oS+LPS$r${mY=5?^YZlaQ;kE>dmDk^xv>GMte0FfPX4*ARLKaJ1 zuE^HW@CY5sP3mg3Y07z{3^KiN+pULN8!Atx^3)ChnqNSjoT9kEI!(JyusOFwHtfWG z(9KP{qa1$RKaGQAjsa5l5nkTay22(Ez~}bYT&bq6j)6_`fI0fdT72ZIsVMTx8A!eBo9Z1XrkO!Y~ZC# z;tlpKB~_wfcVRy%6Jd-nlB*b^w23D`DFQx}YuaAW%voH;$iMjhX{pr`c(82L z#_FQsT=&gC>pFyR*pP_rP#p2A`~Q=mu7#iy*3?Qjjhl1b%`PI81uOJF%&VtMAmM~} z;bro=`L|!6*f^~r4UNqG1SE1~)}V&r|AYR&-;xgTd!$l7zel^*Wg6p6PSyOZRK7F3 zy~*?CY0_)9Q0f)4>#`Sx)p|*?HJvuo*Fxr>sv`@7;*(;q*JeuD{2BI<_}lj~o4GRg zMZHc;pVz3S{gcz`WYIhReul8N*4B7Tr+|Ozs$`BcVt0N&vBCxuh|%YL8lHTq1FP$Z zr!O=P(*wFFTpC;oXO$ZR|FI6TM5w@>gN6xKaN|v(QAaK&0lT&7ZANKVELP{UXg&1- zoNMk{bHYE>Asr6Yze7aQDu2K+l{~!F(=bhw?W5edYRvmY%~WbsGjoF zyLbYfW2U~Oj!bhJ07c8_%jvL-%?ByJ2*LcXK~Wiw4BO4wWt5vUgDy^GN`UHV^~RSz zGc%KFUXmmDpV}-*#_t~dA)Y!mD14Ac*5qB{@=Uy-fKlCIQ^`v zy6+qAAn=iS0>|!mSE|JhTl9Zw;jQde06eL*>B3p+w4({f88?T?{|aLN2De0P=;kcV zcJJEn2Y?JbVs>wAJ%vQo`#P5YRp{?6`}^&E%;*UI7L=7qmz9^B{ml_pR#7ooHG}^Z z#i$QMt#BNYRXzfZ5P$EonUK-@LyW47E@8ISWIdTY)z!tyQ!W~W*4It$cSw7yW&gZs z<9Lsi|D5H(!JkyE;H?~Y{79hxedF#IUeHeawyF4k1QyE=9olEJP!ZQ)vnXw0Vd3q^23eHPg;ETn;&CyDfRa3zop7VUG0RqE<)}x z-Q)1BtgOPs=Z(Yu3zIn@#BSr}mKLpBwo5}X<_G+I-zImkBk2Mjh z3k>Wqe|@b-BV^eK0ON~1vHd%L#iTP;9Pz!7hA};|%w4!FVUpE0|IkgKackC5D)#?q zr4R|ymVZTQ=?KaArHO7anwg9BNI-7($N#5&e^cOWofD#MwXwWBqVN_)Lr@#=mVC6( zlGUo6mJAVY8VPUWSW>chcXo)3Bow}j;1{zEnV&k(x@zMGijKT-?^;8(aZuO<9f zj(%`Y2~ZbG%qvKMKih51xw-!Lw6DwLRJx7z{c^)L>qMu52L>RLZ|VGLDr_tTf_j}0 z6A0K=+GNd`^6%#BX8QH>0YibJuI(Yisp!9_zTynr!UH0|S3qsMHa_z%o0Suf~+t|8u(Na_VN3xsd7F1c62BsoaZp4(7yjSAy-R5; z!F_);$-EFY4j)bbR#;bPYdr0LW*Zp|%%xEz-YR+o%ac`qD8{W;29^}{S2$<|^_wu8 z&Ur(R%2Vya1>Ls&sVSSM>seK5O>Vox5TTf1ugF+6{+WBArurkJU`3$!#kUA!Vr|U* z$dkwZ(f0qW3^~01YUS#wL8v6n!DyuNGCLQc%_0Bu?i@+P?mGN?Cj3u97>dnqMr8i& z=*-^~_xAAYXCth>YFpU?y@7JN+D*67{MUc`uJ^jKlNpvHG(zy$$X7h(li$b3kTlx>xA7q*NB8=a5C9!!v7_HLLcCm zrN-;b#uyHU<0y|o09f-#k^jN$zg=)!_3i`VIO<{3`4T^8)*($IPT4X4l9Q1 zHv1Je56I);?y_y&2({DN)sRe7s|r%EcM9ov_NtFU_hvDF(nU;q%njQ9y9RW^Kb_=js8n~X+J_YXLRo|i_TG4z%e=@!=|?UmzeHlc1t&WRd-9`cx2L5WR4XA zAr>V3ogto87Db~0MY@XZliqJo0y=Nw2SYwN*+O-+if&k5i(dzTfCe8@>BDQ(zZ}jp ziXUy)WEwrU?hg`skjE08PF69c^14McJUgdO63klwGrZqz&7-*e?xftaQ{1$F7^6;? zapx3aG5v3o`2=AqmsOtk+p}P7YfpfQ7R@0vKXYLSip+M*mH|j#<${jQf9+xFH;X95 zaf-jr+9h9Iu|W~XlTip!9iA(k8`otWE7~N)g~+&$xCsjmlhs)@Dr8R^he14sfL`MB z)sIv*t9ZMhkeAH2?ZPK4rd=VBBeS&wrjz=;q!3hrEJtzUd+V#HU{F(U2(+(u; za4*pqD=9KTIA)BG!9P$Jh?GCBn*MjZ$^8AlLrr~m=lJ!^AUixJ1k*vY73O_+Txp&} z6;IaxMd0}4EPnN(S#HB~mvPnOQW#+@gZt;+1m|Yrwl&gQCdgZ&>UqqdAU|J)W6eR7 zdm9cX;*^Zc=9VvUv%cGQeqSOB5$;(~1%iUd6fX$5ikVq1RPU$$;I&gjgFEEJF1Mrt zTrY)2TXxN}B>a3_2ebZI>}6e|iEEg4} zL_7E|%qD?uXUQ%Sh!S|%f_?4W)matH6%PTN{h5`on-X}Id_Qj>pOmARfz_x_)4NCd z^KdfBad$BFX!G51#Rcmv!A#FVLRgkS9ZI_+eeXD*v5F)M6EI@<|M7D}G7L-QMXcXT zcEhpc{Gp_Nmt3*28msiO;c5!Z-UW*L8_Zv3$%O=(QdxKpY zw6nzui-(CkkKk%R>@le0-LmkmkGftp4EVIykEQU@<0?+q_*&il(GDb}*UOB$(&vY= zooO<`oPvI}h!_^ywBcFa!RN=!r&?{ndwy%yQ53~?k5ToiC=u8DYw)HblpL9+A~Me9 zi@DiaY4X{afZo@sw|fsOy;W<^YYn%@w9oF{{~)Nuj&41K|$ z66z;;WIhDRru{7!38%eUweR||P=_AYv}{gfWob#9w}K;~w&e=LY3RVKq@$zrImwvu z|8a3LQJD8t{=gT;7;{bG9X;%k3KCjph*9Y8iG4^~w1SKS_1u=#I&0rJZC{^Gs}k-l zIsxp~+aB9NDnB;U?K|k2)?L%X2b7j4@D@=A1?m_#vt4s4H}jeB^}VmemaWSx_uMnj zs`o^4z5#xF`m>ddA%*5z$a0GgmKkgtB7Q+4k#BjOmLMI;u5KCpesHxrG-g$tc|bQw z?%HxZD(S{xw(Y@ZC=fo+!hiCwWibctqT^ow{q=Z}@SSAMhS7xVUynZ}kYWd#eY6nn zmMv>0yXE-P(2&Z0(i?=btCi_AO2_KB)BoUFM2{W*jVdYIy{6*zCi~5nbB1Z?+a#Ch zf{s0Zla|xiVV0MCh=Yq}o8e>UdE;uDkiOHWbFlziM(r=}wuHG+*6ER-KK4|>|L~+c zIOdgde%G#ex=2EwX~9_Ero)MBKtA#wAptvb}Q z8Dw5(wHD*iY+o1kfE~yK^>a8vQMz2yO{hbnm4e5r{ndvRKmrru%9U{GJTTYV(t0)Dgh#+ z^FMtbQLJo!By9H)?WJ__ahkQ?A~*pAeL7Q#*>iv64y5)NS6q`hTn*rjUGI-o)(XCC zqnXuDaLBA)#t1?*t2&>oBYyrBMi|lPdYb=pRnP7IeBLy6THR$Qb+_SVzx;EGzh27D zkq+A;is2B>0pJkTDI5sU#%tX0wBf#6GFdm5Vmdq(McT+}^+Q1XlU{GkOF@Bx%TU8P zhIM^OK^I5A_+KTQ!SLgH8Ktit_TpLdcA=zZ9A)a>`Izr7@YXW5+#%;$`pm&04$!O4 z!xwjb1-(J4!?F8r^!*<_1!4P|z3~V4I-JHGy3r*W{R;(t4HgO#ZoHh%<+(3B*U(mh z1C1y8+u;Mn6%0h4@^cArU*++iGkorruRODUPI4cF@O9?TssF)J5HtzbAEK}hFx}A&q~0nMGG{$5W#BWukoNOITPfU%r$soBk}_^yDpFc8>5ZHC??vuCps$upjtn zWx%e%qm#0{npU>LqRekxHy632YrVLt{~>Cv&ftX8X23bMc8BrNaQ~y$UoHjpir2Xe zrx-#$b!yaANr7+PKbJjk@9IV_A*BXDp&lakc9xJ~aD>52-q2TUW!m6}58D{qG6qla zpIw)X_!nsK{Y4yOC5;+cdJq`GjEpW|7;IvAPD-H7!gfxw&Yd>eJy^Z&&fHnsg90@p zkreR1lNC`0>M+Fq*VYv3L=u8(dA?{ftJ=u#*0u{oE@szi28s}7-s)|oH;EYh0yX!c z*1Z=vY)kVPL!-oGx%DkY9(3^eP>Vn2{b6HjFM;}o6^6ge-XGC_LKRz&Z7R>bsMS}d zIZIM{mxB8I)*g-mj^mZ(^^`#ThUPd0tc(-hc6EQwB^qQbD;hMtQk>x(o*u;n4>S7;k}C8Y2+GAf>7Ln%5R$qMQ?m`asWQczA~HJ zPfAg$$E?bFW|>vHIbdKqPrN>T`8>e1Z?bLpZ}kOrgN~gZRN#8Ot0;rzNi{S5l|`4)l?9h4*EW$8>I({Rf@@0J^s4hz1(cUL15hGM z=OOULZF&~7vN~RPuv)w9gPPHkIF59K?++e68Dd8_cDK>VUDu>)`6bat{AErsy->op zNvF|7I$XS8lcmK^o6JELN|Lz`2?_#nV@z%1$QzDB#QUL?(TLUG(>k!jXS+vc$#1c| zF*%3^3?|9=&7Xyk`r_MH{ak1Dyghp8bkf@nP;0s4Tb27aW&LXar>`!v zLzC^^8!5blj-pX$8qK(58d9YBw`;dX-qSv;NE zGF3XYS?R627Fbu~IW}n9blzO>!iZ)zHoBC(8UA9Kf2MfA_Q|aeiBw;VLtHcfJ<U>VpG%q?&ne-NM_{vHl{$?&}4IfEaWH~mCWtN%eI>M8TJ>UeDNs$H_s?L=$ z!o3c%LmgY6s0InJ7QHY-;&~8mF58Rp+XUw=Ue)KZS#mQo0bH97RYodPL>WL_<3j`N z!vr^RX|Xf(nNNhO`4>#$rRjs``zU4S-`9Gu_q|e|1iwWeDJ0%_n&!Ndv)3g-v-!SR z)9(^(9|>7iq01k_bBOW4p3=|4UcD~iPkL5AtdrHgQlM>u3<(M2t$%1yj-?Y87~R;9#!h|K%t`ZDhS*HP zUJpy$ORM-q%zdg{b4MvHC(ttsS)$9KIH06Ep&74j;cS(tEipsm;bsqr4kTpDnsp#qgSK22fNuI4l zBfXeUr0we|IMW1PGd8W&ozd`b2PE-|Hh=Nop}d?^QApjKnX+xKonTk=W*@H*FI{1A zyU_-w>@Nzru|1ha2_pHn4A3N^ zBSYrkVPZFkp1#jM`>>Ix_)#hjR&a+Ka_eHF9*_O;WY?Z{|0I)~F-%?92!;Gj%hy^>6=RWX<*K}X6X*8I~uB6)AIx%_#jM-yD?wr2?=Vfrw zZ}DAB!B%WuV*ZyHirJjeW)en-x6b{(Alu|iTZJzO_ub-v)0eUWD*`H$ncDu?m1NF; zqqfd(r5?7%$v@9zwV#wGXq=e-iVqwzK6u{kM6*w6v)G{Mqlr}W4&Y-cRyi#JUv31t zpB`|*8z5Exx@*kW3;9-=aUb@%bAZL^l3L=R%~~wM;J2dpv=|=J%hXdJD_7mHXv62gw>I|EgLJ$2 zG{QB(Z?QthSj}hGV-v^^vYDz}^MTI4y9s--F4Ryp@a&0BQSxj2lvJ8ZbV5cFXvX5K z)pTv4FIbWpw2bptTMb|Gp?mLI7u4%Zf;IU}hhz0M((k`@h!1}hf+{Ydi8LM9eZ99B zaaq(OlCizcBtTBf%Jb;0!I~na$#$;u)u2i3b_>wPp6WMsZQx1GY=sj#QWkBlpA-U2O>w-)yerv-^n1*)RmY0;wS+d;xbi+KYEx1u|5ya90Hy;CO z@mOy6;iz^09Gdh`U*8LEkv%>wb{+tD0$di+o5sncP?avzxeav{y5rrCCl*HoKMh5w z_6!8Vs0$o*H|NCw+-1+*D2hXv@2J#MkZ#T3aPg{M#-*d z`Z$#}h<~3VPprh5|GIo^LKCMK;QYz!%uQE;iA~rcb0%V;eR<4m?-0lU_Gn}Xrn;K2 zH(;dX)HkqCE;~1ExKteyzkppTgb%TDLBbjqiRPHZqOu@~4T!tBh z^u%7{EV+(Po-`E{n;wV69{cke6P{dpkH2F<2!ap7t+WKhyRWk1zwzmGV6qJJOLypV zxpm#v!8|OPg!v_!EWW+Lb%!KgClRqq)UCo)!4lx)yK_oAp+7}Vz}VW{A( zuq(+AUv{|gLy6bAE|dIdL*xd@U{G@-#S7ArVl;Bht{~g&gV~^_oGv~+1lIlyGJZN zabI@XyXGW6A$lNI2o+8lse;>dzFJDT!6XN10CWob`q=f9RLgfvBZv_>_wM!6MZEoj zz1#F?4}4DlmrDU=QR~K;Xu(Wa7%E}A1F~XcUK=ruu-t4Wy z))7)Y*@Q$*uZHzhD9?jNKXn;9jFGTBk;bBO_@^vN2ODAbi?JwC*eHmpOE%fNPL(~5 z>G5&-aG3UiH`pQG@$&GuW1epq*H+MuqBRnvv51s;!C4P~&I_})W1OkuOxcCE?6vx7 zaSo+u)G!l~C5!7%`_|<5=$>mMtw^s7cAEvshR*TkhnnPoOmmI`jOf3k&*OFOjkw;9W&XwtxP?%0A_RGr;3vFRBa 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≤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 ljs1tW<2om|@r8dJC_J3mpDzCM{{fGm`62)S literal 0 HcmV?d00001 diff --git a/graalpy/graalpy-micronaut-guide/src/main/java/org/example/Application.java b/graalpy/graalpy-micronaut-guide/src/main/java/org/example/Application.java new file mode 100644 index 0000000..f277708 --- /dev/null +++ b/graalpy/graalpy-micronaut-guide/src/main/java/org/example/Application.java @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at https://opensource.org/license/UPL. + */ + +package org.example; +import io.micronaut.runtime.Micronaut; + +public class Application { + + public static void main(String[] args) { + Micronaut.run(Application.class, args); + } +} \ No newline at end of file diff --git a/graalpy/graalpy-micronaut-guide/src/main/java/org/example/GraalPyContext.java b/graalpy/graalpy-micronaut-guide/src/main/java/org/example/GraalPyContext.java new file mode 100644 index 0000000..c5e44dd --- /dev/null +++ b/graalpy/graalpy-micronaut-guide/src/main/java/org/example/GraalPyContext.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at https://opensource.org/license/UPL. + */ + +package org.example; + +import io.micronaut.context.annotation.Context; +import jakarta.annotation.PreDestroy; +import org.graalvm.python.embedding.utils.GraalPyResources; + +@Context // ① +public final class GraalPyContext { + + static final String PYTHON = "python"; + + private final org.graalvm.polyglot.Context context; + + public GraalPyContext() { + context = GraalPyResources.createContext(); // ② + context.initialize(PYTHON); // ③ + } + + org.graalvm.polyglot.Context get() { + return context; // ④ + } + + @PreDestroy + void close() { + try { + context.close(true); // ⑤ + } catch (Exception e) { + // ignore + } + } +} diff --git a/graalpy/graalpy-micronaut-guide/src/main/java/org/example/SentimentAnalysis.java b/graalpy/graalpy-micronaut-guide/src/main/java/org/example/SentimentAnalysis.java new file mode 100644 index 0000000..47b5351 --- /dev/null +++ b/graalpy/graalpy-micronaut-guide/src/main/java/org/example/SentimentAnalysis.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at https://opensource.org/license/UPL. + */ + +package org.example; + +import io.micronaut.context.annotation.Bean; +import org.graalvm.polyglot.Value; +import java.util.Map; +import static org.example.GraalPyContext.PYTHON; + +@Bean +public class SentimentAnalysis { + + private final SentimentIntensityAnalyzer sentimentIntensityAnalyzer; + + public SentimentAnalysis(GraalPyContext context) { + Value value = context.get().eval(PYTHON, """ + from vader_sentiment.vader_sentiment import SentimentIntensityAnalyzer + SentimentIntensityAnalyzer() # ① + """); + sentimentIntensityAnalyzer = value.as(SentimentIntensityAnalyzer.class); // ② + } + + public Map getPolarityScores(String text) { + return sentimentIntensityAnalyzer.polarity_scores(text); // ③ + } +} diff --git a/graalpy/graalpy-micronaut-guide/src/main/java/org/example/SentimentAnalysisController.java b/graalpy/graalpy-micronaut-guide/src/main/java/org/example/SentimentAnalysisController.java new file mode 100644 index 0000000..7e1ac97 --- /dev/null +++ b/graalpy/graalpy-micronaut-guide/src/main/java/org/example/SentimentAnalysisController.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at https://opensource.org/license/UPL. + */ + +package org.example; + +import io.micronaut.http.annotation.*; +import io.micronaut.scheduling.TaskExecutors; +import io.micronaut.scheduling.annotation.ExecuteOn; +import io.micronaut.views.View; +import java.util.Map; + +@Controller // ① +public class SentimentAnalysisController { + + private final SentimentAnalysis sentimentAnalysis; + + SentimentAnalysisController(SentimentAnalysis sentimentAnalysis) { // ② + this.sentimentAnalysis = sentimentAnalysis; + } + + @Get // ③ + @View("index") // ④ + public void index() { + + } + + @Get(value = "/analyze") // ⑤ + @ExecuteOn(TaskExecutors.BLOCKING) // ⑥ + public Map answer(String text) { + return sentimentAnalysis.getPolarityScores(text); // ⑦ + } +} diff --git a/graalpy/graalpy-micronaut-guide/src/main/java/org/example/SentimentIntensityAnalyzer.java b/graalpy/graalpy-micronaut-guide/src/main/java/org/example/SentimentIntensityAnalyzer.java new file mode 100644 index 0000000..f5db524 --- /dev/null +++ b/graalpy/graalpy-micronaut-guide/src/main/java/org/example/SentimentIntensityAnalyzer.java @@ -0,0 +1,13 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at https://opensource.org/license/UPL. + */ + +package org.example; + +import java.util.Map; + +public interface SentimentIntensityAnalyzer { + Map polarity_scores(String text); // ① +} diff --git a/graalpy/graalpy-micronaut-guide/src/main/resources/META-INF/native-image/proxy-config.json b/graalpy/graalpy-micronaut-guide/src/main/resources/META-INF/native-image/proxy-config.json new file mode 100644 index 0000000..21c91f9 --- /dev/null +++ b/graalpy/graalpy-micronaut-guide/src/main/resources/META-INF/native-image/proxy-config.json @@ -0,0 +1,3 @@ +[ + ["org.example.SentimentIntensityAnalyzer"] +] \ No newline at end of file diff --git a/graalpy/graalpy-micronaut-guide/src/main/resources/application.properties b/graalpy/graalpy-micronaut-guide/src/main/resources/application.properties new file mode 100644 index 0000000..a44e917 --- /dev/null +++ b/graalpy/graalpy-micronaut-guide/src/main/resources/application.properties @@ -0,0 +1,2 @@ +#Mon Sep 02 15:53:44 CEST 2024 +micronaut.application.name=demo diff --git a/graalpy/graalpy-micronaut-guide/src/main/resources/logback.xml b/graalpy/graalpy-micronaut-guide/src/main/resources/logback.xml new file mode 100644 index 0000000..2d77bda --- /dev/null +++ b/graalpy/graalpy-micronaut-guide/src/main/resources/logback.xml @@ -0,0 +1,14 @@ + + + + + + %cyan(%d{HH:mm:ss.SSS}) %gray([%thread]) %highlight(%-5level) %magenta(%logger{36}) - %msg%n + + + + + + + diff --git a/graalpy/graalpy-micronaut-guide/src/main/resources/views/index.html b/graalpy/graalpy-micronaut-guide/src/main/resources/views/index.html new file mode 100644 index 0000000..b7f82ad --- /dev/null +++ b/graalpy/graalpy-micronaut-guide/src/main/resources/views/index.html @@ -0,0 +1,87 @@ + + + + Demo App + + + + + + + +

+
+
+

Sentiment analysis demo

+

Input messages and the application will visualize the sentiment.

+
+
+
+
+
+ + +
+
+
+
+
+
+
+ + \ No newline at end of file diff --git a/graalpy/graalpy-micronaut-guide/src/test/java/org/example/SentimentAnalysisControllerTest.java b/graalpy/graalpy-micronaut-guide/src/test/java/org/example/SentimentAnalysisControllerTest.java new file mode 100644 index 0000000..cd8911a --- /dev/null +++ b/graalpy/graalpy-micronaut-guide/src/test/java/org/example/SentimentAnalysisControllerTest.java @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at https://opensource.org/license/UPL. + */ + +package org.example; + +import io.micronaut.http.client.HttpClient; +import io.micronaut.http.client.annotation.Client; +import io.micronaut.test.extensions.junit5.annotation.MicronautTest; +import org.junit.jupiter.api.Test; +import java.util.Map; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@MicronautTest // ① +class SentimentAnalysisControllerTest { + @Test + void testAnalyzeResponse(@Client("/") HttpClient client) { // ② + Map response = client.toBlocking().retrieve("/analyze?text=happy", Map.class); // ③ + assertTrue(response.get("compound") > 0.1); + response = client.toBlocking().retrieve("/analyze?text=sad", Map.class); + assertTrue(response.get("compound") < -0.1); + } +} diff --git a/graalpy/graalpy-native-extensions-guide/.mvn/wrapper/maven-wrapper.properties b/graalpy/graalpy-native-extensions-guide/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..93a0d79 --- /dev/null +++ b/graalpy/graalpy-native-extensions-guide/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,19 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +wrapperVersion=3.3.2 +distributionType=only-script +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.4/apache-maven-3.9.4-bin.zip diff --git a/graalpy/graalpy-native-extensions-guide/README.md b/graalpy/graalpy-native-extensions-guide/README.md new file mode 100644 index 0000000..50990b3 --- /dev/null +++ b/graalpy/graalpy-native-extensions-guide/README.md @@ -0,0 +1,353 @@ +# Using Python Native Extension in a Java SE Application + +*Warning: +GraalPy support for native [Python extensions](https://packaging.python.org/en/latest/guides/packaging-binary-extensions) is experimental.* + +Python libraries that incorporate native Python extensions, such as *NumPy* or *scikit-learn*, +can be used and shipped with Java applications. + +The [GraalPy Maven artifacts](https://central.sonatype.com/artifact/org.graalvm.polyglot/python) and [GraalVM Polyglot APIs](https://www.graalvm.org/latest/reference-manual/embed-languages/) allow flexible integration with different project setups. + +Using Python packages in Java projects often requires a bit more setup, due to the nature of the Python packaging ecosystem. +GraalPy provides a [python-embedding](https://central.sonatype.com/artifact/org.graalvm.python/python-embedding) package that simplifies the required setup to ship Python packages as Java resources or in separate folders. +The important entry points to do so are the [VirtualFileSystem](https://github.com/oracle/graalpython/blob/master/docs/user/Embedding-Build-Tools.md#virtual-filesystem) and the [GraalPyResources](https://github.com/oracle/graalpython/blob/master/docs/user/Embedding-Build-Tools.md#deployment) classes. + +## 1. Getting Started + +In this guide, we will use Python package [polyleven](https://pypi.org/project/polyleven), +which is a native Python extension, to calculate the [Levenshtein distance](https://en.wikipedia.org/wiki/Levenshtein_distance). + +We will develop a CLI app that once for all settles the common source of contention: whether something is a fruit. +For each command line argument, the app will print an indication whether it is a fruit or not. +We want to guard our users against typos, so if the argument appears to be mistyped name of a fruit, +we add the standard "did you mean 'xyz'" message. For identifying the typos we will use the +Levenshtein distance. + +## 2. What you will need + +To complete this guide, you will need the following: + +* Some time on your hands +* A decent text editor or IDE +* A supported JDK[^1], preferably the latest [GraalVM JDK](https://graalvm.org/downloads/) +* C compiler toolchain (e.g., GCC) + +[^1]: Oracle JDK 17 and OpenJDK 17 are supported with interpreter only. +GraalVM JDK 21, Oracle JDK 21, OpenJDK 21 and newer with [JIT compilation](https://www.graalvm.org/latest/reference-manual/embed-languages/#runtime-optimization-support). +Note: GraalVM for JDK 17 is **not supported**. + +## 3. Solution + +We recommend that you follow the instructions in the next sections and create the application step by step. +However, you can go right to the [completed example](https://github.com/graalvm/graalpy-demos/tree/master/nativext). + +## 4. Writing the application + +You can start with any Maven application that runs on JDK 17 or newer. +We will use a default Maven application [generated](https://maven.apache.org/archetypes/maven-archetype-quickstart/) from an archetype. + +```shell +mvn archetype:generate -DarchetypeGroupId=org.apache.maven.archetypes \ + -DarchetypeArtifactId=maven-archetype-quickstart -DarchetypeVersion=1.5 \ + -DgroupId=example -DartifactId=example -Dpackage=org.example \ + -Dversion=1.0-SNAPSHOT -DinteractiveMode=false +``` + +## 4.1 Dependency configuration + +Add the required dependencies for GraalPy in the dependency section of the POM. + +`pom.xml` +```xml + + org.graalvm.python + python + 24.1.0 + pom + + + + org.graalvm.python + python-embedding + 24.1.0 + +``` + +❶ The `python` dependency is a meta-package that transitively depends on all resources and libraries to run GraalPy. + +❷ Note that the `python` package is not a JAR - it is simply a `pom` that declares more dependencies. + +❸ The `python-embedding` dependency provides the APIs to manage and use GraalPy from Java. + +## 4.2 Adding packages + +Most Python packages are hosted on [PyPI](https://pypi.org) and can be installed via the `pip` tool. +The Python ecosystem has conventions about the filesystem layout of installed packages that need to be kept in mind when embedding into Java. +You can use the GraalPy plugin to manage Python packages for you. + +`pom.xml` +```xml + + + + org.graalvm.python + graalpy-maven-plugin + 24.1.0 + + + + + polyleven==0.8 + + + + process-graalpy-resources + + + + + + +``` + +❶ The `packages` section lists all Python packages optionally with [requirement specifiers](https://pip.pypa.io/en/stable/reference/requirement-specifiers/). +In this case, we install the `polyleven` package and pin it to version `0.8`. Because we are not +specifying `` the Maven plugin will embed the packages into the +resulting JAR as a standard Java resource. + +## 4.3 Java Code + +Open file `src/main/java/org/example/App.java` and replace the generated code +with the following: + +`App.java` +``` +package org.example; + +import org.graalvm.polyglot.*; +import org.graalvm.python.embedding.utils.GraalPyResources; + +import java.util.Set; + +public class App { + private static final Set FRUITS = Set.of( + "apple", "banana", "peach", "grape", "orange", + "pear", "mango", "pineapple", "lemon", "lime", "apricot"); // ① + + public static void main(String[] args) { + try (Context context = GraalPyResources.createContext()) { // ② + Value pythonBindings = context.getBindings("python"); // ③ + pythonBindings.putMember("fruits", FRUITS.toArray()); // ④ + + // ⑤ + context.eval("python", """ + import polyleven + def get_similar_word(value): + words = [x for x in fruits if polyleven.levenshtein(x, value) <= 1] # ⑥ + return words[0] if words else None + """); + + + Value getSimilarWord = pythonBindings.getMember("get_similar_word"); // ⑦ + + for (String value : args) { + if (FRUITS.contains(value)) { + System.out.printf("✅ %s%n", value); + } else { + System.out.printf("❌ %s", value); + Value similarWord = getSimilarWord.execute(value); // ⑧ + if (!similarWord.isNull()) { // ⑨ + System.out.printf(" (did you mean '%s')%n", similarWord.asString()); // ⑩ + } else { + System.out.println(); + } + } + } + } + } +} +``` + +❶ A Java set is used as our database of fruit names. + +❷ GraalPy provides APIs to make setting up a context to load Python packages from Java as easy as possible. +Static method `GraalPyResources.createContext` will create a Context preconfigured to lookup Python +packages in Java resources, which should be generated by the GraalPy Maven plugin we added in +the previous section. + +❸ Retrieve the Python "bindings". This is an object that provides access to Python global namespace. +One can read, add, or remove global variables using this object. + +❹ Assign the Java set with fruit names to a global variable `fruits` in Python + +❺ Execute Python code that defines a function named `get_similar_word`. Note that the Python code +imports our package `polyleven` and that the function `get_similar_word` references the global +variable `fruits`. + +❻ The Java set exposed as global variable to Python can be iterated over using Python +list comprehension. + +❼ Retrieve the Python function that was just defined. The object can be used to execute the function from Java. + +❽ Execute the Python function from Java. The `execute` method takes variadic number of arguments. +Java String will appear as Python string to the Python function. + +❾ Use the `Value.isNull` Java API to determine if the result is Python `None` value. + +❿ Convert the result to a Java String representation. + +## 5. Running the application + +If you followed along with the example, you can now compile: + +```shell +mvn package +``` + +The output should show messages from the installation of the `polyleven` package. The C code in +this package will be compiled as part of the installation. Python package maintainers +can choose to publish binary wheels (packaging format used in Python) for GraalPy, which would remove +the compilation step and a pre-built binary for your system would be just extracted from the wheel. + +If there are any errors during the build, change the package in `pom.xml` from `polyleven` to some +pure Python package, such as `art==6.2` and run `mvn package` again. If a pure Python package +can be installed successfully, the problem is likely going to be your C compiler toolchain setup. +Look at the log of the failing installation for error messages that may help you to resolve the issue. + +If the compilation is successful, you can now run your application from the commandline: + +```shell +mvn exec:java -Dexec.mainClass=org.example.App -Dexec.args="appkle pear anana tomato" +[INFO] ... +❌ appkle (did you mean 'apple') +✅ pear +❌ anana (did you mean 'banana') +❌ strawberry +❌ tomato +``` + +## 6. Identify Native Extensions + +It may not always be clear whether some specific Python package or some of its transitive dependencies +contain native extensions. Moreover, the native extensions may not be executed at runtime. +Since GraalPy support for native extensions is experimental and there are +some limitations when native extensions are used (see next section), it is useful to know +if and when the program loads native extensions. GraalPy has an option to warn when +an experimental feature is used. This log can be enabled with context options. Copy `App.java` +to create a new class `AppLogging.java` and update the context initialization code as follows: + +`AppLogging.java` +``` +try (Context context = GraalPyResources.contextBuilder() + .option("log.python.capi.level", "WARNING") // ① + .option("python.WarnExperimentalFeatures", "true") // ② + .build()) { +``` + +❶ Set the log level to `WARNING` for loggers whose names begin with `capi.level` + +❷ Set GraalPy option to log a warning every time a native extension is loaded. + +When the application is run this time, it will print a waning that a native extension `polylevel` +is being loaded: + +```shell +mvn exec:java -Dexec.mainClass=org.example.AppLogging -Dexec.args="appkle pear anana tomato" +[INFO] ... +[python::CExtContext] WARNING: Loading C extension module polyleven from +'/graalpy_vfs/venv/lib/python3.11/site-packages/polyleven.graalpy241dev156644dd29-311-native-x86_64-linux.so'. +Support for the Python C API is considered experimental. +... +``` + +In the log we can see that the `polyleven` C extension module was loaded and the filesystem path +that it was loaded from. Note that this is a Virtual Filesystem path. The shared library is extracted +to a temporary location on the real filesystem and loaded. + +## 7. Native Extensions Limitations + +## 7.1 Portability + +As discussed in section 4.2. and as we saw in the log output in section 6, the Python package is bundled into +the resulting JAR as Java resource. Python native extensions are native shared libraries and as such they are +platform dependent, therefore the resulting JAR now becomes platform dependent and cannot be, for example, +packaged on Linux AMD64 and run on Apple M1. + +It is possible to manually [build cross-platform JARs](https://github.com/oracle/graalpython/blob/master/docs/user/README.md#creating-cross-platform-jars-with-native-python-packages) +that contain resources for more than one system. + +## 7.2 Single Context + +Python extensions are often not built with multi-tenancy in mind and assume that they are initialized only +once per process and run only in a single Python interpreter instance. Because of that, for the time being, +GraalPy does not support loading native extensions in two or more contexts per JVM or native-image process. + +To demonstrate this, copy `App.java` to `MultiContextApp.java` and change the code to inspect each argument +using a fresh new `Context`: + +`MultiContextApp.java` +``` + public static String getSimilarWord(String value) { + try (Context context = GraalPyResources.contextBuilder().build()) { + context.eval("python", """ + import polyleven + def get_similar_word(value): + words = [x for x in fruits if polyleven.levenshtein(x, value) <= 1] + return words[0] if words else None + """); + + Value pythonBindings = context.getBindings("python"); + pythonBindings.putMember("fruits", FRUITS.toArray()); + Value result = pythonBindings + .getMember("get_similar_word") + .execute(value); + return result.isNull() ? null : result.asString(); + } + } + + public static void main(String[] args) { + for (String value : args) { + if (FRUITS.contains(value)) { + System.out.printf("✅ %s%n", value); + } else { + System.out.printf("❌ %s", value); + String similarWord = getSimilarWord(value); + if (similarWord != null) { + System.out.printf(" (did you mean '%s')%n", similarWord); + } else { + System.out.println(); + } + } + } + } +``` + +Run the application: + +```shell +mvn package exec:java -Dexec.mainClass=org.example.MultiContextApp -Dexec.args="appkle pear anana strawberry tomato" +[INFO] ... +❌ appkle (did you mean 'apple') +✅ pear +❌ ananaTraceback (most recent call last): + File "Unnamed", line 1, in +SystemError: GraalPy option 'NativeModules' is set to false, but the 'llvm' language, which is required for this feature, is not available. If this is a GraalPy standalone distribution: this indicates internal error. If GraalPy was used as a Maven dependency: are you missing a runtime dependency 'org.graalvm.polyglot:llvm{-community}'? +``` + +The error refers to the LLVM runtime, which can be used as an execution engine for native extensions +and which provides multi-context support. However, using the LLVM runtime is not recommended, because +it requires compilation with specialized LLVM toolchain, has significant warm-up, memory footprint, +and its support may be removed in some future versions of GraalPy. + + +## 8. Next steps + +- Use GraalPy with popular Java frameworks, such as [Spring Boot](../graalpy-spring-boot-guide/README.md) or [Micronaut](../graalpy-micronaut-guide/README.md) +- Follow along how you can manually [install Python packages and files](../graalpy-custom-venv-guide/README.md) if the Maven plugin gives not enough control +- [Freeze](../graalpy-freeze-dependencies-guide/README.md) transitive Python dependencies for reproducible builds +- [Migrate from Jython](../graalpy-jython-guide/README.md) to GraalPy + + +- Learn more about the GraalPy [Maven plugin](https://www.graalvm.org/latest/reference-manual/python/Embedding-Build-Tools/) +- Learn more about the Polyglot API for [embedding languages](https://www.graalvm.org/latest/reference-manual/embed-languages/) +- Explore in depth with GraalPy [reference manual](https://www.graalvm.org/latest/reference-manual/python/) \ No newline at end of file diff --git a/graalpy/graalpy-native-extensions-guide/mvnw b/graalpy/graalpy-native-extensions-guide/mvnw new file mode 100755 index 0000000..19529dd --- /dev/null +++ b/graalpy/graalpy-native-extensions-guide/mvnw @@ -0,0 +1,259 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Apache Maven Wrapper startup batch script, version 3.3.2 +# +# Optional ENV vars +# ----------------- +# JAVA_HOME - location of a JDK home dir, required when download maven via java source +# MVNW_REPOURL - repo url base for downloading maven distribution +# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output +# ---------------------------------------------------------------------------- + +set -euf +[ "${MVNW_VERBOSE-}" != debug ] || set -x + +# OS specific support. +native_path() { printf %s\\n "$1"; } +case "$(uname)" in +CYGWIN* | MINGW*) + [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")" + native_path() { cygpath --path --windows "$1"; } + ;; +esac + +# set JAVACMD and JAVACCMD +set_java_home() { + # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched + if [ -n "${JAVA_HOME-}" ]; then + if [ -x "$JAVA_HOME/jre/sh/java" ]; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACCMD="$JAVA_HOME/jre/sh/javac" + else + JAVACMD="$JAVA_HOME/bin/java" + JAVACCMD="$JAVA_HOME/bin/javac" + + if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then + echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2 + echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2 + return 1 + fi + fi + else + JAVACMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v java + )" || : + JAVACCMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v javac + )" || : + + if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then + echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2 + return 1 + fi + fi +} + +# hash string like Java String::hashCode +hash_string() { + str="${1:-}" h=0 + while [ -n "$str" ]; do + char="${str%"${str#?}"}" + h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296)) + str="${str#?}" + done + printf %x\\n $h +} + +verbose() { :; } +[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; } + +die() { + printf %s\\n "$1" >&2 + exit 1 +} + +trim() { + # MWRAPPER-139: + # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds. + # Needed for removing poorly interpreted newline sequences when running in more + # exotic environments such as mingw bash on Windows. + printf "%s" "${1}" | tr -d '[:space:]' +} + +# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties +while IFS="=" read -r key value; do + case "${key-}" in + distributionUrl) distributionUrl=$(trim "${value-}") ;; + distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;; + esac +done <"${0%/*}/.mvn/wrapper/maven-wrapper.properties" +[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties" + +case "${distributionUrl##*/}" in +maven-mvnd-*bin.*) + MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ + case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in + *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;; + :Darwin*x86_64) distributionPlatform=darwin-amd64 ;; + :Darwin*arm64) distributionPlatform=darwin-aarch64 ;; + :Linux*x86_64*) distributionPlatform=linux-amd64 ;; + *) + echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2 + distributionPlatform=linux-amd64 + ;; + esac + distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip" + ;; +maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;; +*) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;; +esac + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}" +distributionUrlName="${distributionUrl##*/}" +distributionUrlNameMain="${distributionUrlName%.*}" +distributionUrlNameMain="${distributionUrlNameMain%-bin}" +MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}" +MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")" + +exec_maven() { + unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || : + exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD" +} + +if [ -d "$MAVEN_HOME" ]; then + verbose "found existing MAVEN_HOME at $MAVEN_HOME" + exec_maven "$@" +fi + +case "${distributionUrl-}" in +*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;; +*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;; +esac + +# prepare tmp dir +if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then + clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; } + trap clean HUP INT TERM EXIT +else + die "cannot create temp dir" +fi + +mkdir -p -- "${MAVEN_HOME%/*}" + +# Download and Install Apache Maven +verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +verbose "Downloading from: $distributionUrl" +verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +# select .zip or .tar.gz +if ! command -v unzip >/dev/null; then + distributionUrl="${distributionUrl%.zip}.tar.gz" + distributionUrlName="${distributionUrl##*/}" +fi + +# verbose opt +__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR='' +[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v + +# normalize http auth +case "${MVNW_PASSWORD:+has-password}" in +'') MVNW_USERNAME='' MVNW_PASSWORD='' ;; +has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;; +esac + +if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then + verbose "Found wget ... using wget" + wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl" +elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then + verbose "Found curl ... using curl" + curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl" +elif set_java_home; then + verbose "Falling back to use Java to download" + javaSource="$TMP_DOWNLOAD_DIR/Downloader.java" + targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName" + cat >"$javaSource" <<-END + public class Downloader extends java.net.Authenticator + { + protected java.net.PasswordAuthentication getPasswordAuthentication() + { + return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() ); + } + public static void main( String[] args ) throws Exception + { + setDefault( new Downloader() ); + java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() ); + } + } + END + # For Cygwin/MinGW, switch paths to Windows format before running javac and java + verbose " - Compiling Downloader.java ..." + "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java" + verbose " - Running Downloader.java ..." + "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")" +fi + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +if [ -n "${distributionSha256Sum-}" ]; then + distributionSha256Result=false + if [ "$MVN_CMD" = mvnd.sh ]; then + echo "Checksum validation is not supported for maven-mvnd." >&2 + echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + elif command -v sha256sum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c >/dev/null 2>&1; then + distributionSha256Result=true + fi + elif command -v shasum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then + distributionSha256Result=true + fi + else + echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2 + echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + fi + if [ $distributionSha256Result = false ]; then + echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2 + echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2 + exit 1 + fi +fi + +# unzip and move +if command -v unzip >/dev/null; then + unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip" +else + tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar" +fi +printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url" +mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME" + +clean || : +exec_maven "$@" diff --git a/graalpy/graalpy-native-extensions-guide/mvnw.cmd b/graalpy/graalpy-native-extensions-guide/mvnw.cmd new file mode 100644 index 0000000..b150b91 --- /dev/null +++ b/graalpy/graalpy-native-extensions-guide/mvnw.cmd @@ -0,0 +1,149 @@ +<# : batch portion +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Apache Maven Wrapper startup batch script, version 3.3.2 +@REM +@REM Optional ENV vars +@REM MVNW_REPOURL - repo url base for downloading maven distribution +@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output +@REM ---------------------------------------------------------------------------- + +@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0) +@SET __MVNW_CMD__= +@SET __MVNW_ERROR__= +@SET __MVNW_PSMODULEP_SAVE=%PSModulePath% +@SET PSModulePath= +@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @( + IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B) +) +@SET PSModulePath=%__MVNW_PSMODULEP_SAVE% +@SET __MVNW_PSMODULEP_SAVE= +@SET __MVNW_ARG0_NAME__= +@SET MVNW_USERNAME= +@SET MVNW_PASSWORD= +@IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*) +@echo Cannot start maven from wrapper >&2 && exit /b 1 +@GOTO :EOF +: end batch / begin powershell #> + +$ErrorActionPreference = "Stop" +if ($env:MVNW_VERBOSE -eq "true") { + $VerbosePreference = "Continue" +} + +# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties +$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl +if (!$distributionUrl) { + Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" +} + +switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) { + "maven-mvnd-*" { + $USE_MVND = $true + $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip" + $MVN_CMD = "mvnd.cmd" + break + } + default { + $USE_MVND = $false + $MVN_CMD = $script -replace '^mvnw','mvn' + break + } +} + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +if ($env:MVNW_REPOURL) { + $MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" } + $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')" +} +$distributionUrlName = $distributionUrl -replace '^.*/','' +$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$','' +$MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain" +if ($env:MAVEN_USER_HOME) { + $MAVEN_HOME_PARENT = "$env:MAVEN_USER_HOME/wrapper/dists/$distributionUrlNameMain" +} +$MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' +$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME" + +if (Test-Path -Path "$MAVEN_HOME" -PathType Container) { + Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME" + Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" + exit $? +} + +if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) { + Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl" +} + +# prepare tmp dir +$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile +$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir" +$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null +trap { + if ($TMP_DOWNLOAD_DIR.Exists) { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } + } +} + +New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null + +# Download and Install Apache Maven +Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +Write-Verbose "Downloading from: $distributionUrl" +Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +$webclient = New-Object System.Net.WebClient +if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) { + $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD) +} +[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 +$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum +if ($distributionSha256Sum) { + if ($USE_MVND) { + Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." + } + Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash + if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) { + Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property." + } +} + +# unzip and move +Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null +Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null +try { + Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null +} catch { + if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) { + Write-Error "fail to move MAVEN_HOME" + } +} finally { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } +} + +Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" diff --git a/graalpy/graalpy-native-extensions-guide/pom.xml b/graalpy/graalpy-native-extensions-guide/pom.xml new file mode 100644 index 0000000..903b743 --- /dev/null +++ b/graalpy/graalpy-native-extensions-guide/pom.xml @@ -0,0 +1,54 @@ + + + 4.0.0 + + org.example + native-ext + 1.0-SNAPSHOT + jar + fruit-arbitrator + + + 17 + 17 + UTF-8 + + + + + org.graalvm.python + python + 24.1.0 + pom + + + + org.graalvm.python + python-embedding + 24.1.0 + + + + + + + org.graalvm.python + graalpy-maven-plugin + 24.1.0 + + + + + polyleven==0.8 + + + + process-graalpy-resources + + + + + + + diff --git a/graalpy/graalpy-native-extensions-guide/src/main/java/org/example/App.java b/graalpy/graalpy-native-extensions-guide/src/main/java/org/example/App.java new file mode 100644 index 0000000..6615ece --- /dev/null +++ b/graalpy/graalpy-native-extensions-guide/src/main/java/org/example/App.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at https://opensource.org/license/UPL. + */ +package org.example; + +import org.graalvm.polyglot.*; +import org.graalvm.python.embedding.utils.GraalPyResources; + +import java.util.Set; + +public class App { + private static final Set FRUITS = Set.of( + "apple", "banana", "peach", "grape", "orange", + "pear", "mango", "pineapple", "lemon", "lime", "apricot"); // ① + + public static void main(String[] args) { + try (Context context = GraalPyResources.createContext()) { // ② + Value pythonBindings = context.getBindings("python"); // ③ + pythonBindings.putMember("fruits", FRUITS.toArray()); // ④ + + // ⑤ + context.eval("python", """ + import polyleven + def get_similar_word(value): + words = [x for x in fruits if polyleven.levenshtein(x, value) <= 1] # ⑥ + return words[0] if words else None + """); + + + Value getSimilarWord = pythonBindings.getMember("get_similar_word"); // ⑦ + + for (String value : args) { + if (FRUITS.contains(value)) { + System.out.printf("✅ %s%n", value); + } else { + System.out.printf("❌ %s", value); + Value similarWord = getSimilarWord.execute(value); // ⑧ + if (!similarWord.isNull()) { // ⑨ + System.out.printf(" (did you mean '%s')%n", similarWord.asString()); // ⑩ + } else { + System.out.println(); + } + } + } + } + } +} \ No newline at end of file diff --git a/graalpy/graalpy-native-extensions-guide/src/main/java/org/example/AppLogging.java b/graalpy/graalpy-native-extensions-guide/src/main/java/org/example/AppLogging.java new file mode 100644 index 0000000..825da56 --- /dev/null +++ b/graalpy/graalpy-native-extensions-guide/src/main/java/org/example/AppLogging.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at https://opensource.org/license/UPL. + */ +package org.example; + +import org.graalvm.polyglot.Context; +import org.graalvm.polyglot.Value; +import org.graalvm.python.embedding.utils.GraalPyResources; + +import java.util.Set; + +public class AppLogging { + private static final Set FRUITS = Set.of( + "apple", "banana", "peach", "grape", "orange", + "pear", "mango", "pineapple", "lemon", "lime", "apricot"); + + public static void main(String[] args) { + try (Context context = GraalPyResources.contextBuilder() + .option("log.python.capi.level", "WARNING") // ① + .option("python.WarnExperimentalFeatures", "true") // ② + .build()) { + Value pythonBindings = context.getBindings("python"); + pythonBindings.putMember("fruits", FRUITS.toArray()); + + context.eval("python", """ + import polyleven + def get_similar_word(value): + words = [x for x in fruits if polyleven.levenshtein(x, value) <= 1] + return words[0] if words else None + """); + + + Value getSimilarWord = pythonBindings.getMember("get_similar_word"); + + for (String value : args) { + if (FRUITS.contains(value)) { + System.out.printf("✅ %s%n", value); + } else { + System.out.printf("❌ %s", value); + Value similarWord = getSimilarWord.execute(value); + if (!similarWord.isNull()) { + System.out.printf(" (did you mean '%s')%n", similarWord.asString()); + } else { + System.out.println(); + } + } + } + } + } +} \ No newline at end of file diff --git a/graalpy/graalpy-native-extensions-guide/src/main/java/org/example/MultiContextApp.java b/graalpy/graalpy-native-extensions-guide/src/main/java/org/example/MultiContextApp.java new file mode 100644 index 0000000..91817b0 --- /dev/null +++ b/graalpy/graalpy-native-extensions-guide/src/main/java/org/example/MultiContextApp.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at https://opensource.org/license/UPL. + */ +package org.example; + +import org.graalvm.polyglot.*; +import org.graalvm.python.embedding.utils.GraalPyResources; + +import java.util.Set; + +public class MultiContextApp { + private static final Set FRUITS = Set.of( + "apple", "banana", "peach", "grape", "orange", + "pear", "mango", "pineapple", "lemon", "lime", "apricot"); + + public static String getSimilarWord(String value) { + try (Context context = GraalPyResources.contextBuilder().build()) { + context.eval("python", """ + import polyleven + def get_similar_word(value): + words = [x for x in fruits if polyleven.levenshtein(x, value) <= 1] + return words[0] if words else None + """); + + Value pythonBindings = context.getBindings("python"); + pythonBindings.putMember("fruits", FRUITS.toArray()); + Value result = pythonBindings + .getMember("get_similar_word") + .execute(value); + return result.isNull() ? null : result.asString(); + } + } + + public static void main(String[] args) { + for (String value : args) { + if (FRUITS.contains(value)) { + System.out.printf("✅ %s%n", value); + } else { + System.out.printf("❌ %s", value); + String similarWord = getSimilarWord(value); + if (similarWord != null) { + System.out.printf(" (did you mean '%s')%n", similarWord); + } else { + System.out.println(); + } + } + } + } +} diff --git a/graalpy/graalpy-spring-boot-guide/.mvn/wrapper/maven-wrapper.properties b/graalpy/graalpy-spring-boot-guide/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..d58dfb7 --- /dev/null +++ b/graalpy/graalpy-spring-boot-guide/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,19 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +wrapperVersion=3.3.2 +distributionType=only-script +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip diff --git a/graalpy/graalpy-spring-boot-guide/README.md b/graalpy/graalpy-spring-boot-guide/README.md new file mode 100644 index 0000000..c4eb30f --- /dev/null +++ b/graalpy/graalpy-spring-boot-guide/README.md @@ -0,0 +1,485 @@ +## GraalPy Spring Boot Guide + +## 1. Getting Started + +In this guide, we will use the Python library [vaderSentiment](https://github.com/cjhutto/vaderSentiment) from a Spring Boot application written in Java. +![Screenshot of the app](screenshot.png) + +## 2. What you will need + +To complete this guide, you will need the following: + +* Some time on your hands +* A decent text editor or IDE +* A supported JDK[^1], preferably the latest [GraalVM JDK](https://graalvm.org/downloads/) + + [^1]: Oracle JDK 17 and OpenJDK 17 are supported with interpreter only. + GraalVM JDK 21, Oracle JDK 21, OpenJDK 21 and newer with [JIT compilation](https://www.graalvm.org/latest/reference-manual/embed-languages/#runtime-optimization-support). + Note: GraalVM for JDK 17 is **not supported**. + +## 3. Solution + +We recommend that you follow the instructions in the next sections and create the application step by step. +However, you can go right to the [completed example](./). + +## 4. Writing the application + +You can use [Spring Initializr](https://start.spring.io/) project generator to generate a basic Spring Boot project skeleton. To generate a project: + +1. Navigate to https://start.spring.io/ +2. GraalPy currently provides a plugin for *Maven*, but not yet for *Gradle*. Select **Maven** as the build system. +3. Click on **Dependencies** and select **Spring Web** +4. Click on **Dependencies** and select **Thymeleaf** +5. Click on **Generate**. Download and extract the generated ZIP file + +### 4.1. Application + +The generated Spring Boot application will contain the file _DemoApplication.java_, which is used when running the application via Maven or via deployment. +You can also run the main class directly within your IDE if it is configured correctly. + +`src/main/java/com/example/demo/DemoApplication.java` +```java +package com.example.demo; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class DemoApplication { + public static void main(String[] args) { + SpringApplication.run(DemoApplication.class, args); + } +} +``` + +### 4.2 Dependency configuration + +Add the required dependencies for GraalPy in the dependency section of the POM. + +`pom.xml` +```xml + + org.graalvm.python + python + 24.1.0 + pom + + + org.graalvm.python + python-embedding + 24.1.0 + +``` + +❶ The `python` dependency is a meta-package that transitively depends on all resources and libraries to run GraalPy. + +❷ Note that the `python` package is not a JAR - it is simply a `pom` that declares more dependencies. + +❸ The `python-embedding` dependency provides the APIs to manage and use GraalPy from Java. + +### 4.3 Adding packages - graalpy-maven-plugin configuration + +Most Python packages are hosted on [PyPI](https://pypi.org) and can be installed via the `pip` tool. +The Python ecosystem has conventions about the filesystem layout of installed packages that need to be kept in mind when embedding into Java. +You can use the GraalPy Maven plugin to manage Python packages for you. + +Also add the `graalpy-maven-plugin` configuration into the plugins section of the POM. + +`pom.xml` +```xml + + org.graalvm.python + graalpy-maven-plugin + 24.1.0 + + + + + vader-sentiment==3.2.1.1 + requests + + + + process-graalpy-resources + + + + +``` + +❶ The `packages` section lists all Python packages optionally with [requirement specifiers](https://pip.pypa.io/en/stable/reference/requirement-specifiers/). + +❷ Python packages and their versions can be specified as if used with pip. +Install and pin the `vader-sentiment` package to version `3.2.1.1`. + +❸ The `vader_sentiment` package does not declare `requests` as a dependency so it has to done so manually at this place. + +### 4.4 Creating a Python context + +GraalPy provides APIs to make setting up a context to load Python packages from Java as easy as possible. + +Create a Java class which will serve as a wrapper bean for the GraalPy context: + +`src/main/java/com/example/demo/GraalPyContext.java` +```java +package com.example.demo; + +import jakarta.annotation.PreDestroy; +import org.graalvm.polyglot.Context; +import org.graalvm.polyglot.Value; +import org.graalvm.python.embedding.utils.GraalPyResources; +import org.springframework.stereotype.Component; + +@Component // ① +public class GraalPyContext { + static final String PYTHON = "python"; + + private final Context context; + + public GraalPyContext() { + context = GraalPyResources.contextBuilder().build(); // ② + context.initialize(PYTHON); // ③ + } + + public Value eval(String source) { + return context.eval(PYTHON, source); // ④ + } + + @PreDestroy + public void close() { + context.close(true); // ⑤ + } +} +``` + +❶ Eagerly initialize as a singleton component. + +❷ The created GraalPy context will serve as a single access point to GraalPy for the whole application. + +❸ Initializing a GraalPy context isn't cheap, so we do so already at creation time to avoid delayed response time. + +❹ Evaluate Python code in the GraalPy context. + +❺ Close the GraalPy context at application shutdown. + +### 4.5 Using a Python library from Java + +After reading the [vaderSentiment](https://github.com/cjhutto/vaderSentiment) docs, you can now write the Java interface that matches the Python type and function you want to call. + +GraalPy makes it easy to access Python objects via these interfaces. +Java method names are mapped directly to Python method names. +Return values and arguments are mapped according to a set of [generic rules](https://www.graalvm.org/latest/reference-manual/python/Modern-Python-on-JVM/#java-to-python-types-automatic-conversion) as well as the [Target type mapping](https://www.graalvm.org/truffle/javadoc/org/graalvm/polyglot/Value.html#target-type-mapping-heading). +The names of the interfaces can be chosen freely, but it makes sense to base them on the Python types, as we do below. + +Your application will call the Python function `polarity_scores(text)` from the `vader_sentiment.SentimentIntensityAnalyzer` Python class. + +In oder to do so, create a Java interface with a method matching that function: + +`src/main/java/com/example/demo/SentimentIntensityAnalyzer.java` +```java +package com.example.demo; + +import java.util.Map; + +public interface SentimentIntensityAnalyzer { + Map polarity_scores(String text); // ① +} +``` + +❶ The Java method to call into `SentimentIntensityAnalyzer.polarity_scores(text)`. +The Python return value is a `dict` and can be directly converted to a Java Map on the fly. +The same applies to the argument, which is a Python String and therefore also a String on Java side. + +Using this Java interface and the GraalPy context, you can now construct a bean which calls the `SentimentIntensityAnalyzer.polarity_scores(text)` Python function: + +`src/main/java/com/example/demo/SentimentAnalysisService.java` +```java +package com.example.demo; + +import org.springframework.stereotype.Service; + +import java.util.Map; + +@Service +public class SentimentAnalysisService { + private final SentimentIntensityAnalyzer sentimentIntensityAnalyzer; + + public SentimentAnalysisService(GraalPyContext context) { + var value = context.eval(""" + from vader_sentiment.vader_sentiment import SentimentIntensityAnalyzer + SentimentIntensityAnalyzer() # ① + """); + sentimentIntensityAnalyzer = value.as(SentimentIntensityAnalyzer.class); // ② + } + + public Map getSentimentScore(String text) { + return sentimentIntensityAnalyzer.polarity_scores(text); // ③ + } +} +``` + +❶ The executed Python snippet imports the `vader_sentiment.SentimentIntensityAnalyzer` Python class into the GraalPy context and returns a new instance of it. +Note that the GraalPy context preserves its state and an eventual subsequent `eval` call accessing `SentimentIntensityAnalyzer` would not require an import anymore. + +❷ Map the obtained `org.graalvm.polyglot.Value` to the `SentimentIntensityAnalyzer` type. + +❸ Return the `sentimentIntensityAnalyzer` object. + +### 4.6 Index page + +The application will have a simple chat-like view, which takes text as input and return its sentiment value in form of an emoticon. + +Create a html file, which uses *jQuery* to query an API endpoint for the sentiment value. + +`src/main/resources/templates/index.html` +```html + + + + Demo App + + + + + + + +
+
+
+

Sentiment analysis demo

+

Input messages and the application will visualize the sentiment.

+
+
+
+
+
+ + +
+
+
+
+
+
+
+ + +``` + +### 4.7 Controller + +To create a microservice that provides a simple sentiment analysis, you also need a controller: + +`src/main/java/com/example/demo/DemoController.java` +```java +package com.example.demo; + +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.*; + +import java.util.Map; + +@Controller +public class DemoController { + private final SentimentAnalysisService sentimentAnalysisService; // ① + + public DemoController(SentimentAnalysisService sentimentAnalysisService) { + this.sentimentAnalysisService = sentimentAnalysisService; + } + + @GetMapping("/") + public String answer() { // ② + return "index"; + } + + @GetMapping(value = "/analyze", produces = "application/json") + @ResponseBody + public Map answer(@RequestParam String text) { // ③ + return sentimentAnalysisService.getSentimentScore(text); // ④ + } +} +``` + +❶ Inject our service commponent + +❷ Serve the main page + +❸ Serve the sentiment analysis endpoint + +❹ Use the `SentimentAnalysisService` component to call the `SentimentIntensityAnalyzer.polarity_scores(text)` Python function. + +### 4.8 Test + +Create a test to verify that when you make a GET request to `/analyze` you get the expected sentiment score response: + +`src/test/java/com/example/demo/DemoApplicationTests.java` +```java +package com.example.demo; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.web.servlet.MockMvc; + +import static org.hamcrest.Matchers.greaterThan; +import static org.hamcrest.Matchers.lessThan; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@SpringBootTest +@AutoConfigureMockMvc +@DirtiesContext // Necessary to shut down GraalPy context +class DemoApplicationTests { + + @Autowired + private MockMvc mockMvc; // ① + + @Test + void testIndex() throws Exception { // ② + mockMvc.perform(get("/")).andExpect(status().isOk()); + } + + @Test + void testSentimentAnalysis() throws Exception { // ③ + mockMvc.perform(get("/analyze").param("text", "I'm happy")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.compound", greaterThan(0.1))); + mockMvc.perform(get("/analyze").param("text", "This sucks")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.compound", lessThan(-0.1))); + } +} +``` + +❶ Use Spring MVC Test framework to make requests to the controller. + +❷ Use MVC mock to make request to the index page and verify it returned an Ok response. + +❸ Use MVC mock to make requests to the `/analyze` endpoint and verify the compound sentiment values are within expected ranges. + +## 5. Testing the Application + +To run the tests: + +```bash +./mvnw test +``` + +## 6. Running the Application + +To run the application: + +```bash +./mvnw spring-boot:run +``` + +This will start the application on port 8080. + +## 7. GraalVM Native Executable + +### 7.1. Native Executable metadata + +The [GraalVM](https://www.graalvm.org/) Native Image compilation requires metadata to properly run code that uses [dynamic proxies](https://www.graalvm.org/latest/reference-manual/native-image/metadata/#dynamic-proxy). + +For the case that also a native executable has to be generated, create a proxy configuration file: + +`src/main/resources/META-INF/native-image/proxy-config.json` +```json +[ + ["com.example.demo.SentimentIntensityAnalyzer"] +] +``` + +### 7.2. Generate a Native Executable with GraalVM + +We will use GraalVM, the polyglot embeddable virtual machine, to generate a native executable of our Spring Boot application. + +Compiling native executables ahead of time with GraalVM improves startup time and reduces the memory footprint of JVM-based applications. + +Make sure that the `JAVA_HOME` environmental variable is set to the location of a GraalVM installation. +We recommend using a GraalVM 24.1. + +To generate a native executable using Maven, run: + +```bash +./mvnw -Pnative native:compile +``` + +The native executable is created in the `target` directory and can be run with: + +```bash +./target/demo +``` + +## 8. Next steps + +- Use GraalPy in a [Java SE application](../graalpy-javase-guide/README.md) +- Use GraalPy with [Micronaut](../graalpy-micronaut-guide/README.md) +- Install and use Python packages that rely on [native code](../graalpy-native-extensions-guide/README.md), e.g. for data science and machine learning +- Follow along how you can manually [install Python packages and files](../graalpy-custom-venv-guide/README.md) if the Maven plugin gives not enough control +- [Freeze](../graalpy-freeze-dependencies-guide/README.md) transitive Python dependencies for reproducible builds +- [Migrate from Jython](../graalpy-jython-guide/README.md) to GraalPy + + +- Learn more about the GraalPy [Maven plugin](https://www.graalvm.org/latest/reference-manual/python/Embedding-Build-Tools/) +- Learn more about the Polyglot API for [embedding languages](https://www.graalvm.org/latest/reference-manual/embed-languages/) +- Explore in depth with GraalPy [reference manual](https://www.graalvm.org/latest/reference-manual/python/) \ No newline at end of file diff --git a/graalpy/graalpy-spring-boot-guide/mvnw b/graalpy/graalpy-spring-boot-guide/mvnw new file mode 100755 index 0000000..19529dd --- /dev/null +++ b/graalpy/graalpy-spring-boot-guide/mvnw @@ -0,0 +1,259 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Apache Maven Wrapper startup batch script, version 3.3.2 +# +# Optional ENV vars +# ----------------- +# JAVA_HOME - location of a JDK home dir, required when download maven via java source +# MVNW_REPOURL - repo url base for downloading maven distribution +# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output +# ---------------------------------------------------------------------------- + +set -euf +[ "${MVNW_VERBOSE-}" != debug ] || set -x + +# OS specific support. +native_path() { printf %s\\n "$1"; } +case "$(uname)" in +CYGWIN* | MINGW*) + [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")" + native_path() { cygpath --path --windows "$1"; } + ;; +esac + +# set JAVACMD and JAVACCMD +set_java_home() { + # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched + if [ -n "${JAVA_HOME-}" ]; then + if [ -x "$JAVA_HOME/jre/sh/java" ]; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACCMD="$JAVA_HOME/jre/sh/javac" + else + JAVACMD="$JAVA_HOME/bin/java" + JAVACCMD="$JAVA_HOME/bin/javac" + + if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then + echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2 + echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2 + return 1 + fi + fi + else + JAVACMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v java + )" || : + JAVACCMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v javac + )" || : + + if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then + echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2 + return 1 + fi + fi +} + +# hash string like Java String::hashCode +hash_string() { + str="${1:-}" h=0 + while [ -n "$str" ]; do + char="${str%"${str#?}"}" + h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296)) + str="${str#?}" + done + printf %x\\n $h +} + +verbose() { :; } +[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; } + +die() { + printf %s\\n "$1" >&2 + exit 1 +} + +trim() { + # MWRAPPER-139: + # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds. + # Needed for removing poorly interpreted newline sequences when running in more + # exotic environments such as mingw bash on Windows. + printf "%s" "${1}" | tr -d '[:space:]' +} + +# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties +while IFS="=" read -r key value; do + case "${key-}" in + distributionUrl) distributionUrl=$(trim "${value-}") ;; + distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;; + esac +done <"${0%/*}/.mvn/wrapper/maven-wrapper.properties" +[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties" + +case "${distributionUrl##*/}" in +maven-mvnd-*bin.*) + MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ + case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in + *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;; + :Darwin*x86_64) distributionPlatform=darwin-amd64 ;; + :Darwin*arm64) distributionPlatform=darwin-aarch64 ;; + :Linux*x86_64*) distributionPlatform=linux-amd64 ;; + *) + echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2 + distributionPlatform=linux-amd64 + ;; + esac + distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip" + ;; +maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;; +*) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;; +esac + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}" +distributionUrlName="${distributionUrl##*/}" +distributionUrlNameMain="${distributionUrlName%.*}" +distributionUrlNameMain="${distributionUrlNameMain%-bin}" +MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}" +MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")" + +exec_maven() { + unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || : + exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD" +} + +if [ -d "$MAVEN_HOME" ]; then + verbose "found existing MAVEN_HOME at $MAVEN_HOME" + exec_maven "$@" +fi + +case "${distributionUrl-}" in +*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;; +*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;; +esac + +# prepare tmp dir +if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then + clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; } + trap clean HUP INT TERM EXIT +else + die "cannot create temp dir" +fi + +mkdir -p -- "${MAVEN_HOME%/*}" + +# Download and Install Apache Maven +verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +verbose "Downloading from: $distributionUrl" +verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +# select .zip or .tar.gz +if ! command -v unzip >/dev/null; then + distributionUrl="${distributionUrl%.zip}.tar.gz" + distributionUrlName="${distributionUrl##*/}" +fi + +# verbose opt +__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR='' +[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v + +# normalize http auth +case "${MVNW_PASSWORD:+has-password}" in +'') MVNW_USERNAME='' MVNW_PASSWORD='' ;; +has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;; +esac + +if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then + verbose "Found wget ... using wget" + wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl" +elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then + verbose "Found curl ... using curl" + curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl" +elif set_java_home; then + verbose "Falling back to use Java to download" + javaSource="$TMP_DOWNLOAD_DIR/Downloader.java" + targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName" + cat >"$javaSource" <<-END + public class Downloader extends java.net.Authenticator + { + protected java.net.PasswordAuthentication getPasswordAuthentication() + { + return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() ); + } + public static void main( String[] args ) throws Exception + { + setDefault( new Downloader() ); + java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() ); + } + } + END + # For Cygwin/MinGW, switch paths to Windows format before running javac and java + verbose " - Compiling Downloader.java ..." + "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java" + verbose " - Running Downloader.java ..." + "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")" +fi + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +if [ -n "${distributionSha256Sum-}" ]; then + distributionSha256Result=false + if [ "$MVN_CMD" = mvnd.sh ]; then + echo "Checksum validation is not supported for maven-mvnd." >&2 + echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + elif command -v sha256sum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c >/dev/null 2>&1; then + distributionSha256Result=true + fi + elif command -v shasum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then + distributionSha256Result=true + fi + else + echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2 + echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + fi + if [ $distributionSha256Result = false ]; then + echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2 + echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2 + exit 1 + fi +fi + +# unzip and move +if command -v unzip >/dev/null; then + unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip" +else + tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar" +fi +printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url" +mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME" + +clean || : +exec_maven "$@" diff --git a/graalpy/graalpy-spring-boot-guide/mvnw.cmd b/graalpy/graalpy-spring-boot-guide/mvnw.cmd new file mode 100644 index 0000000..b150b91 --- /dev/null +++ b/graalpy/graalpy-spring-boot-guide/mvnw.cmd @@ -0,0 +1,149 @@ +<# : batch portion +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Apache Maven Wrapper startup batch script, version 3.3.2 +@REM +@REM Optional ENV vars +@REM MVNW_REPOURL - repo url base for downloading maven distribution +@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output +@REM ---------------------------------------------------------------------------- + +@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0) +@SET __MVNW_CMD__= +@SET __MVNW_ERROR__= +@SET __MVNW_PSMODULEP_SAVE=%PSModulePath% +@SET PSModulePath= +@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @( + IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B) +) +@SET PSModulePath=%__MVNW_PSMODULEP_SAVE% +@SET __MVNW_PSMODULEP_SAVE= +@SET __MVNW_ARG0_NAME__= +@SET MVNW_USERNAME= +@SET MVNW_PASSWORD= +@IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*) +@echo Cannot start maven from wrapper >&2 && exit /b 1 +@GOTO :EOF +: end batch / begin powershell #> + +$ErrorActionPreference = "Stop" +if ($env:MVNW_VERBOSE -eq "true") { + $VerbosePreference = "Continue" +} + +# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties +$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl +if (!$distributionUrl) { + Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" +} + +switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) { + "maven-mvnd-*" { + $USE_MVND = $true + $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip" + $MVN_CMD = "mvnd.cmd" + break + } + default { + $USE_MVND = $false + $MVN_CMD = $script -replace '^mvnw','mvn' + break + } +} + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +if ($env:MVNW_REPOURL) { + $MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" } + $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')" +} +$distributionUrlName = $distributionUrl -replace '^.*/','' +$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$','' +$MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain" +if ($env:MAVEN_USER_HOME) { + $MAVEN_HOME_PARENT = "$env:MAVEN_USER_HOME/wrapper/dists/$distributionUrlNameMain" +} +$MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' +$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME" + +if (Test-Path -Path "$MAVEN_HOME" -PathType Container) { + Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME" + Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" + exit $? +} + +if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) { + Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl" +} + +# prepare tmp dir +$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile +$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir" +$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null +trap { + if ($TMP_DOWNLOAD_DIR.Exists) { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } + } +} + +New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null + +# Download and Install Apache Maven +Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +Write-Verbose "Downloading from: $distributionUrl" +Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +$webclient = New-Object System.Net.WebClient +if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) { + $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD) +} +[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 +$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum +if ($distributionSha256Sum) { + if ($USE_MVND) { + Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." + } + Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash + if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) { + Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property." + } +} + +# unzip and move +Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null +Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null +try { + Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null +} catch { + if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) { + Write-Error "fail to move MAVEN_HOME" + } +} finally { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } +} + +Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" diff --git a/graalpy/graalpy-spring-boot-guide/pom.xml b/graalpy/graalpy-spring-boot-guide/pom.xml new file mode 100644 index 0000000..e06d584 --- /dev/null +++ b/graalpy/graalpy-spring-boot-guide/pom.xml @@ -0,0 +1,78 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 3.3.3 + + + com.example + graalpy-springboot + 0.0.1-SNAPSHOT + graalpyspringboot + Demo project for Spring with GraalPy + + + 21 + UTF-8 + + + + + org.springframework.boot + spring-boot-starter-thymeleaf + + + org.springframework.boot + spring-boot-starter-web + + + + org.graalvm.python + python + 24.1.0 + pom + + + org.graalvm.python + python-embedding + 24.1.0 + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.graalvm.python + graalpy-maven-plugin + 24.1.0 + + + + + vader-sentiment==3.2.1.1 + requests + + + + process-graalpy-resources + + + + + + + diff --git a/graalpy/graalpy-spring-boot-guide/screenshot.png b/graalpy/graalpy-spring-boot-guide/screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..ecf95e96734dc846ed0265097748fa5cdfdbec4c GIT binary patch literal 236583 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%0A_RGr;3vFRBa zKk1)ZY;J&hr(@6@y8vZC$f8%LgkocHw}{g~FvO#nT0rh|@P57Bl72=DdIb1)iP&@6 zyp8FPR}Hg%)M6ur>OL3yx9s-z1Nc&n!?S7Bm~_HEug5{$CFUZ1M!r7VIfc_Oj_$pU zc1Ih2oy5(zIS|969kLlMl*BI*X? zuzY>d21;v)<_$N>bjMJU_eWFTpIfy(6{3ulHe^h4Riq`cYA?!Xwi91` zxnzaE5&oziA%(Ya+%MyHVycIt`VtH@mbKfW7Et+a764vMpgU3ifT_?_(5YKvov?g-qazokjcsuBNGl&b!=KQ0(W;{F`-n64 zgq^wO+a7J}$j(i4R3SP6JJXJXC<_`P%unsRO&N|f}fC;77b(t zp5DUgt3unT+I7XO>c?fvTDto8X8B5A3fLaknRlm+@0HegY=Rk{CIDt@oznu}rHKO?@&p z!hlETgeLN~Umqv9roe z05fTj;<0zTL-L{R>F#+e0Bz?};3<-<`iHLa_l4zo72Mw&6rsA8Ud5T6US7jVz5%I8 zTCP7tWcbmv>Pn=g0Bo0GX}{m_I;ciWNF&>#!Zx2SYEXIF(61Eya|zySCci6?N-)ij z;*id+35>q(DGTIDkfY;s7;2wE}#-K{=#6-7mrT-(4arg#^?)c2#-WM zVloshml};bfV81|O6>0}!qOi{4|$LvP&NS2WH|gXgyG_AH=(_pKAFPnd8Crph_LQJ z-D=}rJj`R#dl5zBpUWbh8IW>2%a0L=Z&QUDY%n^lY~nB?Qn!NmfxyIW8+0i1)lLFp z#)&=81cFvoc)y@-5hXEnV)?QmjzVrN%C%7nDj%a$;5I?4rHJwa!T3>ll#U`~RHq zxWMJMc{N|tiab}<;7P+Z@$7za*rTn{90{m>XX(+a-yqXCa|?~+dbrfz?uT=Y3v!d} zD={*kk3{0iZ9M^Ts^0>N23L4oMdX7uBV7{0mFOa@e@yFXR%vh0)@@_V)QEwo8{0sA zU1P8^7xBT<`CrQHxAHjF^Kh+jr)sZvT||A91if4R($q_aE4&n0a8Ai_Ihl0G5vtx8 zx!q@X(*O3%^Q9C%LtnRLHE#zKnJ!dlU7cO#1QR5_dqmUYal_xPQ-}%*%_{ZVDV(
Fr}9!Iil88q2%Zmr*Q_U{^Oi~wxvH4V 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≤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 ljs1tW<2om|@r8dJC_J3mpDzCM{{fGm`62)S literal 0 HcmV?d00001 diff --git a/graalpy/graalpy-spring-boot-guide/src/main/java/com/example/demo/DemoApplication.java b/graalpy/graalpy-spring-boot-guide/src/main/java/com/example/demo/DemoApplication.java new file mode 100644 index 0000000..fb75f14 --- /dev/null +++ b/graalpy/graalpy-spring-boot-guide/src/main/java/com/example/demo/DemoApplication.java @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at https://opensource.org/license/UPL. + */ + +package com.example.demo; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class DemoApplication { + public static void main(String[] args) { + SpringApplication.run(DemoApplication.class, args); + } +} diff --git a/graalpy/graalpy-spring-boot-guide/src/main/java/com/example/demo/DemoController.java b/graalpy/graalpy-spring-boot-guide/src/main/java/com/example/demo/DemoController.java new file mode 100644 index 0000000..ff12d2d --- /dev/null +++ b/graalpy/graalpy-spring-boot-guide/src/main/java/com/example/demo/DemoController.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at https://opensource.org/license/UPL. + */ + +package com.example.demo; + +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.*; + +import java.util.Map; + +@Controller +public class DemoController { + private final SentimentAnalysisService sentimentAnalysisService; // ① + + public DemoController(SentimentAnalysisService sentimentAnalysisService) { + this.sentimentAnalysisService = sentimentAnalysisService; + } + + @GetMapping("/") + public String answer() { // ② + return "index"; + } + + @GetMapping(value = "/analyze", produces = "application/json") + @ResponseBody + public Map answer(@RequestParam String text) { // ③ + return sentimentAnalysisService.getSentimentScore(text); // ④ + } +} diff --git a/graalpy/graalpy-spring-boot-guide/src/main/java/com/example/demo/GraalPyContext.java b/graalpy/graalpy-spring-boot-guide/src/main/java/com/example/demo/GraalPyContext.java new file mode 100644 index 0000000..345a9d2 --- /dev/null +++ b/graalpy/graalpy-spring-boot-guide/src/main/java/com/example/demo/GraalPyContext.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at https://opensource.org/license/UPL. + */ + +package com.example.demo; + +import jakarta.annotation.PreDestroy; +import org.graalvm.polyglot.Context; +import org.graalvm.polyglot.Value; +import org.graalvm.python.embedding.utils.GraalPyResources; +import org.springframework.stereotype.Component; + +@Component // ① +public class GraalPyContext { + static final String PYTHON = "python"; + + private final Context context; + + public GraalPyContext() { + context = GraalPyResources.contextBuilder().build(); // ② + context.initialize(PYTHON); // ③ + } + + public Value eval(String source) { + return context.eval(PYTHON, source); // ④ + } + + @PreDestroy + public void close() { + context.close(true); // ⑤ + } +} diff --git a/graalpy/graalpy-spring-boot-guide/src/main/java/com/example/demo/SentimentAnalysisService.java b/graalpy/graalpy-spring-boot-guide/src/main/java/com/example/demo/SentimentAnalysisService.java new file mode 100644 index 0000000..d77c161 --- /dev/null +++ b/graalpy/graalpy-spring-boot-guide/src/main/java/com/example/demo/SentimentAnalysisService.java @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at https://opensource.org/license/UPL. + */ + +package com.example.demo; + +import org.springframework.stereotype.Service; + +import java.util.Map; + +@Service +public class SentimentAnalysisService { + private final SentimentIntensityAnalyzer sentimentIntensityAnalyzer; + + public SentimentAnalysisService(GraalPyContext context) { + var value = context.eval(""" + from vader_sentiment.vader_sentiment import SentimentIntensityAnalyzer + SentimentIntensityAnalyzer() # ① + """); + sentimentIntensityAnalyzer = value.as(SentimentIntensityAnalyzer.class); // ② + } + + public Map getSentimentScore(String text) { + return sentimentIntensityAnalyzer.polarity_scores(text); // ③ + } +} diff --git a/graalpy/graalpy-spring-boot-guide/src/main/java/com/example/demo/SentimentIntensityAnalyzer.java b/graalpy/graalpy-spring-boot-guide/src/main/java/com/example/demo/SentimentIntensityAnalyzer.java new file mode 100644 index 0000000..7102f16 --- /dev/null +++ b/graalpy/graalpy-spring-boot-guide/src/main/java/com/example/demo/SentimentIntensityAnalyzer.java @@ -0,0 +1,13 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at https://opensource.org/license/UPL. + */ + +package com.example.demo; + +import java.util.Map; + +public interface SentimentIntensityAnalyzer { + Map polarity_scores(String text); // ① +} diff --git a/graalpy/graalpy-spring-boot-guide/src/main/resources/META-INF/native-image/proxy-config.json b/graalpy/graalpy-spring-boot-guide/src/main/resources/META-INF/native-image/proxy-config.json new file mode 100644 index 0000000..849b7f7 --- /dev/null +++ b/graalpy/graalpy-spring-boot-guide/src/main/resources/META-INF/native-image/proxy-config.json @@ -0,0 +1,3 @@ +[ + ["com.example.demo.SentimentIntensityAnalyzer"] +] diff --git a/graalpy/graalpy-spring-boot-guide/src/main/resources/application.properties b/graalpy/graalpy-spring-boot-guide/src/main/resources/application.properties new file mode 100644 index 0000000..2109a44 --- /dev/null +++ b/graalpy/graalpy-spring-boot-guide/src/main/resources/application.properties @@ -0,0 +1 @@ +spring.application.name=demo diff --git a/graalpy/graalpy-spring-boot-guide/src/main/resources/templates/index.html b/graalpy/graalpy-spring-boot-guide/src/main/resources/templates/index.html new file mode 100644 index 0000000..db25aee --- /dev/null +++ b/graalpy/graalpy-spring-boot-guide/src/main/resources/templates/index.html @@ -0,0 +1,87 @@ + + + + Demo App + + + + + + + +

+
+
+

Sentiment analysis demo

+

Input messages and the application will visualize the sentiment.

+
+
+
+
+
+ + +
+
+
+
+
+
+
+ + diff --git a/graalpy/graalpy-spring-boot-guide/src/test/java/com/example/demo/DemoApplicationTests.java b/graalpy/graalpy-spring-boot-guide/src/test/java/com/example/demo/DemoApplicationTests.java new file mode 100644 index 0000000..0887d8a --- /dev/null +++ b/graalpy/graalpy-spring-boot-guide/src/test/java/com/example/demo/DemoApplicationTests.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at https://opensource.org/license/UPL. + */ + +package com.example.demo; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.web.servlet.MockMvc; + +import static org.hamcrest.Matchers.greaterThan; +import static org.hamcrest.Matchers.lessThan; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@SpringBootTest +@AutoConfigureMockMvc +@DirtiesContext // Necessary to shut down GraalPy context +class DemoApplicationTests { + + @Autowired + private MockMvc mockMvc; // ① + + @Test + void testIndex() throws Exception { // ② + mockMvc.perform(get("/")).andExpect(status().isOk()); + } + + @Test + void testSentimentAnalysis() throws Exception { // ③ + mockMvc.perform(get("/analyze").param("text", "I'm happy")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.compound", greaterThan(0.1))); + mockMvc.perform(get("/analyze").param("text", "This sucks")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.compound", lessThan(-0.1))); + } +}